/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.cxf.rs.security.oauth2.provider;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.crypto.SecretKey;

import org.apache.cxf.common.util.crypto.CryptoUtils;
import org.apache.cxf.common.util.crypto.KeyProperties;
import org.apache.cxf.rs.security.oauth2.common.Client;
import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken;
import org.apache.cxf.rs.security.oauth2.tokens.refresh.RefreshToken;
import org.apache.cxf.rs.security.oauth2.utils.crypto.ModelEncryptionSupport;

public class DefaultEncryptingOAuthDataProvider extends AbstractOAuthDataProvider 
    implements ClientRegistrationProvider {
    protected SecretKey key;
    private Set<String> tokens = Collections.synchronizedSet(new HashSet<String>());
    private ConcurrentHashMap<String, String> refreshTokens = new ConcurrentHashMap<String, String>();
    private ConcurrentHashMap<String, String> clientsMap = new ConcurrentHashMap<String, String>();
    public DefaultEncryptingOAuthDataProvider(String algo, int keySize) {
        this(new KeyProperties(algo, keySize));
    }
    public DefaultEncryptingOAuthDataProvider(KeyProperties props) {
        this(CryptoUtils.getSecretKey(props));
    }
    public DefaultEncryptingOAuthDataProvider(SecretKey key) {
        this.key = key;
    }
    
    @Override
    public Client getClient(String clientId) throws OAuthServiceException {
        return ModelEncryptionSupport.decryptClient(clientsMap.get(clientId), key);
    }

    @Override
    public void setClient(Client client) {
        clientsMap.put(client.getClientId(), ModelEncryptionSupport.encryptClient(client, key));
        
    }
    @Override
    public Client removeClient(String clientId) {
        Client client = getClient(clientId);
        clientsMap.remove(clientId);
        return client;
    }
    @Override
    public List<Client> getClients() {
        List<Client> clients = new ArrayList<Client>(clientsMap.size());
        for (String clientKey : clientsMap.keySet()) {
            clients.add(getClient(clientKey));
        }
        return clients;
    }
    
    @Override
    public ServerAccessToken getAccessToken(String accessToken) throws OAuthServiceException {
        return ModelEncryptionSupport.decryptAccessToken(this, accessToken, key);
    }

    @Override
    public void removeAccessToken(ServerAccessToken accessToken) throws OAuthServiceException {
        revokeAccessToken(accessToken.getTokenKey());
    }

    @Override
    protected void saveAccessToken(ServerAccessToken serverToken) {
        encryptAccessToken(serverToken);
    }

    @Override
    protected boolean revokeAccessToken(String accessTokenKey) {
        return tokens.remove(accessTokenKey);
    }
    
    @Override
    protected void saveRefreshToken(ServerAccessToken at, RefreshToken refreshToken) {
        String encryptedRefreshToken = ModelEncryptionSupport.encryptRefreshToken(refreshToken, key);
        at.setRefreshToken(encryptedRefreshToken);
    }

    @Override
    protected RefreshToken revokeRefreshToken(Client client, String refreshTokenKey) {
        refreshTokens.remove(refreshTokenKey);
        return ModelEncryptionSupport.decryptRefreshToken(this, refreshTokenKey, key);
    }

    private void encryptAccessToken(ServerAccessToken token) {
        String encryptedToken = ModelEncryptionSupport.encryptAccessToken(token, key);
        tokens.add(encryptedToken);
        refreshTokens.put(token.getRefreshToken(), encryptedToken);
        token.setTokenKey(encryptedToken);
    }
}
