/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/security/admin/URMGate.java,v 1.3 2004/07/30 06:52:01 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2004/07/30 06:52:01 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed 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.slide.store.tamino.security.admin;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import org.apache.slide.common.Service;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.macro.ConflictException;
import org.apache.slide.macro.ForbiddenException;
import org.apache.slide.store.tamino.tools.stores.ActionDeclaration;
import org.apache.slide.urm.URMException;
import org.apache.slide.urm.URMForbiddenException;
import org.apache.slide.urm.URMNotImplementedException;
import org.apache.slide.urm.accesscontroler.URMAclAdministrator;
import org.apache.slide.urm.accesscontroler.URMAction;
import org.apache.slide.urm.authenticator.URMAdministrator;
import org.apache.slide.urm.authenticator.URMAuthenticator;
import org.apache.slide.urm.authenticator.URMSubject;
import org.apache.slide.urm.authenticator.rolemanager.URMRole;
import org.apache.slide.urm.authenticator.userdb.URMGroup;
import org.apache.slide.urm.authenticator.userdb.URMUser;
import org.apache.slide.urm.common.URMConstants;
import org.apache.slide.urm.common.URMDeleteException;
import org.apache.slide.urm.common.URMInternalServerException;
import org.apache.slide.urm.common.URMPrincipal;
import org.apache.slide.urm.common.URMSetRoleException;
import org.apache.slide.urm.common.URMUpdateException;
import org.apache.slide.util.JDom;
import org.apache.slide.util.XMLValue;
import org.apache.slide.util.XUri;
import org.apache.slide.webdav.util.AclConstants;
import org.jdom.Element;
import org.jdom.JDOMException;

/**
 ** URMGate represents an gate to the URM API
 **
 ** @author    josef.haiduk@softwareag.com
 ** @version   $Revision: 1.3 $
 **
 **/

class URMGate
{
    static String[] mappedPropertyNames = {
        AclConstants.C_DISPLAYNAME,
        AclConstants.P_ALTERNATE_URI_SET,
        NodeRevisionDescriptor.CREATION_DATE,
        NodeRevisionDescriptor.LAST_MODIFIED,
        NodeRevisionDescriptor.MODIFICATION_DATE,
        NodeRevisionDescriptor.CREATION_USER,
        NodeRevisionDescriptor.MODIFICATION_USER
    };

    static URMPrincipal guest = null;
    URMAdministrator urmAdmWithAdminRole = null;
    URMAdministrator urmAdm = null;
    URMAclAdministrator urmAclAdm = null;
    URMAclAdministrator urmAclAdmWithAdminRole = null;
    URMPrincipal principal = null;
    URMUserDBStore parent;
    XUri uri;
    String uriStr;
    String domain = null;
    
    
    /**
     * Creates a new gate to the URM API
     *
     * @param    parent              an URMUserDBStore
     * @param    uriStr              a  String
     * @param    principal           an URMPrincipal
     *
     * @throws   ServiceAccessException
     *
     */
    URMGate(URMUserDBStore parent, String uriStr, URMPrincipal principal)
        throws ServiceAccessException
    {
        this.parent = parent;
        this.principal = principal;
        try
        {
            urmAdm = principal.getURMAdministrator();
            urmAclAdm = principal.getURMAclAdministrator();
        }
        catch (URMException e)
        {
            throw new ServiceAccessException(parent,e);
        }
        domain = principal.getDomain();
        this.uriStr = uriStr;
        this.uri = new XUri(uriStr);
    }
    
    /**
     * Creates a new gate to the URM API
     *
     * @param    parent              an URMUserDBStore
     * @param    uriStr              a  String
     *
     * @throws   ServiceAccessException
     *
     */
    URMGate(URMUserDBStore parent, String uriStr)
        throws ServiceAccessException
    {
        this.parent = parent;
        principal = parent.getPrincipal();
        try
        {
            urmAdm = principal.getURMAdministrator();
            urmAclAdm = principal.getURMAclAdministrator();
        }
        catch (URMException e)
        {
            throw new ServiceAccessException(parent,e);
        }
        domain = principal.getDomain();
        this.uriStr = uriStr;
        this.uri = new XUri(uriStr);
    }
    
    /**
     * getUrmAdministratorWithAdminRole returns an URMAdministrator with an Admin role
     *
     * @return   an URMAdministrator
     *
     * @throws   URMException
     * @throws   URMSetRoleException
     * @throws   URMNotImplementedException
     * @throws   URMForbiddenException
     * @throws   URMInternalServerException
     *
     */
    URMAdministrator getUrmAdministratorWithAdminRole()
        throws URMException, URMSetRoleException, URMNotImplementedException, URMForbiddenException, URMInternalServerException
    {
        if ( urmAdmWithAdminRole != null )
            return urmAdmWithAdminRole;
        
        //principal.setActiveRole(URMConstants.URM_ADMIN_ROLE);
        //urmAdmWithAdminRole = principal.getURMAdministrator();
        URMPrincipal p = principal.copyPrincipal(URMConstants.URM_ADMIN_ROLE);
        urmAdmWithAdminRole = p.getURMAdministrator();
        return urmAdmWithAdminRole;
        
    }
    
    
    /**
     * getUri returns the uri passed to the construcroe
     *
     * @return   a String
     *
     */
    String getUri()
    {
        return this.uriStr;
    }
    
    /**
     * getProperty returns the property value of a subject. A subject can be a user, group or a role
     
     * @param    subject             an URMSubject
     * @param    key                 a  String
     *
     * @return   a String
     *
     * @throws   ServiceAccessException
     *
     */
    String getProperty(URMSubject subject, String key)
        throws ServiceAccessException
    {
        
        try {
            if ( subject.getType() == URMConstants.USER )
                return (String)((URMUser)subject).getProperty(key);
            
            if ( subject.getType() == URMConstants.GROUP )
                return (String)((URMGroup)subject).getProperty(key);
            
            if ( subject.getType() == URMConstants.ROLE )
                return (String)((URMRole)subject).getProperty(key);
        }
        
        catch (URMForbiddenException e)
        {
            throw new ServiceAccessException(parent,
                        new ForbiddenException(uri.toString(), e));
        }
        catch (URMException e)
        {
            throw new ServiceAccessException(parent, e);
        }
        
        return null;
    }
    /**
     * getProperties returns the subjects properties (user, group or role)as a byte array
     *
     * @param    subject             an URMSubject
     *
     * @return   a byte[]
     *
     * @throws   ServiceAccessException
     *
     */
    byte[] getProperties( URMSubject subject)
        throws ServiceAccessException
    {
        try
        {
            int type = subject.getType();
            Properties p = null;
            if ( type == URMConstants.USER )
                p = ((URMUser)subject).getProperties();
            if ( type == URMConstants.GROUP )
                p = ((URMGroup)subject).getProperties();
            if ( type == URMConstants.ROLE )
                p = ((URMRole)subject).getProperties();
            
            if ( p == null )
                return null;
            String content = getPropertiesAsXml(p);
            return content.getBytes(JDom.UTF_8);
        }
        
        catch (Exception e)
        {
            e.printStackTrace();
            throw new ServiceAccessException(parent,e);
        }
    }
    
    /**
     * getPropertiesAsXml returns an XML representation of the properties
     *
     * @param    p                   a  Properties
     *
     * @return   a String
     *
     */
    static String getPropertiesAsXml(Properties p)
    {
        String content = "<?xml version='1.0' encoding='UTF-8'?>\n<properties>";
        
        Enumeration en = p.propertyNames();
        while (en.hasMoreElements())
        {
            String key = (String)en.nextElement();
            String value = (String)p.get(key);
            content = content +"\n" +
                "<parameter name=\""+key+"\">"+value+"</parameter>";
        }
        content = content +"\n</properties>";
        
        return content;
    }
    
    
    /**
     * updateProperties updates the subjects (user, group or role) properties
     *
     * @param    subject             an URMSubject
     * @param    newProperties       a  Properties
     *
     * @throws   ServiceAccessException
     *
     */
    void updateProperties(URMSubject subject, Properties newProperties )
        throws ServiceAccessException
    {
        if ( newProperties == null )
        {
            return;
        }
        try {
            if ( subject.getType() == URMConstants.USER )
            {
                URMUser admin = getUrmAdministratorWithAdminRole().getUser(subject.getName(), domain);
                admin.setProperties(newProperties);
                return;
            }
            if ( subject.getType() == URMConstants.GROUP )
            {
                URMGroup admin = getUrmAdministratorWithAdminRole().getGroup(subject.getName(), domain);
                admin.setProperties(newProperties);
                return;
            }
            if ( subject.getType() == URMConstants.ROLE )
            {
                URMRole admin = getUrmAdministratorWithAdminRole().getRole(subject.getName());
                admin.setProperties(newProperties);
            }
        }
        catch (URMForbiddenException e) {
            throw new ServiceAccessException(parent,
                    new ForbiddenException(uri.toString(), e));
        }
        catch (URMSetRoleException e) {
            throw new ServiceAccessException(parent,
                    new ForbiddenException(uri.toString(), e));
        }
        catch (URMUpdateException e) {
            throw new ServiceAccessException(parent,
                    new ConflictException(uri.toString(), e));
        }
        catch (URMDeleteException e) {
            throw new ServiceAccessException(parent,
                    new ConflictException(uri.toString(), e));
        }
        catch (URMException e) {
            throw new ServiceAccessException(parent, e);
        }
        
    }
    /**
     * enumerateSubjects puts the subject names prepend with th epath in to the vector
     *
     * @param    en                  an Enumeration
     * @param    v                   a  Vector
     * @param    path                a  String
     *
     * @throws   ServiceAccessException
     *
     */
    static void enumerateSubjects(Enumeration en, Vector v, String path)
        throws ServiceAccessException
    {
        while (en.hasMoreElements())
        {
            String name = ((URMSubject)en.nextElement()).getName();
            String child = new String (path +"/"+ name);
            v.add(child);
        }
    }
    
    /**
     * getAvailableProperties returns a map of available subject (user, group or role) properties
     * The values indicates if the property can be modified.
     *
     * @param    subject             an URMSubject
     *
     * @return   a Map
     *
     * @throws   ServiceAccessException
     *
     */
    Map getAvailableProperties(URMSubject subject)
        throws ServiceAccessException
    {
        try {
            Map map = null;
            if ( subject.getType() == URMConstants.USER )
            {
                map = ((URMUser)subject).getAvailablePropertyNames();
            }
            if ( subject.getType() == URMConstants.GROUP )
            {
                map = ((URMGroup)subject).getAvailablePropertyNames();
            }
            
            if ( subject.getType() == URMConstants.ROLE )
            {
                map = ((URMRole)subject).getAvailablePropertyNames();
            }
            removeMappedKeys(map);
            return map;
        }
        catch (URMForbiddenException e) {
            throw new ServiceAccessException(parent,
                    new ForbiddenException(uri.toString(), e));
        }
        catch (URMException e) {
            throw new ServiceAccessException(parent, e);
        }
    }
    
    /**
     * enumerateSubjects puts subjects prepands with its path into the vector
     *
     * @param    set                 a  Set
     * @param    v                   a  Vector
     *
     */
    void enumerateSubjects(Set set, Vector v)
    {
        Iterator i = set.iterator();
        while (i.hasNext())
        {
            URMSubject subject = (URMSubject)i.next();
            if ( subject.getType() == URMConstants.USER )
            {
                String child = new String (URMUserDBStore.getUsersPath() +"/"+ subject.getName());
                v.add(child);
            }
            if ( subject.getType() == URMConstants.GROUP )
            {
                String child = new String (URMUserDBStore.getGroupsPath() +"/"+ subject.getName());
                v.add(child);
            }
            if ( subject.getType() == URMConstants.ROLE )
            {
                String child = new String (URMUserDBStore.getRolesPath() +"/"+ subject.getName());
                v.add(child);
            }
        }
    }
    
    /**
     * enumerateActions puts the actions prepand whith its path into the vector
     *
     * @param    set                 a  Set
     * @param    v                   a  Vector
     *
     */
    void enumerateActions(Set set, Vector v)
    {
        Iterator i = set.iterator();
        while (i.hasNext())
        {
            URMAction action = (URMAction)i.next();
            String child = new String (ActionDeclaration.SCOPE +"/"+ action.getName());
            v.add(child);
        }
    }
    
    /**
     * getSubjectName returns the subjects name.
     *
     * @param    o                   an Object
     *
     * @return   a String
     *
     */
    static String getSubjectName(Object o)
    {
        URMSubject subject = (URMSubject)o;
        return subject.getName();
    }
    
    /**
     * getNewMembers returns a set of new members from the NodeRevisionDescriptor defined by name
     *
     * @param    nrd                 a  NodeRevisionDescriptor
     * @param    name                a  String representing the property containing the members
     * @param    namespace           a  String representing the namespace of the name
     *
     * @return   a HashSet
     *
     * @throws   JDOMException
     *
     */
    HashSet getNewMembers(NodeRevisionDescriptor nrd, String name, String namespace)
        throws org.jdom.JDOMException
    {
        HashSet newMembers = new HashSet();
        NodeProperty memberSet = null;
        if ( namespace == null )
            memberSet = nrd.getProperty(name);
        else
            memberSet = nrd.getProperty(name, namespace);
        
        if ( memberSet == null )
            return newMembers;
        Object o = memberSet.getValue();
        if ( o == newMembers )
            return newMembers;
        if ( !o.getClass().isInstance("") )
            return newMembers;
        
        XMLValue xv = new XMLValue((String)o);
        Iterator i = xv.iterator();

        while ( i.hasNext() )
        {
            Object obj = i.next();
            if (obj instanceof Element) {
                Element element = (Element) obj;
                String member = getUriFromScope(element.getTextTrim());
                newMembers.add(member);
            }
        }
        return newMembers;
    }
    
    /**
     * updateParents the parents of this role; users, groups and roles
     *
     * @param    nrd                 a  NodeRevisionDescriptor
     *
     * @throws   ServiceAccessException
     *
     */
    public void updateParents(URMSubject subject, NodeRevisionDescriptor nrd)
        throws ServiceAccessException {
        try {
            HashSet newParents = getNewMembers(nrd, AclConstants.P_GROUP_MEMBERSHIP, null);
            Iterator i = newParents.iterator();
            while ( i.hasNext() ) {
                String parentsUri = getUriFromScope((String)i.next());
                URMParent urmParent = new URMParent(parent);
                urmParent.prepareToAdd(subject, parentsUri);
                urmParent.add();
            }
        }
           
        catch (JDOMException e) {
            throw new ServiceAccessException(parent, e);
        }
    }
    
    /**
     * getUriFromScope returns a string startung with /administration/security/....
     *
     * @param    uriStr              a  String
     *
     * @return   a String
     *
     */
    String getUriFromScope(String uriStr)
    {
        XUri x = new XUri(uriStr);
        if ( x.firstSegment().compareTo(URMUserDBStore.getUserDBUri().firstSegment()) == 0 )
            return x.toString();
        else
            return x.suburi(1).toString();
        
    }
    
    /**
     * getModifiableProperties returns a set of mofifiable properties using the map which
     * contains property names and mofifiable flag
     *
     * @param    map                 a  Map
     *
     * @return   a Set
     *
     */
    Set getModifiableProperties(Map map)
    {
        Set keys = map.keySet();
        Iterator i = keys.iterator();
        HashSet propertySet = new HashSet();
        while (i.hasNext() )
        {
            String key = (String)i.next();
            Boolean modifyable = (Boolean)map.get(key);
            if ( modifyable.booleanValue() )
                propertySet.add(key);
        }
        return propertySet;
    }
    
    /**
     * removeMappedKeys removes the mapped properties from the map
     *
     * @param    map                 a  Map
     *
     */
    static void removeMappedKeys(Map map)
    {
        for ( int i=0; i<mappedPropertyNames.length; i++)
        {
            map.remove(mappedPropertyNames[i]);
        }
    }
    
    /**
     * insertMappedProperties inserts mapped properties into the NodeRevisionDescriptor
     *
     * @param    nrd                 a  NodeRevisionDescriptor
     * @param    subject             an URMSubject
     *
     * @throws   ServiceAccessException
     *
     */
    void insertMappedProperties(NodeRevisionDescriptor nrd, URMSubject subject)
        throws ServiceAccessException
    {
        for ( int i=0; i<mappedPropertyNames.length; i++) {
            String value = getProperty(subject,mappedPropertyNames[i]);
            if ( value != null )
                nrd.setProperty(mappedPropertyNames[i], value);
        }
    }
    
    /**
     * getPropertiesToModify returns properties which have to be modified
     *
     * @param    nrd                 a  NodeRevisionDescriptor
     *
     * @return   a Properties
     *
     */
    static Properties getPropertiesToModify(NodeRevisionDescriptor nrd)
    {
        Properties pr = new Properties();
        for ( int i=0; i<mappedPropertyNames.length; i++) {
            String key = mappedPropertyNames[i];
            NodeProperty p = nrd.getProperty(key);
            if ( p != null ) {
                String v = (String)p.getValue();
                if ( v.compareTo("") != 0 )
                    pr.put(key, v);
            }
        }
        return pr;
        
    }
    
    /**
     * megreMembers
     *
     * @param    newMembers          a  HashSet
     * @param    subset              a  HashSet
     * @param    path                a  String
     *
     */
    static void megreMembers(HashSet newMembers, HashSet subset, String path)
    {
        HashSet set = (HashSet)subset.clone();
        Iterator i = set.iterator();
        while ( i.hasNext() ) {
            URMSubject subject = (URMSubject)i.next();
            String member = path+"/"+subject.getName();
            if ( newMembers.contains(member) ) {
                newMembers.remove(member);
                subset.remove(subject);
            }
        }
    }
    
    /**
     * Method megreActions
     *
     * @param    newMembers          a  HashSet
     * @param    subset              a  HashSet
     * @param    path                a  String
     *
     */
    static void megreActions(HashSet newMembers, HashSet subset, String path)
    {
        HashSet set = (HashSet)subset.clone();
        Iterator i = set.iterator();
        while ( i.hasNext() ) {
            URMAction subject = (URMAction)i.next();
            String member = path+"/"+subject.getName();
            if ( newMembers.contains(member) ) {
                newMembers.remove(member);
                subset.remove(subject);
            }
        }
    }
    
    /**
     * Method megreStrings
     *
     * @param    newMembers          a  HashSet
     * @param    subset              a  HashSet
     * @param    path                a  String
     *
     */
    static void megreStrings(HashSet newMembers, HashSet subset, String path)
    {
        HashSet set = (HashSet)subset.clone();
        Iterator i = set.iterator();
        while ( i.hasNext() ) {
            String subject = (String)i.next();
            String member = path+"/"+subject;
            if ( newMembers.contains(member) ) {
                newMembers.remove(member);
                subset.remove(subject);
            }
        }
    }
    
    /**
     * getGuest returns the guest as URMPrincipal
     *
     * @param    parent              a  Service
     *
     * @return   an URMPrincipal
     *
     * @throws   ServiceAccessException
     *
     */
    static URMPrincipal getGuest(Service parent)
        throws ServiceAccessException
    {
        if ( guest != null )
            return guest;
        try {
            URMAuthenticator authenticator = URMAuthenticator.newInstance();
            guest = authenticator.authenticateUser();
            return guest;
        }
        catch (URMException e) {
            e.printStackTrace();
            throw new ServiceAccessException(parent, e);
        }
    }
    
    public void synchronize()
        throws ServiceAccessException
    {
        try {
            getUrmAdministratorWithAdminRole().synchronizeUsersGroupsRoles();
        }
            
        catch (URMSetRoleException e)
        {
            throw new ServiceAccessException(parent,
                    new ForbiddenException(uriStr, e));
        }
        catch (URMForbiddenException e)
        {
            throw new ServiceAccessException(parent,
                    new ForbiddenException(uriStr, e));
        }
        catch (URMException e)
        {
            throw new ServiceAccessException(parent,e);
        }
    }
}


