001: /*
002: * soapUI, copyright (C) 2004-2007 eviware.com
003: *
004: * soapUI is free software; you can redistribute it and/or modify it under the
005: * terms of version 2.1 of the GNU Lesser General Public License as published by
006: * the Free Software Foundation.
007: *
008: * soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009: * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
010: * See the GNU Lesser General Public License for more details at gnu.org.
011: */
012:
013: package com.eviware.soapui.impl.wsdl.support.xsd;
014:
015: import java.io.File;
016: import java.io.IOException;
017: import java.net.MalformedURLException;
018: import java.net.URL;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.HashMap;
022: import java.util.HashSet;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.StringTokenizer;
027:
028: import javax.xml.namespace.QName;
029:
030: import org.apache.log4j.Logger;
031: import org.apache.xmlbeans.SchemaAnnotation;
032: import org.apache.xmlbeans.SchemaLocalElement;
033: import org.apache.xmlbeans.SchemaParticle;
034: import org.apache.xmlbeans.SchemaType;
035: import org.apache.xmlbeans.SchemaTypeLoader;
036: import org.apache.xmlbeans.SchemaTypeSystem;
037: import org.apache.xmlbeans.SimpleValue;
038: import org.apache.xmlbeans.XmlAnySimpleType;
039: import org.apache.xmlbeans.XmlBase64Binary;
040: import org.apache.xmlbeans.XmlBeans;
041: import org.apache.xmlbeans.XmlCursor;
042: import org.apache.xmlbeans.XmlException;
043: import org.apache.xmlbeans.XmlHexBinary;
044: import org.apache.xmlbeans.XmlObject;
045: import org.apache.xmlbeans.XmlOptions;
046: import org.w3c.dom.Document;
047: import org.w3c.dom.Element;
048: import org.w3c.dom.NamedNodeMap;
049: import org.w3c.dom.Node;
050:
051: import com.eviware.soapui.SoapUI;
052: import com.eviware.soapui.impl.wsdl.support.Constants;
053: import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
054: import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlLoader;
055: import com.eviware.soapui.model.settings.SettingsListener;
056: import com.eviware.soapui.settings.WsdlSettings;
057: import com.eviware.soapui.support.Tools;
058:
059: /**
060: * XML-Schema related tools
061: *
062: * @author Ole.Matzura
063: */
064:
065: public class SchemaUtils {
066: private final static Logger log = Logger
067: .getLogger(SchemaUtils.class);
068: private static Map<String, XmlObject> defaultSchemas = new HashMap<String, XmlObject>();
069:
070: static {
071: initDefaultSchemas();
072:
073: SoapUI.getSettings().addSettingsListener(
074: new SettingsListener() {
075:
076: public void settingChanged(String name,
077: String newValue, String oldValue) {
078: if (name.equals(WsdlSettings.SCHEMA_DIRECTORY)) {
079: log.info("Reloading default schemas..");
080: initDefaultSchemas();
081: }
082: }
083: });
084: }
085:
086: public static void initDefaultSchemas() {
087: ClassLoader contextClassLoader = Thread.currentThread()
088: .getContextClassLoader();
089: Thread.currentThread().setContextClassLoader(
090: SoapUI.class.getClassLoader());
091:
092: try {
093: defaultSchemas.clear();
094: loadDefaultSchema(SoapUI.class.getResource("/xop.xsd"));
095: loadDefaultSchema(SoapUI.class
096: .getResource("/XMLSchema.xsd"));
097: loadDefaultSchema(SoapUI.class.getResource("/xml.xsd"));
098: loadDefaultSchema(SoapUI.class.getResource("/swaref.xsd"));
099: loadDefaultSchema(SoapUI.class
100: .getResource("/xmime200505.xsd"));
101: loadDefaultSchema(SoapUI.class
102: .getResource("/xmime200411.xsd"));
103:
104: String schemaDirectory = SoapUI.getSchemaDirectory();
105: if (schemaDirectory != null
106: && schemaDirectory.trim().length() > 0)
107: loadSchemaDirectory(schemaDirectory);
108: } catch (Exception e) {
109: SoapUI.logError(e);
110: } finally {
111: Thread.currentThread().setContextClassLoader(
112: contextClassLoader);
113: }
114: }
115:
116: private static void loadSchemaDirectory(String schemaDirectory)
117: throws IOException, MalformedURLException {
118: File dir = new File(schemaDirectory);
119: if (dir.exists() && dir.isDirectory()) {
120: String[] xsdFiles = dir.list();
121: int cnt = 0;
122:
123: if (xsdFiles != null && xsdFiles.length > 0) {
124: for (int c = 0; c < xsdFiles.length; c++) {
125: try {
126: String xsdFile = xsdFiles[c];
127: if (xsdFile.endsWith(".xsd")) {
128: String filename = schemaDirectory
129: + File.separator + xsdFile;
130: loadDefaultSchema(new URL("file:"
131: + filename));
132: cnt++;
133: }
134: } catch (Exception e) {
135: SoapUI.logError(e);
136: }
137: }
138: }
139:
140: if (cnt == 0)
141: log.warn("Missing schema files in schemaDirectory ["
142: + schemaDirectory + "]");
143: } else
144: log.warn("Failed to open schemaDirectory ["
145: + schemaDirectory + "]");
146: }
147:
148: private static void loadDefaultSchema(URL url) throws XmlException,
149: IOException {
150: XmlObject xmlObject = XmlObject.Factory.parse(url);
151: String targetNamespace = getTargetNamespace(xmlObject);
152:
153: if (defaultSchemas.containsKey(targetNamespace))
154: log.warn("Overriding schema for targetNamespace "
155: + targetNamespace);
156:
157: defaultSchemas.put(targetNamespace, xmlObject);
158:
159: log.info("Added default schema from " + url.getPath()
160: + " with targetNamespace " + targetNamespace);
161: }
162:
163: public static SchemaTypeLoader loadSchemaTypes(String wsdlUrl,
164: SoapVersion soapVersion, WsdlLoader loader)
165: throws SchemaException {
166: ClassLoader contextClassLoader = Thread.currentThread()
167: .getContextClassLoader();
168: Thread.currentThread().setContextClassLoader(
169: SoapUI.class.getClassLoader());
170:
171: try {
172: log.info("Loading schema types from [" + wsdlUrl + "]");
173: ArrayList<XmlObject> schemas = new ArrayList<XmlObject>(
174: getSchemas(wsdlUrl, loader).values());
175:
176: return buildSchemaTypes(schemas, soapVersion);
177: } catch (Exception e) {
178: throw new SchemaException("Error loading schema types", e);
179: } finally {
180: Thread.currentThread().setContextClassLoader(
181: contextClassLoader);
182: }
183: }
184:
185: public static SchemaTypeLoader buildSchemaTypes(
186: List<XmlObject> schemas, SoapVersion soapVersion)
187: throws SchemaException {
188: XmlOptions options = new XmlOptions();
189: options.setCompileNoValidation();
190: options.setCompileNoPvrRule();
191: options.setCompileDownloadUrls();
192: options.setCompileNoUpaRule();
193: options.setValidateTreatLaxAsSkip();
194:
195: if (!SoapUI.getSettings().getBoolean(
196: WsdlSettings.STRICT_SCHEMA_TYPES)) {
197: Set<String> mdefNamespaces = new HashSet<String>();
198:
199: for (XmlObject xObj : schemas) {
200: mdefNamespaces.add(getTargetNamespace(xObj));
201: }
202:
203: options.setCompileMdefNamespaces(mdefNamespaces);
204: }
205:
206: ArrayList errorList = new ArrayList();
207: options.setErrorListener(errorList);
208:
209: XmlCursor cursor = null;
210:
211: try {
212: // remove imports
213: for (int c = 0; c < schemas.size(); c++) {
214: XmlObject s = schemas.get(c);
215:
216: Map map = new HashMap();
217: cursor = s.newCursor();
218: cursor.toStartDoc();
219: if (toNextContainer(cursor))
220: cursor.getAllNamespaces(map);
221: else
222: log.warn("Can not get namespaces for " + s);
223:
224: String tns = getTargetNamespace(s);
225:
226: log.info("schema for [" + tns + "] contained ["
227: + map.toString() + "] namespaces");
228:
229: if (defaultSchemas.containsKey(tns)
230: || tns.equals(getTargetNamespace(soapVersion
231: .getSoapEncodingSchema()))
232: || tns.equals(getTargetNamespace(soapVersion
233: .getSoapEnvelopeSchema()))) {
234: schemas.remove(c);
235: c--;
236: } else {
237: removeImports(s);
238: }
239:
240: cursor.dispose();
241: cursor = null;
242: }
243:
244: schemas.add(soapVersion.getSoapEncodingSchema());
245: schemas.add(soapVersion.getSoapEnvelopeSchema());
246: schemas.addAll(defaultSchemas.values());
247:
248: SchemaTypeSystem sts = XmlBeans.compileXsd(schemas
249: .toArray(new XmlObject[schemas.size()]), XmlBeans
250: .getBuiltinTypeSystem(), options);
251: return XmlBeans.typeLoaderUnion(new SchemaTypeLoader[] {
252: sts, XmlBeans.getBuiltinTypeSystem() });
253: } catch (Exception e) {
254: SoapUI.logError(e);
255: throw new SchemaException(e, errorList);
256: } finally {
257: for (int c = 0; c < errorList.size(); c++) {
258: log.warn("Error: " + errorList.get(c));
259: }
260:
261: if (cursor != null)
262: cursor.dispose();
263: }
264: }
265:
266: public static boolean toNextContainer(XmlCursor cursor) {
267: while (!cursor.isContainer() && !cursor.isEnddoc())
268: cursor.toNextToken();
269:
270: return cursor.isContainer();
271: }
272:
273: public static String getTargetNamespace(XmlObject s) {
274: return ((Document) s.getDomNode()).getDocumentElement()
275: .getAttribute("targetNamespace");
276: }
277:
278: public static Map<String, XmlObject> getSchemas(String wsdlUrl,
279: WsdlLoader loader) throws SchemaException {
280: Map<String, XmlObject> result = new HashMap<String, XmlObject>();
281: getSchemas(wsdlUrl, result, loader, null);
282: return result;
283: }
284:
285: /**
286: * Returns a map mapping urls to corresponding XmlSchema XmlObjects for the specified wsdlUrl
287: */
288:
289: public static void getSchemas(String wsdlUrl,
290: Map<String, XmlObject> existing, WsdlLoader loader,
291: String tns) throws SchemaException {
292: if (existing.containsKey(wsdlUrl))
293: return;
294:
295: log.info("Getting schema " + wsdlUrl);
296:
297: ArrayList errorList = new ArrayList();
298:
299: Map<String, XmlObject> result = new HashMap<String, XmlObject>();
300:
301: try {
302: XmlOptions options = new XmlOptions();
303: options.setCompileNoValidation();
304: options.setSaveUseOpenFrag();
305: options.setErrorListener(errorList);
306: options.setSaveSyntheticDocumentElement(new QName(
307: Constants.XSD_NS, "schema"));
308:
309: XmlObject xmlObject = loader
310: .loadXmlObject(wsdlUrl, options);
311:
312: Document dom = (Document) xmlObject.getDomNode();
313: Node domNode = dom.getDocumentElement();
314: if (domNode.getLocalName().equals("schema")
315: && domNode.getNamespaceURI().equals(
316: Constants.XSD_NS)) {
317: // set targetNamespace (this happens if we are following an include statement)
318: if (tns != null) {
319: Element elm = ((Element) domNode);
320: if (!elm.hasAttribute("targetNamespace")) {
321: elm.setAttribute("targetNamespace", tns);
322: }
323:
324: // check for namespace prefix for targetNamespace
325: NamedNodeMap attributes = elm.getAttributes();
326: int c = 0;
327: for (; c < attributes.getLength(); c++) {
328: Node item = attributes.item(c);
329: if (item.getNodeValue().equals(tns)
330: && item.getNodeName().startsWith(
331: "xmlns"))
332: break;
333: }
334:
335: if (c == attributes.getLength())
336: elm.setAttribute("xmlns", tns);
337: }
338:
339: result.put(wsdlUrl, xmlObject);
340: } else {
341: XmlObject[] schemas = xmlObject
342: .selectPath("declare namespace s='"
343: + Constants.XSD_NS + "' .//s:schema");
344:
345: for (int i = 0; i < schemas.length; i++) {
346: XmlCursor xmlCursor = schemas[i].newCursor();
347: String xmlText = xmlCursor.getObject().xmlText(
348: options);
349: schemas[i] = XmlObject.Factory.parse(xmlText,
350: options);
351: schemas[i].documentProperties().setSourceName(
352: wsdlUrl);
353:
354: result.put(wsdlUrl + "@" + (i + 1), schemas[i]);
355: }
356:
357: XmlObject[] wsdlImports = xmlObject
358: .selectPath("declare namespace s='"
359: + Constants.WSDL11_NS
360: + "' .//s:import/@location");
361: for (int i = 0; i < wsdlImports.length; i++) {
362: String location = ((SimpleValue) wsdlImports[i])
363: .getStringValue();
364: if (location != null) {
365: if (location.startsWith("file:")
366: || location.indexOf("://") > 0) {
367: getSchemas(location, existing, loader, null);
368: } else {
369: getSchemas(Tools.joinRelativeUrl(wsdlUrl,
370: location), existing, loader, null);
371: }
372: }
373: }
374: }
375:
376: existing.putAll(result);
377:
378: XmlObject[] schemas = result.values().toArray(
379: new XmlObject[result.size()]);
380:
381: for (int c = 0; c < schemas.length; c++) {
382: xmlObject = schemas[c];
383:
384: XmlObject[] schemaImports = xmlObject
385: .selectPath("declare namespace s='"
386: + Constants.XSD_NS
387: + "' .//s:import/@schemaLocation");
388: for (int i = 0; i < schemaImports.length; i++) {
389: String location = ((SimpleValue) schemaImports[i])
390: .getStringValue();
391: if (location != null) {
392: if (location.startsWith("file:")
393: || location.indexOf("://") > 0) {
394: getSchemas(location, existing, loader, null);
395: } else {
396: getSchemas(Tools.joinRelativeUrl(wsdlUrl,
397: location), existing, loader, null);
398: }
399: }
400: }
401:
402: XmlObject[] schemaIncludes = xmlObject
403: .selectPath("declare namespace s='"
404: + Constants.XSD_NS
405: + "' .//s:include/@schemaLocation");
406: for (int i = 0; i < schemaIncludes.length; i++) {
407: String location = ((SimpleValue) schemaIncludes[i])
408: .getStringValue();
409: if (location != null) {
410: String targetNS = ((Document) xmlObject
411: .getDomNode()).getDocumentElement()
412: .getAttribute("targetNamespace");
413:
414: if (location.startsWith("file:")
415: || location.indexOf("://") > 0) {
416: getSchemas(location, existing, loader,
417: targetNS);
418: } else {
419: getSchemas(Tools.joinRelativeUrl(wsdlUrl,
420: location), existing, loader,
421: targetNS);
422: }
423: }
424: }
425: }
426: } catch (Exception e) {
427: SoapUI.logError(e);
428: throw new SchemaException(e, errorList);
429: }
430: }
431:
432: /**
433: * Returns a map mapping urls to corresponding XmlObjects for the specified wsdlUrl
434: */
435:
436: public static Map<String, XmlObject> getDefinitionParts(
437: WsdlLoader loader) throws Exception {
438: HashMap<String, XmlObject> result = new HashMap<String, XmlObject>();
439: getDefinitionParts(loader.getBaseURI(), result, loader);
440: return result;
441: }
442:
443: public static void getDefinitionParts(String wsdlUrl,
444: Map<String, XmlObject> existing, WsdlLoader loader)
445: throws Exception {
446: if (existing.containsKey(wsdlUrl))
447: return;
448:
449: XmlObject xmlObject = loader.loadXmlObject(wsdlUrl, null);
450: existing.put(wsdlUrl, xmlObject);
451:
452: XmlObject[] wsdlImports = xmlObject
453: .selectPath("declare namespace s='"
454: + Constants.WSDL11_NS + "' .//s:import");
455: for (int i = 0; i < wsdlImports.length; i++) {
456: String location = wsdlImports[i].getDomNode()
457: .getAttributes().getNamedItem("location")
458: .getNodeValue();
459: if (location != null) {
460: if (location.startsWith("file:")
461: || location.indexOf("://") > 0) {
462: getDefinitionParts(location, existing, loader);
463: } else {
464: getDefinitionParts(Tools.joinRelativeUrl(wsdlUrl,
465: location), existing, loader);
466: }
467: }
468: }
469:
470: XmlObject[] schemaImports = xmlObject
471: .selectPath("declare namespace s='" + Constants.XSD_NS
472: + "' .//s:import/@schemaLocation");
473: for (int i = 0; i < schemaImports.length; i++) {
474: String location = ((SimpleValue) schemaImports[i])
475: .getStringValue();
476: if (location != null) {
477: if (location.startsWith("file:")
478: || location.indexOf("://") > 0) {
479: getDefinitionParts(location, existing, loader);
480: } else {
481: getDefinitionParts(Tools.joinRelativeUrl(wsdlUrl,
482: location), existing, loader);
483: }
484: }
485: }
486:
487: XmlObject[] schemaIncludes = xmlObject
488: .selectPath("declare namespace s='" + Constants.XSD_NS
489: + "' .//s:include/@schemaLocation");
490: for (int i = 0; i < schemaIncludes.length; i++) {
491: String location = ((SimpleValue) schemaIncludes[i])
492: .getStringValue();
493: if (location != null) {
494: if (location.startsWith("file:")
495: || location.indexOf("://") > 0) {
496: getDefinitionParts(location, existing, loader);
497: } else {
498: getDefinitionParts(Tools.joinRelativeUrl(wsdlUrl,
499: location), existing, loader);
500: }
501: }
502: }
503: }
504:
505: /**
506: * Extracts namespaces - used in tool integrations for mapping..
507: */
508:
509: public static Collection<String> extractNamespaces(
510: SchemaTypeSystem schemaTypes) {
511: Set<String> namespaces = new HashSet<String>();
512: SchemaType[] globalTypes = schemaTypes.globalTypes();
513: for (int c = 0; c < globalTypes.length; c++) {
514: namespaces.add(globalTypes[c].getName().getNamespaceURI());
515: }
516:
517: namespaces.remove(Constants.SOAP11_ENVELOPE_NS);
518: namespaces.remove(Constants.SOAP_ENCODING_NS);
519:
520: return namespaces;
521: }
522:
523: /**
524: * Used when creating a TypeSystem from a complete collection of SchemaDocuments so that referenced
525: * types are not downloaded (again)
526: */
527:
528: public static void removeImports(XmlObject xmlObject)
529: throws XmlException {
530: XmlObject[] imports = xmlObject
531: .selectPath("declare namespace s='" + Constants.XSD_NS
532: + "' .//s:import");
533:
534: for (int c = 0; c < imports.length; c++) {
535: XmlCursor cursor = imports[c].newCursor();
536: cursor.removeXml();
537: cursor.dispose();
538: }
539:
540: XmlObject[] includes = xmlObject
541: .selectPath("declare namespace s='" + Constants.XSD_NS
542: + "' .//s:include");
543:
544: for (int c = 0; c < includes.length; c++) {
545: XmlCursor cursor = includes[c].newCursor();
546: cursor.removeXml();
547: cursor.dispose();
548: }
549: }
550:
551: public static boolean isInstanceOf(SchemaType schemaType,
552: SchemaType baseType) {
553: if (schemaType == null)
554: return false;
555: return schemaType.equals(baseType) ? true : isInstanceOf(
556: schemaType.getBaseType(), baseType);
557: }
558:
559: public static boolean isBinaryType(SchemaType schemaType) {
560: return isInstanceOf(schemaType, XmlHexBinary.type)
561: || isInstanceOf(schemaType, XmlBase64Binary.type);
562: }
563:
564: public static String getDocumentation(SchemaParticle particle,
565: SchemaType schemaType) {
566: String result = null;
567: String xsPrefix = null;
568:
569: if (particle instanceof SchemaLocalElement) {
570: SchemaAnnotation annotation = ((SchemaLocalElement) particle)
571: .getAnnotation();
572: if (annotation != null) {
573: XmlObject[] userInformation = annotation
574: .getUserInformation();
575: if (userInformation != null
576: && userInformation.length > 0) {
577: XmlObject xmlObject = userInformation[0];
578: XmlCursor cursor = xmlObject.newCursor();
579: xsPrefix = cursor
580: .prefixForNamespace("http://www.w3.org/2001/XMLSchema");
581: cursor.dispose();
582:
583: result = xmlObject.xmlText(); // XmlUtils.getElementText( ( Element ) userInformation[0].getDomNode());
584: }
585: }
586: }
587:
588: if (result == null && schemaType != null
589: && schemaType.getAnnotation() != null) {
590: XmlObject[] userInformation = schemaType.getAnnotation()
591: .getUserInformation();
592: if (userInformation != null && userInformation.length > 0) {
593: XmlObject xmlObject = userInformation[0];
594: XmlCursor cursor = xmlObject.newCursor();
595: xsPrefix = cursor
596: .prefixForNamespace("http://www.w3.org/2001/XMLSchema");
597: cursor.dispose();
598: result = xmlObject.xmlText(); // = XmlUtils.getElementText( ( Element ) userInformation[0].getDomNode());
599: }
600: }
601:
602: if (result != null) {
603: result = result.trim();
604: if (result.startsWith("<") && result.endsWith(">")) {
605: int ix = result.indexOf('>');
606: if (ix > 0) {
607: result = result.substring(ix + 1);
608: }
609:
610: ix = result.lastIndexOf('<');
611: if (ix >= 0) {
612: result = result.substring(0, ix);
613: }
614: }
615:
616: if (xsPrefix == null || xsPrefix.length() == 0)
617: xsPrefix = "xs:";
618: else
619: xsPrefix += ":";
620:
621: //result = result.trim().replaceAll( "<" + xsPrefix + "br/>", "\n" ).trim();
622: result = result.trim().replaceAll(xsPrefix, "").trim();
623:
624: StringTokenizer st = new StringTokenizer(result, "\r\n");
625: StringBuffer buf = new StringBuffer("<html><body>");
626: boolean added = false;
627:
628: while (st.hasMoreElements()) {
629: String str = st.nextToken().trim();
630: if (str.length() > 0) {
631: if (str.equals("<br/>")) {
632: buf.append("<br>");
633: added = false;
634: continue;
635: }
636:
637: if (added)
638: buf.append("<br>");
639:
640: buf.append(str);
641: added = true;
642: }
643: }
644: buf.append("</body></html>");
645: result = buf.toString();
646: }
647:
648: return result;
649: }
650:
651: public static String[] getEnumerationValues(SchemaType schemaType,
652: boolean addNull) {
653: if (schemaType != null) {
654: XmlAnySimpleType[] enumerationValues = schemaType
655: .getEnumerationValues();
656: if (enumerationValues != null
657: && enumerationValues.length > 0) {
658: if (addNull) {
659: String[] values = new String[enumerationValues.length + 1];
660: values[0] = null;
661:
662: for (int c = 1; c < values.length; c++)
663: values[c] = enumerationValues[c - 1]
664: .getStringValue();
665:
666: return values;
667: } else {
668: String[] values = new String[enumerationValues.length];
669:
670: for (int c = 0; c < values.length; c++)
671: values[c] = enumerationValues[c]
672: .getStringValue();
673:
674: return values;
675: }
676: }
677: }
678:
679: return new String[0];
680: }
681: }
|