| /******************************************************************************* |
| * Copyright (c) 2001, 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.wst.wsdl.validation.internal.wsdl11.xsd; |
| |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.Vector; |
| |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| import com.ibm.wsdl.Constants; |
| |
| /** |
| * Generate a String representation of a schema for an inline schema. Will add imports for unresolved |
| * namespaces. |
| */ |
| public class InlineSchemaGenerator |
| { |
| /** |
| * Generating import statements for inline schema references is |
| * invalid according to the WSDL spec. As some older clients (WSVT) may |
| * depend on this functionality the option to allow this is left |
| * in. This should be removed post 1.5. |
| * TODO: Review removing this preference. |
| */ |
| protected static boolean GENERATE_INLINE_IMPORTS = false; |
| |
| protected static final String SOAP_ENCODING_URI = "http://schemas.xmlsoap.org/soap/encoding/"; |
| protected static final String FILE_PREFIX = "file:///"; |
| protected static final String XMLNS = "xmlns"; |
| protected static final String TARGETNAMESPACE = "targetNamespace"; |
| protected static final String NAMESPACE = "namespace"; |
| protected static final String IMPORT = "import"; |
| protected static final String INCLUDE = "include"; |
| protected static final String SCHEMA = "schema"; |
| protected static final String SCHEMALOCATION = "schemaLocation"; |
| protected static final String TYPE = "type"; |
| protected static final String NAME = "name"; |
| protected static final String[] ignoreNamespaces = |
| { Constants.NS_URI_XSD_1999, Constants.NS_URI_XSD_2000, Constants.NS_URI_XSD_2001 }; |
| |
| protected static InlineSchemaGenerator instance = null; |
| |
| /** |
| * Constructor. |
| */ |
| protected InlineSchemaGenerator() |
| { |
| String genInlineImports = System.getProperty("wsdl.require.inline.imports"); |
| if(genInlineImports != null && genInlineImports.equals("false")) |
| { |
| GENERATE_INLINE_IMPORTS = true; |
| } |
| } |
| |
| /** |
| * Get the instance of the InlineSchemaGenerator. |
| * |
| * @return The instance of the inline schema generator. |
| */ |
| protected static InlineSchemaGenerator getInstance() |
| { |
| if (instance == null) |
| { |
| instance = new InlineSchemaGenerator(); |
| } |
| return instance; |
| } |
| |
| /** |
| * Create a string representation of a schema from the element provided. |
| * |
| * @param element The root element of the schema. |
| * @param elements A list of the elements in the schema in order. |
| * @param filelocation The URI of the file that contains the schema. |
| * @param validImportNSs A set of namespaces for which it's valid to create import statements. |
| * @return A string representation of a schema. |
| */ |
| public static String createXSDString(Element element, List elements, String filelocation, Set validImportNSs) |
| { |
| return InlineSchemaGenerator.createXSDString(element, elements, filelocation, new Hashtable(), validImportNSs); |
| } |
| |
| /** |
| * Creates a String representing the schema model with the root element of |
| * extElem. Calls createXSDStringRecursively to take care of building the String |
| * after it obtains the Element from the UnknownExtensibilityElement. |
| * |
| * @param element The root element of the schema. |
| * @param elements A list to contain the elements in the schema in order. |
| * @param filelocation The location of the file the schema is located in. |
| * @param parentNSs A hashtable of parent namespaces to used to resolve prefixes. |
| * @param validImportNSs A set of namespaces for which it's valid to create import statements. |
| * @return A string representation of the schema with the root element 'element'. |
| */ |
| public static String createXSDString(Element element, List elements, String filelocation, Hashtable parentNSs, Set validImportNSs) |
| { |
| Set importNSs = new TreeSet(); |
| importNSs.addAll(validImportNSs); |
| importNSs.add(SOAP_ENCODING_URI); |
| |
| InlineSchemaGenerator schemaGenerator = InlineSchemaGenerator.getInstance(); |
| Hashtable nsResolver = schemaGenerator.getNSResolver(element); |
| List reqns = schemaGenerator.getNamespacePrefixes(element); |
| Hashtable reqNSDecl = schemaGenerator.resolveNamespaces(reqns, nsResolver, parentNSs); |
| //Hashtable reqNSDecl = schemaGenerator.getRequiredNSDeclarations(reqns, nsResolver, parentNSs); |
| if(GENERATE_INLINE_IMPORTS) |
| { |
| List importNS = schemaGenerator.getImportNamespaces(element); |
| reqns = schemaGenerator.removeImports(reqns, importNS); |
| reqns = schemaGenerator.removeLocalNamespaces(reqns, element); |
| reqns = schemaGenerator.restrictImports(reqns, validImportNSs); |
| } |
| else |
| { |
| reqns.clear(); |
| } |
| return schemaGenerator.createXSDStringRecursively(element, elements, reqns, reqNSDecl, filelocation); |
| } |
| /** |
| * Returns true if the SOAP encoding namespace is required but not imported. |
| * |
| * @param element The root element of the schema. |
| * @param filelocation The location of the file containing the schema. |
| * @param parentNSs A hashtable of the parent namespaces. |
| * @return True if the soap encoding namespace is required but not imported, false otherwise. |
| */ |
| public static boolean soapEncodingRequiredNotImported(Element element, String filelocation, Hashtable parentNSs) |
| { |
| InlineSchemaGenerator schemaGenerator = InlineSchemaGenerator.getInstance(); |
| Hashtable nsResolver = schemaGenerator.getNSResolver(element); |
| List reqns = null; |
| |
| reqns = schemaGenerator.getNamespacePrefixes(element); |
| schemaGenerator.resolveNamespaces(reqns, nsResolver, parentNSs); |
| //schemaGenerator.resolveUndeclaredNamespaces(reqns, parentNSs); |
| List importNS = schemaGenerator.getImportNamespaces(element); |
| reqns = schemaGenerator.removeImports(reqns, importNS); |
| reqns = schemaGenerator.removeLocalNamespaces(reqns, element); |
| return schemaGenerator.checkSOAPEncodingRequired(reqns); |
| } |
| /** |
| * Resolve the undeclared namespaces. |
| * |
| * @param unresolvedNSs A list of unresolved namespaces. |
| * @param nsResolver The namespace resolver to use. |
| * @return A hashtable of prefixes and namespaces. |
| */ |
| // protected Hashtable resolveUndeclaredNamespaces(List unresolvedNSs, Hashtable nsResolver) |
| // { |
| // Hashtable namespaces = new Hashtable(); |
| // if (unresolvedNSs != null && !unresolvedNSs.isEmpty() && nsResolver != null) |
| // { |
| // for (int i = unresolvedNSs.size() - 1; i >= 0; i--) |
| // { |
| // String ns = (String)unresolvedNSs.get(i); |
| // if (ns.equals("")) |
| // { |
| // ns = XMLNS; |
| // } |
| // else |
| // { |
| // ns = XMLNS + ":" + ns; |
| // } |
| // if (nsResolver.containsKey(ns)) |
| // { |
| // //namespaces.remove(i); |
| // //namespaces.add(i, nsResolver.get(ns)); |
| // namespaces.put(ns, nsResolver.get(ns)); |
| // unresolvedNSs.remove(i); |
| // unresolvedNSs.add(i, nsResolver.get(ns)); |
| // } |
| // } |
| // } |
| // return namespaces; |
| // } |
| |
| /** |
| * This recursive method creates the schema String from the root Element. |
| * |
| * @param elem The root element of the schema. |
| * @param elements A list to be created of the elements in the schema in order. |
| * @param requiredNamespaces A list of required namespaces. |
| * @param reqNSDecl A hashtable of required namespace declarations. |
| * @param filelocation The uri of the file that contains this schema. |
| * @return A string representation of this schema. |
| */ |
| protected String createXSDStringRecursively( |
| Element elem, |
| List elements, |
| List requiredNamespaces, |
| Hashtable reqNSDecl, |
| String filelocation) |
| { |
| if (elem == null) |
| return ""; // just in case |
| |
| elements.add(elem); |
| |
| StringBuffer xsdString = new StringBuffer(); |
| String elementName = elem.getTagName(); |
| xsdString.append("<").append(elementName); |
| |
| boolean foundSchemaLocation = false; // flag if schemalocation is defined |
| String namespace = ""; // the namespace if we're checking an import or include |
| String namePrefix = ""; // the xmlns prefix used for the elements |
| // Get all of the attributes for this element and append them to the xsdString |
| NamedNodeMap atts = elem.getAttributes(); |
| for (int i = 0; i < atts.getLength(); i++) |
| { |
| Node n = atts.item(i); |
| xsdString.append(" ").append(n.getNodeName()).append("=\""); |
| String nodeName = n.getNodeName(); |
| if (nodeName.equalsIgnoreCase(SCHEMALOCATION) && filelocation != null) |
| { |
| foundSchemaLocation = true; |
| String relativePath = n.getNodeValue(); |
| xsdString.append(relativePath).append("\""); |
| } |
| else |
| { |
| String nodeValue = n.getNodeValue(); |
| if (nodeName.equalsIgnoreCase(NAMESPACE)) |
| { |
| namespace = nodeValue; |
| } |
| // get the name prefix for this schema to use in generating import statements |
| else if (nodeName.indexOf(XMLNS) != -1) |
| { |
| |
| if (nodeValue.equalsIgnoreCase(elem.getNamespaceURI())) |
| { |
| namePrefix = nodeName; |
| if (namePrefix.equalsIgnoreCase(XMLNS)) |
| { |
| namePrefix = ""; |
| } |
| else |
| { |
| namePrefix = namePrefix.substring(6) + ":"; |
| } |
| } |
| } |
| // Replace old schema namespaces with the new schema namespace. |
| if(nodeValue.equals(Constants.NS_URI_XSD_1999) || nodeValue.equals(Constants.NS_URI_XSD_2000)) |
| { |
| nodeValue = Constants.NS_URI_XSD_2001; |
| } |
| xsdString.append(nodeValue).append("\""); |
| } |
| } |
| if (elementName.equalsIgnoreCase("import") && !foundSchemaLocation) |
| { |
| xsdString.append(" ").append(SCHEMALOCATION).append("=\"").append(namespace).append("\""); |
| } |
| // add in any required NS declarations from parent elements |
| if (reqNSDecl != null) |
| { |
| Enumeration keys = reqNSDecl.keys(); |
| while (keys.hasMoreElements()) |
| { |
| String key = (String)keys.nextElement(); |
| String declNS = (String)reqNSDecl.get(key); |
| if(declNS.equals(Constants.NS_URI_XSD_1999) || declNS.equals(Constants.NS_URI_XSD_2000)) |
| { |
| declNS = Constants.NS_URI_XSD_2001; |
| } |
| xsdString.append(" ").append(key).append("=\"").append(declNS).append("\""); |
| } |
| |
| } |
| xsdString.append(">"); |
| if (requiredNamespaces != null) |
| { |
| Iterator iRequiredNamespaces = requiredNamespaces.iterator(); |
| while (iRequiredNamespaces.hasNext()) |
| { |
| String ns = (String)iRequiredNamespaces.next(); |
| |
| xsdString |
| .append("<") |
| .append(namePrefix) |
| .append(IMPORT) |
| .append(" ") |
| .append(NAMESPACE) |
| .append("=\"") |
| .append(ns) |
| .append("\" ") |
| .append(SCHEMALOCATION) |
| .append("=\"") |
| .append(ns) |
| .append("\"/>"); |
| } |
| |
| } |
| xsdString.append("\n"); |
| |
| // call the method recursively for each child element |
| NodeList childNodes = elem.getChildNodes(); |
| |
| for (int i = 0; i < childNodes.getLength() || i < 5; i++) |
| { |
| Node n = childNodes.item(i); |
| // we only want nodes that are Elements |
| if (n instanceof Element) |
| { |
| Element child = (Element)n; |
| xsdString.append(createXSDStringRecursively(child, elements, null, null, filelocation)); |
| } |
| } |
| |
| xsdString.append("</").append(elem.getTagName()).append(">"); |
| |
| return xsdString.toString(); |
| |
| } |
| /** |
| * Get a list of all the namespace prefixes that are used for elements or types in the schema. |
| * |
| * @param elem The root element of the schema to check for namespace prefixes. |
| * @return A list of namespace prefixes for the element and all its children. |
| */ |
| protected List getNamespacePrefixes(Element elem) |
| { |
| List namespace = new ArrayList(); |
| |
| // call the method recursively for each child element |
| // register all the child types first |
| NodeList childNodes = elem.getChildNodes(); |
| int numChildren = childNodes.getLength(); |
| for (int i = 0; i < numChildren; i++) |
| { |
| Node n = childNodes.item(i); |
| // we only want nodes that are Elements |
| if (n instanceof Element) |
| { |
| Element child = (Element)n; |
| List childns = getNamespacePrefixes(child); |
| for (int j = childns.size() - 1; j >= 0; j--) |
| { |
| String ns = (String)childns.get(j); |
| |
| if (!namespace.contains(ns)) |
| { |
| namespace.add(ns); |
| } |
| } |
| } |
| } |
| // Add the namespace of the current element |
| String elemNS = elem.getPrefix(); |
| // if there is no namespace prefix set it to the empty prefix. |
| if(elemNS == null) |
| { |
| elemNS = ""; |
| } |
| if (!namespace.contains(elemNS)) |
| { |
| namespace.add(elemNS); |
| } |
| // now add all of the current element's namespaces |
| // don't include import and schema elements |
| String localname = elem.getLocalName(); |
| if (!localname.equals(IMPORT) && !localname.equals(INCLUDE) && !localname.equals(SCHEMA)) |
| { |
| NamedNodeMap atts = elem.getAttributes(); |
| for (int i = 0; i < atts.getLength(); i++) |
| { |
| Node n = atts.item(i); |
| |
| String nodeName = n.getNodeName(); |
| // removed restriction that we're only looking at types |
| // if (nodeName.equalsIgnoreCase(TYPE)) |
| // { |
| // don't take namespace info from attributes defining namespaces. |
| // that includes xmlns, targetNamespace |
| if (nodeName.indexOf(XMLNS) != -1 || nodeName.equals(TARGETNAMESPACE) || nodeName.equals(NAME)) |
| { |
| continue; |
| } |
| // Grab namespace prefixes from attributes. |
| else |
| { |
| int colonIndex = nodeName.indexOf(":"); |
| if(colonIndex != -1 && (colonIndex + 1 < nodeName.length() && nodeName.charAt(colonIndex + 1) != '/')) |
| { |
| String prefix = nodeName.substring(0, colonIndex); |
| if (!namespace.contains(prefix)) |
| { |
| |
| namespace.add(prefix); |
| } |
| } |
| } |
| String nodeValue = n.getNodeValue(); |
| |
| |
| int colonIndex = nodeValue.indexOf(":"); |
| // Don't take namespace info from attributes with the default namespace, that is attributes |
| // that are not prefixed. (colonIndex == -1) |
| // If the colonIndex is followed by a / then it is a URI and not |
| // namespace qualified. |
| if (colonIndex == -1 || (colonIndex + 1 < nodeValue.length() && nodeValue.charAt(colonIndex + 1) == '/')) |
| { |
| continue; |
| } |
| // here we have found a colon delimiter so we need the namespace defined here |
| else |
| { |
| nodeValue = nodeValue.substring(0, colonIndex); |
| } |
| if (!namespace.contains(nodeValue)) |
| { |
| |
| namespace.add(nodeValue); |
| } |
| } |
| } |
| |
| return namespace; |
| |
| } |
| |
| /** |
| * Get a list of all the namespaces that have an import statement. |
| * |
| * @param elem The root element of the schema. |
| * @return A list of all the namespaces that are imported. |
| */ |
| protected List getImportNamespaces(Element elem) |
| { |
| List namespace = new Vector(); |
| |
| // call the method recursively for each child element |
| // register all the child types first |
| NodeList childNodes = elem.getChildNodes(); |
| |
| for (int i = 0; i < childNodes.getLength() || i < 5; i++) |
| { |
| Node n = childNodes.item(i); |
| // we only want nodes that are Elements |
| if (n instanceof Element) |
| { |
| Element child = (Element)n; |
| List childns = getImportNamespaces(child); |
| for (int j = childns.size() - 1; j >= 0; j--) |
| { |
| String ns = (String)childns.get(j); |
| if (!namespace.contains(ns)) |
| { |
| namespace.add(ns); |
| } |
| } |
| } |
| } |
| // if this is an import element get the namespace and add it to the list |
| if (elem.getLocalName().equalsIgnoreCase(IMPORT)) |
| { |
| NamedNodeMap atts = elem.getAttributes(); |
| for (int i = 0; i < atts.getLength(); i++) |
| { |
| Node n = atts.item(i); |
| |
| String nodeName = n.getNodeName(); |
| if (nodeName.equalsIgnoreCase(NAMESPACE)) |
| { |
| String nodeValue = n.getNodeValue(); |
| if (!namespace.contains(nodeValue)) |
| { |
| |
| namespace.add(nodeValue); |
| } |
| } |
| } |
| } |
| |
| return namespace; |
| |
| } |
| |
| /** |
| * Return a Hashtable with namespace prefixes as keys from the given element. |
| * |
| * @param elem The root element of the schema. |
| * @return A hashtable with namespace prefixes mapped to namespaces. |
| */ |
| protected Hashtable getNSResolver(Element elem) |
| { |
| Hashtable nsResolver = new Hashtable(); |
| |
| NamedNodeMap atts = elem.getAttributes(); |
| for (int i = 0; i < atts.getLength(); i++) |
| { |
| Node n = atts.item(i); |
| |
| String nodeName = n.getNodeName(); |
| if (nodeName.indexOf(XMLNS) != -1) |
| { |
| String nodeValue = n.getNodeValue(); |
| String namePrefix = nodeName; |
| |
| if (namePrefix.equalsIgnoreCase(XMLNS)) |
| { |
| namePrefix = ""; |
| } |
| else |
| { |
| namePrefix = namePrefix.substring(6); |
| } |
| nsResolver.put(namePrefix, nodeValue); |
| |
| } |
| } |
| return nsResolver; |
| |
| } |
| |
| /** |
| * Resolve the namespaces in the given namespaces list with the two namespace |
| * resolver hashtables provided. Return a list of all the namespace that need |
| * to be declared. |
| * First resolve against the local namespaces with nsResolver. |
| * Next resolve against the parent namespace with parentNSResolver. |
| * A side affect of this method is the namespaces list is left with only those |
| * namespaces that are resolved and the resolved entities are placed in the |
| * list instead of the original entries. |
| * For ex. If you provide a list such as {xsd, intf} and only xsd can be resolved |
| * you will end up with the list {http://www.w3.org/2001/XMLSchema} |
| * |
| * @param namespaces The list of namespaces to resolve. |
| * @param nsResolver The hashtable to be used as the local resolver. |
| * @param parentNSResolver The hashtable to be used as the parent namespace resolver. |
| * @return A Hashtable of namespaces that must be declared. |
| */ |
| protected Hashtable resolveNamespaces(List namespaces, Hashtable nsResolver, Hashtable parentNSResolver) |
| { |
| Hashtable reqNSDecl = new Hashtable(); |
| if (namespaces != null && !namespaces.isEmpty() && nsResolver != null) |
| { |
| for (int i = namespaces.size() - 1; i >= 0; i--) |
| { |
| String ns = (String)namespaces.get(i); |
| // Remove the namespace from the list. |
| namespaces.remove(i); |
| // First try to resolve against the local namespace resolver. |
| if (nsResolver.containsKey(ns)) |
| { |
| Object resolvedNS = nsResolver.get(ns); |
| // Only add the namespace if it's not already in the list. |
| if(!namespaces.contains(resolvedNS)) |
| { |
| namespaces.add(i, nsResolver.get(ns)); |
| } |
| } |
| // Next try to resolve against the parent namespace resolver. |
| else |
| { |
| if (ns.equals("")) |
| { |
| ns = XMLNS; |
| } |
| else |
| { |
| ns = XMLNS + ":" + ns; |
| } |
| if (parentNSResolver.containsKey(ns)) |
| { |
| Object resolvedNS = parentNSResolver.get(ns); |
| // Only add the namespace if it's not already in the list. |
| if(!namespaces.contains(resolvedNS)) |
| { |
| namespaces.add(i, resolvedNS); |
| } |
| // Still need to declare the namespace though. |
| reqNSDecl.put(ns, resolvedNS); |
| } |
| } |
| |
| } |
| } |
| return reqNSDecl; |
| } |
| |
| /** |
| * Remove any namespace from the namespaces list if it is in the import list. |
| * |
| * @param namespaces The namespaces list. |
| * @param importedNamespaces A list of imported namespaces. |
| * @return The list of namespaces without the imported namespaces. |
| */ |
| protected List removeImports(List namespaces, List importedNamespaces) |
| { |
| if (namespaces != null && importedNamespaces != null && !importedNamespaces.isEmpty()) |
| { |
| Iterator iImportedNS = importedNamespaces.iterator(); |
| while (iImportedNS.hasNext()) |
| { |
| String iNS = (String)iImportedNS.next(); |
| |
| namespaces.remove(iNS); |
| } |
| } |
| return namespaces; |
| } |
| |
| /** |
| * Remove the local namespace for the schema and the namespaces listed in the ignoreNamespaces |
| * list from the namespaces list provided. |
| * |
| * @param namespaces The list of local namespaces. |
| * @param elem The root element of the schema. |
| * @return The list of namespaces with the local namespaces removed. |
| */ |
| protected List removeLocalNamespaces(List namespaces, Element elem) |
| { |
| if (namespaces != null && elem != null) |
| { |
| String ns = elem.getAttribute(TARGETNAMESPACE); |
| namespaces.remove(ns); |
| |
| for (int i = ignoreNamespaces.length - 1; i >= 0; i--) |
| { |
| // keep removing the namespace until it is not in the list |
| if (namespaces.remove(ignoreNamespaces[i])) |
| { |
| i++; |
| } |
| } |
| } |
| return namespaces; |
| } |
| |
| /** |
| * Remove all the namespaces in the namespaces list that aren't contained in the |
| * validImportNSs set. |
| * |
| * @param namespaces A list of namespaces. |
| * @param validImportNSs A set of valid namespaces. |
| * @return A list of namespaces that does not contain any members which aren't in the validImportNSs set. |
| */ |
| protected List restrictImports(List namespaces, Set validImportNSs) |
| { |
| Iterator nsIter = namespaces.iterator(); |
| while(nsIter.hasNext()) |
| { |
| String ns = (String)nsIter.next(); |
| if(!validImportNSs.contains(ns)) |
| { |
| namespaces.remove(ns); |
| nsIter = namespaces.iterator(); |
| } |
| } |
| return namespaces; |
| } |
| |
| /** |
| * Returns true if the SOAP encoding namespace is in the list of required namespaces, |
| * false otherwise. |
| * |
| * @param reqns The list of namespaces to check for the SOAP encoding namespace. |
| * @return True if the SOAP encoding namespaces is in the list, false otherwise. |
| */ |
| protected boolean checkSOAPEncodingRequired(List reqns) |
| { |
| if (reqns.contains(SOAP_ENCODING_URI)) |
| { |
| return true; |
| } |
| return false; |
| } |
| } |