//=============================================================================
//===
//=== 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 org.fao.geonet.Constants;
import org.fao.geonet.exceptions.OperationAbortedEx;
import jeeves.server.context.ServiceContext;
import jeeves.server.dispatchers.guiservices.XmlFile;
import jeeves.server.overrides.ConfigurationOverrides;
import org.fao.geonet.kernel.schema.SchemaPlugin;
import org.fao.geonet.repository.SchematronCriteriaGroupRepository;
import org.fao.geonet.repository.SchematronRepository;
import org.fao.geonet.utils.BinaryFile;
import org.fao.geonet.utils.IO;
import org.fao.geonet.utils.Log;
import org.fao.geonet.utils.Xml;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.constants.Geonet.Namespaces;
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.domain.Pair;
import org.fao.geonet.kernel.setting.SettingInfo;
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 org.springframework.context.ApplicationContext;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
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.regex.Pattern;
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 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);
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 numberOfCoreSchemasAdded = 0;
/** Active readers count */
private static int activeReaders = 0;
/** Active writers count */
private static int activeWriters = 0;
public static String registerXmlCatalogFiles(String path, String schemapluginUriCatalog) {
String webapp = path + "WEB-INF" + File.separator;
//--- Set jeeves.xml.catalog.files property
//--- this is critical to schema support so must be set correctly
String catalogProp = System.getProperty(Constants.XML_CATALOG_FILES);
if (catalogProp == null) {
catalogProp = "";
}
if (!catalogProp.equals("")) {
Log.info(Geonet.SCHEMA_MANAGER, "Overriding " + Constants.XML_CATALOG_FILES + " property (was set to " + catalogProp + ")");
}
catalogProp = webapp + "oasis-catalog.xml;" + schemapluginUriCatalog;
System.setProperty(Constants.XML_CATALOG_FILES, catalogProp);
Log.info(Geonet.SCHEMA_MANAGER, Constants.XML_CATALOG_FILES + " property set to " + catalogProp);
String blankXSLFile = path + "xsl" + File.separator + "blanks.xsl";
System.setProperty(Constants.XML_CATALOG_BLANKXSLFILE, blankXSLFile);
Log.info(Geonet.SCHEMA_MANAGER, Constants.XML_CATALOG_BLANKXSLFILE + " property set to " + blankXSLFile);
return webapp;
}
//--------------------------------------------------------------------------
//---
//--- Constructor
//---
//--------------------------------------------------------------------------
/**
* initialize and configure schema manager. should only be on startup.
*
* @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)
*/
public void configure(ApplicationContext applicationContext, 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()) {
Log.info(Geonet.SCHEMA_MANAGER, "Loading schema " + saSchemas[i] + "...");
processSchema(applicationContext, schemaPluginsDir + FS, saSchemas[i], schemaPluginCatRoot);
}
}
}
// for each schema check that dependent schemas are already loaded
checkDependencies(schemaPluginCatRoot);
}
writeSchemaPluginCatalog(schemaPluginCatRoot);
}
/**
* 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();
}
}
public static SchemaPlugin getSchemaPlugin(ServiceContext context, String schemaIdentifier) {
String schemaBeanIdentifier = schemaIdentifier + "SchemaPlugin";
SchemaPlugin schemaPlugin = null;
try {
schemaPlugin = (SchemaPlugin) context.getApplicationContext().getBean(schemaBeanIdentifier);
String iso19139SchemaIdentifier = "iso19139";
if (schemaPlugin == null && schemaIdentifier.startsWith(iso19139SchemaIdentifier)){
// For ISO19139 profiles, get the ISO19139 bean if no custom one defined
// Can't depend here on ISO19139SchemaPlugin to avoid to introduce
// circular ref.
schemaBeanIdentifier = iso19139SchemaIdentifier + "SchemaPlugin";
schemaPlugin = (SchemaPlugin) context.getApplicationContext().getBean(schemaBeanIdentifier);
}
} catch (Exception e) {
// No bean for this schema
if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER)) {
Log.debug(Geonet.SCHEMA_MANAGER, "No bean defined for the schema plugin '" +
schemaIdentifier + "'. " +
e.getMessage());
}
}
return schemaPlugin;
}
/**
* 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(ApplicationContext applicationContext, String name, InputStream in) throws Exception {
beforeWrite();
try {
realAddPluginSchema(applicationContext, 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(ApplicationContext applicationContext, 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(applicationContext, 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, Geonet.Namespaces.XSI);
} else {
schemaLoc = nsUri +" "+ schemaUrl;
out = new Attribute("schemaLocation", schemaLoc, Geonet.Namespaces.XSI);
}
} // else return null - no schema xsd exists - could be dtd
} else {
if (nsUri == null || nsUri.equals("")) {
out = new Attribute("noNamespaceSchemaLocation", schemaLoc, Geonet.Namespaces.XSI);
} else {
out = new Attribute("schemaLocation", schemaLoc, Geonet.Namespaces.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(ApplicationContext applicationContext, String name, InputStream in) throws Exception {
Element schemaPluginCatRoot = getSchemaPluginCatalog();
// -- create schema directory
File dir = new File(schemaPluginsDir, name);
IO.mkdirs(dir, name+" schema plugin dir");
try {
unpackSchemaZipArchive(dir, in);
// -- add schema using the addSchema method
processSchema(applicationContext, 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());
}
File dirFile = new File(dir, entry.getName());
if (!dirFile.mkdir() && !dirFile.exists()) {
throw new IOException("Unable to create directory: "+dirFile);
}
} 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(ApplicationContext applicationContext, 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();
// -- add any oasis catalog files to Jeeves.XML_CATALOG_FILES system
// -- property for resolver to pick up
if (new File(oasisCatFile).exists()) {
String catalogProp = System.getProperty(Constants.XML_CATALOG_FILES);
if (catalogProp == null)
catalogProp = ""; // shouldn't happen
if (catalogProp.equals("")) {
catalogProp = oasisCatFile;
} else {
catalogProp = catalogProp + ";" + oasisCatFile;
}
System.setProperty(Constants.XML_CATALOG_FILES, catalogProp);
Xml.resetResolver();
}
SchematronRepository schemaRepo = applicationContext.getBean(SchematronRepository.class);
SchematronCriteriaGroupRepository criteriaGroupRepository = applicationContext.getBean(SchematronCriteriaGroupRepository.class);
MetadataSchema mds = new SchemaLoader().load(xmlSchemaFile, xmlSubstitutionsFile, schemaRepo, criteriaGroupRepository);
mds.setName(name);
mds.setSchemaDir(path);
mds.loadSchematronRules(basePath);
// -- 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));
mds.setOperationFilters(extractOperationFilters(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 " + Constants.XML_CATALOG_FILES + " is " + System.getProperty(Constants.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", buildSchemaFolderPath(name));
} 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);
}
deleteDir(new File(dir));
String pubSchemaDir = resourcePath + FS + Geonet.Path.SCHEMAS + name;
if(Log.isDebugEnabled(Geonet.SCHEMA_MANAGER)){
Log.debug(Geonet.SCHEMA_MANAGER, "Removing published schemas directory "+pubSchemaDir);
}
deleteDir(new File(pubSchemaDir));
}
/**
* 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(ApplicationContext applicationContext, 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(applicationContext, 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
*/
@SuppressWarnings("unchecked")
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;
}
/**
* 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 Map<String, Pair<String, Element>> extractOperationFilters(String xmlIdFile) throws Exception {
Element root = Xml.loadFile(xmlIdFile);
Element filters = root.getChild("filters", GEONET_SCHEMA_NS);
Map<String, Pair<String, Element>> filterRules =
new HashMap<String, Pair<String, Element>>();
if (filters == null) {
return filterRules;
} else {
for(Object rule : filters.getChildren("filter", GEONET_SCHEMA_NS)) {
if (rule instanceof Element) {
Element ruleElement = (Element) rule;
String xpath = ruleElement.getAttributeValue("xpath");
String ifNotOperation = ruleElement.getAttributeValue("ifNotOperation");
Element markedElement = ruleElement.getChild("keepMarkedElement", GEONET_SCHEMA_NS);
if (StringUtils.isNotBlank(ifNotOperation) &&
StringUtils.isNotBlank(xpath)) {
filterRules.put(ifNotOperation, Pair.read(xpath, markedElement));
}
}
}
}
return filterRules;
}
/**
* 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);
@SuppressWarnings("unchecked")
List<Element> children = autodetect.getChildren();
return children;
}
/**
* 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 {
if (!(new File(xmlConvFile).exists())) {
if(Log.isDebugEnabled(Geonet.SCHEMA_MANAGER))
Log.debug(Geonet.SCHEMA_MANAGER, "Schema conversions file not present");
return new ArrayList<Element>();
} 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");
@SuppressWarnings("unchecked")
List<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());
@SuppressWarnings("unchecked")
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") {
@SuppressWarnings("unchecked")
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") {
@SuppressWarnings("unchecked")
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;
@SuppressWarnings("unchecked")
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;
@SuppressWarnings("unchecked")
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;
@SuppressWarnings("unchecked")
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;
@SuppressWarnings("unchecked")
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 = StringUtils.deleteWhitespace(needle.getValue());
String tempVal = StringUtils.deleteWhitespace(tempElement.getValue());
returnVal = Pattern.matches(needleVal, tempVal);
if(Log.isDebugEnabled(Geonet.SCHEMA_MANAGER)) {
Log.debug(Geonet.SCHEMA_MANAGER, " Pattern " + needleVal + " applied to value " + tempVal + " match: " + returnVal);
}
if (returnVal) {
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 void deleteDir(File dir) {
try {
FileUtils.deleteDirectory(dir);
} catch (IOException e) {
Log.warning(Geonet.SCHEMA_MANAGER, "Unable to delete directory: "+dir);
}
}
/**
* 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 = context.getBean(SettingInfo.class);
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);
IO.mkdirs(webAppDir, "Schema directory");
File webAppDirSchemaXSD = new File(webAppDir, name);
deleteDir(webAppDirSchemaXSD);
IO.mkdirs(webAppDirSchemaXSD, "webapp dir schema xsd");
// 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) {
BinaryFile.copy(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()) {
BinaryFile.copy(fileSchemaDir, new File(webAppDirSchemaXSD, "schema"));
}
}
}