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

import java.security.Principal;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import org.apache.slide.authenticate.CredentialsToken;
import org.apache.slide.common.AbstractService;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceConnectionFailedException;
import org.apache.slide.common.ServiceDisconnectionFailedException;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.ServiceResetFailedException;
import org.apache.slide.common.Uri;
import org.apache.slide.security.NodePermission;
import org.apache.slide.store.SecurityStore;
import org.apache.slide.store.Store;
import org.apache.slide.store.tamino.common.IDescriptors;
import org.apache.slide.store.tamino.security.XURMAccessor;
import org.apache.slide.urm.URMException;
import org.apache.slide.urm.common.URMPrincipal;
import org.apache.slide.util.Configuration;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XThreadMap;

/**
 * Does not extend XChildStore because the parent is not necessarily an
 * XParentStore.
 */
public class XPermissionStore extends AbstractService implements SecurityStore {
    /** stores state for connected stores - remains empty if security is disabled */
    private final XThreadMap accessors;
    
    /** true if security is enabled **/
    private boolean enabled;
    
    /** null if parent store is not an XParentStore */
    private XDescriptorsStore descriptorsStore;
    
    public XPermissionStore() {
        this.accessors = new XThreadMap();
        this.descriptorsStore = null;
    }
    
    //--
    
    public void setParameters(Hashtable parameters) {
        // ignored
    }
    
    public synchronized void initialize(NamespaceAccessToken token) throws ServiceInitializationFailedException {
        this.enabled = Configuration.useIntegratedSecurity();
        
        super.initialize( token );
    }
    
    /** indicates that a connect call is needed */
    public boolean isConnected() {
        return !enabled || accessors.get() != null;
    }
    
    /**
     * Connects only the proper credentials are supplied.
     */
    public synchronized void connect(CredentialsToken credentialsToken) throws ServiceConnectionFailedException {
        Principal principal;
        Store store;
        
        if (isConnected()) {
            // Slide issues multiple connect call for the same store in the same
            // thread. We process only the first
            return;
        }
        if (!enabled) {
            throw new XAssertionFailed("a disabled XPermissionStore is always connected");
        }
        store = namespace.getStore(scope);
        if (store instanceof XParentStore) {
            descriptorsStore = ((XParentStore) store).getDescriptorsStore();
        } else {
            descriptorsStore = null;
        }
        if (credentialsToken != null) {
            principal = credentialsToken.getPrincipal();
            if (principal instanceof URMPrincipal) {
                try {
                    accessors.add(new XURMAccessor((URMPrincipal) principal, namespace.getConfig()));
                } catch (URMException e) {
                    throw new ServiceConnectionFailedException(this, e);
                }
            } else {
                // no suitable credentials to connect -- do nothing
            }
        } else {
            // no suitable credentials to connect -- do nothing
        }
    }
    
    public int prepare(Xid xid) throws XAException {
        try {
            return super.prepare( xid );
        } catch (XAException e) {
            if (enabled) {
                accessors.remove();
            }
            throw e;
        }
    }
    
    public void commit( Xid xid, boolean onePhase ) throws XAException {
        try {
            super.commit( xid, onePhase );
            if (enabled) {
                accessor().endTransaction(true);
            }
        } finally {
            if (enabled) {
                accessors.remove();
            }
        }
    }
    
    public void rollback( Xid xid ) throws XAException {
        try {
            super.rollback( xid );
            if (enabled) {
                accessor().endTransaction(false);
            }
        } finally {
            if (enabled) {
                accessors.remove();
            }
        }
    }
    
    //--
    
    public void disconnect() throws ServiceDisconnectionFailedException {
        throw new XAssertionFailed("no longer called");
    }
    
    public synchronized void reset() throws ServiceResetFailedException {
        throw new XAssertionFailed("no longer called");
    }
    
    public synchronized void connect() throws ServiceConnectionFailedException {
        throw new XAssertionFailed("no longer called");
    }
    
    //--
    
    public void grantPermission(Uri uri, NodePermission permission) throws ServiceAccessException {
        assertEnabled();
        try {
            accessor().grant(getUuri(uri, true), permission);
        } catch (URMException e) {
            throw new ServiceAccessException(this, e);
        }
    }
    
    public void revokePermission(Uri uri, NodePermission permission) throws ServiceAccessException {
        assertEnabled();
        try {
            accessor().revoke(getUuri(uri, true), permission);
        } catch (URMException e) {
            throw new ServiceAccessException(this, e);
        }
    }
    
    public void revokePermissions(Uri uri) throws ServiceAccessException {
        assertEnabled();
        try {
            accessor().revokeAll(getUuri(uri, true));
        } catch (URMException e) {
            throw new ServiceAccessException(this, e);
        }
    }
    
    /**
     * Enumerate permissions on the resource given by uri.
     *
     * @param uri Uri of the resource
     * @return permissions as enumeration
     * @exception ServiceAccessException Error accessing the Descriptors Store
     */
    public Enumeration enumeratePermissions(Uri uri) throws ServiceAccessException {
        if (!enabled) {
            // TODO: slide shouldn't call enumPermissions if security is switched off.
            // If we can get rid of these calls we can use
            //    assertEnabled()
            // here instead of:
            return XURMAccessor.EMPTY_ENUM;
        }
        try {
            return accessor().enumeration(uri.toString(), getUuri(uri, false));
        } catch (URMException e) {
            throw new ServiceAccessException(this, e);
        }
    }
    
    
    //--
    
    private XURMAccessor accessor() {
        Object obj;
        
        obj = accessors.get();
        if (obj == null) {
            throw new XAssertionFailed("not connected");
        }
        return (XURMAccessor) obj;
    }
    
    private String getUuri(Uri uri, boolean writeLock) throws ServiceAccessException {
        IDescriptors desc;
        
        if (descriptorsStore == null) {
            // TODO: no locking!! -- does this cause multi-user problems?
            return uri.toString();
        } else {
            if (writeLock) {
                // use write locks to prevent other threads from ACL-modifying the same resource.
                desc = descriptorsStore.resolveWriteable(uri);
            } else {
                desc = descriptorsStore.resolve(uri);
            }
            if (desc == null) {
                // slide is expected to assert existing uris before touching a permissions ...
                throw new XAssertionFailed("complaints to mhm ..." + uri);
            } else {
                return desc.getUuri();
            }
        }
    }
    
    private void assertEnabled() throws ServiceAccessException {
        if (!enabled) {
            throw new XAssertionFailed("security disabled");
        }
    }
}


