/* 
 * 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.dao.impl;

import java.util.Collection;

import org.apache.commons.lang.StringUtils;
import org.apache.jetspeed.security.SecurityException;
import org.apache.jetspeed.security.mapping.EntitySearchResultHandler;
import org.apache.jetspeed.security.mapping.ldap.dao.EntityDAO;
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.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.Filter;

/**
 * DAO for fetching relationships between LDAP entities. A relationship between two entities is created by adding an attribute value to either the entity on the
 * "from" site of the relationship, or the entity on the other side ("to"). An example: a relation "hasRole" from one entity (e.g. of type "user") to another
 * entity (e.g. of type "role"), can be defined by using an attribute "role" on the user, which is a multi-value attribute. The value of that attribute uniquely
 * identifies the role (the id of the role entity). The relationship can also be defined by specifying an attribute on the role which holds the unique id of the
 * user, e.g. through a multi-value "member" attribute.
 * 
 * @author <a href="mailto:ddam@apache.org">Dennis Dam</a>
 * @version $Id: AttributeBasedRelationDAO.java 930298 2010-04-02 15:39:03Z ate $
 */
public class AttributeBasedRelationDAO extends AbstractRelationDAO
{
    private String  relationAttribute;
    private boolean useFromEntityAttribute;
    private boolean attributeContainsInternalId; // if internal ID ( = DN) is

    // not used, then the attribute
    // contains the ID(s).
    public void getRelatedEntitiesFrom(EntityDAO fromDAO, EntityDAO toDAO, Entity fromEntity, EntitySearchResultHandler handler) throws SecurityException
    {
        internalGetRelatedEntities(fromDAO, toDAO, useFromEntityAttribute, fromEntity, handler);
    }

    public void getRelatedEntitiesTo(EntityDAO fromDAO, EntityDAO toDAO, Entity toEntity, EntitySearchResultHandler handler) throws SecurityException
    {
        internalGetRelatedEntities(toDAO, fromDAO, !useFromEntityAttribute, toEntity, handler);
    }

    private void internalGetRelatedEntities(EntityDAO fromDAO, EntityDAO toDAO, boolean useFromEntityAttribute, Entity entity, 
                                           EntitySearchResultHandler handler) throws SecurityException
    {
        if (useFromEntityAttribute)
        {
            Attribute relationAttrValue = entity.getAttribute(relationAttribute);
            if (relationAttrValue != null)
            {
                Collection<String> values = relationAttrValue.getValues();
                AttributeDef attrDef = relationAttrValue.getDefinition();
                if (attrDef.isMultiValue() && attrDef.isRequired())
                {
                    String defaultValue = attrDef.requiresDnDefaultValue() ? entity.getInternalId() : attrDef.getRequiredDefaultValue();
                    values.remove(defaultValue);
                }
                if (attributeContainsInternalId)
                {
                    toDAO.getEntitiesByInternalId(values, handler);
                }
                else
                {
                    toDAO.getEntitiesById(values, handler);
                }
            }
        }
        else
        {
            // can be either the id or the internalId of the from entity
            String fromEntityUsedIdValue = attributeContainsInternalId ? getInternalId(entity, fromDAO) : entity.getId();
            // TODO : throw exception when no ID / internal ID can be found for
            // the entity
            if (!StringUtils.isEmpty(fromEntityUsedIdValue))
            {
                // fetch entities using target Entity DAO with a specific filter
                // on the member attribute
                Filter memberAttrFilter = new EqualsFilter(relationAttribute, fromEntityUsedIdValue);
                toDAO.getEntities(memberAttrFilter, handler);
            }
        }
    }

    private String getInternalId(Entity entity, EntityDAO entityDao) throws SecurityException
    {
        if (StringUtils.isEmpty(entity.getInternalId()))
        {
            // apparently internalId is not stored in the DB => fetch it from
            // LDAP store
            entity = entityDao.getEntity(entity.getId());
        }
        return entity.getInternalId();
    }

    public void setRelationAttribute(String relationAttribute)
    {
        this.relationAttribute = relationAttribute;
    }

    public void setUseFromEntityAttribute(boolean useFromEntityAttribute)
    {
        this.useFromEntityAttribute = useFromEntityAttribute;
    }

    public void setAttributeContainsInternalId(boolean attributeContainsInternalId)
    {
        this.attributeContainsInternalId = attributeContainsInternalId;
    }

    public void addRelation(EntityDAO sourceDao, EntityDAO targetDao, String sourceEntityId, String targetEntityId) throws SecurityException
    {
        if (useFromEntityAttribute)
        {            
            sourceDao.addRelation(sourceEntityId, targetDao.getInternalId(targetEntityId, true), relationAttribute);
        }
        else
        {
            targetDao.addRelation(targetEntityId, sourceDao.getInternalId(sourceEntityId, true), relationAttribute);
        }
    }

    public void removeRelation(EntityDAO sourceDao, EntityDAO targetDao, String sourceEntityId, String targetEntityId) throws SecurityException
    {
        if (useFromEntityAttribute)
        {
            String internalEntityId = targetDao.getInternalId(targetEntityId, false);
            if (internalEntityId != null)
            {
                sourceDao.removeRelation(sourceEntityId, internalEntityId, relationAttribute);
            }
        }
        else
        {
            String internalEntityId = sourceDao.getInternalId(sourceEntityId, false);
            if (internalEntityId != null)
            {
                targetDao.removeRelation(targetEntityId, internalEntityId, relationAttribute);
            }
        }
    }

}
