/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.xml;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.geotools.factory.FactoryRegistry;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.xml.schema.Attribute;
import org.geotools.xml.schema.AttributeGroup;
import org.geotools.xml.schema.ComplexType;
import org.geotools.xml.schema.Element;
import org.geotools.xml.schema.Group;
import org.geotools.xml.schema.Schema;
import org.geotools.xml.schema.SimpleType;
import org.xml.sax.SAXException;
/**
* SchemaFactory purpose.
*
* <p>
* This is the main entry point into the XSI parsing routines.
* </p>
*
* <p>
* Example Use:
* <pre><code>
*
*
*
* Schema x = SchemaFactory.getInstance("MyTargetNameSpace",new URI("MyNameSpaceURI");
*
*
*
* </code></pre>
* </p>
*
* @author dzwiers, Refractions Research, Inc. http://www.refractions.net
* @author $Author:$ (last modification)
* @source $URL$
* @version $Id$
*/
public class SchemaFactory {
protected static SchemaFactory is = new SchemaFactory();
/*
* Holds onto instances when they are created ... my version of object
* pooling Q: is there a better way? might suck up too much memory ... A:
* not really, but the JVM might be better ... use the class to make
* instances
*/
private Map schemas = loadSchemas();
/*
* The SAX parser to use if one is required ... isn't loaded until first
* use.
*/
private SAXParser parser;
protected static SchemaFactory getInstance() {
return is;
}
/*
*/
private Map loadSchemas() {
schemas = new HashMap();
ClassLoader[] cls = findLoaders();
String serviceId = "META-INF/services/" + Schema.class.getName();
for (int i = 0; i < cls.length; i++) {
try {
Enumeration e = cls[i].getResources(serviceId);
while (e.hasMoreElements()) {
URL res = (URL) e.nextElement();
BufferedReader rd = new BufferedReader(new InputStreamReader(
res.openStream(), "UTF-8"));
while (rd.ready()) {
String factoryClassName = rd.readLine().trim();
try {
Schema s = (Schema) cls[i].loadClass(factoryClassName)
.getDeclaredMethod("getInstance", new Class[0])
.invoke(null, new Object[0]);
schemas.put(s.getTargetNamespace(), s);
} catch (IllegalArgumentException e1) {
XSISAXHandler.logger.warning(e1.toString());
} catch (SecurityException e1) {
XSISAXHandler.logger.warning(e1.toString());
} catch (IllegalAccessException e1) {
XSISAXHandler.logger.warning(e1.toString());
} catch (InvocationTargetException e1) {
XSISAXHandler.logger.warning(e1.toString());
} catch (NoSuchMethodException e1) {
XSISAXHandler.logger.warning(e1.toString());
} catch (ClassNotFoundException e1) {
XSISAXHandler.logger.warning(e1.toString());
}
}
rd.close();
}
} catch (IOException e) {
XSISAXHandler.logger.warning(e.toString());
}
}
return schemas;
}
// stolen from FactoryFinder.findLoaders
private ClassLoader[] findLoaders() {
// lets get a class loader. By using the Thread's class loader, we allow
// for more flexability.
ClassLoader contextLoader = null;
try {
contextLoader = Thread.currentThread().getContextClassLoader();
} catch (SecurityException se) {
// do nothing
}
ClassLoader systemLoader = ReferencingFactoryFinder.class.getClassLoader();
ClassLoader[] classLoaders;
if ((contextLoader == null) || (contextLoader == systemLoader)) {
classLoaders = new ClassLoader[1];
classLoaders[0] = systemLoader;
} else {
classLoaders = new ClassLoader[2];
classLoaders[0] = contextLoader;
classLoaders[1] = systemLoader;
}
return classLoaders;
}
/**
* Returns an instance of the desired class. There is no provision for: a)
* same instances each call b) different instances each call c) this
* factory being thread safe
*
* @param targetNamespace
* @param desiredSchema URI the uri of which you want a schema instance.
*
* @return Schema an instance of the desired schema.
*
* @throws SAXException
*/
public static Schema getInstance(URI targetNamespace, URI desiredSchema)
throws SAXException {
return getInstance(targetNamespace, desiredSchema, Level.WARNING);
}
public static Schema getInstance(URI targetNamespace, InputStream is1)
throws SAXException {
return getInstance(targetNamespace, is1, Level.WARNING);
}
public synchronized static Schema getInstance(String targetNamespace) {
try {
URI uri = new URI(targetNamespace );
return getInstance( uri );
} catch (URISyntaxException e) {
return null;
}
}
/**
* Returns an instance of the targetNamespace if it can be found ... null
* otherwise. targetNamespaces which can be found are either hard-coded
* namespaces (SchemaFactory.properties), have already been parsed or were
* registered.
*
* @param targetNamespace
*
*
* @see #registerSchema(Strin,Schema)
*/
public synchronized static Schema getInstance(URI targetNamespace) {
return getInstance().getRealInstance(targetNamespace);
}
//TODO cache this on schema registry
public synchronized static Schema[] getSchemas(String prefix){
if(prefix == null)
return null;
SchemaFactory sf = getInstance();
Iterator i = sf.schemas.values().iterator();
List l = new LinkedList();
while(i.hasNext()){
Schema s = (Schema)i.next();
if(prefix.equals(s.getPrefix()))
l.add(s);
}
return (Schema[])l.toArray(new Schema[l.size()]);
}
private synchronized Schema getRealInstance(URI targetNamespace) {
Schema r = (Schema) schemas.get(targetNamespace);
if (r != null) {
return r;
}
// if (mappings.containsKey(targetNamespace)) {
// ClassLoader cl = SchemaFactory.class.getClassLoader();
//
// try {
// Class c = cl.loadClass((String) mappings.get(targetNamespace));
// r = (Schema) c.getConstructor(new Class[0]).newInstance(new Object[0]);
// schemas.put(targetNamespace, r);
//
// return r;
// } catch (Exception e) {
// return null;
// }
// }
return null;
}
/**
* Returns an instance of the desired class. There is no provision for: a)
* same instances each call b) different instances each call c) this
* factory being thread safe
*
* @param targetNamespace The targetNamespace to search for.
* @param desiredSchema URI the uri of which you want a schema instance.
* @param level Level
*
* @return Schema an instance of the desired schema.
*
* @throws SAXException When something goes wrong
*/
public synchronized static Schema getInstance(URI targetNamespace,
URI desiredSchema, Level level) throws SAXException {
return getInstance().getRealInstance(targetNamespace, desiredSchema,
level);
}
private synchronized Schema getRealInstance(URI targetNamespace2,
URI desiredSchema, Level level) throws SAXException {
URI targetNamespace=targetNamespace2;
if ((targetNamespace == null) || (schemas.get(targetNamespace) == null)) {
setParser();
XSISAXHandler contentHandler = getSAXHandler(desiredSchema);
XSISAXHandler.setLogLevel(level);
try {
parser.parse(desiredSchema.toString(), contentHandler);
} catch (IOException e) {
// TODO error handler should be notified of a connectionexception: operation timed out.
throw new SAXException(e);
}
Schema schema = contentHandler.getSchema();
if ((targetNamespace == null) || "".equals(targetNamespace)) {
targetNamespace=schema.getTargetNamespace();
}
if( schemas.get(targetNamespace)!=null ){
schema=merge(schema, (Schema) schemas.get(targetNamespace));
}
schemas.put(targetNamespace, schema);
return schema;
} else {
if ( !((Schema) schemas.get(targetNamespace)).includesURI(
desiredSchema)) {
Schema sh = (Schema) schemas.get(targetNamespace);
setParser();
XSISAXHandler contentHandler = getSAXHandler(desiredSchema);
XSISAXHandler.setLogLevel(level);
try {
parser.parse(desiredSchema.toString(), contentHandler);
} catch (IOException e) {
throw new SAXException(e);
}
sh = merge(sh, contentHandler.getSchema());
schemas.put(targetNamespace, sh); // over-write
}
}
return (Schema) schemas.get(targetNamespace);
}
protected XSISAXHandler getSAXHandler(URI uri) {
return new XSISAXHandler(uri);
}
public static synchronized Schema getInstance(URI targetNamespace,
InputStream is1, Level level) throws SAXException {
return getInstance().getRealInstance(targetNamespace, is1, level);
}
private synchronized Schema getRealInstance(URI targetNamespace2,
InputStream is1, Level level) throws SAXException {
URI targetNamespace=targetNamespace2;
if ((targetNamespace == null) || (schemas.get(targetNamespace) == null)) {
XSISAXHandler contentHandler = parseSchema(is1, level);
if ((targetNamespace == null) || "".equals(targetNamespace)) {
targetNamespace=contentHandler.getSchema().getTargetNamespace();
}
Schema schema = contentHandler.getSchema();
if( schemas.get(targetNamespace)!=null ){
schema=merge(schema, (Schema) schemas.get(targetNamespace));
}
schemas.put(targetNamespace, schema);
} else {
Schema sh = (Schema) schemas.get(targetNamespace);
XSISAXHandler contentHandler = parseSchema(is1, level);
sh = merge(sh, contentHandler.getSchema());
schemas.put(targetNamespace, sh); // over-write
}
return (Schema) schemas.get(targetNamespace);
}
private XSISAXHandler parseSchema( InputStream is1, Level level ) throws SAXException {
setParser();
XSISAXHandler contentHandler = getSAXHandler(null);
XSISAXHandler.setLogLevel(level);
try {
parser.parse(is1, contentHandler);
} catch (IOException e) {
throw new SAXException(e);
}
return contentHandler;
}
/*
* Creates a new Schema from merging the two schemas passed in. for instance
* information, such as id, or version, s1 takes precedence.
*/
private Schema merge(Schema s1, Schema s2) throws SAXException {
return new MergedSchema(s1, s2);
}
/**
* Registers a Schema instance with the factory. A clone is NOT created
* within this method. The Schema passed in is associated with the
* specified targetNamespace. The Schema is not tested to ensure the
* intended targetNamespace (schema.getTargetNamespace()) is equal to
* targetNamespace. The ramifications is that you may hack wildly within
* the repository, but we aware you may have some 'undocumented features'
* as a result (odd Schemas being returned).
*
* @param targetNamespace
* @param schema
*/
public static void registerSchema(URI targetNamespace, Schema schema) {
getInstance().registerRealSchema(targetNamespace, schema);
}
private void registerRealSchema(URI targetNamespace, Schema schema) {
schemas.put(targetNamespace, schema);
}
/*
* convinience method to create an instance of a SAXParser if it is null.
*/
private void setParser() throws SAXException {
if (parser == null) {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(false);
try {
parser = spf.newSAXParser();
} catch (ParserConfigurationException e) {
throw new SAXException(e);
} catch (SAXException e) {
throw new SAXException(e);
}
}
}
/**
* The Schema will contain references to the elements within the two
* Schemas provided in the constructor. In most cases this is used to
* incorporate additional definitions into a targetNamespace by parsing an
* additional schema file.
*
* @author dzwiers
*
* @see Schema
*/
private static class MergedSchema implements Schema {
/*
* This is all fairly self explanatory.
*
* @see Schema
*/
private AttributeGroup[] attributeGroups;
private Attribute[] attributes;
private int block;
private int finaL;
private ComplexType[] complexTypes;
private Element[] elements;
private Group[] groups;
private String id;
private String version;
private String prefix;
private URI targetNamespace;
private Schema[] imports;
private SimpleType[] simpleTypes;
private boolean aForm;
private boolean eForm;
private URI uri;
/**
* This completes the merge of two schemas, s1 and s2. When there is a
* conflict in data between the two schemas, s1 is assumed to be
* correct.
*
* @param s1 Schema (Tie Winner)
* @param s2 Schema (Tie Loser)
*
* @throws SAXException When some thing bad happens (for example
* merging two targetNamespaces)
*/
public MergedSchema(Schema s1, Schema s2) throws SAXException {
if ((s1.getId() == null) || s1.getId().equals("")) {
id = s2.getId();
} else {
id = s1.getId();
}
if ((s1.getVersion() == null) || s1.getVersion().equals("")) {
version = s2.getVersion();
} else {
version = s1.getVersion();
}
if ((s1.getTargetNamespace() == null)
|| s1.getTargetNamespace().equals("")) {
targetNamespace = s2.getTargetNamespace();
} else {
if ((s2.getTargetNamespace() != null)
&& !s1.getTargetNamespace().equals(s2
.getTargetNamespace())) {
throw new SAXException(
"cannot merge two target namespaces. "
+ s1.getTargetNamespace() + " "
+ s2.getTargetNamespace());
}
targetNamespace = s1.getTargetNamespace();
}
aForm = s1.isAttributeFormDefault() || s2.isAttributeFormDefault();
eForm = s1.isElementFormDefault() || s2.isElementFormDefault();
HashMap m = new HashMap();
AttributeGroup[] ag1 = s1.getAttributeGroups();
if (ag1 == null) {
ag1 = new AttributeGroup[0];
}
AttributeGroup[] ag2 = s2.getAttributeGroups();
if (ag2 == null) {
ag2 = new AttributeGroup[0];
}
for (int i = 0; i < ag1.length; i++)
m.put(ag1[i].getName(), ag1[i]);
for (int i = 0; i < ag2.length; i++)
if (!m.containsKey(ag2[i].getName())) {
m.put(ag2[i].getName(), ag2[i]);
}
attributeGroups = new AttributeGroup[m.size()];
Object[] obj = m.values().toArray();
for (int i = 0; i < obj.length; i++)
attributeGroups[i] = (AttributeGroup) (obj[i]);
m = new HashMap();
Attribute[] a1 = s1.getAttributes();
if (a1 == null) {
a1 = new Attribute[0];
}
Attribute[] a2 = s2.getAttributes();
if (a2 == null) {
a2 = new Attribute[0];
}
for (int i = 0; i < a1.length; i++)
m.put(a1[i].getName(), a1[i]);
for (int i = 0; i < a2.length; i++)
if (!m.containsKey(a2[i].getName())) {
m.put(a2[i].getName(), a2[i]);
}
attributes = new Attribute[m.size()];
obj = m.values().toArray();
for (int i = 0; i < obj.length; i++)
attributes[i] = (Attribute) (obj[i]);
block = s1.getBlockDefault() | s2.getBlockDefault();
finaL = s1.getFinalDefault() | s2.getFinalDefault();
m = new HashMap();
ComplexType[] c1 = s1.getComplexTypes();
if (c1 == null) {
c1 = new ComplexType[0];
}
ComplexType[] c2 = s2.getComplexTypes();
if (c2 == null) {
c2 = new ComplexType[0];
}
for (int i = 0; i < c1.length; i++)
m.put(c1[i].getName(), c1[i]);
for (int i = 0; i < c2.length; i++)
if (!m.containsKey(c2[i].getName())) {
m.put(c2[i].getName(), c2[i]);
}
complexTypes = new ComplexType[m.size()];
obj = m.values().toArray();
for (int i = 0; i < obj.length; i++)
complexTypes[i] = (ComplexType) (obj[i]);
m = new HashMap();
SimpleType[] ss1 = s1.getSimpleTypes();
if (ss1 == null) {
ss1 = new SimpleType[0];
}
SimpleType[] ss2 = s2.getSimpleTypes();
if (ss2 == null) {
ss2 = new SimpleType[0];
}
for (int i = 0; i < ss1.length; i++)
m.put(ss1[i].getName(), ss1[i]);
for (int i = 0; i < ss2.length; i++)
if (!m.containsKey(ss2[i].getName())) {
m.put(ss2[i].getName(), ss2[i]);
}
simpleTypes = new SimpleType[m.size()];
obj = m.values().toArray();
for (int i = 0; i < obj.length; i++)
simpleTypes[i] = (SimpleType) (obj[i]);
m = new HashMap();
Element[] e1 = s1.getElements();
if (e1 == null) {
e1 = new Element[0];
}
Element[] e2 = s2.getElements();
if (e2 == null) {
e2 = new Element[0];
}
for (int i = 0; i < e1.length; i++)
m.put(e1[i].getName(), e1[i]);
for (int i = 0; i < e2.length; i++)
if (!m.containsKey(e2[i].getName())) {
m.put(e2[i].getName(), e2[i]);
}
elements = new Element[m.size()];
obj = m.values().toArray();
for (int i = 0; i < obj.length; i++)
elements[i] = (Element) (obj[i]);
m = new HashMap();
Group[] g1 = s1.getGroups();
if (g1 == null) {
g1 = new Group[0];
}
Group[] g2 = s2.getGroups();
if (g2 == null) {
g2 = new Group[0];
}
for (int i = 0; i < g1.length; i++)
m.put(g1[i].getName(), g1[i]);
for (int i = 0; i < g2.length; i++)
if (!m.containsKey(g2[i].getName())) {
m.put(g2[i].getName(), g2[i]);
}
groups = new Group[m.size()];
obj = m.values().toArray();
for (int i = 0; i < obj.length; i++)
groups[i] = (Group) (obj[i]);
m = new HashMap();
Schema[] i1 = s1.getImports();
if (i1 == null) {
i1 = new Schema[0];
}
Schema[] i2 = s2.getImports();
if (i2 == null) {
i2 = new Schema[0];
}
for (int i = 0; i < i1.length; i++)
m.put(i1[i].getTargetNamespace(), i1[i]);
for (int i = 0; i < i2.length; i++)
if (!m.containsKey(i2[i].getTargetNamespace())) {
m.put(i2[i].getTargetNamespace(), i2[i]);
}
imports = new Schema[m.size()];
obj = m.values().toArray();
for (int i = 0; i < obj.length; i++)
imports[i] = (Schema) (obj[i]);
URI u1 = s1.getURI();
URI u2 = s2.getURI();
if (u1 == null) {
uri = u2;
} else {
if (u2 == null) {
uri = u1;
} else {
uri = u2.relativize(u1);
}
}
if ((s1.getPrefix() == null) || s1.getPrefix().equals("")) {
prefix = s2.getPrefix();
} else {
prefix = s1.getPrefix();
}
}
/**
* @see schema.Schema#getAttributeGroups()
*/
public AttributeGroup[] getAttributeGroups() {
return attributeGroups;
}
/**
* @see schema.Schema#getAttributeDescriptors()
*/
public Attribute[] getAttributes() {
return attributes;
}
/**
* @see schema.Schema#getBlockDefault()
*/
public int getBlockDefault() {
return block;
}
/**
* @see schema.Schema#getComplexTypes()
*/
public ComplexType[] getComplexTypes() {
return complexTypes;
}
/**
* @see schema.Schema#getElements()
*/
public Element[] getElements() {
return elements;
}
/**
* @see schema.Schema#getFinalDefault()
*/
public int getFinalDefault() {
return finaL;
}
/**
* @see schema.Schema#getGroups()
*/
public Group[] getGroups() {
return groups;
}
/**
* @see schema.Schema#getId()
*/
public String getId() {
return id;
}
/**
* @see schema.Schema#getImports()
*/
public Schema[] getImports() {
return imports;
}
/**
* @see schema.Schema#getSimpleTypes()
*/
public SimpleType[] getSimpleTypes() {
return simpleTypes;
}
/**
* @see schema.Schema#getTargetNamespace()
*/
public URI getTargetNamespace() {
return targetNamespace;
}
/**
* @see schema.Schema#getVersion()
*/
public String getVersion() {
return version;
}
/**
* @see schema.Schema#includesURI(java.net.URI)
*/
public boolean includesURI(URI uri1) {
if (uri1 == null || this.uri == null) {
return false;
}
return this.uri.equals(uri1);
}
/**
* @see schema.Schema#isAttributeFormDefault()
*/
public boolean isAttributeFormDefault() {
return aForm;
}
/**
* @see schema.Schema#isElementFormDefault()
*/
public boolean isElementFormDefault() {
return eForm;
}
/**
* @see schema.Schema#getURIs()
*/
public URI getURI() {
return uri;
}
public String getPrefix() {
return prefix;
}
/**
* Returns the implementation hints. The default implementation returns en empty map.
*/
public Map getImplementationHints() {
return Collections.EMPTY_MAP;
}
}
}