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