/******************************************************************************* * ALMA - Atacama Large Millimeter Array * Copyright (c) COSYLAB - Control System Laboratory, 2011 * (in the framework of the ALMA collaboration). * All rights reserved. * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *******************************************************************************/ package com.cosylab.cdb.jdal; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.logging.Logger; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.exolab.castor.net.URIException; import org.exolab.castor.net.URILocation; import org.exolab.castor.net.URIResolver; import org.exolab.castor.net.util.URILocationImpl; import org.exolab.castor.net.util.URIResolverImpl; import org.exolab.castor.xml.XMLException; import org.exolab.castor.xml.schema.ComplexType; import org.exolab.castor.xml.schema.ElementDecl; import org.exolab.castor.xml.schema.Schema; import org.exolab.castor.xml.schema.XMLType; import org.exolab.castor.xml.schema.reader.Sax2ComponentReader; import org.exolab.castor.xml.schema.reader.SchemaUnmarshaller; import org.xml.sax.InputSource; import org.xml.sax.Parser; import org.xml.sax.SAXException; public class XSDElementTypeResolver { protected Parser parser; private final Logger logger; DALURIResolver uriResolver; private final String root; public XSDElementTypeResolver(String root, Logger logger) throws ParserConfigurationException, SAXException { this.root = root; this.logger = logger; initializeParser(); } protected void initializeParser() throws ParserConfigurationException, SAXException { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(false); SAXParser saxParser = factory.newSAXParser(); parser = saxParser.getParser(); String schemas = DALImpl.getSchemas(root, logger); if (schemas == null) schemas = ""; // do urn to file mapping for schemas Map<String, File> schemaName2File = new HashMap<String, File>(); StringTokenizer tokenizer = new StringTokenizer(schemas); while (tokenizer.hasMoreTokens()) { String urn = tokenizer.nextToken(); String fileName = tokenizer.nextToken(); schemaName2File.put(getXSDElementName(urn) + ".xsd", new File(fileName)); } uriResolver = new DALURIResolver(schemaName2File, logger); } private static class XMLNSElement { String xmlns; String element; public XMLNSElement(String xmlns, String element) { this.xmlns = xmlns; this.element = element; } public boolean equals(Object other) { if (other instanceof XMLNSElement) { XMLNSElement o = (XMLNSElement)other; return xmlns.equals(o.xmlns) && element.equals(o.element); } else return false; } public int hashCode() { return xmlns.hashCode() * 29 + element.hashCode(); } } protected Map<XMLNSElement, String> cache = new HashMap<XMLNSElement, String>(); public String[] getElementTypes(final String parentElementName, final String[] xmlns, final String[] elements) { if (xmlns.length != elements.length) throw new IllegalArgumentException("xmlns.length != elements.length"); // it's up to user to String[] types = new String[elements.length]; if (types.length == 0) return types; int hits = 0; for (int index = 0; index < elements.length; index++) { XMLNSElement el = new XMLNSElement(xmlns[index]+parentElementName, elements[index]); if (cache.containsKey(el)) { types[index] = cache.get(el); hits++; } } // all found in cache if (hits == elements.length) return types; try { Set<String> processedNs = new HashSet<String>(); for (String ns : xmlns) { if (processedNs.contains(ns)) continue; processedNs.add(ns); List<String> list = new ArrayList<String>(); for (int i = 0; i < xmlns.length; i++) if (types[i] == null && ns.equals(xmlns[i])) list.add(elements[i]); if (list.size() > 0) { String[] nsElements = list.toArray(new String[list.size()]); String[] nsTypes = internalGetElementTypes(parentElementName, ns, nsElements); // not very fast, but this is not critical here (also everything is cached) for (int j = 0; j < elements.length; j++) if (types[j] == null) for (int i = 0; i < nsElements.length; i++) if (nsElements[i].equals(elements[j])) types[j] = nsTypes[i]; } } } catch (Throwable th) { th.printStackTrace(); } return types; } protected Map<String, Boolean> doesExtendMap = new HashMap<String, Boolean>(); /** * @TODO: Change this method to not rely on the xsd file name to match a part of the xml namespace. * For example, all xsd files could be parsed using a xerces XMLGrammarLoader * (see http://xerces.apache.org/xerces2-j/faq-grammars.html#faq-3). * This xsd cache could then be queried here for the given namespace, * and it could also be shared with the xml parser in {@link DALImpl}. */ public boolean doesExtend(final String xmlns, final String baseTypeName) throws SAXException, IOException, URIException, XMLException { final String key = xmlns + " " + baseTypeName; if (doesExtendMap.containsKey(key)) return doesExtendMap.get(key); final String elementName = getXSDElementName(xmlns); Schema schema = null; try { SchemaUnmarshaller schemaUnmarshaller = new SchemaUnmarshaller(); schemaUnmarshaller.setURIResolver(uriResolver); Sax2ComponentReader handler = new Sax2ComponentReader(schemaUnmarshaller); parser.setDocumentHandler(handler); parser.setErrorHandler(handler); parser.parse(new InputSource(uriResolver.resolve(elementName + ".xsd", null).getReader())); schema = schemaUnmarshaller.getSchema(); } catch (Exception e) { logger.warning("Failed to locate or parse schema '" + elementName + ".xsd', continuing with the assumption that '" + xmlns + "' does not extend '" + baseTypeName + "'."); doesExtendMap.put(key, false); return false; } String elementTypeName = null; Enumeration structures; for (structures = schema.getElementDecls(); structures.hasMoreElements(); ) { ElementDecl elementDecl = (ElementDecl) structures.nextElement(); if (elementDecl.getName().equals(elementName)) { elementTypeName = elementDecl.getType().getName(); break; } } if (elementTypeName != null) { for (structures = schema.getComplexTypes(); structures.hasMoreElements(); ) { ComplexType complexType = (ComplexType) structures.nextElement(); XMLType baseType = complexType.getBaseType(); if (baseType != null) { if (baseType.getName().equals(baseTypeName)) { doesExtendMap.put(key, true); return true; } else { boolean recursive = doesExtend("urn:schemas-cosylab-com:" + baseType.getName() + ":1.0", baseTypeName); if (recursive) { doesExtendMap.put(key, true); return true; } } } } } doesExtendMap.put(key, false); return false; } protected String[] internalGetElementTypes(String parentElementName, final String xmlns, final String[] elements) throws SAXException, IOException, URIException, XMLException { String[] types = new String[elements.length]; if (types.length == 0) return types; final String elementName = getXSDElementName(xmlns); SchemaUnmarshaller schemaUnmarshaller = new SchemaUnmarshaller(); schemaUnmarshaller.setURIResolver(uriResolver); Sax2ComponentReader handler = new Sax2ComponentReader(schemaUnmarshaller); parser.setDocumentHandler(handler); parser.setErrorHandler(handler); parser.parse(new InputSource(uriResolver.resolve(elementName + ".xsd", null).getReader())); Schema schema = schemaUnmarshaller.getSchema(); String elementTypeName = null; Enumeration structures; for (structures = schema.getElementDecls(); structures.hasMoreElements(); ) { ElementDecl elementDecl = (ElementDecl) structures.nextElement(); if (elementDecl.getName().equals(elementName)) { elementTypeName = elementDecl.getType().getName(); break; } } // fallback: use parentName if (elementTypeName == null && parentElementName != null) for (structures = schema.getElementDecls(); structures.hasMoreElements(); ) { ElementDecl elementDecl = (ElementDecl) structures.nextElement(); if (elementDecl.getName().equals(parentElementName)) { elementTypeName = elementDecl.getType().getName(); break; } } if (elementTypeName != null) { for (structures = schema.getComplexTypes(); structures.hasMoreElements(); ) { ComplexType complexType = (ComplexType) structures.nextElement(); if (complexType.getName().equals(elementTypeName)) { for (int i = 0; i < elements.length; i++) { ElementDecl element = complexType.getElementDecl(elements[i]); if (element != null) { XMLType type = element.getType(); types[i] = type.getName(); if (types[i] == null) types[i] = type.getBaseType().getName(); } // fill cache, also with null cache.put(new XMLNSElement(xmlns+parentElementName, elements[i]), types[i]); } break; } } } return types; } protected static class DALURIResolver implements URIResolver { private Map<String, File> schemaName2File; private final Logger logger; public DALURIResolver(Map<String, File> schemaName2File, Logger logger) { this.schemaName2File = schemaName2File; this.logger = logger; } /** * @param href * the (schema) file name etc. * @param documentBase * is ignored */ public URILocation resolve(String href, String documentBase) throws URIException { logger.finest("DALURIResolver#resolve(" + href + ", " + documentBase + ") called."); File schemaFile = schemaName2File.get(href); if (schemaFile == null) { int lastSlash = href.lastIndexOf('/'); if (lastSlash != -1) { return resolve(href.substring(lastSlash + 1), documentBase); } else { String msg = "schema file '" + href + "' not found!"; logger.warning(msg); throw new URIException(msg); } } URILocation ret = new URILocationImpl(schemaFile.getAbsolutePath()); logger.finest("DALURIResolver#resolve(" + href + ", " + documentBase + ") returning " + ret.getAbsoluteURI()); return ret; } /** * Returns location for URN. * * @see URIResolverImpl#resolveURN(String) */ public URILocation resolveURN(String urn) throws URIException { return resolve(getXSDElementName(urn) + ".xsd", null); } } /** * @param xmlns * @return */ private static final String getXSDElementName(final String xmlns) { final int endPos = xmlns.lastIndexOf(':'); String elementName = xmlns.substring(0, endPos); final int pos = elementName.lastIndexOf(':'); elementName = elementName.substring(pos+1); return elementName; } // public static void main(String[] args) throws Throwable { // XSDElementTypeResolver etr = new XSDElementTypeResolver(); // System.out.println(etr.doesExtend("urn:schemas-cosylab-com:LO2Base:1.0", "ControlDevice")); // /* // String[] types; // types = etr.getElementTypes(null, new String[] {"urn:schemas-cosylab-com:RampedPowerSupply:1.0"}, new String[] { "current" }); // for (String type : types) // System.out.println(type); // */ // } /** * @return the uriResolver */ public DALURIResolver getUriResolver() { return uriResolver; } }