/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/tools/repairer/MetadataVersionCheck.java,v 1.4 2004/12/15 10:38:27 pnever Exp $
 * $Revision: 1.4 $
 * $Date: 2004/12/15 10:38:27 $
 *
 * ====================================================================
 *
 * 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.tools.repairer;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.transaction.xa.XAException;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.security.NodePermission;
import org.apache.slide.store.tamino.common.IDescriptors;
import org.apache.slide.store.tamino.common.XDatastoreException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.common.XUriCache;
import org.apache.slide.store.tamino.datastore.XDbHandler;
import org.apache.slide.store.tamino.datastore.XPathFactory;
import org.apache.slide.store.tamino.jdomobjects.XDescriptors;
import org.apache.slide.store.tamino.jdomobjects.XFactory;
import org.apache.slide.store.tamino.jdomobjects.XJdom;
import org.apache.slide.store.tamino.security.XURMAccessor;
import org.apache.slide.store.tamino.tools.stores.XNamespace;
import org.apache.slide.store.tamino.tools.stores.XStoreTypeList;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.urm.URMException;
import org.apache.slide.util.JDom;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XException;
import org.apache.slide.util.XUri;
import org.jdom.Element;


/**
 ** Check metadata version.
 **
 ** @author    Peter.Nevermann@softwareag.com
 ** @version   $Revision: 1.4 $
 **
 ** @see       AbstractCheck
 **/
public class MetadataVersionCheck extends AbstractCheck implements RepairConstants {
    private final XNamespace ns;
    private final List historyPaths;
    private final XUriCache uriCache;
    private final XURMAccessor urmAccessor;
    /**
     ** Default constructor.
     **
     ** @pre        (taminoClient != null)
     **
     ** @param      taminoClient       the current database session
     **/
    protected MetadataVersionCheck(XNamespace ns, XTaminoClient taminoClient, XURMAccessor urmAccessor) throws XException {
        super(taminoClient, MSG_START_METADATA_VERSION, MSG_DESCR_METADATA_VERSION);
        
        this.ns = ns;
        this.historyPaths = ns.getStoresByType(XStoreTypeList.HISTORY_NAME);
        this.uriCache = new XUriCache();
        this.urmAccessor = urmAccessor;
    }
    
    /**
     * Test meta data for existing documents.
     * This method tests for all metadata whether the entered document exists.
     * In case of non-existing document the meta data information will be deleted.
     */
    public void check() throws XException {
        String oldVersion;
        
        oldVersion = taminoClient.getDescriptorsAccessor().getSchemaVersion();
        if (isOldMetadataSchemaVersion(oldVersion)) {
            logAndApplyPatch(new UpgradeMetadataSchema(taminoCollection, oldVersion));
        }
        upgrade();
    }
    
    private boolean isOldMetadataSchemaVersion(String version) throws XException {
        return !XGlobals.SCHEMA_VERSION.equals(version);
    }
    
    private void upgrade() throws XException {
        Iterator iter;
        String uriOrUuri;
        Collection urisOrUuris;
        Element root;
        String oldVersion;
        
        // note: uri == uuri in 0_x schemas. Thus, the following lists gets both uri- and uuri descriptors
        urisOrUuris = taminoClient.getDescriptorsAccessor().getUurisMap(XPathFactory.uuris(taminoCollection)).values();
        iter = urisOrUuris.iterator();
        while (iter.hasNext()) {
            uriOrUuri = (String) iter.next();
            root = taminoClient.getDescriptorsAccessor().doReadDescriptorsDocument(uriOrUuri);
            oldVersion = JDom.getAttribute(root, XJdom.A_DTD_VERSION);
            if (oldVersion.startsWith("0")) {
                upgrade_0_x(uriOrUuri /* it's an uri in 0.x schemas */, root, oldVersion);
            } else if (oldVersion.equals(XGlobals.SCHEMA_VERSION)) {
                // up-to-date, not migration
            } else {
                // in particular, migration from 1.0 is not supported
                throw new XException("cannot migrate from version " + oldVersion);
            }
        }
    }
    
    private void upgrade_0_x(String uri, Element root, String oldVersion)
        throws XException
    {
        boolean useBinding;
        XDescriptors newDesc;
        boolean upgradeHistory;
        
        useBinding = RepairHandler.useBinding(uri);
        newDesc = XFactory.load_0_x_Descriptor(useBinding, root, uriCache);
        upgradeHistory = historyPaths.contains(ns.lookupStoreByScope(new XUri(uri).getParent()));
        logAndApplyPatch(new UpgradeMetadata(oldVersion, uri, newDesc, upgradeHistory, getPermissionList_0_x(root), urmAccessor));
    }
    
    public static class UpgradeMetadataSchema extends Patch {
        private final String taminoCollection;
        private final String oldVersion;
        
        public UpgradeMetadataSchema(String taminoCollection, String oldVersion) {
            this.taminoCollection = taminoCollection;
            this.oldVersion = oldVersion;
        }
        
        public void apply(XTaminoClient taminoClient) throws XException {
            String schemaLanguage = taminoClient.getSchemaLanguage( XGlobals.META_COLLECTION );
            if( XGlobals.TSD2_SCHEMA_VERSION.equals(schemaLanguage) && XGlobals.SCHEMA_VERSION_0_1.equals(oldVersion) ) {
                // intermediate step required when migrating from dtdVersion 0.1:
                // Tamino cannot change object type of node nodeProperty from PCDATA to ANY
                // *and* add the schadow child element in one step !!!
                taminoClient.createMetaDataSchema( taminoCollection, "intermediate_0.1_0.2_metadata_schema" );
            }
            XDbHandler.checkPropertiesEnabled(taminoClient.getUtilAccessor(), schemaLanguage);
            taminoClient.getUtilAccessor().createMetaDataSchema(taminoCollection);
        }
        
        public void log(XRepairLogWriter logWriter) {
            logWriter.println(TC_CHECK_DETAILS, MSG_FOUND_OUTDATED_METADATA_SCHEMA, new Object[]{ oldVersion });
        }
    }
    
    public static class UpgradeMetadata extends Patch {
        /** always holds uris -- uuris cannot be contained because dtds before 1.0 don't use them */
        private final String uri;
        private final String oldVersion;
        private final XDescriptors newDesc;
        private final boolean history;
        private final List permissions;
        private final XURMAccessor urmAccessor;
        
        public UpgradeMetadata(String uri, String oldVersion, XDescriptors n, boolean history,
                               List permissions, XURMAccessor urmAccessor) {
            this.uri = uri;
            this.oldVersion = oldVersion;
            this.newDesc = n;
            this.history = history;
            this.permissions = permissions;
            this.urmAccessor = urmAccessor;
        }
        
        public void apply(XTaminoClient taminoClient) throws XDatastoreException {
            updatePermissions();
            if (history) {
                upgradeHistory(taminoClient);
            }
            taminoClient.getDescriptorsAccessor().update(newDesc);
        }
        
        private void updatePermissions() throws XDatastoreException {
            Iterator iter;
            NodePermission p;
            
            iter = permissions.iterator();
            if (iter.hasNext()) {
                if (urmAccessor == null) {
                    throw new XDatastoreException("No adminitrator to upgrade permissions. Please specify an administrator on the command line.");
                }
                do {
                    p = (NodePermission) iter.next();
                    try {
                        urmAccessor.grant(newDesc.getUuri(), p);
                    } catch (URMException e) {
                        throw new XDatastoreException(e);
                    }
                } while (iter.hasNext());
                try {
                    urmAccessor.endTransaction(true);
                } catch (XAException e) {
                    throw new XDatastoreException(e);
                }
            }
        }
        
        private void upgradeHistory(XTaminoClient taminoClient) throws XDatastoreException {
            
            // get the versions
            ObjectNode onode = newDesc.getUuriObjectNode();
            Map vDescs = new HashMap();
            Enumeration chEnum = onode.enumerateBindings();
            while( chEnum.hasMoreElements() ) {
                ObjectNode.Binding binding = (ObjectNode.Binding) chEnum.nextElement();
                String uuri = binding.getUuri();
                try {
                    IDescriptors vd = taminoClient.getDescriptorsAccessor().readOneRwDescriptorsByUuri(uuri);
                    vd.setReadOnly( false );
                    vDescs.put( uuri, vd );
                } catch (XException e) {
                    throw new XAssertionFailed(e); // TODO
                }
            }
            
            // distribute descriptor objects over versions
            Enumeration nrdEnum = newDesc.getRevisionDescriptorList();
            while( nrdEnum.hasMoreElements() ) {
                NodeRevisionDescriptor nrd = (NodeRevisionDescriptor)nrdEnum.nextElement();
                NodeRevisionNumber nrn = nrd.getRevisionNumber();
                if( !NodeRevisionNumber.HIDDEN_0_0.equals(nrn) ) {
                    String vUri = uri + "/" + nrn;
                    IDescriptors vd = (IDescriptors)vDescs.get( vUri );
                    newDesc.removeRevisionDescriptor( nrd );
                    vd.setRevisionDescriptor( nrd );
                }
            }
            
            Iterator iter = vDescs.values().iterator();
            while (iter.hasNext()) {
                taminoClient.getDescriptorsAccessor().update((XDescriptors) iter.next() );
            }
        }
        
        public void log(XRepairLogWriter logWriter) {
            logWriter.println(TC_CHECK_DETAILS, MSG_FOUND_OUTDATED_METADATA, new Object[]{ uri, oldVersion });
        }
    }
    
    //--
    
    /** @return list of NodePermissions **/
    public static List getPermissionList_0_x(Element root) {
        List xmlList;
        List slideList;
        Iterator it;
        
        xmlList = root.getChild(XJdom.E_PERMISSIONS).getChildren();
        slideList = new ArrayList(xmlList.size());
        it = xmlList.iterator();
        while (it.hasNext()) {
            slideList.add(getPermission_0_x((Element) it.next()));
        }
        
        return slideList;
    }
    
    public static NodePermission getPermission_0_x(Element nodePermissionElement) {
        return
            new NodePermission (null  /* no object uri */ ,
                                nodePermissionElement.getAttributeValue(XJdom.A_SUBJECT_URI),
                                nodePermissionElement.getAttributeValue(XJdom.A_ACTION_URI),
                                nodePermissionElement.getAttributeValue
                                    (XJdom.A_INHERITANCE).equals (XJdom.TRUE) ? true : false,
                                nodePermissionElement.getAttributeValue
                                    (XJdom.A_NEGATIVE).equals (XJdom.TRUE) ? true : false);
    }
    
}


