/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/security/XURMAccessor.java,v 1.1 2004/03/25 16:18:03 juergen Exp $
 * $Revision: 1.1 $
 * $Date: 2004/03/25 16:18:03 $
 *
 * ====================================================================
 *
 * 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;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import javax.transaction.xa.XAException;
import org.apache.slide.common.NamespaceConfig;
import org.apache.slide.security.NodePermission;
import org.apache.slide.structure.ActionNode;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.urm.URMException;
import org.apache.slide.urm.accesscontroler.URMAce;
import org.apache.slide.urm.accesscontroler.URMAcl;
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.URMSubject;
import org.apache.slide.urm.common.URMConstants;
import org.apache.slide.urm.common.URMInternalServerException;
import org.apache.slide.urm.common.URMPrincipal;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XUri;

public class XURMAccessor {
    public static final Enumeration EMPTY_ENUM = new Vector().elements();
    
    private final NamespaceConfig config;
    private final URMPrincipal principal;
    private final URMAdministrator admin;
    private final URMAclAdministrator aclAdmin;
    
    /**
     ** Domain of the principal, may be null. Since slide does not have domain arguments,
     ** we use this whereever we need a domain. Note that this might fail for
     ** for users in global groups in Windows
     **/
    private final String domain;
    
    private boolean transactionStarted;
    
    public XURMAccessor(URMPrincipal principal, NamespaceConfig config) throws URMException {
        this.config = config;
        this.principal = principal;
        this.admin = principal.getURMAdministrator();
        this.aclAdmin = principal.getURMAclAdministrator();
        this.domain = principal.getDomain();
        this.transactionStarted = false;
    }
    
    //-- main functionality
    
    public Enumeration enumeration(String objectUri, String uuri) throws URMException {
        URMAcl acl;
        
        acl = getAcl(uuri);
        if (acl == null) {
            return EMPTY_ENUM;
        } else {
            return toSlide(objectUri, acl).elements();
        }
    }
    
    public void grant(String uuri, NodePermission permission) throws URMException {
        URMAcl acl;
        
        beginTransaction();
        if (!permission.isInheritable()) {
            throw new XAssertionFailed("not supported");
        }
        acl = getAcl(uuri);
        if (acl == null) {
            aclAdmin.createAcl(uuri,
                               toUrmSubject(principal) /* TODO: "main" branch from revisionDescriptor? pn ... */,
                               true /* always inheritable */);
            acl = getAcl(uuri);
            if (acl == null) {
                throw new XAssertionFailed(uuri);
            }
        }
        acl.defineAce(permission.isNegative()? URMConstants.DENY : URMConstants.GRANT,
                      toUrmSubject(permission),
                      toUrmAction(permission),
                      getSize(acl), permission.isInvert());
    }
    
    public void revokeAll(String uuri) throws URMException {
        URMAcl acl;
        
        beginTransaction();
        acl = getAcl(uuri);
        if (acl == null) {
            // nothing to do
        } else {
            aclAdmin.deleteAcl(uuri);
        }
    }
    
    public void revoke(String uuri, NodePermission permission) throws URMException {
        URMAcl acl;
        Vector lst;
        int i;
        int max;
        
        beginTransaction();
        acl = getAcl(uuri);
        if (acl == null) {
            // not found - ignored silently
            return;
        }
        lst = toSlide("not used", acl);
        max = lst.size();
        for (i = 0; i < max; i++) {
            if (permissionEquals(permission, (NodePermission) lst.get(i))) {
                break;
            }
        }
        if (i == max) {
            // not found - ignored silently
            return;
        }
        if (lst.size() == 1) {
            aclAdmin.deleteAcl(uuri);
        } else {
            acl.removeAce(i);
        }
    }
    
    public void endTransaction(boolean commit) throws XAException {
        if (transactionStarted) {
            try {
                aclAdmin.endTransaction(commit);
            } catch (URMException e) {
                e.printStackTrace();
                throw new XAException( XAException.XA_RBCOMMFAIL );
            }
        } else {
            // do nothing - this method can also be called if there have been
            // no modifications
        }
    }
    
    
    //-- helper code
    
    private void beginTransaction() throws URMException {
        if (transactionStarted) {
            return;
        }
        aclAdmin.beginTransaction();
        transactionStarted = true;
    }
    
    /**
     * @return never null
     */
    private NodePermission toSlide(String objectUri, URMAce ace) throws URMException {
        NodePermission permission;
        
        permission = new NodePermission(objectUri, toSlideSubjectUri(ace), toSlideActionUri(ace), true /* always inheritable */, ace.getType() == URMConstants.DENY);
        permission.setInvert(ace.getInvert());
        return permission;
    }
    
    private String toSlideSubjectUri(URMAce ace) throws URMException {
        URMSubject subject;
        
        subject = ace.getSubject();
        switch (subject.getType()) {
            case URMConstants.ALL:
                return SubjectNode.ALL_URI;
            case URMConstants.ROLE:
                if (subject.getName().equals(URMConstants.URM_GUEST_ROLE)) {
                    return SubjectNode.UNAUTHENTICATED_URI;
                }
                else {
                    return config.getRolesPath()+"/"+subject.getName();
                }
            case URMConstants.GROUP:
                return config.getGroupsPath()+"/"+subject.getName();
            case URMConstants.USER:
                return config.getUsersPath()+"/"+subject.getName();
            default:
                throw new XAssertionFailed(subject.getClass().getName());
        }
    }
    
    private String toSlideActionUri(URMAce ace) throws URMException {
        URMAction action;
        String name;
        
        action = ace.getAction();
        name = action.getName();
        if (name.equals(URMConstants.URM_ALL_ACTION)) {
            return ActionNode.ALL_URI;
        } else {
            return config.getActionsPath() + "/" + name;
        }
    }
    
    private URMSubject toUrmSubject(URMPrincipal principal) throws URMException {
        String name;
        
        name = principal.getName();
        URMSubject subj = principal.getActiveRole();
        if (subj == null) {
            // *no* special case for ALL user!
            subj = admin.getUser(name, principal.getDomain());
        }
        return subj;
    }
    
    private URMSubject toUrmSubject(NodePermission permission) throws URMException {
        URMSubject subject;
        
        subject = toUrmSubjectUnchecked(permission);
        if (subject == null) {
            throw new XAssertionFailed("no urm subject for subject uri '" + permission.getSubjectUri() + "'");
        }
        return subject;
    }
    
    private URMSubject toUrmSubjectUnchecked(NodePermission permission) throws URMException {
        String subjectUri;
        String name;
        URMSubject subject;
        XUri uri;
        String prefix;
        int size;
        
        subjectUri = permission.getSubjectUri();
        if (subjectUri == SubjectNode.ALL_URI) {
            return admin.getALLSubject();
        } else if (subjectUri == SubjectNode.SELF_URI) {
            // TODO: support special self subject in urm and return it here
            return toUrmSubject(principal);
        } else if (subjectUri == SubjectNode.OWNER_URI) {
            // TODO: support special owner subject in urm and return it here
            return toUrmSubject(principal);
        } else if (subjectUri == SubjectNode.UNAUTHENTICATED_URI) {
            return admin.getRole(URMConstants.URM_GUEST_ROLE);
        } else {
            uri = new XUri(subjectUri);
            size = uri.size();
            if (size == 0) {
                throw new URMException("empty subject: " + subjectUri);
            }
            prefix = uri.suburi(0, uri.size() - 1).toString();
            name = uri.lastSegment();
            if (prefix.equals(config.getUsersPath())) {
                subject = admin.getUser(name, domain);
                if (subject == null) {
                    throw new URMException("unkown user: " + subjectUri);
                }
            } else if (prefix.equals(config.getRolesPath())) {
                subject = admin.getRole(name);
                if (subject == null) {
                    throw new URMException("unkown role: " + subjectUri);
                }
            } else if (prefix.equals(config.getGroupsPath())) {
                subject = admin.getGroup(name, domain);
                if (subject == null) {
                    throw new URMException("unkown group: " + subjectUri);
                }
            } else {
                throw new URMException("unkown subject: " + subjectUri);
            }
            return subject;
        }
    }
    
    private URMAction toUrmAction(NodePermission permission) throws URMException {
        String actionUri;
        String name;
        URMAction action;
        
        actionUri = permission.getActionUri();
        if (actionUri == ActionNode.ALL_URI) {
            return aclAdmin.getAction(URMConstants.URM_ALL_ACTION);
        }
        name = new XUri(actionUri).lastSegment();
        action = aclAdmin.getAction(name);
        if (action == null) {
            throw new URMException("unkown action: " + name);
        }
        return action;
    }
    
    private Vector toSlide(String objectUri, URMAcl acl) throws URMException {
        List lst;
        Vector result;
        Iterator iter;
        
        
        lst = acl.getAllAces();
        result = new Vector(lst.size());
        iter = lst.iterator();
        while (iter.hasNext()) {
            result.add(toSlide(objectUri, (URMAce) iter.next()));
        }
        return result;
    }
    
    private URMAcl getAcl(String uuri) throws URMException {
        return aclAdmin.getAcl(uuri);
    }
    
    private static int getSize(URMAcl acl) throws URMInternalServerException {
        return acl.getAllAces().size();
    }
    
    /**
     * Same as NodePermission.equals, but does not compare objectUri.
     */
    private static boolean permissionEquals(NodePermission left, NodePermission right) {
        return (left.getSubjectUri().equals(right.getSubjectUri()))
            && (left.getActionUri().equals(right.getActionUri()))
            && (left.isNegative() == right.isNegative());
    }
}

