package org.bouncycastle.x509.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.Principal;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.CertificatePair;
import org.bouncycastle.jce.X509LDAPCertStoreParameters;
import org.bouncycastle.jce.provider.X509AttrCertParser;
import org.bouncycastle.jce.provider.X509CRLParser;
import org.bouncycastle.jce.provider.X509CertPairParser;
import org.bouncycastle.jce.provider.X509CertParser;
import org.bouncycastle.util.StoreException;
import org.bouncycastle.x509.X509AttributeCertStoreSelector;
import org.bouncycastle.x509.X509AttributeCertificate;
import org.bouncycastle.x509.X509CRLStoreSelector;
import org.bouncycastle.x509.X509CertPairStoreSelector;
import org.bouncycastle.x509.X509CertStoreSelector;
import org.bouncycastle.x509.X509CertificatePair;

/**
 * This is a general purpose implementation to get X.509 certificates, CRLs,
 * attribute certificates and cross certificates from a LDAP location.
 * <p/>
 * At first a search is performed in the ldap*AttributeNames of the
 * {@link org.bouncycastle.jce.X509LDAPCertStoreParameters} with the given
 * information of the subject (for all kind of certificates) or issuer (for
 * CRLs), respectively, if a {@link org.bouncycastle.x509.X509CertStoreSelector} or
 * {@link org.bouncycastle.x509.X509AttributeCertificate} is given with that
 * details.
 * <p/>
 * For the used schemes see:
 * <ul>
 * <li><a href="http://www.ietf.org/rfc/rfc2587.txt">RFC 2587</a>
 * <li><a
 * href="http://www3.ietf.org/proceedings/01mar/I-D/pkix-ldap-schema-01.txt">Internet
 * X.509 Public Key Infrastructure Additional LDAP Schema for PKIs and PMIs</a>
 * </ul>
 */
public class LDAPStoreHelper
{

    // TODO: cache results

    private X509LDAPCertStoreParameters params;

    public LDAPStoreHelper(X509LDAPCertStoreParameters params)
    {
        this.params = params;
    }

    /**
     * Initial Context Factory.
     */
    private static String LDAP_PROVIDER = "com.sun.jndi.ldap.LdapCtxFactory";

    /**
     * Processing referrals..
     */
    private static String REFERRALS_IGNORE = "ignore";

    /**
     * Security level to be used for LDAP connections.
     */
    private static final String SEARCH_SECURITY_LEVEL = "none";

    /**
     * Package Prefix for loading URL context factories.
     */
    private static final String URL_CONTEXT_PREFIX = "com.sun.jndi.url";

    private DirContext connectLDAP() throws NamingException
    {
        Properties props = new Properties();
        props.setProperty(Context.INITIAL_CONTEXT_FACTORY, LDAP_PROVIDER);
        props.setProperty(Context.BATCHSIZE, "0");

        props.setProperty(Context.PROVIDER_URL, params.getLdapURL());
        props.setProperty(Context.URL_PKG_PREFIXES, URL_CONTEXT_PREFIX);
        props.setProperty(Context.REFERRAL, REFERRALS_IGNORE);
        props.setProperty(Context.SECURITY_AUTHENTICATION,
            SEARCH_SECURITY_LEVEL);

        DirContext ctx = new InitialDirContext(props);
        return ctx;
    }

    private String parseDN(String subject, String dNAttributeName)
    {
        String temp = subject;
        int begin = temp.toLowerCase().indexOf(
            dNAttributeName.toLowerCase() + "=");
        if (begin == -1)
        {
            return "";
        }
        temp = temp.substring(begin + dNAttributeName.length());
        int end = temp.indexOf(',');
        if (end == -1)
        {
            end = temp.length();
        }
        while (temp.charAt(end - 1) == '\\')
        {
            end = temp.indexOf(',', end + 1);
            if (end == -1)
            {
                end = temp.length();
            }
        }
        temp = temp.substring(0, end);
        begin = temp.indexOf('=');
        temp = temp.substring(begin + 1);
        if (temp.charAt(0) == ' ')
        {
            temp = temp.substring(1);
        }
        if (temp.startsWith("\""))
        {
            temp = temp.substring(1);
        }
        if (temp.endsWith("\""))
        {
            temp = temp.substring(0, temp.length() - 1);
        }
        return temp;
    }

    private Set createCerts(List list, X509CertStoreSelector xselector)
        throws StoreException
    {
        Set certSet = new HashSet();

        Iterator it = list.iterator();
        X509CertParser parser = new X509CertParser();
        while (it.hasNext())
        {
            try
            {
                parser.engineInit(new ByteArrayInputStream((byte[])it
                    .next()));
                X509Certificate cert = (X509Certificate)parser
                    .engineRead();
                if (xselector.match((Object)cert))
                {
                    certSet.add(cert);
                }

            }
            catch (Exception e)
            {

            }
        }

        return certSet;
    }

    /**
     * Can use the subject and serial and the subject and serialNumber of the
     * certificate of the given of the X509CertStoreSelector. If a certificate
     * for checking is given this has higher precedence.
     *
     * @param xselector             The selector with the search criteria.
     * @param attrs                 Attributes which contain the certificates in the LDAP
     *                              directory.
     * @param attrNames             Attribute names in teh LDAP directory which correspond to the
     *                              subjectAttributeNames.
     * @param subjectAttributeNames Subject attribute names (like "CN", "O", "OU") to use to
     *                              search in the LDAP directory
     * @return A list of found DER encoded certificates.
     * @throws StoreException if an error occurs while searching.
     */
    private List certSubjectSerialSearch(X509CertStoreSelector xselector,
                                         String[] attrs, String attrNames[], String subjectAttributeNames[])
        throws StoreException
    {
        // TODO: support also subjectAltNames?
        List list = new ArrayList();

        String subject = null;
        String serial = null;

        subject = getSubjectAsString(xselector);

        if (xselector.getSerialNumber() != null)
        {
            serial = xselector.getSerialNumber().toString();
        }
        if (xselector.getCertificate() != null)
        {
            subject = xselector.getCertificate().getSubjectX500Principal().getName("RFC1779");
            serial = xselector.getCertificate().getSerialNumber().toString();
        }

        String attrValue = null;
        if (subject != null)
        {
            for (int i = 0; i < subjectAttributeNames.length; i++)
            {
                attrValue = parseDN(subject, subjectAttributeNames[i]);
                list
                    .addAll(search(attrNames, "*" + attrValue + "*",
                        attrs));
            }
        }
        if (serial != null && params.getSearchForSerialNumberIn() != null)
        {
            attrValue = serial;
            list.addAll(search(
                splitString(params.getSearchForSerialNumberIn()),
                                                  attrValue, attrs));
        }
        if (serial == null && subject == null)
        {
            list.addAll(search(attrNames, "*", attrs));
        }

        return list;
    }



    /**
     * Can use the subject of the forward certificate of the set certificate
     * pair or the subject of the forward
     * {@link org.bouncycastle.x509.X509CertStoreSelector} of the given
     * selector.
     *
     * @param xselector             The selector with the search criteria.
     * @param attrs                 Attributes which contain the attribute certificates in the
     *                              LDAP directory.
     * @param attrNames             Attribute names in the LDAP directory which correspond to the
     *                              subjectAttributeNames.
     * @param subjectAttributeNames Subject attribute names (like "CN", "O", "OU") to use to
     *                              search in the LDAP directory
     * @return A list of found DER encoded certificate pairs.
     * @throws StoreException if an error occurs while searching.
     */
    private List crossCertificatePairSubjectSearch(
        X509CertPairStoreSelector xselector, String[] attrs,
        String attrNames[], String subjectAttributeNames[])
        throws StoreException
    {
        List list = new ArrayList();

        // search for subject
        String subject = null;

        if (xselector.getForwardSelector() != null)
        {
            subject = getSubjectAsString(xselector.getForwardSelector());
        }
        if (xselector.getCertPair() != null)
        {
            if (xselector.getCertPair().getForward() != null)
            {
                subject = xselector.getCertPair().getForward()
                    .getSubjectX500Principal().getName("RFC1779");
            }
        }
        String attrValue = null;
        if (subject != null)
        {
            for (int i = 0; i < subjectAttributeNames.length; i++)
            {
                attrValue = parseDN(subject, subjectAttributeNames[i]);
                list
                    .addAll(search(attrNames, "*" + attrValue + "*",
                        attrs));
            }
        }
        if (subject == null)
        {
            list.addAll(search(attrNames, "*", attrs));
        }

        return list;
    }

    /**
     * Can use the entityName of the holder of the attribute certificate, the
     * serialNumber of attribute certificate and the serialNumber of the
     * associated certificate of the given of the X509AttributeCertSelector.
     *
     * @param xselector             The selector with the search criteria.
     * @param attrs                 Attributes which contain the attribute certificates in the
     *                              LDAP directory.
     * @param attrNames             Attribute names in the LDAP directory which correspond to the
     *                              subjectAttributeNames.
     * @param subjectAttributeNames Subject attribute names (like "CN", "O", "OU") to use to
     *                              search in the LDAP directory
     * @return A list of found DER encoded attribute certificates.
     * @throws StoreException if an error occurs while searching.
     */
    private List attrCertSubjectSerialSearch(
        X509AttributeCertStoreSelector xselector, String[] attrs,
        String attrNames[], String subjectAttributeNames[])
        throws StoreException
    {
        List list = new ArrayList();

        // search for serialNumber of associated cert,
        // serialNumber of the attribute certificate or DN in the entityName
        // of the holder

        String subject = null;
        String serial = null;

        Collection serials = new HashSet();
        Principal principals[] = null;
        if (xselector.getHolder() != null)
        {
            // serialNumber of associated cert
            if (xselector.getHolder().getSerialNumber() != null)
            {
                serials.add(xselector.getHolder().getSerialNumber()
                    .toString());
            }
            // DN in the entityName of the holder
            if (xselector.getHolder().getEntityNames() != null)
            {
                principals = xselector.getHolder().getEntityNames();
            }
        }

        if (xselector.getAttributeCert() != null)
        {
            if (xselector.getAttributeCert().getHolder().getEntityNames() != null)
            {
                principals = xselector.getAttributeCert().getHolder()
                    .getEntityNames();
            }
            // serialNumber of the attribute certificate
            serials.add(xselector.getAttributeCert().getSerialNumber()
                .toString());
        }
        if (principals != null)
        {
            // only first should be relevant
            if (principals[0] instanceof X500Principal)
            {
                subject = ((X500Principal)principals[0])
                    .getName("RFC1779");
            }
            else
            {
                // strange ...
                subject = principals[0].getName();
            }
        }
        if (xselector.getSerialNumber() != null)
        {
            serials.add(xselector.getSerialNumber().toString());
        }

        String attrValue = null;
        if (subject != null)
        {
            for (int i = 0; i < subjectAttributeNames.length; i++)
            {
                attrValue = parseDN(subject, subjectAttributeNames[i]);
                list
                    .addAll(search(attrNames, "*" + attrValue + "*",
                        attrs));
            }
        }
        if (serials.size() > 0
            && params.getSearchForSerialNumberIn() != null)
        {
            Iterator it = serials.iterator();
            while (it.hasNext())
            {
                serial = (String)it.next();
                list.addAll(search(splitString(params.getSearchForSerialNumberIn()), serial, attrs));
            }
        }
        if (serials.size() == 0 && subject == null)
        {
            list.addAll(search(attrNames, "*", attrs));
        }

        return list;
    }

    /**
     * Can use the issuer of the given of the X509CRLStoreSelector.
     *
     * @param xselector            The selector with the search criteria.
     * @param attrs                Attributes which contain the attribute certificates in the
     *                             LDAP directory.
     * @param attrNames            Attribute names in the LDAP directory which correspond to the
     *                             subjectAttributeNames.
     * @param issuerAttributeNames Issuer attribute names (like "CN", "O", "OU") to use to search
     *                             in the LDAP directory
     * @return A list of found DER encoded CRLs.
     * @throws StoreException if an error occurs while searching.
     */
    private List cRLIssuerSearch(X509CRLStoreSelector xselector,
                                 String[] attrs, String attrNames[], String issuerAttributeNames[])
        throws StoreException
    {
        List list = new ArrayList();

        String issuer = null;
        Collection issuers = new HashSet();
        if (xselector.getIssuers() != null)
        {
            issuers.addAll(xselector.getIssuers());
        }
        if (xselector.getCertificateChecking() != null)
        {
            issuers.add(getCertificateIssuer(xselector.getCertificateChecking()));
        }
        if (xselector.getAttrCertificateChecking() != null)
        {
            Principal principals[] = xselector.getAttrCertificateChecking().getIssuer().getPrincipals();
            for (int i=0; i<principals.length; i++)
            {
                if (principals[i] instanceof X500Principal)
                {
                    issuers.add(principals[i]);        
                }
            }
        }
        Iterator it = issuers.iterator();
        while (it.hasNext())
        {
            issuer = ((X500Principal)it.next()).getName("RFC1779");
            String attrValue = null;

            for (int i = 0; i < issuerAttributeNames.length; i++)
            {
                attrValue = parseDN(issuer, issuerAttributeNames[i]);
                list
                    .addAll(search(attrNames, "*" + attrValue + "*",
                        attrs));
            }
        }
        if (issuer == null)
        {
            list.addAll(search(attrNames, "*", attrs));
        }

        return list;
    }

    /**
     * Returns a <code>List</code> of encodings of the certificates, attribute
     * certificates, CRL or certificate pairs.
     *
     * @param attributeNames The attribute names to look for in the LDAP.
     * @param attributeValue The value the attribute name must have.
     * @param attrs          The attributes in the LDAP which hold the certificate,
     *                       attribute certificate, certificate pair or CRL in a found
     *                       entry.
     * @return A <code>List</code> of byte arrays with the encodings.
     * @throws StoreException if an error occurs getting the results from the LDAP
     *                        directory.
     */
    private List search(String attributeNames[], String attributeValue,
                        String[] attrs) throws StoreException
    {
        String filter = null;
        if (attributeNames == null)
        {
            filter = null;
        }
        else
        {
            filter = "";
            if (attributeValue.equals("**"))
            {
                attributeValue = "*";
            }
            for (int i = 0; i < attributeNames.length; i++)
            {
                filter += "(" + attributeNames[i] + "=" + attributeValue + ")";
            }
            filter = "(|" + filter + ")";
        }
        String filter2 = "";
        for (int i = 0; i < attrs.length; i++)
        {
            filter2 += "(" + attrs[i] + "=*)";
        }
        filter2 = "(|" + filter2 + ")";

        String filter3 = "(&" + filter + "" + filter2 + ")";
        if (filter == null)
        {
            filter3 = filter2;
        }
        List list;
        list = getFromCache(filter3);
        if (list != null)
        {
            return list;
        }
        DirContext ctx = null;
        list = new ArrayList();
        try
        {

            ctx = connectLDAP();

            SearchControls constraints = new SearchControls();
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
            constraints.setCountLimit(0);
            constraints.setReturningAttributes(attrs);
            NamingEnumeration results = ctx.search(params.getBaseDN(), filter3,
                constraints);
            while (results.hasMoreElements())
            {
                SearchResult sr = (SearchResult)results.next();
                NamingEnumeration enumeration = ((Attribute)(sr
                    .getAttributes().getAll().next())).getAll();
                while (enumeration.hasMore())
                {
                    list.add(enumeration.next());
                }
            }
            addToCache(filter3, list);
        }
        catch (NamingException e)
        {
            // skip exception, unfortunately if an attribute type is not
            // supported an exception is thrown

        }
        finally
        {
            try
            {
                if (null != ctx)
                {
                    ctx.close();
                }
            }
            catch (Exception e)
            {
            }
        }
        return list;
    }

    private Set createCRLs(List list, X509CRLStoreSelector xselector)
        throws StoreException
    {
        Set crlSet = new HashSet();

        X509CRLParser parser = new X509CRLParser();
        Iterator it = list.iterator();
        while (it.hasNext())
        {
            try
            {
                parser.engineInit(new ByteArrayInputStream((byte[])it
                    .next()));
                X509CRL crl = (X509CRL)parser.engineRead();
                if (xselector.match((Object)crl))
                {
                    crlSet.add(crl);
                }
            }
            catch (StreamParsingException e)
            {

            }
        }

        return crlSet;
    }

    private Set createCrossCertificatePairs(List list,
                                            X509CertPairStoreSelector xselector) throws StoreException
    {
        Set certPairSet = new HashSet();

        int i = 0;
        while (i < list.size())
        {
            X509CertificatePair pair;
            try
            {
                // first try to decode it as certificate pair
                try
                {
                    X509CertPairParser parser = new X509CertPairParser();
                    parser.engineInit(new ByteArrayInputStream(
                        (byte[])list.get(i)));
                    pair = (X509CertificatePair)parser.engineRead();
                }
                catch (StreamParsingException e)
                {
                    // now try it to construct it the forward and reverse
                    // certificate
                    byte[] forward = (byte[])list.get(i);
                    byte[] reverse = (byte[])list.get(i + 1);
                    pair = new X509CertificatePair(new CertificatePair(
                        Certificate
                            .getInstance(new ASN1InputStream(
                            forward).readObject()),
                        Certificate
                            .getInstance(new ASN1InputStream(
                                reverse).readObject())));
                    i++;
                }
                if (xselector.match((Object)pair))
                {
                    certPairSet.add(pair);
                }
            }
            catch (CertificateParsingException e)
            {
                // try next
            }
            catch (IOException e)
            {
                // try next
            }
            i++;
        }

        return certPairSet;
    }

    private Set createAttributeCertificates(List list,
                                            X509AttributeCertStoreSelector xselector) throws StoreException
    {
        Set certSet = new HashSet();

        Iterator it = list.iterator();
        X509AttrCertParser parser = new X509AttrCertParser();
        while (it.hasNext())
        {
            try
            {
                parser.engineInit(new ByteArrayInputStream((byte[])it
                    .next()));
                X509AttributeCertificate cert = (X509AttributeCertificate)parser
                    .engineRead();
                if (xselector.match((Object)cert))
                {
                    certSet.add(cert);
                }
            }
            catch (StreamParsingException e)
            {

            }
        }

        return certSet;
    }

    /**
     * Returns the CRLs for issued certificates for other CAs matching the given
     * selector. <br>
     * The authorityRevocationList attribute includes revocation information
     * regarding certificates issued to other CAs.
     *
     * @param selector The CRL selector to use to find the CRLs.
     * @return A possible empty collection with CRLs
     * @throws StoreException
     */
    public Collection getAuthorityRevocationLists(X509CRLStoreSelector selector)
        throws StoreException
    {
        String[] attrs = splitString(params.getAuthorityRevocationListAttribute());
        String attrNames[] = splitString(params
            .getLdapAuthorityRevocationListAttributeName());
        String issuerAttributeNames[] = splitString(params
            .getAuthorityRevocationListIssuerAttributeName());

        List list = cRLIssuerSearch(selector, attrs, attrNames,
            issuerAttributeNames);
        Set resultSet = createCRLs(list, selector);
        if (resultSet.size() == 0)
        {
            X509CRLStoreSelector emptySelector = new X509CRLStoreSelector();
            list = cRLIssuerSearch(emptySelector, attrs, attrNames,
                issuerAttributeNames);

            resultSet.addAll(createCRLs(list, selector));
        }
        return resultSet;
    }

    /**
     * Returns the revocation list for revoked attribute certificates.
     * <p/>
     * The attributeCertificateRevocationList holds a list of attribute
     * certificates that have been revoked.
     *
     * @param selector The CRL selector to use to find the CRLs.
     * @return A possible empty collection with CRLs.
     * @throws StoreException
     */
    public Collection getAttributeCertificateRevocationLists(
        X509CRLStoreSelector selector) throws StoreException
    {
        String[] attrs = splitString(params
            .getAttributeCertificateRevocationListAttribute());
        String attrNames[] = splitString(params
            .getLdapAttributeCertificateRevocationListAttributeName());
        String issuerAttributeNames[] = splitString(params
            .getAttributeCertificateRevocationListIssuerAttributeName());

        List list = cRLIssuerSearch(selector, attrs, attrNames,
            issuerAttributeNames);
        Set resultSet = createCRLs(list, selector);
        if (resultSet.size() == 0)
        {
            X509CRLStoreSelector emptySelector = new X509CRLStoreSelector();
            list = cRLIssuerSearch(emptySelector, attrs, attrNames,
                issuerAttributeNames);

            resultSet.addAll(createCRLs(list, selector));
        }
        return resultSet;
    }

    /**
     * Returns the revocation list for revoked attribute certificates for an
     * attribute authority
     * <p/>
     * The attributeAuthorityList holds a list of AA certificates that have been
     * revoked.
     *
     * @param selector The CRL selector to use to find the CRLs.
     * @return A possible empty collection with CRLs
     * @throws StoreException
     */
    public Collection getAttributeAuthorityRevocationLists(
        X509CRLStoreSelector selector) throws StoreException
    {
        String[] attrs = splitString(params.getAttributeAuthorityRevocationListAttribute());
        String attrNames[] = splitString(params
            .getLdapAttributeAuthorityRevocationListAttributeName());
        String issuerAttributeNames[] = splitString(params
            .getAttributeAuthorityRevocationListIssuerAttributeName());

        List list = cRLIssuerSearch(selector, attrs, attrNames,
            issuerAttributeNames);
        Set resultSet = createCRLs(list, selector);
        if (resultSet.size() == 0)
        {
            X509CRLStoreSelector emptySelector = new X509CRLStoreSelector();
            list = cRLIssuerSearch(emptySelector, attrs, attrNames,
                issuerAttributeNames);

            resultSet.addAll(createCRLs(list, selector));
        }
        return resultSet;
    }

    /**
     * Returns cross certificate pairs.
     *
     * @param selector The selector to use to find the cross certificates.
     * @return A possible empty collection with {@link X509CertificatePair}s
     * @throws StoreException
     */
    public Collection getCrossCertificatePairs(
        X509CertPairStoreSelector selector) throws StoreException
    {
        String[] attrs = splitString(params.getCrossCertificateAttribute());
        String attrNames[] = splitString(params.getLdapCrossCertificateAttributeName());
        String subjectAttributeNames[] = splitString(params
            .getCrossCertificateSubjectAttributeName());
        List list = crossCertificatePairSubjectSearch(selector, attrs,
            attrNames, subjectAttributeNames);
        Set resultSet = createCrossCertificatePairs(list, selector);
        if (resultSet.size() == 0)
        {
            X509CertStoreSelector emptyCertselector = new X509CertStoreSelector();
            X509CertPairStoreSelector emptySelector = new X509CertPairStoreSelector();

            emptySelector.setForwardSelector(emptyCertselector);
            emptySelector.setReverseSelector(emptyCertselector);
            list = crossCertificatePairSubjectSearch(emptySelector, attrs,
                attrNames, subjectAttributeNames);
            resultSet.addAll(createCrossCertificatePairs(list, selector));
        }
        return resultSet;
    }

    /**
     * Returns end certificates.
     * <p/>
     * The attributeDescriptorCertificate is self signed by a source of
     * authority and holds a description of the privilege and its delegation
     * rules.
     *
     * @param selector The selector to find the certificates.
     * @return A possible empty collection with certificates.
     * @throws StoreException
     */
    public Collection getUserCertificates(X509CertStoreSelector selector)
        throws StoreException
    {
        String[] attrs = splitString(params.getUserCertificateAttribute());
        String attrNames[] = splitString(params.getLdapUserCertificateAttributeName());
        String subjectAttributeNames[] = splitString(params
            .getUserCertificateSubjectAttributeName());

        List list = certSubjectSerialSearch(selector, attrs, attrNames,
            subjectAttributeNames);
        Set resultSet = createCerts(list, selector);
        if (resultSet.size() == 0)
        {
            X509CertStoreSelector emptySelector = new X509CertStoreSelector();
            list = certSubjectSerialSearch(emptySelector, attrs, attrNames,
                subjectAttributeNames);
            resultSet.addAll(createCerts(list, selector));
        }

        return resultSet;
    }

    /**
     * Returns attribute certificates for an attribute authority
     * <p/>
     * The aAcertificate holds the privileges of an attribute authority.
     *
     * @param selector The selector to find the attribute certificates.
     * @return A possible empty collection with attribute certificates.
     * @throws StoreException
     */
    public Collection getAACertificates(X509AttributeCertStoreSelector selector)
        throws StoreException
    {
        String[] attrs = splitString(params.getAACertificateAttribute());
        String attrNames[] = splitString(params.getLdapAACertificateAttributeName());
        String subjectAttributeNames[] = splitString(params.getAACertificateSubjectAttributeName());

        List list = attrCertSubjectSerialSearch(selector, attrs, attrNames,
            subjectAttributeNames);
        Set resultSet = createAttributeCertificates(list, selector);
        if (resultSet.size() == 0)
        {
            X509AttributeCertStoreSelector emptySelector = new X509AttributeCertStoreSelector();
            list = attrCertSubjectSerialSearch(emptySelector, attrs, attrNames,
                subjectAttributeNames);
            resultSet.addAll(createAttributeCertificates(list, selector));
        }

        return resultSet;
    }

    /**
     * Returns an attribute certificate for an authority
     * <p/>
     * The attributeDescriptorCertificate is self signed by a source of
     * authority and holds a description of the privilege and its delegation
     * rules.
     *
     * @param selector The selector to find the attribute certificates.
     * @return A possible empty collection with attribute certificates.
     * @throws StoreException
     */
    public Collection getAttributeDescriptorCertificates(
        X509AttributeCertStoreSelector selector) throws StoreException
    {
        String[] attrs = splitString(params.getAttributeDescriptorCertificateAttribute());
        String attrNames[] = splitString(params
            .getLdapAttributeDescriptorCertificateAttributeName());
        String subjectAttributeNames[] = splitString(params
            .getAttributeDescriptorCertificateSubjectAttributeName());

        List list = attrCertSubjectSerialSearch(selector, attrs, attrNames,
            subjectAttributeNames);
        Set resultSet = createAttributeCertificates(list, selector);
        if (resultSet.size() == 0)
        {
            X509AttributeCertStoreSelector emptySelector = new X509AttributeCertStoreSelector();
            list = attrCertSubjectSerialSearch(emptySelector, attrs, attrNames,
                subjectAttributeNames);
            resultSet.addAll(createAttributeCertificates(list, selector));
        }

        return resultSet;
    }

    /**
     * Returns CA certificates.
     * <p/>
     * The cACertificate attribute of a CA's directory entry shall be used to
     * store self-issued certificates (if any) and certificates issued to this
     * CA by CAs in the same realm as this CA.
     *
     * @param selector The selector to find the certificates.
     * @return A possible empty collection with certificates.
     * @throws StoreException
     */
    public Collection getCACertificates(X509CertStoreSelector selector)
        throws StoreException
    {
        String[] attrs = splitString(params.getCACertificateAttribute());
        String attrNames[] = splitString(params.getLdapCACertificateAttributeName());
        String subjectAttributeNames[] = splitString(params
            .getCACertificateSubjectAttributeName());
        List list = certSubjectSerialSearch(selector, attrs, attrNames,
            subjectAttributeNames);
        Set resultSet = createCerts(list, selector);
        if (resultSet.size() == 0)
        {
            X509CertStoreSelector emptySelector = new X509CertStoreSelector();
            list = certSubjectSerialSearch(emptySelector, attrs, attrNames,
                subjectAttributeNames);
            resultSet.addAll(createCerts(list, selector));
        }
        return resultSet;
    }

    /**
     * Returns the delta revocation list for revoked certificates.
     *
     * @param selector The CRL selector to use to find the CRLs.
     * @return A possible empty collection with CRLs.
     * @throws StoreException
     */
    public Collection getDeltaCertificateRevocationLists(
        X509CRLStoreSelector selector) throws StoreException
    {
        String[] attrs = splitString(params.getDeltaRevocationListAttribute());
        String attrNames[] = splitString(params.getLdapDeltaRevocationListAttributeName());
        String issuerAttributeNames[] = splitString(params
            .getDeltaRevocationListIssuerAttributeName());
        List list = cRLIssuerSearch(selector, attrs, attrNames,
            issuerAttributeNames);
        Set resultSet = createCRLs(list, selector);
        if (resultSet.size() == 0)
        {
            X509CRLStoreSelector emptySelector = new X509CRLStoreSelector();
            list = cRLIssuerSearch(emptySelector, attrs, attrNames,
                issuerAttributeNames);

            resultSet.addAll(createCRLs(list, selector));
        }
        return resultSet;
    }

    /**
     * Returns an attribute certificate for an user.
     * <p/>
     * The attributeCertificateAttribute holds the privileges of a user
     *
     * @param selector The selector to find the attribute certificates.
     * @return A possible empty collection with attribute certificates.
     * @throws StoreException
     */
    public Collection getAttributeCertificateAttributes(
        X509AttributeCertStoreSelector selector) throws StoreException
    {
        String[] attrs = splitString(params.getAttributeCertificateAttributeAttribute());
        String attrNames[] = splitString(params
            .getLdapAttributeCertificateAttributeAttributeName());
        String subjectAttributeNames[] = splitString(params
            .getAttributeCertificateAttributeSubjectAttributeName());
        List list = attrCertSubjectSerialSearch(selector, attrs, attrNames,
            subjectAttributeNames);
        Set resultSet = createAttributeCertificates(list, selector);
        if (resultSet.size() == 0)
        {
            X509AttributeCertStoreSelector emptySelector = new X509AttributeCertStoreSelector();
            list = attrCertSubjectSerialSearch(emptySelector, attrs, attrNames,
                subjectAttributeNames);
            resultSet.addAll(createAttributeCertificates(list, selector));
        }

        return resultSet;
    }

    /**
     * Returns the certificate revocation lists for revoked certificates.
     *
     * @param selector The CRL selector to use to find the CRLs.
     * @return A possible empty collection with CRLs.
     * @throws StoreException
     */
    public Collection getCertificateRevocationLists(
        X509CRLStoreSelector selector) throws StoreException
    {
        String[] attrs = splitString(params.getCertificateRevocationListAttribute());
        String attrNames[] = splitString(params
            .getLdapCertificateRevocationListAttributeName());
        String issuerAttributeNames[] = splitString(params
            .getCertificateRevocationListIssuerAttributeName());
        List list = cRLIssuerSearch(selector, attrs, attrNames,
            issuerAttributeNames);
        Set resultSet = createCRLs(list, selector);
        if (resultSet.size() == 0)
        {
            X509CRLStoreSelector emptySelector = new X509CRLStoreSelector();
            list = cRLIssuerSearch(emptySelector, attrs, attrNames,
                issuerAttributeNames);

            resultSet.addAll(createCRLs(list, selector));
        }
        return resultSet;
    }

    private Map cacheMap = new HashMap(cacheSize);

    private static int cacheSize = 32;

    private static long lifeTime = 60 * 1000;

    private synchronized void addToCache(String searchCriteria, List list)
    {
        Date now = new Date(System.currentTimeMillis());
        List cacheEntry = new ArrayList();
        cacheEntry.add(now);
        cacheEntry.add(list);
        if (cacheMap.containsKey(searchCriteria))
        {
            cacheMap.put(searchCriteria, cacheEntry);
        }
        else
        {
            if (cacheMap.size() >= cacheSize)
            {
                // replace oldest
                Iterator it = cacheMap.entrySet().iterator();
                long oldest = now.getTime();
                Object replace = null;
                while (it.hasNext())
                {
                    Map.Entry entry = (Map.Entry)it.next();
                    long current = ((Date)((List)entry.getValue()).get(0))
                        .getTime();
                    if (current < oldest)
                    {
                        oldest = current;
                        replace = entry.getKey();
                    }
                }
                cacheMap.remove(replace);
            }
            cacheMap.put(searchCriteria, cacheEntry);
        }
    }

    private List getFromCache(String searchCriteria)
    {
        List entry = (List)cacheMap.get(searchCriteria);
        long now = System.currentTimeMillis();
        if (entry != null)
        {
            // too old
            if (((Date)entry.get(0)).getTime() < (now - lifeTime))
            {
                return null;
            }
            return (List)entry.get(1);
        }
        return null;
    }

    /*
     * spilt string based on spaces
     */
    private String[] splitString(String str)
    {
        return str.split("\\s+");
    }

    private String getSubjectAsString(X509CertStoreSelector xselector)
    {
        try
        {
            byte[] encSubject = xselector.getSubjectAsBytes();
            if (encSubject != null)
            {
                return new X500Principal(encSubject).getName("RFC1779");
            }
        }
        catch (IOException e)
        {
            throw new StoreException("exception processing name: " + e.getMessage(), e);
        }
        return null;
    }

    private X500Principal getCertificateIssuer(X509Certificate cert)
    {
        return cert.getIssuerX500Principal();
    }
}
