/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/store/XUnavailableStore.java,v 1.4 2004/07/30 06:52:03 ozeigermann Exp $
 * $Revision: 1.4 $
 * $Date: 2004/07/30 06:52: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.store;

import com.softwareag.common.instrumentation.logging.Level;
import com.softwareag.common.instrumentation.logging.Logger;
import com.softwareag.common.instrumentation.logging.LoggerFactory;
import com.softwareag.common.instrumentation.logging.LoggerUtil;
import java.util.HashMap;
import java.util.Vector;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.content.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.macro.ForbiddenException;
import org.apache.slide.store.ContentStore;
import org.apache.slide.store.LockStore;
import org.apache.slide.store.NodeStore;
import org.apache.slide.store.RevisionDescriptorStore;
import org.apache.slide.store.RevisionDescriptorsStore;
import org.apache.slide.store.SecurityStore;
import org.apache.slide.store.tamino.common.XForbiddenException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.jdomobjects.XFactory;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.JDom;
import org.apache.slide.util.XUri;
import org.jdom.Document;
import org.jdom.Element;


/**
 ** Configuration store. Manages the domain configuration (Domain.xml).
 **
 ** @author    peter.nevermann@softwareag.com
 ** @version   0.1
 **
 **/
public class XUnavailableStore extends XMemoryStore implements ContentStore, LockStore,
NodeStore, RevisionDescriptorsStore, RevisionDescriptorStore, SecurityStore, XGlobals {

    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);

    /** constants */
    private static final String UNAVAIL_FILE = "ServiceInitializationFailedException.xml";

    private String reason = null; // the reason for the unavailability

    private String unavailContent = null;

    /**
     ** Default constructor.
     ** @param reason reason
     **/
    public XUnavailableStore( String reason ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>" );

        this.reason = reason;

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }

    /**
     ** Initialize the caches.
     **/
    private void init() {
        objects = new HashMap();
        descriptors = new HashMap();
        descriptor = new HashMap();
        content = new HashMap();
        permissions = new HashMap();
        locks = new HashMap();
    }

    /**
     ** Contruct content of error file.
     **/
    private String unavailContent() {
        if( unavailContent == null ) {
            Document doc = new Document( (Element)null );
            Element root = new Element( "exception" );
            Element msg = new Element( "message" );
            Element url = new Element( "url" );
            msg.addContent( reason );
            String surl =
                parameters.get(TAMINO_BASE)+XUri.SEP+
                parameters.get(TAMINO_DATABASE)+XUri.SEP+
                parameters.get(TAMINO_COLLECTION);
            url.addContent( surl );
            root.addContent( msg );
            root.addContent( url );
            doc.setRootElement( root );
            try {
                unavailContent = JDom.outputter().outputString(doc);
            }
            catch( Exception x ) {
                unavailContent =
                    "<!--Message:\n"+ reason+
                    "\nTamino URL:\n"+
                    parameters.get(TAMINO_BASE)+XUri.SEP+
                    parameters.get(TAMINO_DATABASE)+XUri.SEP+
                    parameters.get(TAMINO_COLLECTION)+
                    "-->";
            }
        }
        return unavailContent;

//      return
//          "Message:\n"+ reason+
//          "\nTamino URL:\n"+
//          parameters.get("taminoBase")+URI_DELIM+
//          parameters.get("database")+URI_DELIM+
//          parameters.get("collection");
    }

    // ------------------------------------------------------------------------
    // ContentStore interface
    // ------------------------------------------------------------------------

    /**
     * Retrieve revision content.
     *
     * @param uri Uri
     * @param revisionDescriptor revision descriptor
     * @return the node revision content
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception RevisionNotFoundException revision not found
     */
    public NodeRevisionContent retrieveRevisionContent(
    Uri uri, NodeRevisionDescriptor revisionDescriptor)
    throws ServiceAccessException, RevisionNotFoundException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "retrieveRevisionContent", new Object[] {uri,
            (revisionDescriptor!=null
               ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
               : null) } );

        NodeRevisionContent result = new NodeRevisionContent();

        String uriRel = uri.getRelative();
        XUri uh = new XUri(uriRel);
        if( !isValid(uh))
            throw new RevisionNotFoundException( uri.toString(), revisionDescriptor.getRevisionNumber());

        // Ignore if content-length <= 0
        if( revisionDescriptor.getContentLength() > 0 ) {
            result.setContent( unavailContent().toCharArray() );
        }

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "retrieveRevisionContent", result );
        return result;
    }

    /**
     * Create a new revision
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception RevisionAlreadyExistException revision already exists
     */
    public void createRevisionContent( Uri uri, NodeRevisionDescriptor revisionDescriptor,
    NodeRevisionContent revisionContent )
    throws ServiceAccessException, RevisionAlreadyExistException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "createRevisionContent", new Object[] {uri,
            (revisionDescriptor!=null
                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                 : null), revisionContent} );

        String uriRel = uri.getRelative();
        if( !"".equals(uriRel) && !XUri.SEP.equals(uriRel) )
//            throw new ServiceAccessException( this, "Service for scope '"+scope+"' unavailable" );
            throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );

        super.createRevisionContent( uri, revisionDescriptor, revisionContent );

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createRevisionContent" );
    }

    /**
     * Modify the latest revision of an object.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception RevisionNotFoundException revision not found
     */
    public void storeRevisionContent( Uri uri, NodeRevisionDescriptor revisionDescriptor,
    NodeRevisionContent revisionContent)
    throws ServiceAccessException, RevisionNotFoundException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "storeRevisionContent", new Object[] {uri,
            (revisionDescriptor!=null
                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                 : null), revisionContent} );

        String uriRel = uri.getRelative();
        if( !"".equals(uriRel) && !XUri.SEP.equals(uriRel) )
//            throw new ServiceAccessException( this, "Service for scope '"+scope+"' unavailable" );
            throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );

        super.storeRevisionContent( uri, revisionDescriptor, revisionContent );

        if (logger.isLoggable(Level.FINE))
            logger.exiting( CLASSNAME, "storeRevisionContent" );
    }

    // ------------------------------------------------------------------------
    // NodeStore interface
    // ------------------------------------------------------------------------

    /**
     * Retrieve an object from the Descriptors Store.
     *
     * @param uri Uri of the object we want to retrieve
     * @return object node
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to retrieve was not found
     */
    public ObjectNode retrieveObject(Uri uri)
    throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "retrieveObject",
            new Object[] {uri} );

        String uriStr = uri.toString();
        Vector children = new Vector();
        ObjectNode result = (ObjectNode) objects.get( uriStr );

        if( result == null ) {
            String uriRel = uri.getRelative();
            XUri uh = new XUri(uriRel);

            if( !isValid(uh))
                throw new ObjectNotFoundException( uri );

            if( uh.isRoot() ) {
                // this is the root
                throw new ObjectNotFoundException( uri );
            }
            else {
                // Construct the revision descriptor and descriptors objects
                NodeRevisionDescriptor rd = XFactory.createMemberNRD( uriStr );
                NodeRevisionDescriptors rds = XFactory.createNRDs( uriStr );

                // Construct the revision content
                NodeRevisionContent rc = new NodeRevisionContent();
                rc.setContent( unavailContent().toCharArray() );
                // update content length
                rd.setContentLength(unavailContent().length());

                descriptors.put( uriStr, rds );
                descriptor.put( uriStr+"-1.0", rd ); // XMemoryStore is version aware
                content.put( uriStr, rc );
            }

            result = new SubjectNode( uriStr, children, new Vector() );
            objects.put( uriStr, result );
        }

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "retrieveObject",
            (result!=null ? result.getUri() : null) );
        return result;
    }

    /**
     * Store an object in the Descriptors Store.
     *
     * @param uri Uri of the object we want to update
     * @param object Object to update
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to update was not found
     */
    public void storeObject(Uri uri, ObjectNode object)
    throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "storeObject",
            new Object[] {uri, (object!=null ? object.getUri() : null)} );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "storeObject" );


        // Allow the UnavailableStore to create it's root node; all other requests are blocked
        if (!uri.toString().equals(this.scope.toString()))
            throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );
    }

    /**
     * Create a new object in the Descriptors Store.
     *
     * @param uri Uri of the object we want to create
     * @param object SlideObject
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectAlreadyExistsException An object already exists
     * at this Uri
     */
    public void createObject(Uri uri, ObjectNode object)
    throws ServiceAccessException, ObjectAlreadyExistsException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "createObject",
            new Object[] {uri, (object!=null ? object.getUri() : null)} );

        String uriStr = uri.toString();

        if( objects.get(uriStr) != null )
            throw new ObjectAlreadyExistsException(uriStr);

        Vector children = new Vector();
        String uriRel = uri.getRelative();
        XUri uh = new XUri(uriRel);

        if( !isValid(uh) )
            throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );


        if( uh.isRoot() ) {
            // this is the root
            children.add( uriStr+XUri.SEP+UNAVAIL_FILE );
        }

        ObjectNode newObj = new SubjectNode( uriStr, children, new Vector() );
        objects.put( uriStr, newObj );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "createObject" );
    }

    // ------------------------------------------------------------------------
    // RevisionDescriptorsStore interface
    // ------------------------------------------------------------------------

    /**
     * Create new revision descriptors.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptors
    (Uri uri, NodeRevisionDescriptors revisionDescriptors)
    throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "createRevisionDescriptors", new Object[] {uri,
            (revisionDescriptors!=null ? revisionDescriptors.getUri() : null)} );

        String uriRel = uri.getRelative();
        if( !"".equals(uriRel) && !XUri.SEP.equals(uriRel) )
//            throw new ServiceAccessException( this, "Service for scope '"+scope+"' unavailable" );
            throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );

        super.createRevisionDescriptors( uri, revisionDescriptors );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "createRevisionDescriptors" );
    }

    /**
     * Update revision descriptors.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptors
    (Uri uri, NodeRevisionDescriptors revisionDescriptors)
    throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "storeRevisionDescriptors", new Object[] {uri,
            (revisionDescriptors!=null ? revisionDescriptors.getUri() : null)} );

        String uriRel = uri.getRelative();
        if( !"".equals(uriRel) && !XUri.SEP.equals(uriRel) )
//            throw new ServiceAccessException( this, "Service for scope '"+scope+"' unavailable" );
            throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );

        super.createRevisionDescriptors( uri, revisionDescriptors );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "storeRevisionDescriptors" );
    }

    // ------------------------------------------------------------------------
    // RevisionDescriptorStore interface
    // ------------------------------------------------------------------------

    /**
     * Create new revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptor
    (Uri uri, NodeRevisionDescriptor revisionDescriptor) throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "createRevisionDescriptor", new Object[] {uri, (revisionDescriptor!=null
            ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
            : null)} );

        String uriRel = uri.getRelative();
        if( !"".equals(uriRel) && !XUri.SEP.equals(uriRel) )
//            throw new ServiceAccessException( this, "Service for scope '"+scope+"' unavailable" );
            throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );

        super.createRevisionDescriptor( uri, revisionDescriptor );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "createRevisionDescriptor" );
    }

    /**
     * Update revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptor
    (Uri uri, NodeRevisionDescriptor revisionDescriptor)
    throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "storeRevisionDescriptor", new Object[] {uri, (revisionDescriptor!=null
            ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
            : null)} );

        String uriRel = uri.getRelative();
        if( !"".equals(uriRel) && !XUri.SEP.equals(uriRel) )
//            throw new ServiceAccessException( this, "Service for scope '"+scope+"' unavailable" );
            throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );

        super.storeRevisionDescriptor( uri, revisionDescriptor );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "storeRevisionDescriptor" );
    }

    /**
     * Remove revision descriptor.
     *
     * @param uri Uri
     * @param number Revision number
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptor(Uri uri, NodeRevisionNumber number)
    throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "removeRevisionDescriptor", new Object[] {uri, number} );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "removeRevisionDescriptor" );

//        throw new ServiceAccessException( this, "Service for scope '"+scope+"' unavailable" );
        throw new ServiceAccessException( this,
                new ForbiddenException(uri.toString(), new XForbiddenException( "The store is unavailable" )) );
    }

    private static boolean isValid(XUri uri) {
        return uri.size() == 0 || (uri.size() == 1 && uri.firstSegment().equals(UNAVAIL_FILE));
    }
}

