/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/store/XSchemaStore.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 com.softwareag.tamino.db.api.common.TException;
import com.softwareag.tamino.db.api.objectModel.TXMLObject;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
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.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.macro.ConflictException;
import org.apache.slide.macro.ForbiddenException;
import org.apache.slide.store.tamino.common.XDatastoreException;
import org.apache.slide.store.tamino.common.XForbiddenException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.datastore.ContentCollection;
import org.apache.slide.store.tamino.datastore.IContentCollection;
import org.apache.slide.store.tamino.datastore.XConnection;
import org.apache.slide.store.tamino.datastore.XConnectionKey;
import org.apache.slide.store.tamino.datastore.XConnectionPool;
import org.apache.slide.store.tamino.datastore.XUtilDBAccessor;
import org.apache.slide.store.tamino.datastore.schema.XDoctypeCache;
import org.apache.slide.store.tamino.datastore.schema.XSchema;
import org.apache.slide.store.tamino.jdomobjects.XFactory;
import org.apache.slide.store.tamino.store.XMemoryStore;
import org.apache.slide.store.tamino.tools.Env;
import org.apache.slide.store.tamino.tools.stores.XDomainFileHandler;
import org.apache.slide.store.tamino.tools.stores.XNamespace;
import org.apache.slide.store.tamino.tools.stores.XNamespaceConfig;
import org.apache.slide.store.tamino.tools.stores.XStore;
import org.apache.slide.store.tamino.tools.stores.XStoreGroup;
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.Strings;
import org.apache.slide.util.XException;
import org.apache.slide.util.XUri;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;


/**
 * Implementation of all aspects (content and descriptors) stores in order
 * to WebDAV-enable the access to the Tamino content data schemas.
 *
 * TODO: merge duplicated code
 *
 * @author Peter.Nevermann@softwareag.com
 *
 * @version $Revision: 1.4 $
 */
public class XSchemaStore extends XMemoryStore implements 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
        XS_NAMESPACE_URI = XGlobals.XML_SCHEMA_NAMESPACE_URI,
        TSD_NAMESPACE_URI = XGlobals.TAMINO_TSD_3_NAMESPACE_URI,
        INO_NAMESPACE_URI = "http://namespaces.softwareag.com/tamino/response2",
        TSD3_SCHEMA_ELM = "schema",
        TSD3_ANNOTATION_ELM = "annotation",
        TSD3_APPINFO_ELM = "appinfo",
        TSD3_SCHEMAINFO_ELM = "schemaInfo",
        TSD3_COLLECTION_ELM = "collection",
        TSD2_DOCTYPE_ELM = "doctype",
        TSD2_COLLECTION_ELM = "collection",
        ROOTPATH = "rootpath",
        DELETE_CASCADES = "deleteCascades",
        METADATA_FILENAME = "metadata.xml",
        E_METADATA = "metadata",
        E_STORE = "store",
        E_SCHEMA = "schema",
        E_COLLECTION = "collection",
        A_NAME = "name",
        A_URL = "url",
        A_SCHEMADEFINED = "schemadefined",
        A_EXTENSTION= "extension",
        EOF_DUMMY = "@";
    
    
    private XNamespace ns;
    private String xsDefaultFileExtension = "xsd";
    private String currentThreadName = "";
    private Set openTaminoConnections = null; // set of connection keys
    private String rootpath = null;
    private boolean deleteCascades = false;
    private Metadata metadata = null;
    
    
    /**
     ** Default constructor.
     **/
    public XSchemaStore() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>" );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }
    
    // Inherited from XMemoryStore
    public synchronized void initialize( NamespaceAccessToken token )
        throws ServiceInitializationFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "initialize",
                                                            new Object[] {(token!=null ? token.getName() : null)} );
        
        super.initialize( token );
        
        try {
            this.ns = XDomainFileHandler.get().getDomain().getNamespace(token.getName());
            this.openTaminoConnections = new HashSet();
            this.xsDefaultFileExtension = (String)parameters.get( "XMLSchemaDefaultFileExtension" );
            this.metadata = new Metadata();
            
            metadata.init();
            metadata.commit();
            initNamespace();
        }
        catch( Exception x ) {
            throw new ServiceInitializationFailedException( this, x );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "initialize" );
    }
    
    /**
     * Method closeTaminoConnections
     *
     * @param    commit              a  boolean
     *
     * @throws   TException
     * @throws   XDatastoreException
     *
     */
    private void closeTaminoConnections( boolean commit ) throws TException, XDatastoreException {
        XConnectionPool cpool = XConnectionPool.getInstance();
        Iterator c = openTaminoConnections.iterator();
        while( c.hasNext() ) {
            XConnectionKey key = (XConnectionKey)c.next();
            XConnection xcon = cpool.getXConnection( key );
            if( commit )
                xcon.commit();
            else
                xcon.rollback();
            xcon.close();
            c.remove();
        }
    }
    
    // Inherited from XMemoryStore
    public void setParameters(Hashtable parameters)
        throws ServiceParameterErrorException, ServiceParameterMissingException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setParameters", new Object[] {parameters} );
        
        super.setParameters( parameters );
        String rp = (String)parameters.get( ROOTPATH );
        if( rp == null ) {
            rootpath = "";
        }
        else {
            UriHandler uh = new UriHandler( rp );
            rootpath = uh.getPath();
        }
        
        String dc = (String)parameters.get( DELETE_CASCADES );
        if( "true".equalsIgnoreCase(dc) || "yes".equalsIgnoreCase(dc) )
            deleteCascades = true;
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "setParameters" );
    }
    
    /**
     * Clear the caches
     */
    protected void clearCaches() {
        objects.clear();
        descriptors.clear();
        descriptor.clear();
        content.clear();
        permissions.clear();
        locks.clear();
    }
    
    // Inherited from XMemoryStore
    public void commit( Xid xid, boolean onePhase ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "commit",
                                                            new Object[] {xid, new Boolean(onePhase)}  );
        
        super.commit( xid, onePhase );
        
        try {
            closeTaminoConnections( true );
            metadata.commit();
        }
        catch( Exception x ) {
            throw new XAException( x.getMessage() );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "commit" );
    }
    
    // Inherited from XMemoryStore
    public void rollback( Xid xid ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "rollback",
                                                            new Object[] {xid}  );
        
        super.rollback( xid );
        
        try {
            clearCaches();
            metadata.rollback();
            initNamespace();
            closeTaminoConnections( false );
            XUtilDBAccessor.clearCache();
        }
        catch( Exception x ) {
            throw new XAException( x.getMessage() );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "rollback" );
    }
    
    
    // Inherited from XMemoryStore
    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) } );
        
        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        if( !uh.isSchemaFile() )
            throw new RevisionNotFoundException( uriStr, revisionDescriptor.getRevisionNumber() );
        
        String xmlcontent = getSchemaAsString( uri );
        if( xmlcontent == null )
            throw new RevisionNotFoundException(
                uriStr, revisionDescriptor.getRevisionNumber() );
        
        NodeRevisionContent rc = new NodeRevisionContent();
        try {
            byte[] content = xmlcontent.getBytes("UTF-8");
            rc.setContent( content );
            revisionDescriptor.setContentLength(content.length);
        } catch (java.io.UnsupportedEncodingException e) {throw new ServiceAccessException(this, e);}
        // update content length
        //        revisionDescriptor.setLastModified( new Date() );
        
        descriptor.put( uriStr+"-1.0", revisionDescriptor );  // XMemoryStore is version aware
        
        NodeRevisionContent result = rc;
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "retrieveRevisionContent", result );
        return result;
    }
    
    // Inherited from XMemoryStore
    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 uriStr = uri.toString();
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        
        String xmlcontent = getSchemaAsString( uri );
        if( xmlcontent != null )
            throw new RevisionAlreadyExistException(
                uriStr, revisionDescriptor.getRevisionNumber() );
        
        try {
            defineSchema(uri, revisionDescriptor, revisionContent);
            // update metadata
            String store = uh.getStoreName();
            String schema = uh.getSchemaName();
            String ext = uh.getFileExtension();
            metadata.createSchemaElm( store, schema, ext );
        }
        catch( Exception x ) {
            throw new ServiceAccessException( this, new ConflictException(uriStr, x) );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createRevisionContent" );
    }
    
    // Inherited from XMemoryStore
    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 uriStr = uri.toString();
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        
        String xmlcontent = getSchemaAsString( uri );
        if( xmlcontent == null )
            throw new RevisionNotFoundException(
                uriStr, revisionDescriptor.getRevisionNumber() );
        
        try {
            defineSchema(uri, revisionDescriptor, revisionContent);
            // update metadata
            String store = uh.getStoreName();
            String schema = uh.getSchemaName();
            String ext = uh.getFileExtension();
            metadata.updateSchemaElm( store, schema, ext );
        }
        catch( Exception x ) {
            throw new ServiceAccessException( this, new ConflictException(String.valueOf(uri), x) );
        }
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting( CLASSNAME, "storeRevisionContent" );
    }
    
    // Inherited from XMemoryStore
    public void removeRevisionContent( Uri uri, NodeRevisionDescriptor revisionDescriptor )
        throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeRevisionContent", new Object[] {uri,
                            (revisionDescriptor!=null
                                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                                 : null)} );
        
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        
        String xmlcontent = getSchemaAsString( uri );
        if( xmlcontent != null ) {
            try {
                String store = uh.getStoreName();
                String schema = uh.getSchemaName();
                XStore physical = ns.getStore(store);
                String taminoCollection = physical.getTaminoCollection();
                String taminoDbUrl = physical.getTaminoDbUrl();
                if( !deleteCascades && getDbAccessor(store).schemaHasInstances(taminoDbUrl, taminoCollection, schema) )
                    throw new ServiceAccessException(
                        this,
                        new ConflictException("Cannot delete schema "+schema+" in collection "+taminoCollection+" of DB "+taminoDbUrl+"; delete instances first.") );
                
                undefineSchema( uri );
            }
            catch( Exception e ) {
                throw new ServiceAccessException( this, e );
            }
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeRevisionContent" );
    }
    
    // Inherited from XMemoryStore
    public ObjectNode retrieveObject(Uri uri)
        throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "retrieveObject",
                                                            new Object[] {uri} );
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        
        try {
            String store = uh.getStoreName();
            if( uh.isStoreFolder() && metadata.needRefresh(store) ) {
                metadata.init();
                metadata.commit();
                initNamespace();
            }
        }
        catch( Exception e ) {}
        
        ObjectNode result = super.retrieveObject( uri );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "retrieveObject",
                                                               (result!=null ? result.getUri() : null) );
        return result;
    }
    
    // Inherited from XMemoryStore
    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();
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        
        if( !uh.isSchemaFile() )
            throw new ServiceAccessException(
                this,
                new ForbiddenException(uriStr, new XForbiddenException( "Invalid uri: "+uriStr )) );
        
        super.createObject( uri, object );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "createObject" );
    }
    
    /**
     * Method defineSchema
     *
     * @param    uri                 an Uri
     * @param    revisionDescriptor  a  NodeRevisionDescriptor
     * @param    revisionContent     a  NodeRevisionContent
     *
     * @throws   XException
     * @throws   IOException
     * @throws   ServiceAccessException
     * @throws   JDOMException
     *
     */
    private String defineSchema( Uri uri, NodeRevisionDescriptor revisionDescriptor, NodeRevisionContent revisionContent ) throws XException, IOException, ServiceAccessException, JDOMException {
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        String result = null;
        
        if( !uh.isSchemaFile() )
            throw new ServiceAccessException( this,
                                             new ForbiddenException(String.valueOf(uri), new XForbiddenException( "Invalid uri: "+uri.toString() )) );
        
        // Ignore if content-length <= 0
        if( revisionDescriptor.getContentLength() > 0 ) {
            String store = uh.getStoreName();
            String schema = uh.getSchemaName();
            Iterator collIt = metadata.getInvolvedCollections(store).entrySet().iterator();
            while( collIt.hasNext() ) {
                Map.Entry e = (Map.Entry)collIt.next();
                String taminoCollection = (String)e.getValue();
                Document schemadoc = parseSchemaDocument( revisionContent, taminoCollection, schema );
                XMLOutputter out = new XMLOutputter();
                result = out.outputString(schemadoc);
                TXMLObject schemaObj = TXMLObject.newInstance( result );
                getDbAccessor( store, true ).createSchema( taminoCollection, schemaObj );
                // update doctype cache
                //                XSchema xschema = XSchema.newInstance( schemadoc.getRootElement() );
                XSchema xschema = XSchema.newInstance( getSchemaElement(uri) );
                XDoctypeCache.getSingleInstance().addDoctype( getContentCollection(uri),
                                                             xschema.getDoctypes());
            }
        }
        return result;
    }
    
    private void undefineSchema( Uri uri ) throws XException, ServiceAccessException {
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        
        if( !uh.isSchemaFile() )
            throw new ServiceAccessException( this,
                                             new ForbiddenException(String.valueOf(uri), new XForbiddenException( "Invalid uri: "+uri.toString() )) );
        
        String store = uh.getStoreName();
        String schema = uh.getSchemaName();
        Iterator collIt = metadata.getInvolvedCollections(store).entrySet().iterator();
        while( collIt.hasNext() ) {
            Map.Entry e = (Map.Entry)collIt.next();
            String taminoCollection = (String)e.getValue();
            Element schemaElm = getSchemaElement( uri );
            XSchema xschema = XSchema.newInstance( schemaElm );
            // delete
            getDbAccessor( store, true ).deleteSchema( taminoCollection, schema );
            metadata.removeSchemaElm( store, schema );
            XDoctypeCache.getSingleInstance().deleteDoctype( getContentCollection(uri),
                                                            xschema.getDoctypes());
        }
    }
    
    /**
     * Method parseSchemaDocument
     *
     * @param    revisionContent     a  NodeRevisionContent
     * @param    taminoCollection    a  String
     * @param    schemaFileName      a  String
     *
     * @return   a Document
     *
     * @throws   XException
     * @throws   IOException
     * @throws   JDOMException
     *
     */
    private Document parseSchemaDocument( NodeRevisionContent revisionContent, String taminoCollection, String schemaFileName ) throws XException, IOException, JDOMException {
        SAXBuilder parser = new SAXBuilder();
        Document result = parser.build( revisionContent.streamContent() );
        Element rootElm = result.getRootElement();
        if( rootElm == null )
            return result;
        if( TSD3_SCHEMA_ELM.equals(rootElm.getName()) && XS_NAMESPACE_URI.equals(rootElm.getNamespaceURI()) ) {
            Element annotationElm = rootElm.getChild( TSD3_ANNOTATION_ELM, Namespace.getNamespace(XS_NAMESPACE_URI) );
            if( annotationElm == null )
                return result;
            Element appinfoElm = annotationElm.getChild( TSD3_APPINFO_ELM, Namespace.getNamespace(XS_NAMESPACE_URI) );
            if( appinfoElm == null )
                return result;
            Element schemainfoElm = appinfoElm.getChild( TSD3_SCHEMAINFO_ELM, Namespace.getNamespace(TSD_NAMESPACE_URI) );
            if( schemainfoElm == null )
                return result;
            schemainfoElm.setAttribute( "name", schemaFileName );
            Element collectionElm = schemainfoElm.getChild( TSD3_COLLECTION_ELM, Namespace.getNamespace(TSD_NAMESPACE_URI) );
            if( collectionElm == null )
                return result;
            collectionElm.setAttribute( "name", taminoCollection );
        }
        else if( TSD2_COLLECTION_ELM.equals(rootElm.getName()) && INO_NAMESPACE_URI.equals(rootElm.getNamespaceURI()) ) {
            rootElm.setAttribute( "name", taminoCollection, Namespace.getNamespace(INO_NAMESPACE_URI) );
            Element doctypeElm = rootElm.getChild( TSD2_DOCTYPE_ELM, Namespace.getNamespace(INO_NAMESPACE_URI) );
            if( doctypeElm == null )
                return result;
            doctypeElm.setAttribute( "name", schemaFileName, Namespace.getNamespace(INO_NAMESPACE_URI) );
        }
        else if( TSD2_DOCTYPE_ELM.equals(rootElm.getName()) && INO_NAMESPACE_URI.equals(rootElm.getNamespaceURI()) ) {
            rootElm.setAttribute( "name", schemaFileName, Namespace.getNamespace(INO_NAMESPACE_URI) );
            // create the collection envelope
            Element collectionElm = new Element( TSD2_COLLECTION_ELM, Namespace.getNamespace(INO_NAMESPACE_URI) );
            collectionElm.setAttribute( "name", taminoCollection, Namespace.getNamespace(INO_NAMESPACE_URI) );
            collectionElm.setAttribute( "key", "ID001", Namespace.getNamespace(INO_NAMESPACE_URI) );
            result.detachRootElement();
            collectionElm.addContent( rootElm );
            result.setRootElement( collectionElm );
        }
        return result;
    }
    
    /**
     * Method initNamespace
     *
     */
    private void initNamespace() throws ServiceAccessException {
        if( metadata.mdoc == null )
            return;
        
        String rUri = scope.toString();
        Vector rCld = new Vector();
        Element rElm = metadata.getRootElm();
        Iterator stit = rElm.getChildren(E_STORE).iterator();
        while( stit.hasNext() ) {
            Element stElm = (Element)stit.next();
            String store = stElm.getAttributeValue( A_NAME );
            String stUri = rUri+XUri.SEP+store;
            rCld.add( stUri );
            
            Vector stCld = new Vector();
            Iterator scit = stElm.getChildren(E_SCHEMA).iterator();
            while( scit.hasNext() ) {
                Element scElm = (Element)scit.next();
                String schema = scElm.getAttributeValue( A_NAME );
                String ext = scElm.getAttributeValue( A_EXTENSTION );
                
                String scUri = stUri+XUri.SEP+schema+ext;
                stCld.add( scUri );
                
                ObjectNode scOn = new SubjectNode( scUri, new Vector(), new Vector() );
                objects.put( scUri, scOn );
                NodeRevisionDescriptors scNrds = XFactory.createNRDs( scUri );
                descriptors.put( scUri, scNrds );
                NodeRevisionDescriptor scNrd = XFactory.createMemberNRD( scUri, "text/xml; charset=utf-8" );
                // get the XML content to set the content length
                String xmlcontent = getSchemaAsString( new Uri(namespace, scUri) );
                long contentlength = 0;
                if( xmlcontent != null )
                    contentlength = xmlcontent.getBytes().length;
                scNrd.setContentLength( contentlength );
                descriptor.put( scUri+"-1.0", scNrd );
            }
            
            ObjectNode stOn = new SubjectNode( stUri, stCld, new Vector() );
            objects.put( stUri, stOn );
            NodeRevisionDescriptors stNrds = XFactory.createNRDs( stUri );
            descriptors.put( stUri, stNrds );
            NodeRevisionDescriptor stNrd = XFactory.createCollectionNRD( stUri );
            descriptor.put( stUri+"-1.0", stNrd );
        }
        
        ObjectNode rOn = new SubjectNode( rUri, rCld, new Vector() );
        objects.put( rUri, rOn );
        NodeRevisionDescriptors rNrds = XFactory.createNRDs( rUri );
        descriptors.put( rUri, rNrds );
        NodeRevisionDescriptor rNrd = XFactory.createCollectionNRD( rUri );
        descriptor.put( rUri+"-1.0", rNrd );
    }
    
    /**
     * Get the schema. Null if none.
     */
    private String getSchemaAsString( Uri uri ) throws ServiceAccessException {
        // get the schema
        String result = null;
        try {
            String uriRel = uri.getRelative();
            UriHandler uh = new UriHandler( uriRel );
            String store = uh.getStoreName();
            String schemaName = uh.getSchemaName();
            String taminoCollection = ns.getStore(store).getTaminoCollection();
            result = getDbAccessor( store ).getSchemaAsString( taminoCollection, schemaName );
        }
        catch( Exception e ) {
            throw new ServiceAccessException( this, e );
        }
        if( result != null && result.length() == 0 )
            result = null;
        return result;
    }
    
    /**
     * Get the schema. Null if none.
     */
    private Element getSchemaElement( Uri uri ) throws ServiceAccessException {
        // get the schema
        Element result = null;
        try {
            String uriRel = uri.getRelative();
            UriHandler uh = new UriHandler( uriRel );
            String store = uh.getStoreName();
            String schemaName = uh.getSchemaName();
            String taminoCollection = ns.getStore(store).getTaminoCollection();
            result = getDbAccessor( store ).getSchemaElement( taminoCollection, schemaName );
        }
        catch( Exception e ) {
            throw new ServiceAccessException( this, e );
        }
        return result;
    }
    
    private IContentCollection getContentCollection( Uri uri ) throws ServiceAccessException {
        // get the schema
        IContentCollection result = null;
        try {
            String uriRel = uri.getRelative();
            UriHandler uh = new UriHandler( uriRel );
            String store = uh.getStoreName();
            XStore physical = ns.getStore(store);
            String taminoBase = physical.getTaminoBase();
            String taminoDb = physical.getTaminoDatabase();
            String taminoCollection = physical.getTaminoCollection();
            result = new ContentCollection( taminoBase, taminoDb, taminoCollection );
        }
        catch( Exception e ) {
            throw new ServiceAccessException( this, e );
        }
        return result;
    }
    
    /**
     * Get the DB session for the specified store.
     */
    private XUtilDBAccessor getDbAccessor( String store ) throws XException {
        return getDbAccessor( store, false );
    }
    
    /**
     * Get the DB session for the specified store.
     */
    private XUtilDBAccessor getDbAccessor( String store, boolean localTransactionMode ) throws XException {
        String databaseUri = ns.getStore(store).getTaminoDbUrl();
        XNamespaceConfig config = ns.getConfig();
        XConnectionKey ckey = new XConnectionKey(databaseUri, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd() );
        try {
            XConnectionPool cpool = XConnectionPool.getInstance();
            XConnection xcon = cpool.getXConnection( ckey );
            if( localTransactionMode )
                xcon.startTransactionIfNecessary();
            if( !openTaminoConnections.contains(ckey) ) {
                openTaminoConnections.add( ckey );
            }
            return new XUtilDBAccessor(xcon.getTConnection());
        }
        catch( TException e ) {
            throw new XException( "Could not create Tamino connection for "+ckey, e );
        }
        catch( XDatastoreException e ) {
            throw new XException( "Could not create Tamino connection for "+ckey, e );
        }
    }
    
    
    private class Metadata {
        
        private Document mdoc = null;
        private File mf = null;
        private long schemaCacheLastModified = 0;
        
        /**
         * Method commit
         *
         */
        void commit() throws IOException {
            XMLOutputter xmlout = JDom.outputter();
            OutputStream outstrm = new BufferedOutputStream(new FileOutputStream(mf));
            xmlout.output( mdoc, outstrm );
            outstrm.flush();
            outstrm.close();
        }
        
        void rollback() throws JDOMException, TException, XException, IOException {
            init();
            commit();
        }
        
        boolean needRefresh( String store ) throws XException, TException {
            return (schemaCacheLastModified != XUtilDBAccessor.getSchemaCacheLastModified());
        }
        
        /**
         * Method init
         *
         * @throws   ServiceAccessException
         *
         */
        void init() throws JDOMException, TException, XException, IOException {
            File rf = new File( rootpath );
            if( !rf.exists() )
                rf.mkdir();
            
            this.mf = new File( rootpath+XUri.SEP+METADATA_FILENAME );
            if( mf.exists() ) {
                SAXBuilder parser = new SAXBuilder();
                this.mdoc = parser.build( mf );
            }
            
            // create the meta doc
            Element newrootElm = new Element( E_METADATA );
            Document newMdoc = new Document( newrootElm );
            
            Iterator stores = ns.getPublicStoreGroups().iterator();
            while( stores.hasNext() ) {
                String store = ((XStoreGroup)stores.next()).name;
                Element newstElm = new Element( E_STORE );
                newstElm.setAttribute( A_NAME, store );
                newrootElm.addContent( newstElm );
                
                String taminoCollection = ns.getStore(store).getTaminoCollection();
                String schemaLang = getDbAccessor( store ).getSchemaLanguage( taminoCollection );
                Set snames = getDbAccessor( store ).getSchemaNames( taminoCollection );
                this.schemaCacheLastModified = XUtilDBAccessor.getSchemaCacheLastModified();
                
                // get configured collections of participating deltav stores
                Map collections = getInvolvedCollections( store );
                
                Iterator j = snames.iterator();
                while( j.hasNext() ) {
                    String schema = (String)j.next();
                    String fileExt = XUri.XML_SUFF;
                    if( TSD3_SCHEMA_VERSION.equals(schemaLang) ) {
                        Element scElm = getSchemaElm( store, schema );
                        if( scElm != null )
                            fileExt = scElm.getAttributeValue( A_EXTENSTION );
                        else
                            fileExt = "."+xsDefaultFileExtension;
                    }
                    Element newscElm = new Element( E_SCHEMA );
                    newscElm.setAttribute( A_NAME, schema );
                    newscElm.setAttribute( A_EXTENSTION, fileExt );
                    newstElm.addContent( newscElm );
                    
                    Iterator k = collections.entrySet().iterator();
                    while( k.hasNext() ) {
                        Map.Entry e = (Map.Entry)k.next();
                        String curl = (String)e.getKey();
                        String c = (String)e.getValue();
                        boolean defined = getDbAccessor(store).isSchema( c, schema );
                        
                        Element newcollElm = new Element( E_COLLECTION );
                        newcollElm.setAttribute( A_URL, curl );
                        newcollElm.setAttribute( A_SCHEMADEFINED, String.valueOf(defined) );
                        newscElm.addContent( newcollElm );
                    }
                }
            }
            
            this.mdoc = newMdoc;
        }
        
        /**
         * Method getInvolvedCollections
         *
         * @param    store               a  String
         *
         * @return   a Map
         *
         * @throws   XException
         *
         */
        private Map getInvolvedCollections( String group ) throws XException {
            return ns.getStoreGroup(group).getInvolvedCollections();
        }
        
        /**
         * Method getRootElm
         *
         * @return   an Element
         *
         */
        Element getRootElm() {
            return mdoc.getRootElement();
        }
        
        /**
         * Get the metadata node for the specified store and schema.
         *
         * @param    store               a  String
         * @param    schema              a  String
         *
         * @return   an Element
         *
         */
        Element getSchemaElm( String store, String schema ) {
            Element result = null;
            boolean found = false;
            if( mdoc != null ) {
                Iterator stit = mdoc.getRootElement().getChildren(E_STORE).iterator();
                while( stit.hasNext() && !found ) {
                    Element stElm = (Element)stit.next();
                    if( store.equals(stElm.getAttributeValue(A_NAME)) ) {
                        Iterator scit = stElm.getChildren(E_SCHEMA).iterator();
                        while( scit.hasNext() && !found ) {
                            Element scElm = (Element)scit.next();
                            if( schema.equals(scElm.getAttributeValue(A_NAME)) ) {
                                result = scElm;
                                found = true;
                            }
                        }
                    }
                }
            }
            return result;
        }
        
        /**
         * Get the metadata node for the specified store.
         *
         * @param    store               a  String
         * @param    schema              a  String
         *
         * @return   an Element
         *
         */
        Element getStoreElm( String store ) {
            Element result = null;
            boolean found = false;
            if( mdoc != null ) {
                Iterator stit = mdoc.getRootElement().getChildren(E_STORE).iterator();
                while( stit.hasNext() && !found ) {
                    Element stElm = (Element)stit.next();
                    if( store.equals(stElm.getAttributeValue(A_NAME)) ) {
                        result = stElm;
                        found = true;
                    }
                }
            }
            return result;
        }
        
        /**
         * Method createSchemaElm
         *
         * @param    store               a  String
         * @param    schema              a  String
         * @param    ext                 a  String
         *
         */
        void createSchemaElm( String store, String schema, String ext ) throws XException {
            Element stElm = getStoreElm( store );
            Element scElm = new Element( E_SCHEMA );
            scElm.setAttribute( A_NAME, schema );
            scElm.setAttribute( A_EXTENSTION, ext );
            stElm.addContent( scElm );
            
            Iterator k = getInvolvedCollections(store).entrySet().iterator();
            while( k.hasNext() ) {
                Map.Entry e = (Map.Entry)k.next();
                String curl = (String)e.getKey();
                
                Element newcollElm = new Element( E_COLLECTION );
                newcollElm.setAttribute( A_URL, curl );
                newcollElm.setAttribute( A_SCHEMADEFINED, "true" );
                scElm.addContent( newcollElm );
            }
        }
        
        /**
         * Method updateSchemaElm
         *
         * @param    store               a  String
         * @param    schema              a  String
         * @param    ext                 a  String
         *
         */
        void updateSchemaElm( String store, String schema, String ext ) {
            Element scElm = getSchemaElm( store, schema );
            scElm.setAttribute( A_EXTENSTION, ext );
        }
        
        void removeSchemaElm( String store, String schema ) {
            Element stElm = getStoreElm( store );
            Element scElm = getSchemaElm( store, schema );
            stElm.removeContent( scElm );
        }
    }
    
    /**
     ** Uri handler. TODO: dump
     **
     ** @author    peter.nevermann@softwareag.com
     ** @version   0.1
     **/
    static class UriHandler {
        
        private final String PH_START = "${";
        private final String PH_END = "}";
        
        /** tokens **/
        String[] tokens = null;
        /** resolved **/
        boolean resolved = false;
        
        /**
         ** Default constructor.
         **
         ** @pre        true
         ** @post       true
         **
         ** @param      uri the uri
         **/
        UriHandler( String uri ) {
            StringTokenizer ut = new StringTokenizer( uri, XUri.SEP );
            int ntok = ut.countTokens();
            this.tokens = new String[ntok];
            
            for( int i = 0; i < ntok; i++ )
                tokens[i] = ut.nextToken();
        }
        
        /**
         ** Check whether the uriRel is corresponds to the root folder
         **
         ** @return     true if the uriRel corresponds to the root folder
         **/
        boolean isRoot() {
            return (tokens.length == 0);
        }
        
        /**
         ** Return a file system path for the resolved uri
         **
         ** @return     the path
         **/
        String getPath() {
            StringBuffer b = new StringBuffer();
            resolve();
            for( int i = 0; i < tokens.length; i++ ) {
                if( i > 0 )
                    b.append(File.separator);
                b.append(tokens[i]);
            }
            return b.toString();
        }
        
        /**
         ** Check whether the uriRel is corresponds to a schema file
         ** @return     true if the uriRel corresponds to a schema file
         **/
        boolean isSchemaFile() {
            boolean result = false;
            if( tokens.length == 2 ) {
                if( Strings.endsWithIgnoreCase(tokens[1], XUri.XML_SUFF) )
                    result = true;
                else if( Strings.endsWithIgnoreCase(tokens[1], XSD_SUFF) )
                    result = true;
                else if( Strings.endsWithIgnoreCase(tokens[1], TSD_SUFF) )
                    result = true;
            }
            return result;
        }
        
        /**
         ** Check whether the uriRel is corresponds to a folder
         ** @return     true if the uriRel corresponds to a folder
         **/
        boolean isStoreFolder() {
            return (tokens.length == 1);
        }
        
        /**
         ** Get the file name of this uri (without extension).
         ** @return     the file name
         **/
        String getSchemaName() {
            String result = null;
            if( isSchemaFile() ) {
                result = Strings.stripExtension( tokens[tokens.length - 1] );
            }
            return result;
        }
        
        /**
         ** Get the file extension of this uri.
         ** @return     the file extension
         **/
        String getFileExtension() {
            String result = null;
            if( isSchemaFile() ) {
                int idx = tokens[tokens.length - 1].lastIndexOf( '.' );
                if( idx < 0 )
                    result = "";
                else
                    result = tokens[tokens.length - 1].substring( idx );
            }
            return result;
        }
        
        /**
         ** Get the store name (=folder name).
         ** @return     the file name
         **/
        String getStoreName() {
            String result = null;
            if( isStoreFolder() || (isSchemaFile() && tokens.length == 2) ) {
                result = tokens[0];
            }
            return result;
        }
        
        /**
         ** Resolve ${property} placeholders
         **
         **/
        private void resolve() {
            if( !resolved ) {
                for( int i = 0; i < tokens.length; i++ ) {
                    if( tokens[i].startsWith(PH_START) && tokens[i].endsWith(PH_END) ) {
                        String p = tokens[i].substring(2, tokens[i].length() - 1);
                        tokens[i] = Env.get().getProperty(p);
                    }
                }
                resolved = true;
            }
        }
        
        // Inherited from java.lang.Object
        public String toString() {
            String result = null;
            if( tokens.length == 0 ) {
                result = "/";
            }
            else {
                StringBuffer b = new StringBuffer();
                for( int i = 0; i < tokens.length; i++ )
                    b.append( "/" ).append( tokens[i] );
                result = b.toString();
            }
            return result;
        }
    }
}






