org.fao.geonet.kernel.SchemaManager.java Source code

Java tutorial

Introduction

Here is the source code for org.fao.geonet.kernel.SchemaManager.java

Source

//=============================================================================
//===
//=== SchemaManager
//===
//=============================================================================
//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
//===   This program is free software; you can redistribute it and/or modify
//===   it under the terms of the GNU General Public License as published by
//===   the Free Software Foundation; either version 2 of the License, or (at
//===   your option) any later version.
//===
//===   This program is distributed in the hope that it will be useful, but
//===   WITHOUT ANY WARRANTY; without even the implied warranty of
//===   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//===   General Public License for more details.
//===
//===   You should have received a copy of the GNU General Public License
//===   along with this program; if not, write to the Free Software
//===   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
//===
//===   Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
//===   Rome - Italy. email: geonetwork@osgeo.org
//==============================================================================

package org.fao.geonet.kernel;

import jeeves.constants.Jeeves;
import jeeves.exceptions.OperationAbortedEx;
import jeeves.server.context.ServiceContext;
import jeeves.server.dispatchers.guiservices.XmlFile;
import jeeves.server.overrides.ConfigurationOverrides;
import jeeves.utils.Log;
import jeeves.utils.Xml;
import org.apache.commons.lang.StringUtils;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.constants.Geonet.Namespaces;
import org.fao.geonet.csw.common.Csw;
import org.fao.geonet.exceptions.NoSchemaMatchesException;
import org.fao.geonet.exceptions.SchemaMatchConflictException;
import org.fao.geonet.kernel.schema.MetadataSchema;
import org.fao.geonet.kernel.schema.SchemaLoader;
import org.fao.geonet.kernel.search.spatial.Pair;
import org.fao.geonet.kernel.setting.SettingInfo;
import org.fao.geonet.util.FileCopyMgr;
import org.jdom.Attribute;
import org.jdom.Content;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.filter.ElementFilter;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * Class that handles all functions relating to metadata schemas. This 
 * includes inserting/removing/updating metadata schemas for use in GeoNetwork
 * as well as determining whether a metadata record belongs to any of the 
 * metadata schemas known to GeoNetwork.
 *
 * The Schema class holds all information describing a schema in GeoNetwork. 
 * The SchemaManager holds a map of Schema objects known to GeoNetwork. 
 *
 */
public class SchemaManager {
    private Map<String, Schema> hmSchemas = new HashMap<String, Schema>();
    private String[] fnames = { "labels.xml", "codelists.xml", "strings.xml" };
    private String schemaPluginsDir;
    private String schemaPluginsCat;
    private boolean createOrUpdateSchemaCatalog;
    private String defaultLang;
    private String defaultSchema;
    private String FS = File.separator;
    private String basePath;
    private String resourcePath;
    private int numberOfSchemasAdded = 0;
    private int numberOfCoreSchemasAdded = 0;

    private static final int MODE_NEEDLE = 0;
    private static final int MODE_ROOT = 1;
    private static final int MODE_NEEDLEWITHVALUE = 2;
    private static final int MODE_ATTRIBUTEWITHVALUE = 3;
    private static final int MODE_NAMESPACE = 4;

    private static final String GEONET_SCHEMA_URI = "http://geonetwork-opensource.org/schemas/schema-ident";
    private static final Namespace GEONET_SCHEMA_PREFIX_NS = Namespace.getNamespace("gns", GEONET_SCHEMA_URI);
    private static final Namespace GEONET_SCHEMA_NS = Namespace.getNamespace(GEONET_SCHEMA_URI);

    /** Active readers count */
    private static int activeReaders = 0;
    /** Active writers count */
    private static int activeWriters = 0;

    private static SchemaManager schemaManager = null;

    //--------------------------------------------------------------------------
    //---
    //--- Constructor
    //---
    //--------------------------------------------------------------------------

    /**   Constructor
       *
       * @param basePath the web app base path
       * @param schemaPluginsCat the schema catalogue file
       * @param sPDir the schema plugin directory
       * @param defaultLang the default language (taken from context)
       * @param defaultSchema the default schema (taken from config.xml)
      */
    private SchemaManager(String basePath, String resourcePath, String schemaPluginsCat, String sPDir,
            String defaultLang, String defaultSchema, boolean createOrUpdateSchemaCatalog) throws Exception {

        hmSchemas.clear();

        this.basePath = basePath;
        this.resourcePath = resourcePath;
        this.schemaPluginsDir = sPDir;
        this.defaultLang = defaultLang;
        this.defaultSchema = defaultSchema;
        this.schemaPluginsCat = schemaPluginsCat;
        this.createOrUpdateSchemaCatalog = createOrUpdateSchemaCatalog;

        Element schemaPluginCatRoot = getSchemaPluginCatalogTemplate();

        // -- check the plugin directory and add any schemas already in there
        String[] saSchemas = new File(this.schemaPluginsDir).list();
        if (saSchemas == null) {
            Log.error(Geonet.SCHEMA_MANAGER, "Cannot scan plugin schemas directory : " + schemaPluginsDir);
        } else {
            for (int i = 0; i < saSchemas.length; i++) {
                if (!saSchemas[i].equals("CVS") && !saSchemas[i].startsWith(".")) {
                    File schemaDir = new File(this.schemaPluginsDir + FS + saSchemas[i]);
                    if (schemaDir.isDirectory()) {
                        processSchema(schemaPluginsDir + FS, saSchemas[i], schemaPluginCatRoot);
                    }
                }
            }
            // for each schema check that dependent schemas are already loaded 
            checkDependencies(schemaPluginCatRoot);
        }

        writeSchemaPluginCatalog(schemaPluginCatRoot);

    }

    /**
     * Returns singleton instance.
     *
     * @param basePath
     * @param sPDir
     * @param defaultLang
     * @param defaultSchema
     * @return
     * @throws Exception
     */
    public synchronized static SchemaManager getInstance(String basePath, String resourcePath,
            String schemaPluginsCat, String sPDir, String defaultLang, String defaultSchema,
            boolean createOrUpdateSchemaCatalog) throws Exception {
        if (schemaManager == null) {
            schemaManager = new SchemaManager(basePath, resourcePath, schemaPluginsCat, sPDir, defaultLang,
                    defaultSchema, createOrUpdateSchemaCatalog);
        }
        return schemaManager;
    }

    /**
     * Ensures singleton-ness by preventing cloning.
     *
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    //--------------------------------------------------------------------------
    //---
    //--- API methods
    //---
    //--------------------------------------------------------------------------

    /**
      * Return the MetadataSchema objects
     *
     * @param name the metadata schema we want the MetadataSchema for
      * @return
     */
    public MetadataSchema getSchema(String name) {

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);

            if (schema == null)
                throw new IllegalArgumentException("Schema not registered : " + name);

            final MetadataSchema mds = schema.getMetadataSchema();
            return mds;
        } finally {
            afterRead();
        }
    }

    /**
    * Return the Id and Version of the schema
    *
    * @param name the metadata schema we want the MetadataSchema for
    * @return Pair with schema Id and Version
    */
    public Pair<String, String> getIdVersion(String name) {

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);

            if (schema == null)
                throw new IllegalArgumentException("Schema not registered : " + name);

            return Pair.read(schema.getId(), schema.getVersion());
        } finally {
            afterRead();
        }
    }

    /**
      * Adds a plugin schema to the list of schemas registered here.
     *
     * @param name the metadata schema we want to add
     * @param in stream containing a zip archive of the schema to add
      * @throws Exception
     */
    public void addPluginSchema(String name, InputStream in) throws Exception {

        beforeWrite();
        try {
            realAddPluginSchema(name, in);
        } finally {
            afterWrite();
        }
    }

    /**
      * Updates a plugin schema in the list of schemas registered here.
     *
     * @param name the metadata schema we want to update
     * @param in stream containing a zip archive of the schema to update
      * @throws Exception
     */
    public void updatePluginSchema(String name, InputStream in) throws Exception {

        beforeWrite();
        try {
            // -- delete schema, trap any exception here as we need to say
            // -- why the update failed
            try {
                boolean doDependencies = false;
                realDeletePluginSchema(name, doDependencies);
            } catch (Exception e) {
                String errStr = "Could not update schema " + name
                        + ", remove of outdated schema failed. Exception message if any is " + e.getMessage();
                Log.error(Geonet.SCHEMA_MANAGER, errStr);
                e.printStackTrace();
                throw new OperationAbortedEx(errStr, e);
            }

            // -- add the new one
            realAddPluginSchema(name, in);
        } finally {
            afterWrite();
        }
    }

    /**
      * Returns the schema directory.
     *
     * @param name the metadata schema we want the directory for
      * @return
      */
    public String getSchemaDir(String name) {

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);

            if (schema == null)
                throw new IllegalArgumentException("Schema not registered : " + name);

            return schema.getDir();
        } finally {
            afterRead();
        }
    }

    /**
      * Returns the schema location as a JDOM attribute - this can be  either an xsi:schemaLocation or
      * xsi:noNamespaceSchemaLocation depending on the schema.
     *
     * @param name the metadata schema we want the schemaLocation for
      * @param context
      * @return
      */
    public Attribute getSchemaLocation(String name, ServiceContext context) {

        Attribute out = null;

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);

            if (schema == null)
                throw new IllegalArgumentException("Schema not registered : " + name);

            String nsUri = schema.getMetadataSchema().getPrimeNS();
            String schemaLoc = schema.getSchemaLocation();
            String schemaFile = schema.getDir() + "schema.xsd";

            if (schemaLoc.equals("")) {
                if (new File(schemaFile).exists()) { // build one 
                    String schemaUrl = getSchemaUrl(context, name);
                    if (nsUri == null || nsUri.equals("")) {
                        out = new Attribute("noNamespaceSchemaLocation", schemaUrl, Csw.NAMESPACE_XSI);
                    } else {
                        schemaLoc = nsUri + " " + schemaUrl;
                        out = new Attribute("schemaLocation", schemaLoc, Csw.NAMESPACE_XSI);
                    }
                } // else return null - no schema xsd exists - could be dtd
            } else {
                if (nsUri == null || nsUri.equals("")) {
                    out = new Attribute("noNamespaceSchemaLocation", schemaLoc, Csw.NAMESPACE_XSI);
                } else {
                    out = new Attribute("schemaLocation", schemaLoc, Csw.NAMESPACE_XSI);
                }
            }
            return out;
        } finally {
            afterRead();
        }
    }

    /**
      * Returns the schema templatesdirectory.
     *
     * @param name the metadata schema we want the templates directory for
      * @return
      */
    public String getSchemaTemplatesDir(String name) {

        beforeRead();
        try {
            String dir = getSchemaDir(name);

            dir = dir + FS + "templates";
            if (!new File(dir).exists()) {
                return null;
            }
            return dir;
        } finally {
            afterRead();
        }
    }

    /**
      * Returns the schema sample data directory.
     *
     * @param name the metadata schema we want the sample data directory for
      * @return
     */
    public String getSchemaSampleDataDir(String name) {

        beforeRead();
        try {
            String dir = getSchemaDir(name);

            dir = dir + FS + "sample-data";
            if (!new File(dir).exists()) {
                return null;
            }
            return dir;
        } finally {
            afterRead();
        }
    }

    /**
      * Returns the schema csw presentation directory.
     *
     * @param name the metadata schema we want the csw present info directory
      * @return
      */
    public String getSchemaCSWPresentDir(String name) {

        beforeRead();
        try {
            String dir = getSchemaDir(name);

            dir = dir + "present" + FS + "csw";

            return dir;
        } finally {
            afterRead();
        }
    }

    /**
      * Return the schema information (usually localized codelists, labels etc) XmlFile objects.
     *
     * @param name the metadata schema we want schema info for
      * @return
      */
    public Map<String, XmlFile> getSchemaInfo(String name) {

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);

            if (schema == null)
                throw new IllegalArgumentException("Schema not registered : " + name);

            return schema.getInfo();
        } finally {
            afterRead();
        }
    }

    /**
      * Returns the list of schema names that have been registered.
      *
      * @return
      */
    public Set<String> getSchemas() {

        beforeRead();
        try {
            return hmSchemas.keySet();
        } finally {
            afterRead();
        }
    }

    /**
      * Returns the schema converter elements for a schema (as a list of cloned elements).
     *
     * @param name the metadata schema we want search
     * @throws Exception if schema is not registered
      * @return
     */
    public List<Element> getConversionElements(String name) throws Exception {

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);
            List<Element> childs = schema.getConversionElements();
            List<Element> dChilds = new ArrayList<Element>();
            for (Element child : childs) {
                if (child != null)
                    dChilds.add((Element) child.clone());
            }
            return dChilds;
        } finally {
            afterRead();
        }
    }

    /**
      * Return the schema converter(s) that produce the specified namespace.
     *
     * @param name the metadata schema we want search
     * @param namespaceUri the namespace URI we are looking for
     * @return List of XSLTs that produce this namespace URI (full pathname)
     * @throws Exception if schema is not registered
     */
    public List<String> existsConverter(String name, String namespaceUri) throws Exception {

        List<String> result = new ArrayList<String>();

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);
            List<Element> converterElems = schema.getConversionElements();
            for (Element elem : converterElems) {
                String nsUri = elem.getAttributeValue("nsUri");
                if (nsUri != null && nsUri.equals(namespaceUri)) {
                    String xslt = elem.getAttributeValue("xslt");
                    if (xslt != null) {
                        result.add(schema.getDir() + FS + xslt);
                    }
                }
            }
            return result;
        } finally {
            afterRead();
        }
    }

    /**
      * Whether the schema named in the parameter exist.
     *
     * @param name the metadata schema we want to check existence of
      * @return
     */
    public boolean existsSchema(String name) {

        beforeRead();
        try {
            return hmSchemas.containsKey(name);
        } finally {
            afterRead();
        }
    }

    /**
      * Deletes the schema from the schema information hash tables.
     *
     * @param name the metadata schema we want to delete - can only be a plugin schema
      * @throws Exception
     */
    public void deletePluginSchema(String name) throws Exception {

        beforeWrite();
        try {
            boolean doDependencies = true;
            realDeletePluginSchema(name, doDependencies);

        } finally {
            afterWrite();
        }
    }

    /**
      * Gets the SchemaSuggestions class for the supplied schema name.
     *
     * @param name the metadata schema whose suggestions class we want
      * @return
     */
    public SchemaSuggestions getSchemaSuggestions(String name) {

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);

            if (schema == null)
                throw new IllegalArgumentException("Schema suggestions not registered : " + name);

            return schema.getSuggestions();
        } finally {
            afterRead();
        }
    }

    /**
      * Gets the namespace URI from the schema information (XSD) for the supplied prefix.
     *
     * @param name the metadata schema whose namespaces we are searching
     * @param prefix the namespace prefix we want the URI for
      * @return
     */
    public String getNamespaceURI(String name, String prefix) {

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);

            if (schema == null)
                throw new IllegalArgumentException("Schema not registered : " + name);

            MetadataSchema mds = schema.getMetadataSchema();
            return mds.getNS(prefix);
        } finally {
            afterRead();
        }
    }

    /**
    * Gets the namespaces from schema information (XSD) as a string for use
     * as a list of namespaces.
     *
     * @param name the metadata schema whose namespaces we want
    * @return
     */
    public String getNamespaceString(String name) {

        beforeRead();
        try {
            Schema schema = hmSchemas.get(name);

            if (schema == null)
                throw new IllegalArgumentException("Schema not registered : " + name);

            MetadataSchema mds = schema.getMetadataSchema();
            StringBuilder sb = new StringBuilder();
            for (Namespace ns : mds.getSchemaNS()) {
                if (ns.getPrefix().length() != 0 && ns.getURI().length() != 0) {
                    sb.append("xmlns:" + ns.getPrefix() + "=\"" + ns.getURI() + "\" ");
                }
            }
            return sb.toString().trim();
        } finally {
            afterRead();
        }
    }

    /**
      * Used to detect the schema of an imported metadata file.
      *
      * @param md the imported metadata file
      * @return
      * @throws org.fao.geonet.exceptions.NoSchemaMatchesException
      * @throws org.fao.geonet.exceptions.SchemaMatchConflictException
      */
    public String autodetectSchema(Element md) throws SchemaMatchConflictException, NoSchemaMatchesException {
        return autodetectSchema(md, defaultSchema);
    }

    /**
     *
     * @param md
     * @param defaultSchema
     * @return
     * @throws SchemaMatchConflictException
     * @throws NoSchemaMatchesException
     */
    public String autodetectSchema(Element md, String defaultSchema)
            throws SchemaMatchConflictException, NoSchemaMatchesException {

        beforeRead();
        try {
            String schema = null;

            // -- check the autodetect elements for all schemas with the most
            // -- specific test first, then in order of increasing generality, 
            // -- first match wins
            schema = compareElementsAndAttributes(md, MODE_ATTRIBUTEWITHVALUE);
            if (schema != null) {
                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                    Log.debug(Geonet.SCHEMA_MANAGER,
                            "  => Found schema " + schema + " using AUTODETECT(attributes) examination");
            }

            if (schema == null) {
                schema = compareElementsAndAttributes(md, MODE_NEEDLEWITHVALUE);
                if (schema != null) {
                    if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                        Log.debug(Geonet.SCHEMA_MANAGER, "  => Found schema " + schema
                                + " using AUTODETECT(elements with value) examination");
                }
            }

            if (schema == null) {
                schema = compareElementsAndAttributes(md, MODE_NEEDLE);
                if (schema != null) {
                    if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                        Log.debug(Geonet.SCHEMA_MANAGER,
                                "  => Found schema " + schema + " using AUTODETECT(elements) examination");
                }
            }

            if (schema == null) {
                schema = compareElementsAndAttributes(md, MODE_ROOT);
                if (schema != null) {
                    if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                        Log.debug(Geonet.SCHEMA_MANAGER, "  => Found schema " + schema
                                + " using AUTODETECT(elements with root) examination");
                }
            }

            if (schema == null) {
                schema = compareElementsAndAttributes(md, MODE_NAMESPACE);
                if (schema != null) {
                    if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                        Log.debug(Geonet.SCHEMA_MANAGER,
                                "  => Found schema " + schema + " using AUTODETECT(namespaces) examination");
                }
            }

            // -- If nothing has matched by this point choose defaultSchema supplied
            // -- as argument to this method as long as its reasonable
            if (schema == null && defaultSchema != null) {
                String defaultSchemaOrDependencySchema = checkNamespace(md, defaultSchema);
                if (defaultSchemaOrDependencySchema != null) {
                    Log.warning(Geonet.SCHEMA_MANAGER,
                            "  Autodetecting schema failed for " + md.getName() + " in namespace "
                                    + md.getNamespace() + ". Using default schema or one of its dependency: "
                                    + defaultSchemaOrDependencySchema);
                    schema = defaultSchemaOrDependencySchema;
                }
            }

            // -- if the default schema failed then throw an exception
            if (schema == null) {
                throw new NoSchemaMatchesException(
                        "Autodetecting schema failed for metadata record with root element " + md.getName()
                                + " in namespace " + md.getNamespace() + ".");
            }

            return schema;
        } finally {
            afterRead();
        }
    }

    //--------------------------------------------------------------------------
    // -- Private methods
    //--------------------------------------------------------------------------

    /**
     * Check that schema is present and that the record can be assigned
     * to it - namespace of metadata schema is compared with prime namespace
     * of metadata record.
     *
     * @param md the metadata record being checked for prime namespace equality
     * @param schema the name of the metadata schema we want to test
      * @return
     */
    private String checkNamespace(Element md, String schema) {
        String result = null;

        try {
            MetadataSchema mds = getSchema(schema);
            if (mds != null) {
                String primeNs = mds.getPrimeNS();
                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                    Log.debug(Geonet.SCHEMA_MANAGER, "  primeNs " + primeNs + " for schema " + schema);
                if (md.getNamespace().getURI().equals(primeNs)) {
                    result = schema;
                } else {
                    // Check if the metadata could match a schema dependency 
                    // (If preferredSchema is an ISO profil a fragment or subtemplate
                    // may match ISO core schema and should not be rejected).
                    Schema sch = hmSchemas.get(schema);
                    List<Element> dependsList = sch.getDependElements();
                    for (Element depends : dependsList) {
                        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                            Log.debug(Geonet.SCHEMA_MANAGER,
                                    "  checkNamespace for dependency: " + depends.getText());
                        return checkNamespace(md, depends.getText());
                    }
                }
            }
        } catch (Exception e) {
            Log.warning(Geonet.SCHEMA_MANAGER, "Schema " + schema + " not registered?");
        }

        return result;
    }

    /**
     * Invoked just before reading, waits until reading is allowed.
     */
    private synchronized void beforeRead() {
        while (activeWriters > 0) {
            try {
                wait();
            } catch (InterruptedException iex) {
                // TODO what to do
            }
        }
        ++activeReaders;
    }

    /**
     * Invoked just after reading.
     */
    private synchronized void afterRead() {
        --activeReaders;
        notifyAll();
    }

    /**
     * Invoked just before writing, waits until writing is allowed.
     */
    private synchronized void beforeWrite() {
        while (activeReaders > 0 || activeWriters > 0) {
            try {
                wait();
            } catch (InterruptedException iex) {
            }
        }
        ++activeWriters;
    }

    /**
     * Invoked just after writing.
     */
    private synchronized void afterWrite() {
        --activeWriters;
        notifyAll();
    }

    /**
      * Really delete the schema from the schema information hash tables.
     *
     * @param name the metadata schema we want to delete - can only be a plugin schema
    * @throws Exception when something goes wrong
     */
    private void realDeletePluginSchema(String name, boolean doDependencies) throws Exception {

        Schema schema = hmSchemas.get(name);
        if (schema != null) {
            if (doDependencies) {
                List<String> dependsOnMe = getSchemasThatDependOnMe(name);
                if (dependsOnMe.size() > 0) {
                    String errStr = "Cannot remove schema " + name
                            + " because the following schemas list it as a dependency: " + dependsOnMe;
                    Log.error(Geonet.SCHEMA_MANAGER, errStr);
                    throw new OperationAbortedEx(errStr);
                }
            }

            removeSchemaInfo(name);
        }
    }

    /**
      * Really add a plugin schema to the list of schemas registered here.
     *
     * @param name the metadata schema we want to add
     * @param in stream containing a zip archive of the schema to add
      * @throws Exception
      */
    private void realAddPluginSchema(String name, InputStream in) throws Exception {
        Element schemaPluginCatRoot = getSchemaPluginCatalog();

        // -- create schema directory 
        File dir = new File(schemaPluginsDir, name);
        dir.mkdirs();

        try {
            unpackSchemaZipArchive(dir, in);

            // -- add schema using the addSchema method
            processSchema(schemaPluginsDir + FS, name, schemaPluginCatRoot);

            // -- check that dependent schemas are already loaded 
            Schema schema = hmSchemas.get(name);
            checkDepends(name, schema.getDependElements());

            writeSchemaPluginCatalog(schemaPluginCatRoot);
        } catch (Exception e) {
            e.printStackTrace();
            hmSchemas.remove(name);
            deleteDir(dir);
            throw new OperationAbortedEx("Failed to add schema " + name + " : " + e.getMessage(), e);
        }
    }

    /**
      * helper to copy zipentry to on disk file.
       *
     * @param in the InputStream to copy from (usually a zipEntry)
     * @param out the OutputStream to copy to (eg. file output stream)
      * @throws Exception
     */
    private static void copyInputStream(InputStream in, OutputStream out) throws Exception {
        byte[] buffer = new byte[1024];
        int len;

        while ((len = in.read(buffer)) >= 0)
            out.write(buffer, 0, len);

        out.close();
    }

    /**
      * unpack the schema supplied as a zip archive into the appropriate dir.
     *
     * @param dir the directory into which the zip archive will be unpacked
     * @param in the schema zip archive
      * @throws Exception
     */
    private void unpackSchemaZipArchive(File dir, InputStream in) throws Exception {

        // -- unpack the schema zip archive into it
        ZipInputStream zipStream = new ZipInputStream(in);

        ZipEntry entry = zipStream.getNextEntry();
        while (entry != null) {

            if (entry.isDirectory()) {
                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                    Log.debug(Geonet.SCHEMA_MANAGER, "Creating directory " + entry.getName());
                (new File(dir, entry.getName())).mkdir();
            } else {
                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                    Log.debug(Geonet.SCHEMA_MANAGER, "Creating file " + entry.getName());
                copyInputStream(zipStream,
                        new BufferedOutputStream(new FileOutputStream(new File(dir, entry.getName()))));
            }
            entry = zipStream.getNextEntry();
        }
        zipStream.close();
    }

    /**
      * Loads the metadata schema from disk and adds it to the pool.
     *
     * @param fromAppPath webapp path
     * @param name schema name
     * @param schemaPluginCatRoot
      * @param xmlSchemaFile name of XML schema file (usually schema.xsd)
     * @param xmlSuggestFile name of schema suggestions file
     * @param xmlSubstitutionsFile name schema substitutions file
     * @param xmlIdFile name of XML file that identifies the schema
     * @param oasisCatFile name of XML OASIS catalog file
     * @param conversionsFile name of XML conversions file
      * @throws Exception
     */
    private void addSchema(String fromAppPath, String name, Element schemaPluginCatRoot, String xmlSchemaFile,
            String xmlSuggestFile, String xmlSubstitutionsFile, String xmlIdFile, String oasisCatFile,
            String conversionsFile) throws Exception {
        String path = new File(xmlSchemaFile).getParent();

        // -- set the resolver to use this oasis catalog whilst reading the schemas

        String catalogProp = System.getProperty(Jeeves.XML_CATALOG_FILES);
        if (new File(oasisCatFile).exists()) {
            System.setProperty(Jeeves.XML_CATALOG_FILES, oasisCatFile);
            Xml.resetResolver();
        } else {
            oasisCatFile = "";
        }

        MetadataSchema mds = new SchemaLoader().load(xmlSchemaFile, xmlSubstitutionsFile);
        mds.setName(name);
        mds.setSchemaDir(path);
        mds.loadSchematronRules(basePath);
        mds.setOasisCatalog(oasisCatFile);

        // -- add the oasis catalog file to Jeeves.XML_CATALOG_FILES system 
        // -- property for resolver to pick up - useful during schema parsing

        if (catalogProp == null)
            catalogProp = ""; // shouldn't happen
        if (catalogProp.equals("")) {
            catalogProp = oasisCatFile;
        } else {
            catalogProp = catalogProp + ";" + oasisCatFile;
        }
        System.setProperty(Jeeves.XML_CATALOG_FILES, catalogProp);
        Xml.resetResolver();

        // -- add cached xml files (schema codelists and label files) 
        // -- as Jeeves XmlFile objects (they need not exist)

        String base = fromAppPath + name + FS + "loc";
        Map<String, XmlFile> xfMap = new HashMap<String, XmlFile>();

        for (String fname : fnames) {
            String filePath = path + FS + "loc" + FS + defaultLang + FS + fname;
            if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                Log.debug(Geonet.SCHEMA_MANAGER, "Searching for " + filePath);
            if (new File(filePath).exists()) {
                Element config = new Element("xml");
                config.setAttribute("name", name);
                config.setAttribute("base", base);
                config.setAttribute("file", fname);
                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                    Log.debug(Geonet.SCHEMA_MANAGER, "Adding XmlFile " + Xml.getString(config));
                XmlFile xf = new XmlFile(config, defaultLang, true);
                xfMap.put(fname, xf);
            } else {
                Log.warning(Geonet.SCHEMA_MANAGER, "Unable to load loc file: " + filePath);
            }
        }

        Pair<String, String> idInfo = extractIdInfo(xmlIdFile, name);

        mds.setReadwriteUUID(extractReadWriteUuid(xmlIdFile));
        Log.debug(Geonet.SCHEMA_MANAGER, "  UUID is read/write mode: " + mds.isReadwriteUUID());

        putSchemaInfo(name, idInfo.one(), // uuid of schema
                idInfo.two(), // version of schema
                mds, path + FS, new SchemaSuggestions(xmlSuggestFile), extractADElements(xmlIdFile), xfMap, true, // all schemas are plugin schemas now
                extractSchemaLocation(xmlIdFile), extractConvElements(conversionsFile), extractDepends(xmlIdFile));

        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
            Log.debug(Geonet.SCHEMA_MANAGER,
                    "Property " + Jeeves.XML_CATALOG_FILES + " is " + System.getProperty(Jeeves.XML_CATALOG_FILES));

        // -- Add entry for presentation xslt to schemaPlugins catalog
        // -- if this schema is a plugin schema
        int baseNrInt = getHighestSchemaPluginCatalogId(name, schemaPluginCatRoot);
        if (baseNrInt == 0)
            baseNrInt = numberOfCoreSchemasAdded;
        if (baseNrInt != -1) {
            createUriEntryInSchemaPluginCatalog(name, baseNrInt, schemaPluginCatRoot);
        }

        // -- copy schema.xsd and schema directory from schema to 
        // -- <web_app_dir>/xml/schemas/<schema_name>
        copySchemaXSDsToWebApp(name, path);

    }

    /**
      * Read the elements from the schema plugins catalog for use by other methods.
     *
      * @return
      * @throws Exception
      */
    private Element getSchemaPluginCatalog() throws Exception {
        // -- open schemaPlugins catalog, get children named uri
        return Xml.loadFile(schemaPluginsCat);
    }

    /**
     * Read the empty template for the schema plugins oasis catalog.
     * @return
     * @throws Exception
     */
    private Element getSchemaPluginCatalogTemplate() throws Exception {
        return Xml.loadFile(basePath + FS + "WEB-INF" + FS + Geonet.File.SCHEMA_PLUGINS_CATALOG);
    }

    /**
     * Build a path to the schema plugin folder
     *
     * @param name the name of the schema to use
     * @return
     */
    private String buildSchemaFolderPath(String name) {
        return schemaPluginsDir.replace('\\', '/') + "/" + name.replace('\\', '/');
    }

    /**
      * Deletes the presentation xslt from the schemaplugin oasis catalog.
     *
     * @param root the list of elements from the schemaplugin-uri-catalog
     * @param name the name of the schema to use
      * @return
      * @throws Exception
     */
    private Element deleteSchemaFromPluginCatalog(String name, Element root) throws Exception {
        @SuppressWarnings(value = "unchecked")
        List<Content> contents = root.getContent();

        String ourUri = buildSchemaFolderPath(name);

        int index = -1;
        for (Content content : contents) {
            Element uri = null;

            if (content instanceof Element)
                uri = (Element) content;
            else
                continue; // skip this

            if (!uri.getName().equals("uri") || !uri.getNamespace().equals(Namespaces.OASIS_CATALOG)) {
                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                    Log.debug(Geonet.SCHEMA_MANAGER,
                            "Skipping element " + uri.getQualifiedName() + ":" + uri.getNamespace());
                continue;
            }

            // -- if already mapped then exit
            if (uri.getAttributeValue("uri").equals(ourUri))
                index = root.indexOf(uri);
        }

        if (index != -1)
            root.removeContent(index);
        return root;
    }

    /**
      * Gets the next available blank number that can be used to map the presentation xslt used by the schema (see
      * metadata-utils.xsl and Geonet.File.METADATA_MAX_BLANKS). If the presentation xslt is already mapped then we exit
      * early with return value -1.
     *
     * @param root the list of elements from the schemaplugin-uri-catalog
     * @param name the name of the schema to use
      * @return
      * @throws Exception
     */
    private int getHighestSchemaPluginCatalogId(String name, Element root) throws Exception {
        @SuppressWarnings("unchecked")
        List<Content> contents = root.getContent();

        String baseBlank = Geonet.File.METADATA_BASEBLANK;
        String ourUri = buildSchemaFolderPath(name);

        for (Content content : contents) {
            Element uri = null;

            if (content instanceof Element)
                uri = (Element) content;
            else
                continue; // skip this

            if (!uri.getName().equals("rewriteURI") || !uri.getNamespace().equals(Namespaces.OASIS_CATALOG)) {
                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                    Log.debug(Geonet.SCHEMA_MANAGER,
                            "Skipping element " + uri.getQualifiedName() + ":" + uri.getNamespace());
                continue;
            }

            // -- if already mapped then exit
            if (uri.getAttributeValue("rewritePrefix").equals(ourUri))
                return -1;

            String nameAttr = uri.getAttributeValue("uriStartString");
            if (nameAttr.startsWith(Geonet.File.METADATA_BLANK)) {
                if (nameAttr.compareTo(baseBlank) > 0)
                    baseBlank = nameAttr;
            }
        }

        // -- get highest appropriate number
        String baseNr = baseBlank.replace(Geonet.File.METADATA_BLANK, "");
        int baseNrInt = 0;
        try {
            baseNrInt = Integer.parseInt(baseNr);
        } catch (NumberFormatException nfe) {
            nfe.printStackTrace();
            throw new IllegalArgumentException("Cannot decode blank number from " + baseBlank);
        }
        return baseNrInt;
    }

    /**
      * Creates a uri remap entry in the schema plugins catalog for the presentation xslt used by the schema.
     *
     * @param name the name of the schema to use
     * @param baseNrInt the number of the plugin schema to map the presentation xslt to
     * @param root the list of elements from the schemaplugin-uri-catalog
      * @throws Exception
     */
    private void createUriEntryInSchemaPluginCatalog(String name, int baseNrInt, Element root) throws Exception {

        baseNrInt = baseNrInt + 1;

        Element newBlank = new Element("rewriteURI", Namespaces.OASIS_CATALOG);
        //Element newBlank = new Element("uri", Geonet.OASIS_CATALOG_NAMESPACE);
        if (baseNrInt <= Geonet.File.METADATA_MAX_BLANKS) {
            String zero = "";
            if (baseNrInt < 10)
                zero = "0";
            newBlank.setAttribute("uriStartString", Geonet.File.METADATA_BLANK + zero + baseNrInt);
            newBlank.setAttribute("rewritePrefix",
                    new File(buildSchemaFolderPath(name)).toURI().toURL().toString());
        } else {
            throw new IllegalArgumentException(
                    "Exceeded maximum number of plugin schemas " + Geonet.File.METADATA_MAX_BLANKS);
        }

        // -- write out new schemaPlugins catalog and re-init the resolvers that
        // -- use this catalog

        root.addContent(newBlank);
    }

    /**
      * Writes the schema plugin catalog out.
     *
     * @param root the list of elements from the schemaplugin-uri-catalog
      * @throws Exception
     */
    private void writeSchemaPluginCatalog(Element root) throws Exception {
        if (createOrUpdateSchemaCatalog) {
            Xml.writeResponse(new Document((Element) root.detach()),
                    new BufferedOutputStream(new FileOutputStream(new File(schemaPluginsCat))));
            Xml.resetResolver();
            Xml.clearTransformerFactoryStylesheetCache();
        }
    }

    /**
      * Puts information into the schema information hashtables.
     *
     * @param id schema id (uuid)
     * @param version schema version
     * @param name schema name
     * @param mds MetadataSchema object with details of XML schema info
     * @param schemaDir path name of schema directory
     * @param sugg SchemaSuggestions object
     * @param adElems List of autodetect XML elements (as JDOM Elements)
     * @param xfMap Map containing XML localized info files (as Jeeves XmlFiles)
     * @param isPlugin true if schema is a plugin schema
     * @param schemaLocation namespaces and URLs of their xsds
     * @param convElems List of elements in conversion file
     * @param dependElems List of depend XML elements (as JDOM Elements) 
     */
    private void putSchemaInfo(String name, String id, String version, MetadataSchema mds, String schemaDir,
            SchemaSuggestions sugg, List<Element> adElems, Map<String, XmlFile> xfMap, boolean isPlugin,
            String schemaLocation, List<Element> convElems, List<Element> dependElems) {

        Schema schema = new Schema();

        schema.setId(id);
        schema.setVersion(version);
        schema.setMetadataSchema(mds);
        schema.setDir(schemaDir);
        schema.setSuggestions(sugg);
        schema.setAutodetectElements(adElems);
        schema.setInfo(xfMap);
        schema.setPluginSchema(isPlugin);
        schema.setSchemaLocation(schemaLocation);
        schema.setConversionElements(convElems);
        schema.setDependElements(dependElems);

        hmSchemas.put(name, schema);
    }

    /**
      * Deletes information from the schema information hashtables, the schema directory itself and the mapping for the
      * schema presentation xslt from the schemaplugins oasis catalog.
     *
     * @param name schema name
      * @throws Exception
     */
    private void removeSchemaInfo(String name) throws Exception {
        Schema schema = hmSchemas.get(name);

        removeSchemaDir(schema.getDir(), name);
        hmSchemas.remove(name);

        Element schemaPluginCatRoot = getSchemaPluginCatalog();
        schemaPluginCatRoot = deleteSchemaFromPluginCatalog(name, schemaPluginCatRoot);
        writeSchemaPluginCatalog(schemaPluginCatRoot);

    }

    /**
      * Deletes information in the schema directory and removes published schemas from the webapp.
     *
     * @param dir schema directory to remove
     * @param name of schema being removed  
       */
    private void removeSchemaDir(String dir, String name) {
        // -- FIXME: get schema directory and zip it up into the deleted metadata 
        // -- directory?

        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
            Log.debug(Geonet.SCHEMA_MANAGER, "Removing schema directory " + dir);
        boolean deleteOp = deleteDir(new File(dir));
        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
            Log.debug(Geonet.SCHEMA_MANAGER, "Delete operation returned " + deleteOp);

        String pubSchemaDir = resourcePath + FS + Geonet.Path.SCHEMAS + name;
        Log.debug(Geonet.SCHEMA_MANAGER, "Removing published schemas directory " + pubSchemaDir);
        deleteOp = deleteDir(new File(pubSchemaDir));
        Log.debug(Geonet.SCHEMA_MANAGER, "Delete operation returned " + deleteOp);
    }

    /**
      * Processes schemas in either web/xml/schemas or schema plugin directory.
     *
     * @param schemasDir path name of directory containing schemas
     * @param saSchema schema in schemasDir to process
      * @param schemaPluginCatRoot 
      * @return
     */
    private void processSchema(String schemasDir, String saSchema, Element schemaPluginCatRoot)
            throws OperationAbortedEx {

        String schemaFile = schemasDir + saSchema + "/" + Geonet.File.SCHEMA;
        String suggestFile = schemasDir + saSchema + "/" + Geonet.File.SCHEMA_SUGGESTIONS;
        String substitutesFile = schemasDir + saSchema + "/" + Geonet.File.SCHEMA_SUBSTITUTES;
        String idFile = schemasDir + saSchema + "/" + Geonet.File.SCHEMA_ID;
        String oasisCatFile = schemasDir + saSchema + "/" + Geonet.File.SCHEMA_OASIS;
        String conversionsFile = schemasDir + saSchema + "/" + Geonet.File.SCHEMA_CONVERSIONS;

        if (!(new File(idFile).exists())) {
            Log.error(Geonet.SCHEMA_MANAGER,
                    "    Skipping : " + saSchema + " as it doesn't have " + Geonet.File.SCHEMA_ID);
            return;
        }

        Log.info(Geonet.SCHEMA_MANAGER, "    Adding xml schema : " + saSchema);

        String stage = "";
        try {
            // validate the schema-ident file before reading it
            stage = "reading schema-ident file " + idFile;
            Element root = Xml.loadFile(idFile);
            stage = "validating schema-ident file " + idFile;
            Xml.validate(new Document(root));

            if (hmSchemas.containsKey(saSchema)) { // exists so ignore it
                Log.error(Geonet.SCHEMA_MANAGER, "Schema " + saSchema + " already exists - cannot add!");
            } else {
                stage = "adding the schema information";
                addSchema(schemasDir, saSchema, schemaPluginCatRoot, schemaFile, suggestFile, substitutesFile,
                        idFile, oasisCatFile, conversionsFile);
            }
        } catch (Exception e) {
            String errStr = "Failed whilst " + stage + ". Exception message if any is " + e.getMessage();
            Log.error(Geonet.SCHEMA_MANAGER, errStr);
            e.printStackTrace();
            throw new OperationAbortedEx(errStr, e);
        }

    }

    /**
    * Check dependencies for all schemas - remove those that fail.
     *
     * @param schemaPluginCatRoot 
     *
     */
    private void checkDependencies(Element schemaPluginCatRoot) throws Exception {
        List<String> removes = new ArrayList<String>();

        // process each schema to see whether its dependencies are present
        for (String schemaName : hmSchemas.keySet()) {
            Schema schema = hmSchemas.get(schemaName);
            try {
                checkDepends(schemaName, schema.getDependElements());
            } catch (Exception e) {
                Log.error(Geonet.SCHEMA_MANAGER, "check dependencies failed: " + e.getMessage());
                // add the schema to list for removal 
                removes.add(schemaName);
            }
        }

        // now remove any that failed the dependency test
        for (String removeSchema : removes) {
            hmSchemas.remove(removeSchema);
            deleteSchemaFromPluginCatalog(removeSchema, schemaPluginCatRoot);
        }

    }

    /**
    * Get list of schemas that depend on supplied schema name.
     *
     * @param schemaName Schema being checked 
    * @return List of schemas that depend on schemaName.
     */
    public List<String> getSchemasThatDependOnMe(String schemaName) {

        List<String> myDepends = new ArrayList<String>();

        // process each schema to see whether its dependencies are present
        for (String schemaNameToTest : hmSchemas.keySet()) {
            if (schemaNameToTest.equals(schemaName))
                continue;

            Schema schema = hmSchemas.get(schemaNameToTest);
            List<Element> dependsList = schema.getDependElements();
            for (Element depends : dependsList) {
                if (depends.getText().equals(schemaName)) {
                    myDepends.add(schemaNameToTest);
                }
            }
        }

        return myDepends;
    }

    /**
    * Check schema dependencies (depend elements).
     *
     * @param thisSchema Schema being checked 
    * @param dependsList List of depend elements for schema.
    * @throws Exception
     */
    private void checkDepends(String thisSchema, List<Element> dependsList) throws Exception {
        // process each dependency to see whether it is present
        for (Element depends : dependsList) {
            String schema = depends.getText();
            if (schema.length() > 0) {
                if (!hmSchemas.containsKey(schema)) {
                    throw new IllegalArgumentException(
                            "Schema " + thisSchema + " depends on " + schema + ", but that schema is not loaded");
                }
            }
        }
    }

    /**
    * Extract schema dependencies (depend elements).
     *
     * @param xmlIdFile name of schema XML identification file
     * @return depends elements as a List
    * @throws Exception
     */
    private List<Element> extractDepends(String xmlIdFile) throws Exception {
        Element root = Xml.loadFile(xmlIdFile);

        // get list of depends elements from schema-ident.xml
        List<Element> dependsList = root.getChildren("depends", GEONET_SCHEMA_NS);
        if (dependsList.size() == 0) {
            dependsList = root.getChildren("depends", GEONET_SCHEMA_PREFIX_NS);
        }
        return dependsList;
    }

    /**
     * true if schema requires to synch the uuid column schema info
     * with the uuid in the metadata record (updated on editing or in UFO).
     * 
     * @param xmlIdFile
     * @return
     * @throws Exception
     */
    private boolean extractReadWriteUuid(String xmlIdFile) throws Exception {
        Element root = Xml.loadFile(xmlIdFile);

        String id = root.getChildText("readwriteUuid", GEONET_SCHEMA_NS);
        if (id == null) {
            return false;
        } else {
            if ("true".equals(id)) {
                return true;
            }
        }
        return false;
    }

    /**
      * Extract schema version and uuid info from identification file and compare specified name with name in
      * identification file.
     *
     * @param xmlIdFile name of schema XML identification file
      * @param name
      * @return
      * @throws Exception
     */
    private Pair<String, String> extractIdInfo(String xmlIdFile, String name) throws Exception {
        // FIXME: should be validating parser
        Element root = Xml.loadFile(xmlIdFile);

        Element id = root.getChild("id", GEONET_SCHEMA_NS);
        if (id == null)
            id = root.getChild("id", GEONET_SCHEMA_PREFIX_NS);

        Element version = root.getChild("version", GEONET_SCHEMA_NS);
        if (version == null)
            version = root.getChild("version", GEONET_SCHEMA_PREFIX_NS);

        Element idName = root.getChild("name", GEONET_SCHEMA_NS);
        if (idName == null)
            idName = root.getChild("name", GEONET_SCHEMA_PREFIX_NS);

        if (!idName.getText().equals(name))
            throw new IllegalArgumentException("Schema name supplied " + name
                    + " does not match the name of the schema in the schema-id.xml file " + idName.getText());

        return Pair.read(id.getText(), version.getText());
    }

    /**
      * Extracts schema autodetect info from identification file.
     *
     * @param xmlIdFile name of schema XML identification file
      * @return
      * @throws Exception
     */
    private List<Element> extractADElements(String xmlIdFile) throws Exception {
        Element root = Xml.loadFile(xmlIdFile);
        Element autodetect = root.getChild("autodetect", GEONET_SCHEMA_NS);
        if (autodetect == null)
            autodetect = root.getChild("autodetect", GEONET_SCHEMA_PREFIX_NS);
        return autodetect.getChildren();
    }

    /**
      * Extract conversion elements from conversions file.
     *
     * @param xmlConvFile name of schema XML conversions file
      * @return
      * @throws Exception
     */
    private List<Element> extractConvElements(String xmlConvFile) throws Exception {
        List<Element> result = new ArrayList<Element>();
        if (!(new File(xmlConvFile).exists())) {
            if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                Log.debug(Geonet.SCHEMA_MANAGER, "Schema conversions file not present");
        } else {
            Element root = Xml.loadFile(xmlConvFile);
            ConfigurationOverrides.DEFAULT.updateWithOverrides(xmlConvFile, null, basePath, root);

            if (root.getName() != "conversions")
                throw new IllegalArgumentException(
                        "Schema conversions file " + xmlConvFile + " is invalid, no <conversions> root element");
            result = root.getChildren();
        }
        return result;
    }

    /**
      * Extract schemaLocation info from identification file.
     *
      * @param xmlIdFile name of schema XML identification file
      * @return
      * @throws Exception
     */
    private String extractSchemaLocation(String xmlIdFile) throws Exception {
        Element root = Xml.loadFile(xmlIdFile);
        Element schemaLocElem = root.getChild("schemaLocation", GEONET_SCHEMA_NS);
        if (schemaLocElem == null)
            schemaLocElem = root.getChild("schemaLocation", GEONET_SCHEMA_PREFIX_NS);
        return schemaLocElem.getText();
    }

    /**
      * Search all available schemas for one which contains the element(s) or attributes specified in the autodetect
      * info.
     *
     * @param md the XML record whose schema we are trying to find
      * @param mode
      * @return
      * @throws org.fao.geonet.exceptions.SchemaMatchConflictException
      */
    private String compareElementsAndAttributes(Element md, int mode) throws SchemaMatchConflictException {
        String returnVal = null;
        Set<String> allSchemas = getSchemas();
        List<String> matches = new ArrayList<String>();

        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
            Log.debug(Geonet.SCHEMA_MANAGER, "Schema autodetection starting on " + md.getName() + " (Namespace: "
                    + md.getNamespace() + ") using mode: " + mode + "...");

        for (String schemaName : allSchemas) {
            if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                Log.debug(Geonet.SCHEMA_MANAGER, "   Doing schema " + schemaName);
            Schema schema = hmSchemas.get(schemaName);
            List<Element> adElems = schema.getAutodetectElements();

            for (Element elem : adElems) {
                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                    Log.debug(Geonet.SCHEMA_MANAGER, "      Checking autodetect element " + Xml.getString(elem)
                            + " with name " + elem.getName());

                List<Element> elemKids = elem.getChildren();
                boolean match = false;

                Attribute type = elem.getAttribute("type");

                // --- try and find the attribute and value in md 
                if (mode == MODE_ATTRIBUTEWITHVALUE && elem.getName() == "attributes") {
                    List<Attribute> atts = elem.getAttributes();
                    for (Attribute searchAtt : atts) {
                        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                            Log.debug(Geonet.SCHEMA_MANAGER,
                                    "            Finding attribute " + searchAtt.toString());

                        if (isMatchingAttributeInMetadata(searchAtt, md)) {
                            match = true;
                        } else {
                            match = false;
                            break;
                        }
                    }

                    // --- try and find the namespace in md 
                } else if (mode == MODE_NAMESPACE && elem.getName() == "namespaces") {
                    List<Namespace> nss = elem.getAdditionalNamespaces();
                    for (Namespace ns : nss) {
                        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                            Log.debug(Geonet.SCHEMA_MANAGER, "            Finding namespace " + ns.toString());

                        if (isMatchingNamespaceInMetadata(ns, md)) {
                            match = true;
                        } else {
                            match = false;
                            break;
                        }
                    }
                } else {
                    for (Element kid : elemKids) {

                        // --- is the kid the same as the root of the md
                        if (mode == MODE_ROOT && type != null && "root".equals(type.getValue())) {
                            if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                                Log.debug(Geonet.SCHEMA_MANAGER,
                                        "            Comparing " + Xml.getString(kid) + " with " + md.getName()
                                                + " with namespace " + md.getNamespace() + " : "
                                                + (kid.getName().equals(md.getName())
                                                        && kid.getNamespace().equals(md.getNamespace())));
                            if (kid.getName().equals(md.getName())
                                    && kid.getNamespace().equals(md.getNamespace())) {
                                match = true;
                                break;
                            } else {
                                match = false;
                            }
                            // --- try and find the kid in the md (kid only, not value)
                        } else if (mode == MODE_NEEDLE && type != null && "search".equals(type.getValue())) {
                            if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                                Log.debug(Geonet.SCHEMA_MANAGER,
                                        "            Comparing " + Xml.getString(kid) + " with " + md.getName()
                                                + " with namespace " + md.getNamespace() + " : "
                                                + (kid.getName().equals(md.getName())
                                                        && kid.getNamespace().equals(md.getNamespace())));

                            if (isMatchingElementInMetadata(kid, md, false)) {
                                match = true;
                            } else {
                                match = false;
                                break;
                            }
                            // --- try and find the kid in the md (kid + value)
                        } else if (mode == MODE_NEEDLEWITHVALUE) {
                            if (isMatchingElementInMetadata(kid, md, true)) {
                                match = true;
                            } else {
                                match = false;
                                break;
                            }
                        }
                    }
                }
                if (match && (!matches.contains(schemaName)))
                    matches.add(schemaName);
            }
        }

        if (matches.size() > 1) {
            throw new SchemaMatchConflictException("Metadata record with " + md.getName() + " (Namespace "
                    + md.getNamespace() + " matches more than one schema - namely: " + matches.toString()
                    + " - during schema autodetection mode " + mode);
        } else if (matches.size() == 1) {
            returnVal = matches.get(0);
        }

        return returnVal;
    }

    /**
      * This method searches an entire metadata file for an attribute that matches the "needle" metadata attribute
      * arg - A matching attribute has the same name and value.
     *
     * @param needle the XML attribute we are trying to find
     * @param haystack the XML metadata record we are searching
      * @return
      */
    private boolean isMatchingAttributeInMetadata(Attribute needle, Element haystack) {
        boolean returnVal = false;
        Iterator<Element> haystackIterator = haystack.getDescendants(new ElementFilter());

        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
            Log.debug(Geonet.SCHEMA_MANAGER, "Matching " + needle.toString());

        while (haystackIterator.hasNext()) {
            Element tempElement = haystackIterator.next();
            Attribute tempAtt = tempElement.getAttribute(needle.getName());
            if (tempAtt.equals(needle)) {
                returnVal = true;
                break;
            }
        }
        return returnVal;
    }

    /**
      * This method searches all elements of a metadata for a namespace that matches the "needle" namespace arg. (Note:
      * matching namespaces have the same URI, prefix is ignored).
     *
     * @param needle the XML namespace we are trying to find
     * @param haystack the XML metadata record we are searching
      * @return
      */
    private boolean isMatchingNamespaceInMetadata(Namespace needle, Element haystack) {
        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
            Log.debug(Geonet.SCHEMA_MANAGER, "Matching " + needle.toString());

        if (checkNamespacesOnElement(needle, haystack))
            return true;

        Iterator<Element> haystackIterator = haystack.getDescendants(new ElementFilter());
        while (haystackIterator.hasNext()) {
            Element tempElement = haystackIterator.next();
            if (checkNamespacesOnElement(needle, tempElement))
                return true;
        }

        return false;
    }

    /**
      * This method searches an elements and its namespaces for a match with an input namespace.
     *
     * @param ns the XML namespace we are trying to find
     * @param elem the XML metadata element whose namespaces are to be searched
      * @return
      */
    private boolean checkNamespacesOnElement(Namespace ns, Element elem) {
        if (elem.getNamespace().equals(ns))
            return true;
        List<Namespace> nss = elem.getAdditionalNamespaces();
        for (Namespace ans : nss) {
            if (ans.equals(ns))
                return true;
        }
        return false;
    }

    /**
      * This method searches an entire metadata file for an element that matches the "needle" metadata element arg - A
      * matching element has the same name, namespace and value.
     *
     * @param needle the XML element we are trying to find
     * @param haystack the XML metadata record we are searching
     * @param checkValue compare the value of the needle with the value of the element we find in the md
      * @return
      */
    private boolean isMatchingElementInMetadata(Element needle, Element haystack, boolean checkValue) {
        boolean returnVal = false;
        Iterator<Element> haystackIterator = haystack.getDescendants(new ElementFilter());

        String needleName = needle.getName();
        Namespace needleNS = needle.getNamespace();
        if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
            Log.debug(Geonet.SCHEMA_MANAGER, "Matching " + Xml.getString(needle));

        while (haystackIterator.hasNext()) {
            Element tempElement = haystackIterator.next();

            if (tempElement.getName().equals(needleName) && tempElement.getNamespace().equals(needleNS)) {
                if (checkValue) {
                    if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                        Log.debug(Geonet.SCHEMA_MANAGER, "  Searching value for element: " + tempElement.getName());

                    String needleVal = needle.getValue();
                    String[] needleToken = StringUtils.deleteWhitespace(needleVal).split("\\|");
                    String tempVal = StringUtils.deleteWhitespace(tempElement.getValue());

                    for (String t : needleToken) {
                        Log.debug(Geonet.SCHEMA_MANAGER,
                                "    Comparing: '" + t + "' \n****with****\n '" + tempVal + "'");
                        if (tempVal != null && needleVal != null) {
                            returnVal = t.equals(tempVal);
                            if (returnVal) {
                                if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
                                    Log.debug(Geonet.SCHEMA_MANAGER,
                                            "    Found value: " + t + " for needle: " + needleName);
                                break;
                            }
                        }
                    }
                } else {
                    returnVal = true;
                    break;
                }
            }
        }
        return returnVal;
    }

    /**
      * This method deletes all the files and directories inside another the schema dir and then the schema dir itself.
     *
     * @param dir the dir whose contents are to be deleted
      * @return
     */
    private boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success)
                    return false;
            }
        }

        return dir.delete();
    }

    /**
      * Create a URL that can be used to point to a schema XSD delivered by GeoNetwork.
     *
     * @param context the ServiceContext used to get setting manager and appPath
     * @param schemaName the name of the schema
      * @return
     */
    private String getSchemaUrl(ServiceContext context, String schemaName) {
        SettingInfo si = new SettingInfo(context);

        String relativePath = Geonet.Path.SCHEMAS + schemaName + "/schema.xsd";
        return si.getSiteUrl() + context.getBaseUrl() + "/" + relativePath;
    }

    public String getDefaultSchema() {
        return defaultSchema;
    }

    /**
      * Copy the schema.xsd file and the schema directory from the schema plugin directory to the webapp.
     *
     * @param name the name of the schema
     * @param schemaPluginDir the directory containing the schema plugin
     */
    private void copySchemaXSDsToWebApp(String name, String schemaPluginDir) throws Exception {

        FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".xsd") || name.endsWith(".XSD");
            }
        };

        File webAppDir = new File(resourcePath + FS + Geonet.Path.SCHEMAS);
        webAppDir.mkdirs();

        File webAppDirSchemaXSD = new File(webAppDir, name);
        deleteDir(webAppDirSchemaXSD);
        webAppDirSchemaXSD.mkdirs();

        // copy all XSDs from schema plugin dir to webapp schema dir
        File fileSchemaPluginDir = new File(schemaPluginDir);

        String[] schemaFiles = fileSchemaPluginDir.list(filter);
        if (schemaFiles.length > 0) {
            for (String schemaFile : schemaFiles) {
                FileCopyMgr.copyFiles(new File(schemaPluginDir, schemaFile),
                        new File(webAppDirSchemaXSD, schemaFile));
            }
        } else {
            Log.error(Geonet.SCHEMA_MANAGER, "Schema " + name + " does not have any XSD files!");
        }

        File fileSchemaDir = new File(schemaPluginDir, "schema");
        if (fileSchemaDir.exists()) {
            FileCopyMgr.copyFiles(fileSchemaDir, new File(webAppDirSchemaXSD, "schema"));
        }

    }

}