package wsdltypehandler;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class CutXSDFromWSDL extends DefaultHandler {
// this can be removed later, maybe. the path of wsdl file.
String path = null;
public void cutWSDL(String wsdlPath) throws SAXException, IOException,
ParserConfigurationException {
path = wsdlPath;
SAXParserFactory saxfac = SAXParserFactory.newInstance();
saxfac.setNamespaceAware(false);
saxfac.setXIncludeAware(true);
saxfac.setValidating(false);
SAXParser saxParser = saxfac.newSAXParser();
saxParser.parse(wsdlPath, this);
}
// variables that need to be clear up once a type is processed.
private StringBuilder xsdContent = new StringBuilder();
private String typeName = null;
private String tlName = null;
private String tlNamespace = null;
private String documentation = "";
private boolean acceptContent = false;
private Set<String> dependencies = new HashSet<String>();
private Map<String, String> freemarkerReplacement = new HashMap<String, String>();
// variables for all types in the same schema node.
private Map<String, String> nsMappingSchema = new HashMap<String, String>();
private String targetNamespace = null;
// variables for all types in current wsdl.
private Map<String, TypeModel> xsds = new HashMap<String, TypeModel>();
private Map<String, String> nsMappingWSDL = new HashMap<String, String>();
private Map<String, String> tpyeAttrs = new HashMap<String, String>();
private List<String> xmlPath = new ArrayList<String>();
private int noTypeNameCounter = 0;
// node names
private static String WSDL_DEF = "wsdl:definitions";
private static String WSDL_TYPE_DEF = "wsdl:types";
private static String WSDL_SCHEMA_DEF = "xs:schema";
private static String COMPLEX_TYPE_NODE = "xs:complexType";
private static String SIMPLE_TYPE_NODE = "xs:simpleType";
private static String ANNOTATION_NODE = "xs:annotation";
private static String TYPE_DOCUMENTATION_NODE = "xs:documentation";
private static String APPINFO_NODE = "xs:appinfo";
private static String TL_SOURCE_NODE = "typeLibrarySource";
// attribute names
private static String ATTR_TARGET_NS_LB = "targetNamespace";
private static String TYPE_LIBRARY_ATTR = "library";
private static String TYPE_NS_ATTR = "namespace";
private static String TYPE_NAME_ATTR = "name";
private static String TYPE_BASE_ATTR = "base";
private static String TYPE_TYPE_ATTR = "type";
// namespace in attribtue value
private static String XS_NS = "xs:";
private static String XML_NS = "xmlns:";
// post process type variables
// keys needed to resolve for free marker replacement
private static final String EXTERNAL_NS_KEY = "EXTERNAL_NS";
private static final String TARGET_NS_KEY = "EXTERNAL_NS";
private static final String NAMESPACE_KEY = "NAMESPACE";
private static final String TYPELIB_KEY = "TYPE_LIB";
private static final String TYPE_NAME_KEY = "TYPE_NAME";
private static final String W3C_TYPE_NS = "http://www.w3.org/[0-9]{4}/XMLSchema";
private static final Pattern W3C_TYPE_NS_PATTERN = Pattern.compile(
W3C_TYPE_NS, Pattern.CASE_INSENSITIVE);
// import statement and included statement.
private static final String TYPE_INCLUDE = "\r\n\t<xs:include schemaLocation=\"typelib://{0}//{1}.xsd\" />";
private static final String TYPE_IMPORT = "\r\n\t<xs:import namespace=\"{0}\" \r\n"
+ "\t\tschemaLocation=\"typelib://{1}//{2}.xsd\" />";
private String getNameForNoTypeWithoutName() {
noTypeNameCounter++;
return "NoName" + noTypeNameCounter;
}
private boolean isWSDLNode() {
if (xmlPath.size() != 1) {
return false;
}
return WSDL_DEF.equals(xmlPath.get(0));
}
private boolean isSchemaNode() {
if (xmlPath.size() != 3) {
return false;
}
return WSDL_DEF.equals(xmlPath.get(0))
&& WSDL_TYPE_DEF.equals(xmlPath.get(1))
&& WSDL_SCHEMA_DEF.equals(xmlPath.get(2));
}
// private boolean isInSchemaNode() {
// if (xmlPath.size() <= 3) {
// return false;
// }
// return WSDL_DEF.equals(xmlPath.get(0))
// && WSDL_TYPE_DEF.equals(xmlPath.get(1))
// && WSDL_SCHEMA_DEF.equals(xmlPath.get(2));
// }
private boolean isTypeNode() {
if (xmlPath.size() != 4) {
return false;
}
return WSDL_DEF.equals(xmlPath.get(0))
&& WSDL_TYPE_DEF.equals(xmlPath.get(1))
&& WSDL_SCHEMA_DEF.equals(xmlPath.get(2))
&& (COMPLEX_TYPE_NODE.equals(xmlPath.get(3)) || SIMPLE_TYPE_NODE
.equals(xmlPath.get(3)));
}
private boolean isInTypeNode() {
if (xmlPath.size() < 5) {
return false;
}
return WSDL_DEF.equals(xmlPath.get(0))
&& WSDL_TYPE_DEF.equals(xmlPath.get(1))
&& WSDL_SCHEMA_DEF.equals(xmlPath.get(2))
&& (COMPLEX_TYPE_NODE.equals(xmlPath.get(3)) || SIMPLE_TYPE_NODE
.equals(xmlPath.get(3)));
}
private boolean isTypeLibrarySourceNode() {
if (xmlPath.size() != 7) {
return false;
}
return WSDL_DEF.equals(xmlPath.get(0))
&& WSDL_TYPE_DEF.equals(xmlPath.get(1))
&& WSDL_SCHEMA_DEF.equals(xmlPath.get(2))
&& (COMPLEX_TYPE_NODE.equals(xmlPath.get(3)) || SIMPLE_TYPE_NODE
.equals(xmlPath.get(3)))
&& ANNOTATION_NODE.equals(xmlPath.get(4))
&& APPINFO_NODE.equals(xmlPath.get(5))
&& TL_SOURCE_NODE.equals(xmlPath.get(6));
}
private boolean isTypeDocumentationNode() {
if (xmlPath.size() != 6) {
return false;
}
return WSDL_DEF.equals(xmlPath.get(0))
&& WSDL_TYPE_DEF.equals(xmlPath.get(1))
&& WSDL_SCHEMA_DEF.equals(xmlPath.get(2))
&& (COMPLEX_TYPE_NODE.equals(xmlPath.get(3)) || SIMPLE_TYPE_NODE
.equals(xmlPath.get(3)))
&& ANNOTATION_NODE.equals(xmlPath.get(4))
&& TYPE_DOCUMENTATION_NODE.equals(xmlPath.get(5));
}
private boolean isTypeDependencyRelated(String attrName, String attrValue) {
if (TYPE_BASE_ATTR.equals(attrName) == false
&& TYPE_TYPE_ATTR.equals(attrName) == false) {
return false;
}
return true;
}
private static boolean isBasicTypeSchemaNamespace(String namespace) {
Matcher mather = W3C_TYPE_NS_PATTERN.matcher(namespace);
return mather.matches();
}
private boolean isInW3CBasicSchemaTypeNamespace(String typeQName)
throws SAXException {
String[] qName = typeQName.split(":");
if (qName.length != 2) {
throw new SAXException("Invalidated type:" + typeQName);
}
String namespace = nsMappingSchema.get(XML_NS + qName[0]);
return isBasicTypeSchemaNamespace(namespace);
}
private boolean isInTypeLibrary(String typeQName) throws SAXException {
String[] qName = typeQName.split(":");
if (qName.length != 2) {
throw new SAXException("Invalidated type:" + typeQName);
}
String namespace = nsMappingSchema.get(XML_NS + qName[0]);
return findTypeInTypeLibrary(namespace, qName[1]) != null;
}
private Object findTypeInTypeLibrary(String namespace, String typeName) {
return null;
}
// fill EXTERNAL_NS_KEY with all external type namespace. fill TARGET_NS_KEY
// with the real type library namespace
private static final String COMMON_TYPE_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
+ "<xs:schema "
+ "{0}\r\n"
+ "\t attributeFormDefault=\"unqualified\" elementFormDefault=\"qualified\" "
+ "targetNamespace=\"{1}\" version=\"1.0.0\">\r\n";
/**
* key part of this method is to handle dependency. there are four kinds of
* dependencies. 1) Using a standard type from W3C schema type. How to check
* - check the namespace value is W3C_TYPE_NS or not. How to handle - For
* this kind of dependency, just adding a xmlns definition attribute to the
* xs:schema node. 2) Using a type library type. How to check - using the
* namespace and type name to try to find such a type in type library. If
* found, then it is a dependency to a type library type. Otherwise, it is
* not a type library dependency type. How to handle - for a TL type
* dependency, need to add a xs:import statement. TYPE_IMPORT is provided to
* use. just provide the type library name and the type name. also need to
* adding a xmlns definition attribute to the xs:schema node. 3) An internal
* type dependency. How to check - if 2) not match and the namespace of that
* dependency type is the same as the namespace of current type, so it must
* be an internal dependency. How to handle - for internal dependency, there
* are two more thing to handle. First, the name of the dependency type may
* be changed. Second, the namespace of the dependency type may changed (in
* fact, must change). So for the xsd content, the attribute where used the
* dependency type, must use "${}" and waiting to be replaced (already use
* it when adding content to XSD). For the XSD header, also should use "${}"
* and waiting to be replaced. 4) An internal dependency which in fact is
* and external type library dependency. How to check - find the dependency
* from xsds map and method isTypeLibraryType returns true. How to handle -
* first, it is a type library dependency, so there is no need to using free
* marker because the type name will never change. Set the type name value
* to be it self. Then need to handle the namespace part because the
* namespace part is incorrect.
*
*
* Notice that we need to check 2) first because a type may have the same
* namespace with an existing type library. just like a class named MyClass
* in java.lang package.
*/
private void postProcessTypes() throws SAXException {
Set<String> keys = xsds.keySet();
for (String key : keys) {
TypeModel model = xsds.get(key);
if (model.isNeedToImport() == false) {
continue;
}
String content = model.getTypeContent();
StringBuilder header = new StringBuilder();
Map<String, String> typeNSMapping = model.getNsMappingSchema();
// key-value for free marker mapping
Map<String, String> freeMarkerMapping = model
.getFreemarkerReplacement();
// the following Collection instance using set to avoid duplication
// all xmlns attributes for current type
Set<String> xmlnsAttrs = new HashSet<String>();
// all include node for current type
Set<String> includeNodes = new HashSet<String>();
// all importNode for current type
Set<String> importNodes = new HashSet<String>();
// key-value for namespace and xmlns, just for case 4)
Map<String, String> namespaceToXMLNS = new HashMap<String, String>();
for (String dep : model.getDependencies()) {
String[] qName = dep.split(":");
// in fact, before an dependency is added ,this is already
// checked.
if (qName.length != 2) {
throw new SAXException("Invalidated type:" + dep);
}
String nsShort = qName[0];
String shortNSDefinition = XML_NS + nsShort;
String depNamespace = typeNSMapping.get(shortNSDefinition);
String depTypeName = qName[1];
// handle case 1), add ns definition
if (isBasicTypeSchemaNamespace(depNamespace) == true) {
xmlnsAttrs.add("\r\n\t" + shortNSDefinition + "=\""
+ depNamespace + "\"");
namespaceToXMLNS.put(depNamespace, shortNSDefinition);
continue;
}
// handle case 2), add import and xmlns attributes.
Object typeLibratyType = this.findTypeInTypeLibrary(
depNamespace, depTypeName);
if (typeLibratyType != null) {
xmlnsAttrs.add("\r\n\t" + shortNSDefinition + "=\""
+ depNamespace + "\"");
String importNode = MessageFormat.format(TYPE_IMPORT,
depNamespace, "TYPE_LIBRARY_NAME", depTypeName);
importNodes.add(importNode);
namespaceToXMLNS.put(depNamespace, shortNSDefinition);
continue;
}
// handle case 3)
TypeModel depInternalType = xsds.get(depNamespace + ":"
+ depTypeName);
if (model.getNamespace().equals(depNamespace)
&& (depInternalType != null)
&& (depInternalType.isNeedToImport() == true)) {
xmlnsAttrs.add("\r\n\t" + shortNSDefinition + "=\""
+ depNamespace + "\"");
String includeNode = MessageFormat.format(TYPE_INCLUDE,
"${" + TYPELIB_KEY + "}", "${" + depTypeName + "}");
includeNodes.add(includeNode);
namespaceToXMLNS.put(depNamespace, shortNSDefinition);
continue;
}
if (model.getNamespace().equals(depNamespace)
&& (depInternalType != null)
&& (depInternalType.isTypeLibraryType() == true)) {
// FIXME: check if this type do exists in type library
String typelibNamespace = depInternalType
.getTypelibNamespace();
String xmlns = namespaceToXMLNS.get(typelibNamespace);
if (xmlns == null) {
xmlns = createNewXMLNS(namespaceToXMLNS.values());
xmlnsAttrs.add("\r\n\t" + xmlns + "=\""
+ typelibNamespace + "\"");
}
String nsPart = xmlns.substring(xmlns.lastIndexOf(':') + 1);
// change the type to the real type in type library.
freeMarkerMapping.put(dep, nsPart + ":" + depTypeName);
String importNode = MessageFormat.format(TYPE_IMPORT,
typelibNamespace, "TYPE_LIBRARY_NAME",
depInternalType.getTypeName());
importNodes.add(importNode);
continue;
}
System.out.println("Unhandled dependency for type :"
+ model.getTypeName() + ". Dependency not found for "
+ dep);
}
StringBuilder xmlnsString = new StringBuilder();
for (String attr : xmlnsAttrs) {
xmlnsString.append(attr);
}
String schemaNode = MessageFormat.format(COMMON_TYPE_HEADER,
xmlnsString.toString(), "${TARGET_NS_KEY}");
StringBuilder importIncludeString = new StringBuilder();
for (String includeStr : includeNodes) {
importIncludeString.append(includeStr);
}
for (String importStr : importNodes) {
importIncludeString.append(importStr);
}
String fullContent = schemaNode + importIncludeString + "\r\n"
+ model.getTypeContent();
model.setTypeContent(fullContent);
}
}
public String createNewXMLNS(Collection<String> existingXMLNS) {
int counter = 1;
String startXMLNS = XML_NS + "tns";
while (true) {
String xmlns = startXMLNS + counter;
if (existingXMLNS.contains(xmlns) == false) {
return xmlns;
}
counter++;
}
}
@Override
public void endDocument() throws SAXException {
postProcessTypes();
File file = new File(path);
File parentFile = file.getParentFile();
String wsdlFileName = file.getName();
wsdlFileName = wsdlFileName.substring(0, wsdlFileName.lastIndexOf('.'));
File xsdFolder = new File(parentFile, wsdlFileName);
if (xsdFolder.exists() == false || xsdFolder.isDirectory() == false) {
boolean createFolder = xsdFolder.mkdir();
if (createFolder == false) {
throw new SAXException("Unable to create folder:" + xsdFolder);
}
}
Set<String> keys = xsds.keySet();
for (String key : keys) {
String fileName = key.substring(key.lastIndexOf(':') + 1);
File xsdFile = new File(xsdFolder, fileName + ".xsd");
TypeModel model = xsds.get(key);
try {
if (xsdFile.exists() == true && xsdFile.isFile() == true) {
xsdFile.delete();
}
xsdFile.createNewFile();
PrintWriter pw = new PrintWriter(xsdFile);
pw.write(model.getTypeContent());
pw.flush();
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
xmlPath.add(qName);
// current node is wsdl:definitions
if (isWSDLNode() == true) {
// store the namespace attributes in the nsMappingWSDL
for (int index = 0; index < attributes.getLength(); index++) {
String attrName = attributes.getQName(index);
if (attrName.toLowerCase().startsWith((XML_NS))) {
nsMappingWSDL.put(attrName, attributes.getValue(index));
}
}
return;
}
// current node is xs:schema
if (isSchemaNode() == true) {
// first, put all NS mappings of WSDL to schema NS mapping.
nsMappingSchema.putAll(nsMappingWSDL);
for (int index = 0; index < attributes.getLength(); index++) {
String attrName = attributes.getQName(index);
if (attrName.toLowerCase().startsWith((XML_NS))) {
/*
* store the namespace attributes in the nsMappingSchema.
* this is after putting ns attrs into nsMappingWSDL. so if
* there are any duplicate ns definitions, the one in type
* node will be used.
*/
nsMappingSchema.put(attrName, attributes.getValue(index));
} else {
// put other attributes into tpyeAttrs map.
tpyeAttrs.put(attrName, attributes.getValue(index));
}
}
targetNamespace = tpyeAttrs.get(ATTR_TARGET_NS_LB);
return;
}
// current node is a type definition node
if (isTypeNode() == true) {
// begin to add whatever content to xsdContent. this is used in
// character method.
acceptContent = true;
// extract type name from attribute
for (int index = 0; index < attributes.getLength(); index++) {
String attrName = attributes.getQName(index);
String attrValue = attributes.getValue(index);
if (TYPE_NAME_ATTR.equals(attrName)) {
// extract type name attribute value and store it to
// typeName
typeName = attrValue;
}
}
// if there is no type name, just use a default type name;
if (typeName == null) {
typeName = this.getNameForNoTypeWithoutName();
}
}
// current node is typeLibrarySource node, get the library value and
// namespace value
if (isTypeLibrarySourceNode() == true) {
tlName = attributes.getValue(TYPE_LIBRARY_ATTR);
tlNamespace = attributes.getValue(TYPE_NS_ATTR);
}
// current node is in a type definition node or type definition node
if (isInTypeNode() == true || isTypeNode() == true) {
// write whatever into xsdContent,
xsdContent.append("<" + qName);
for (int index = 0; index < attributes.getLength(); index++) {
// trim name and value, just for sure.
String attrName = attributes.getQName(index).trim();
String attrValue = attributes.getValue(index).trim();
// if this attribute is dependency related, using a marker to
// replace it later. and, add the dependency to dependency list.
if (isTypeDependencyRelated(attrName, attrValue) == true) {
dependencies.add(attrValue);
if (isInW3CBasicSchemaTypeNamespace(attrValue) == true
|| isInTypeLibrary(attrValue) == true) {
// it is a standard type or a type library type
} else {
// then it is an internal dependency type, using free
// marker for replacement
attrValue = "${" + attrValue + "}";
freemarkerReplacement.put(attrValue, null);
}
}
xsdContent.append(" " + attrName + "=\"" + attrValue + "\"");
}
xsdContent.append(">");
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if (isInTypeNode() == true || isTypeNode() == true) {
// write whatever into XSD content if it is type node or in type
// node.
xsdContent.append("</" + qName + ">");
}
if (isTypeNode() == true) {
// end of a type node. create a TypeModel instance for current type
TypeModel type = null;
if ((tlName == null) == (tlNamespace == null)) {
type = new TypeModel(typeName, targetNamespace,
nsMappingSchema, xsdContent.toString(),
freemarkerReplacement, documentation, dependencies,
tlName, tlNamespace);
} else {
type = new TypeModel(typeName, targetNamespace,
nsMappingSchema, xsdContent.toString(),
freemarkerReplacement, documentation, dependencies,
tlName, tlNamespace);
type.addError("Type library source is not complete!");
}
// put current type to map, key is the NS:TypeName
String key = targetNamespace + ":" + typeName;
if (xsds.get(key) != null) {
throw new SAXException("Duplicated type found!" + key);
}
xsds.put(key, type);
// clear type content, type name and documentation.
typeName = null;
xsdContent.delete(0, xsdContent.length());
freemarkerReplacement = new HashMap<String, String>();
documentation = "";
// clear tlName and tlNamespace
tlName = null;
tlNamespace = null;
// create a new list for next type
dependencies = new HashSet<String>();
// no longer accept content until meet another type
acceptContent = false;
} else if (isSchemaNode() == true) {
// end of a type schema block. clear schema node related variables -
// schema NS and namespace.
nsMappingSchema = new HashMap<String, String>();
targetNamespace = null;
}
xmlPath.remove(xmlPath.size() - 1);
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if (acceptContent == false) {
return;
}
String content = new String(ch, start, length);
if (isTypeDocumentationNode() == true) {
documentation = content;
}
/*
* SAXP will change these characters to the real characters. But the
* content is in fact used as XSD content and the characters need to be
* transformed back. Otherwise, the XSD content would be invalidated.
*/
content = content.replace("&", "&");
content = content.replace("<", "<");
content = content.replace(">", ">");
content = content.replace("\"", """);
content = content.replace("'", "'");
xsdContent.append(content);
}
public static void main(String[] args) throws SAXException, IOException,
ParserConfigurationException {
File f = new File("F:\\aaaaa");
// File f = new
// File("C:\\Documents and Settings\\mzang\\Desktop\\aaaaa");
for (File wsdl : f.listFiles()) {
if (isWSDL(wsdl) == false) {
continue;
}
CutXSDFromWSDL instance = new CutXSDFromWSDL();
instance.cutWSDL(wsdl.toString());
}
}
private static boolean isWSDL(File f) {
if (f.isFile() == false) {
return false;
}
String uri = f.toString();
int tail = uri.lastIndexOf('.');
if (tail == -1) {
return false;
}
String end = uri.substring(tail + 1);
if (end.equalsIgnoreCase("wsdl") == false) {
return false;
}
return true;
}
}