/* 
 * 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.jetspeed.security.mapping.ldap;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchResult;

import org.apache.commons.lang.StringUtils;
import org.apache.jetspeed.security.JetspeedPrincipal;
import org.apache.jetspeed.security.SecurityAttribute;
import org.apache.jetspeed.security.SecurityAttributes;
import org.apache.jetspeed.security.mapping.EntityFactory;
import org.apache.jetspeed.security.mapping.ldap.dao.LDAPEntityDAOConfiguration;
import org.apache.jetspeed.security.mapping.model.Attribute;
import org.apache.jetspeed.security.mapping.model.AttributeDef;
import org.apache.jetspeed.security.mapping.model.Entity;
import org.apache.jetspeed.security.mapping.model.impl.AttributeImpl;
import org.apache.jetspeed.security.mapping.model.impl.EntityImpl;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapRdn;
import org.springframework.ldap.support.LdapUtils;

/**
 * @author <a href="mailto:ddam@apache.org">Dennis Dam</a>
 * @version $Id: EntityFactoryImpl.java 1101715 2011-05-11 01:14:36Z ate $
 */
public class EntityFactoryImpl implements EntityFactory
{
    private LDAPEntityDAOConfiguration searchConfiguration;
    private boolean createAllowed = true;
    private boolean updateAllowed = true;
    private boolean removeAllowed = true;

    public EntityFactoryImpl(LDAPEntityDAOConfiguration searchConfiguration)
    {
        this.searchConfiguration = searchConfiguration;
    }
    
    public String getEntityType()
    {
        return searchConfiguration.getEntityType();
    }

    public boolean isCreateAllowed()
    {
        return createAllowed;
    }

    public void setCreateAllowed(boolean createAllowed)
    {
        this.createAllowed = createAllowed;
    }

    public boolean isUpdateAllowed()
    {
        return updateAllowed;
    }

    public void setUpdateAllowed(boolean updateAllowed)
    {
        this.updateAllowed = updateAllowed;
    }

    public boolean isRemoveAllowed()
    {
        return removeAllowed;
    }

    public void setRemoveAllowed(boolean removeAllowed)
    {
        this.removeAllowed = removeAllowed;
    }

    protected EntityImpl internalCreateEntity(String entityId, String internalId, Set<Attribute> attributes)
    {
        EntityImpl entity = new EntityImpl(searchConfiguration.getEntityType(), entityId, searchConfiguration.getAttributeDefinitionsMap());
        entity.setAttributes(attributes);
        if (internalId != null)
        {
            entity.setInternalId(internalId);
        }
        return entity;
    }

    public Entity createEntity(JetspeedPrincipal principal)
    {
        Set<Attribute> ldapAttrValues = new HashSet<Attribute>();
        SecurityAttributes sas = principal.getSecurityAttributes();
        for (AttributeDef attrDef : searchConfiguration.getEntityAttributeDefinitionsMap().values())
        {
            if (attrDef.isMapped())
            {
                SecurityAttribute sa = sas.getAttribute(attrDef.getMappedName());
                if (sa != null)
                {
                    // currently only single-valued attributes are supported
                    AttributeImpl attr = new AttributeImpl(attrDef);
                    attr.setValue(sa.getStringValue());
                    ldapAttrValues.add(attr);
                }
            }
        }
        return internalCreateEntity(principal.getName(), null, ldapAttrValues);
    }

    protected List<String> getStringAttributes(Attributes originalAttrs, String name, boolean containsDN)
    {
        ArrayList<String> attributes = null;
        javax.naming.directory.Attribute attribute = originalAttrs.get(name);
        if (attribute != null)
        {
            int size = attribute.size();
            if (size > 0)
            {
                attributes = new ArrayList<String>(size);
                for (int i = 0; i < size; i++)
                {
                    try
                    {
                        String value = (String) attribute.get(i);
                        if (containsDN && !StringUtils.isEmpty(value))
                        {
                            // ensure dn values are all always equally encoded so we can use values.contains(internalId)
                            value = new DistinguishedName(value).toCompactString();
                        }
                        attributes.add(value);
                    }
                    catch (NamingException e)
                    {
                        throw LdapUtils.convertLdapException(e);
                    }
                }
            }
        }
        return attributes;
    }
    
    public Entity loadEntity(Object providerContext)
    {
        Entity entity = null;
        DirContextOperations ctx = null;
        
        if (providerContext instanceof SearchResult)
        {
            ctx = (DirContextOperations) ((SearchResult) (providerContext)).getObject();
        }
        else if (providerContext instanceof DirContextAdapter)
        {
            ctx = (DirContextOperations) providerContext;
        }
        if (ctx != null)
        {
            String entityId = null;
            String dn = ctx.getNameInNamespace();
            Set<Attribute> attributes = new HashSet<Attribute>();
            Attributes attrs = ctx.getAttributes();
            for (AttributeDef attrDef : searchConfiguration.getEntityAttributeDefinitionsMap().values())
            {
                List<String> values = null;
                values = getStringAttributes(attrs, attrDef.getName(), attrDef.requiresDnDefaultValue());
                if (values != null)
                {
                    Attribute a = new AttributeImpl(attrDef);
                    if (attrDef.isMultiValue())
                    {
                        // remove the dummy value for required fields when present.
                        if (attrDef.isRequired())
                        {
                            String defaultValue = attrDef.requiresDnDefaultValue() ? dn : attrDef.getRequiredDefaultValue();
                            values.remove(defaultValue);
                        }
                        if (values.size() != 0)
                        {
                            a.setValues(values);
                        }
                        else
                        {
                            attributes.add(a);
                        }
                    }
                    else
                    {
                        String value = values.get(0);
                        if (attrDef.isEntityIdAttribute())
                        {
                            entityId = value;
                        }
                        a.setValue(value);
                    }
                    attributes.add(a);
                }
            }
            if (entityId == null)
            {
                DistinguishedName name = new DistinguishedName(dn);
                LdapRdn rdn = name.getLdapRdn(name.size() - 1);
                if (rdn.getKey().equals(searchConfiguration.getLdapIdAttribute()))
                {
                    entityId = rdn.getValue();
                }
                else
                {
                    // TODO: throw exception???
                    return null;
                }
            }
            entity = internalCreateEntity(entityId, dn, attributes);
            entity.setLive(true);
        }
        return entity;
    }
}
