1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.impl.wsdl.support.xsd;
14
15 import java.io.File;
16 import java.io.IOException;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.StringTokenizer;
27
28 import javax.xml.namespace.QName;
29
30 import org.apache.log4j.Logger;
31 import org.apache.xmlbeans.SchemaAnnotation;
32 import org.apache.xmlbeans.SchemaLocalElement;
33 import org.apache.xmlbeans.SchemaParticle;
34 import org.apache.xmlbeans.SchemaType;
35 import org.apache.xmlbeans.SchemaTypeLoader;
36 import org.apache.xmlbeans.SchemaTypeSystem;
37 import org.apache.xmlbeans.SimpleValue;
38 import org.apache.xmlbeans.XmlAnySimpleType;
39 import org.apache.xmlbeans.XmlBase64Binary;
40 import org.apache.xmlbeans.XmlBeans;
41 import org.apache.xmlbeans.XmlCursor;
42 import org.apache.xmlbeans.XmlException;
43 import org.apache.xmlbeans.XmlHexBinary;
44 import org.apache.xmlbeans.XmlObject;
45 import org.apache.xmlbeans.XmlOptions;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.Element;
48 import org.w3c.dom.NamedNodeMap;
49 import org.w3c.dom.Node;
50
51 import com.eviware.soapui.SoapUI;
52 import com.eviware.soapui.impl.wsdl.support.Constants;
53 import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
54 import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlLoader;
55 import com.eviware.soapui.model.settings.SettingsListener;
56 import com.eviware.soapui.settings.WsdlSettings;
57 import com.eviware.soapui.support.Tools;
58
59 /***
60 * XML-Schema related tools
61 *
62 * @author Ole.Matzura
63 */
64
65 public class SchemaUtils
66 {
67 private final static Logger log = Logger.getLogger( SchemaUtils.class );
68 private static Map<String,XmlObject> defaultSchemas = new HashMap<String,XmlObject>();
69
70 static
71 {
72 initDefaultSchemas();
73
74 SoapUI.getSettings().addSettingsListener( new SettingsListener() {
75
76 public void settingChanged( String name, String newValue, String oldValue )
77 {
78 if( name.equals( WsdlSettings.SCHEMA_DIRECTORY ))
79 {
80 log.info( "Reloading default schemas.." );
81 initDefaultSchemas();
82 }
83 }});
84 }
85
86 public static void initDefaultSchemas()
87 {
88 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
89 Thread.currentThread().setContextClassLoader( SoapUI.class.getClassLoader() );
90
91 try
92 {
93 defaultSchemas.clear();
94 loadDefaultSchema( SoapUI.class.getResource("/xop.xsd") );
95 loadDefaultSchema( SoapUI.class.getResource("/XMLSchema.xsd") );
96 loadDefaultSchema( SoapUI.class.getResource("/xml.xsd") );
97 loadDefaultSchema( SoapUI.class.getResource("/swaref.xsd") );
98 loadDefaultSchema( SoapUI.class.getResource("/xmime200505.xsd") );
99 loadDefaultSchema( SoapUI.class.getResource("/xmime200411.xsd") );
100
101 String schemaDirectory = SoapUI.getSchemaDirectory();
102 if( schemaDirectory != null && schemaDirectory.trim().length() > 0 )
103 loadSchemaDirectory( schemaDirectory );
104 }
105 catch (Exception e)
106 {
107 SoapUI.logError( e );
108 }
109 finally
110 {
111 Thread.currentThread().setContextClassLoader( contextClassLoader );
112 }
113 }
114
115 private static void loadSchemaDirectory( String schemaDirectory ) throws IOException, MalformedURLException
116 {
117 File dir = new File( schemaDirectory );
118 if( dir.exists() && dir.isDirectory() )
119 {
120 String[] xsdFiles = dir.list();
121 int cnt = 0;
122
123 if( xsdFiles != null && xsdFiles.length > 0 )
124 {
125 for( int c = 0; c < xsdFiles.length; c++ )
126 {
127 try
128 {
129 String xsdFile = xsdFiles[c];
130 if( xsdFile.endsWith( ".xsd" ))
131 {
132 String filename = schemaDirectory + File.separator + xsdFile;
133 loadDefaultSchema( new URL( "file:" + filename ) );
134 cnt++;
135 }
136 }
137 catch( Exception e )
138 {
139 SoapUI.logError( e );
140 }
141 }
142 }
143
144 if( cnt == 0 )
145 log.warn( "Missing schema files in schemaDirectory [" + schemaDirectory + "]" );
146 }
147 else log.warn( "Failed to open schemaDirectory [" + schemaDirectory + "]" );
148 }
149
150 private static void loadDefaultSchema( URL url ) throws XmlException, IOException
151 {
152 XmlObject xmlObject = XmlObject.Factory.parse( url );
153 String targetNamespace = getTargetNamespace( xmlObject );
154
155 if( defaultSchemas.containsKey( targetNamespace ))
156 log.warn( "Overriding schema for targetNamespace " + targetNamespace );
157
158 defaultSchemas.put( targetNamespace, xmlObject );
159
160 log.info( "Added default schema from " + url.getPath() + " with targetNamespace " + targetNamespace );
161 }
162
163 public static SchemaTypeLoader loadSchemaTypes(String wsdlUrl, SoapVersion soapVersion, WsdlLoader loader ) throws SchemaException
164 {
165 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
166 Thread.currentThread().setContextClassLoader( SoapUI.class.getClassLoader() );
167
168 try
169 {
170 log.info("Loading schema types from [" + wsdlUrl + "]");
171 ArrayList<XmlObject> schemas = new ArrayList<XmlObject>(getSchemas(
172 wsdlUrl, loader).values());
173
174 return buildSchemaTypes(schemas, soapVersion);
175 }
176 catch (Exception e)
177 {
178 throw new SchemaException( "Error loading schema types", e );
179 }
180 finally
181 {
182 Thread.currentThread().setContextClassLoader( contextClassLoader );
183 }
184 }
185
186 public static SchemaTypeLoader buildSchemaTypes(List<XmlObject> schemas, SoapVersion soapVersion) throws SchemaException
187 {
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( WsdlSettings.STRICT_SCHEMA_TYPES ))
196 {
197 Set<String> mdefNamespaces = new HashSet<String>();
198
199 for (XmlObject xObj: schemas)
200 {
201 mdefNamespaces.add( getTargetNamespace( xObj ) );
202 }
203
204 options.setCompileMdefNamespaces(mdefNamespaces);
205 }
206
207 ArrayList errorList = new ArrayList();
208 options.setErrorListener( errorList );
209
210 XmlCursor cursor = null;
211
212 try
213 {
214
215 for( int c = 0; c < schemas.size(); c++ )
216 {
217 XmlObject s = schemas.get( c );
218
219 Map map = new HashMap();
220 cursor = s.newCursor();
221 cursor.toStartDoc();
222 if( toNextContainer(cursor))
223 cursor.getAllNamespaces( map );
224 else
225 log.warn( "Can not get namespaces for " + s );
226
227 String tns = getTargetNamespace(s);
228
229 log.info( "schema for [" + tns + "] contained [" + map.toString() + "] namespaces" );
230
231
232 if( defaultSchemas.containsKey( tns ) ||
233 tns.equals( getTargetNamespace(soapVersion.getSoapEncodingSchema()) ) ||
234 tns.equals( getTargetNamespace(soapVersion.getSoapEnvelopeSchema() )))
235 {
236 schemas.remove( c );
237 c--;
238 }
239 else
240 {
241 removeImports(s);
242 }
243
244 cursor.dispose();
245 cursor = null;
246 }
247
248 schemas.add( soapVersion.getSoapEncodingSchema());
249 schemas.add( soapVersion.getSoapEnvelopeSchema());
250 schemas.addAll( defaultSchemas.values() );
251
252 SchemaTypeSystem sts = XmlBeans.compileXsd(
253 schemas.toArray(new XmlObject[schemas.size()]), XmlBeans.getBuiltinTypeSystem(), options);
254 return XmlBeans.typeLoaderUnion(new SchemaTypeLoader[] { sts, XmlBeans.getBuiltinTypeSystem() });
255 }
256 catch (Exception e)
257 {
258 SoapUI.logError( e );
259 throw new SchemaException( e, errorList );
260 }
261 finally
262 {
263 for( int c = 0; c < errorList.size(); c++ )
264 {
265 log.warn( "Error: " + errorList.get( c ));
266 }
267
268 if( cursor != null )
269 cursor.dispose();
270 }
271 }
272
273 public static boolean toNextContainer(XmlCursor cursor)
274 {
275 while( !cursor.isContainer() && !cursor.isEnddoc() )
276 cursor.toNextToken();
277
278 return cursor.isContainer();
279 }
280
281 public static String getTargetNamespace(XmlObject s)
282 {
283 return ((Document)s.getDomNode()).getDocumentElement().getAttribute( "targetNamespace" );
284 }
285
286 public static Map<String,XmlObject> getSchemas( String wsdlUrl, WsdlLoader loader ) throws SchemaException
287 {
288 Map<String,XmlObject> result = new HashMap<String,XmlObject>();
289 getSchemas( wsdlUrl, result, loader, null );
290 return result;
291 }
292
293 /***
294 * Returns a map mapping urls to corresponding XmlSchema XmlObjects for the specified wsdlUrl
295 */
296
297 public static void getSchemas( String wsdlUrl, Map<String,XmlObject> existing, WsdlLoader loader, String tns ) throws SchemaException
298 {
299 if( existing.containsKey( wsdlUrl ))
300 return;
301
302 log.info( "Getting schema " + wsdlUrl );
303
304 ArrayList errorList = new ArrayList();
305
306 Map<String,XmlObject> result = new HashMap<String,XmlObject>();
307
308 try
309 {
310 XmlOptions options = new XmlOptions();
311 options.setCompileNoValidation();
312 options.setSaveUseOpenFrag();
313 options.setErrorListener( errorList );
314 options.setSaveSyntheticDocumentElement(new QName( Constants.XSD_NS, "schema"));
315
316 XmlObject xmlObject = loader.loadXmlObject(wsdlUrl, options);
317
318 Document dom = (Document) xmlObject.getDomNode();
319 Node domNode = dom.getDocumentElement();
320 if (domNode.getLocalName().equals("schema")
321 && domNode.getNamespaceURI().equals(
322 Constants.XSD_NS))
323 {
324
325 if( tns != null )
326 {
327 Element elm = ((Element)domNode);
328 if( !elm.hasAttribute( "targetNamespace" ))
329 {
330 elm.setAttribute( "targetNamespace", tns );
331 }
332
333
334 NamedNodeMap attributes = elm.getAttributes();
335 int c = 0;
336 for( ; c < attributes.getLength(); c++ )
337 {
338 Node item = attributes.item( c );
339 if( item.getNodeValue().equals( tns ) && item.getNodeName().startsWith( "xmlns" ))
340 break;
341 }
342
343 if( c == attributes.getLength() )
344 elm.setAttribute( "xmlns", tns );
345 }
346
347 result.put(wsdlUrl, xmlObject);
348 }
349 else
350 {
351 XmlObject[] schemas = xmlObject
352 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:schema");
353
354 for (int i = 0; i < schemas.length; i++)
355 {
356 XmlCursor xmlCursor = schemas[i].newCursor();
357 String xmlText = xmlCursor.getObject().xmlText(options);
358 schemas[i] = XmlObject.Factory.parse(xmlText, options);
359 schemas[i].documentProperties().setSourceName(wsdlUrl);
360
361 result.put(wsdlUrl + "@" + (i+1), schemas[i]);
362 }
363
364 XmlObject[] wsdlImports = xmlObject
365 .selectPath("declare namespace s='" + Constants.WSDL11_NS + "' .//s:import/@location");
366 for (int i = 0; i < wsdlImports.length; i++)
367 {
368 String location = ((SimpleValue) wsdlImports[i]).getStringValue();
369 if (location != null)
370 {
371 if (location.startsWith("file:")
372 || location.indexOf("://") > 0)
373 {
374 getSchemas(location, existing, loader, null);
375 }
376 else
377 {
378 getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader, null);
379 }
380 }
381 }
382 }
383
384 existing.putAll( result );
385
386 XmlObject[] schemas = result.values().toArray(
387 new XmlObject[result.size()]);
388
389 for (int c = 0; c < schemas.length; c++)
390 {
391 xmlObject = schemas[c];
392
393 XmlObject[] schemaImports = xmlObject
394 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import/@schemaLocation");
395 for (int i = 0; i < schemaImports.length; i++)
396 {
397 String location = ((SimpleValue) schemaImports[i])
398 .getStringValue();
399 if (location != null)
400 {
401 if (location.startsWith("file:")
402 || location.indexOf("://") > 0)
403 {
404 getSchemas(location, existing, loader, null);
405 }
406 else
407 {
408 getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader, null);
409 }
410 }
411 }
412
413 XmlObject[] schemaIncludes = xmlObject
414 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include/@schemaLocation");
415 for (int i = 0; i < schemaIncludes.length; i++)
416 {
417 String location = ((SimpleValue) schemaIncludes[i])
418 .getStringValue();
419 if (location != null)
420 {
421 String targetNS = ((Document)xmlObject.getDomNode()).getDocumentElement().getAttribute( "targetNamespace" );
422
423 if (location.startsWith("file:")
424 || location.indexOf("://") > 0)
425 {
426 getSchemas(location, existing, loader, targetNS);
427 }
428 else
429 {
430 getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader, targetNS);
431 }
432 }
433 }
434 }
435 }
436 catch (Exception e)
437 {
438 SoapUI.logError( e );
439 throw new SchemaException( e, errorList );
440 }
441 }
442
443 /***
444 * Returns a map mapping urls to corresponding XmlObjects for the specified wsdlUrl
445 */
446
447 public static Map<String,XmlObject> getDefinitionParts( WsdlLoader loader ) throws Exception
448 {
449 HashMap<String, XmlObject> result = new HashMap<String,XmlObject>();
450 getDefinitionParts( loader.getBaseURI(), result, loader );
451 return result;
452 }
453
454 public static void getDefinitionParts( String wsdlUrl, Map<String,XmlObject> existing, WsdlLoader loader ) throws Exception
455 {
456 if( existing.containsKey( wsdlUrl ))
457 return;
458
459 XmlObject xmlObject = loader.loadXmlObject( wsdlUrl, null );
460 existing.put( wsdlUrl, xmlObject );
461
462 XmlObject [] wsdlImports = xmlObject.selectPath("declare namespace s='" + Constants.WSDL11_NS + "' .//s:import");
463 for (int i = 0; i < wsdlImports.length; i++)
464 {
465 String location = wsdlImports[i].getDomNode().getAttributes().getNamedItem( "location" ).getNodeValue();
466 if( location != null )
467 {
468 if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
469 {
470 getDefinitionParts( location, existing, loader );
471 }
472 else
473 {
474 getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
475 }
476 }
477 }
478
479 XmlObject[] schemaImports = xmlObject.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import/@schemaLocation");
480 for (int i = 0; i < schemaImports.length; i++)
481 {
482 String location = ((SimpleValue)schemaImports[i]).getStringValue();
483 if( location != null )
484 {
485 if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
486 {
487 getDefinitionParts( location, existing, loader );
488 }
489 else
490 {
491 getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
492 }
493 }
494 }
495
496 XmlObject[] schemaIncludes = xmlObject.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include/@schemaLocation");
497 for (int i = 0; i < schemaIncludes.length; i++)
498 {
499 String location = ((SimpleValue)schemaIncludes[i]).getStringValue();
500 if( location != null )
501 {
502 if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
503 {
504 getDefinitionParts( location, existing, loader );
505 }
506 else
507 {
508 getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
509 }
510 }
511 }
512 }
513
514 /***
515 * Extracts namespaces - used in tool integrations for mapping..
516 */
517
518 public static Collection<String> extractNamespaces( SchemaTypeSystem schemaTypes )
519 {
520 Set<String> namespaces = new HashSet<String>();
521 SchemaType[] globalTypes = schemaTypes.globalTypes();
522 for( int c = 0; c < globalTypes.length; c++ )
523 {
524 namespaces.add( globalTypes[c].getName().getNamespaceURI() );
525 }
526
527 namespaces.remove( Constants.SOAP11_ENVELOPE_NS );
528 namespaces.remove( Constants.SOAP_ENCODING_NS );
529
530 return namespaces;
531 }
532
533 /***
534 * Used when creating a TypeSystem from a complete collection of SchemaDocuments so that referenced
535 * types are not downloaded (again)
536 */
537
538 public static void removeImports(XmlObject xmlObject) throws XmlException
539 {
540 XmlObject[] imports = xmlObject
541 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import");
542
543 for( int c = 0; c < imports.length; c++ )
544 {
545 XmlCursor cursor = imports[c].newCursor();
546 cursor.removeXml();
547 cursor.dispose();
548 }
549
550 XmlObject[] includes = xmlObject
551 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include");
552
553 for( int c = 0; c < includes.length; c++ )
554 {
555 XmlCursor cursor = includes[c].newCursor();
556 cursor.removeXml();
557 cursor.dispose();
558 }
559 }
560
561 public static boolean isInstanceOf( SchemaType schemaType, SchemaType baseType )
562 {
563 if( schemaType == null )
564 return false;
565 return schemaType.equals(baseType) ? true : isInstanceOf( schemaType.getBaseType(), baseType );
566 }
567
568 public static boolean isBinaryType(SchemaType schemaType)
569 {
570 return isInstanceOf( schemaType, XmlHexBinary.type ) ||
571 isInstanceOf( schemaType, XmlBase64Binary.type );
572 }
573
574 public static String getDocumentation( SchemaParticle particle, SchemaType schemaType )
575 {
576 String result = null;
577 String xsPrefix = null;
578
579 if( particle instanceof SchemaLocalElement )
580 {
581 SchemaAnnotation annotation = ((SchemaLocalElement)particle).getAnnotation();
582 if( annotation != null )
583 {
584 XmlObject[] userInformation = annotation.getUserInformation();
585 if( userInformation != null && userInformation.length > 0 )
586 {
587 XmlObject xmlObject = userInformation[0];
588 XmlCursor cursor = xmlObject.newCursor();
589 xsPrefix = cursor.prefixForNamespace( "http://www.w3.org/2001/XMLSchema" );
590 cursor.dispose();
591
592 result = xmlObject.xmlText();
593 }
594 }
595 }
596
597 if( result == null && schemaType != null && schemaType.getAnnotation() != null )
598 {
599 XmlObject[] userInformation = schemaType.getAnnotation().getUserInformation();
600 if( userInformation != null && userInformation.length > 0 )
601 {
602 XmlObject xmlObject = userInformation[0];
603 XmlCursor cursor = xmlObject.newCursor();
604 xsPrefix = cursor.prefixForNamespace( "http://www.w3.org/2001/XMLSchema" );
605 cursor.dispose();
606 result = xmlObject.xmlText();
607 }
608 }
609
610 if( result != null )
611 {
612 result = result.trim();
613 if( result.startsWith( "<" ) && result.endsWith( ">" ))
614 {
615 int ix = result.indexOf( '>' );
616 if( ix > 0 )
617 {
618 result = result.substring( ix+1);
619 }
620
621 ix = result.lastIndexOf( '<' );
622 if( ix >= 0 )
623 {
624 result = result.substring( 0, ix );
625 }
626 }
627
628 if( xsPrefix == null || xsPrefix.length() == 0 )
629 xsPrefix = "xs:";
630 else
631 xsPrefix += ":";
632
633
634 result = result.trim().replaceAll( xsPrefix, "" ).trim();
635
636 StringTokenizer st = new StringTokenizer( result, "\r\n" );
637 StringBuffer buf = new StringBuffer( "<html><body>" );
638 boolean added = false;
639
640 while( st.hasMoreElements() )
641 {
642 String str = st.nextToken().trim();
643 if( str.length() > 0 )
644 {
645 if( str.equals( "<br/>" ))
646 {
647 buf.append( "<br>" );
648 added = false;
649 continue;
650 }
651
652 if( added )
653 buf.append( "<br>" );
654
655 buf.append( str );
656 added = true;
657 }
658 }
659 buf.append( "</body></html>" );
660 result = buf.toString();
661 }
662
663 return result;
664 }
665
666 public static String [] getEnumerationValues( SchemaType schemaType, boolean addNull )
667 {
668 if( schemaType != null )
669 {
670 XmlAnySimpleType[] enumerationValues = schemaType.getEnumerationValues();
671 if( enumerationValues != null && enumerationValues.length > 0 )
672 {
673 if( addNull )
674 {
675 String [] values = new String[enumerationValues.length+1];
676 values[0] = null;
677
678 for( int c = 1; c < values.length; c++ )
679 values[c] = enumerationValues[c-1].getStringValue();
680
681 return values;
682 }
683 else
684 {
685 String [] values = new String[enumerationValues.length];
686
687 for( int c = 0; c < values.length; c++ )
688 values[c] = enumerationValues[c].getStringValue();
689
690 return values;
691 }
692 }
693 }
694
695 return new String[0];
696 }
697 }