View Javadoc

1   /*
2    *  soapUI, copyright (C) 2006 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of the GNU Lesser General Public License as published by the Free Software Foundation; 
6    *  either version 2.1 of the License, or (at your option) any later version.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
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.Node;
48  
49  import com.eviware.soapui.SoapUI;
50  import com.eviware.soapui.impl.wsdl.support.Constants;
51  import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
52  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlLoader;
53  import com.eviware.soapui.model.settings.SettingsListener;
54  import com.eviware.soapui.settings.WsdlSettings;
55  import com.eviware.soapui.support.Tools;
56  
57  /***
58   * XML-Schema related tools
59   * 
60   * @author Ole.Matzura
61   */
62  
63  public class SchemaUtils
64  {
65     private final static Logger log = Logger.getLogger( SchemaUtils.class );
66  	private static Map<String,XmlObject> defaultSchemas = new HashMap<String,XmlObject>();
67  	
68     static
69     {
70     	initDefaultSchemas();
71     	
72     	SoapUI.getSettings().addSettingsListener( new SettingsListener() {
73  
74  			public void settingChanged( String name, String newValue, String oldValue )
75  			{
76  				if( name.equals( WsdlSettings.SCHEMA_DIRECTORY ))
77  				{
78  					log.info( "Reloading default schemas.." );
79  					initDefaultSchemas();
80  				}
81  			}});
82     }
83  
84  	public static void initDefaultSchemas()
85  	{
86  		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
87     	Thread.currentThread().setContextClassLoader( SoapUI.class.getClassLoader() );
88     	
89     	try
90  		{
91     		defaultSchemas.clear();
92     		loadDefaultSchema( SoapUI.class.getResource("/xop.xsd") );
93     		loadDefaultSchema( SoapUI.class.getResource("/XMLSchema.xsd") );
94     		loadDefaultSchema( SoapUI.class.getResource("/xml.xsd") );
95     		loadDefaultSchema( SoapUI.class.getResource("/swaref.xsd") );
96     		loadDefaultSchema( SoapUI.class.getResource("/xmime200505.xsd") );
97     		loadDefaultSchema( SoapUI.class.getResource("/xmime200411.xsd") );
98     		
99     		String schemaDirectory = SoapUI.getSchemaDirectory();
100    		if( schemaDirectory != null && schemaDirectory.trim().length() > 0 )
101    			loadSchemaDirectory( schemaDirectory );
102 		}
103 		catch (Exception e)
104 		{
105 			e.printStackTrace();
106 		}
107 		finally
108    	{
109    		Thread.currentThread().setContextClassLoader( contextClassLoader );
110    	}
111 	}
112 
113 	private static void loadSchemaDirectory( String schemaDirectory ) throws IOException, MalformedURLException
114 	{
115 		File dir = new File( schemaDirectory );
116 		if( dir.exists() && dir.isDirectory() )
117 		{
118 			String[] xsdFiles = dir.list();
119 			int cnt = 0;
120 			
121 			if( xsdFiles != null && xsdFiles.length > 0 )
122 			{
123 				for( int c = 0; c < xsdFiles.length; c++ )
124 				{
125 					try
126 					{
127 						String xsdFile = xsdFiles[c];
128 						if( xsdFile.endsWith( ".xsd" ))
129 						{
130 							String filename = schemaDirectory + File.separator + xsdFile;
131 							loadDefaultSchema( new URL( "file:" + filename ) );
132 							cnt++;
133 						}
134 					}
135 					catch( XmlException e )
136 					{
137 						e.printStackTrace();
138 					}
139 				}
140 			}
141 			
142 			if( cnt == 0 )
143 				log.warn( "Missing schema files in  schemaDirectory [" + schemaDirectory + "]" );
144 		}
145 		else log.warn( "Failed to open schemaDirectory [" + schemaDirectory + "]" );
146 	}
147    
148    private static void loadDefaultSchema( URL url ) throws XmlException, IOException
149    {
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 " + targetNamespace );
155 		
156 		defaultSchemas.put(  targetNamespace, xmlObject );
157 		
158 		log.info( "Added default schema from " + url.getPath() + " with targetNamespace " + targetNamespace );
159    }
160    
161    public static SchemaTypeLoader loadSchemaTypes(String wsdlUrl, SoapVersion soapVersion, WsdlLoader loader ) throws SchemaException
162    {
163    	ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
164    	Thread.currentThread().setContextClassLoader( SoapUI.class.getClassLoader() );
165    	
166    	try
167 		{
168 			log.info("Loading schema types from [" + wsdlUrl + "]");
169 			ArrayList<XmlObject> schemas = new ArrayList<XmlObject>(getSchemas(
170 					wsdlUrl, loader).values());
171 
172 			return buildSchemaTypes(schemas, soapVersion);
173 		}
174 		catch (Exception e)
175 		{
176 			throw new SchemaException( "Error loading schema types", e );
177 		}
178 		finally
179    	{
180    		Thread.currentThread().setContextClassLoader( contextClassLoader );
181    	}
182    }
183 
184    public static SchemaTypeLoader buildSchemaTypes(List<XmlObject> schemas, SoapVersion soapVersion) throws SchemaException
185    {
186       XmlOptions options = new XmlOptions();
187       options.setCompileNoValidation();
188       options.setCompileNoPvrRule();
189       options.setCompileDownloadUrls();
190       options.setCompileNoUpaRule();
191       options.setValidateTreatLaxAsSkip();
192       
193       ArrayList errorList = new ArrayList();
194       options.setErrorListener( errorList );
195 
196       try
197 		{
198       	// remove imports
199 			for( int c = 0; c < schemas.size(); c++ )
200 			{
201 				XmlObject s = schemas.get( c );
202 				
203 				Map map = new HashMap();
204 				XmlCursor cursor = s.newCursor();
205 				cursor.toStartDoc(); 
206 				if( toNextContainer(cursor))
207 					cursor.getAllNamespaces( map );
208 				else
209 					log.warn( "Can not get namespaces for " + s );
210 				
211 				String tns = getTargetNamespace(s);
212 				
213 				log.info( "schema for [" + tns + "] contained [" + map.toString() + "] namespaces" );
214 
215 				
216 				if( defaultSchemas.containsKey( tns ) ||
217 					 tns.equals( getTargetNamespace(soapVersion.getSoapEncodingSchema()) ) ||
218 					 tns.equals( getTargetNamespace(soapVersion.getSoapEnvelopeSchema() )))
219 				{
220 					schemas.remove( c );
221 					c--;
222 				}
223 				else
224 				{
225 					removeImports(s);
226 				}
227 			}
228 
229       	schemas.add( soapVersion.getSoapEncodingSchema());
230       	schemas.add( soapVersion.getSoapEnvelopeSchema());
231       	schemas.addAll( defaultSchemas.values() );
232       	
233       	SchemaTypeSystem sts = XmlBeans.compileXsd(
234       			schemas.toArray(new XmlObject[schemas.size()]), XmlBeans.getBuiltinTypeSystem(), options);
235       	return XmlBeans.typeLoaderUnion(new SchemaTypeLoader[] { sts, XmlBeans.getBuiltinTypeSystem() });
236 		}
237 		catch (Exception e)
238 		{
239 			e.printStackTrace();
240 			throw new SchemaException( e, errorList );
241 		}
242 		finally
243 		{
244 			for( int c = 0; c < errorList.size(); c++ )
245 			{
246 				log.warn( "Error: " + errorList.get( c ));
247 			}
248 		}
249    }
250 
251 	public static boolean toNextContainer(XmlCursor cursor)
252 	{
253 		while( !cursor.isContainer() && !cursor.isEnddoc() )
254 			cursor.toNextToken();
255 		
256 		return cursor.isContainer();
257 	}
258 
259 	public static String getTargetNamespace(XmlObject s)
260 	{
261 		return ((Document)s.getDomNode()).getDocumentElement().getAttribute( "targetNamespace" );
262 	}
263    
264    public static Map<String,XmlObject> getSchemas( String wsdlUrl, WsdlLoader loader ) throws SchemaException
265    {
266    	Map<String,XmlObject> result = new HashMap<String,XmlObject>();
267    	getSchemas( wsdlUrl, result, loader );
268    	return result;
269    }
270    
271    /***
272     * Returns a map mapping urls to corresponding XmlSchema XmlObjects for the specified wsdlUrl
273     */
274    
275    public static void getSchemas( String wsdlUrl, Map<String,XmlObject> existing,  WsdlLoader loader ) throws SchemaException
276    {
277    	if( existing.containsKey( wsdlUrl ))
278    		return; 
279    	
280    	log.info( "Getting schema " + wsdlUrl );
281    	
282    	ArrayList errorList = new ArrayList();
283    	
284    	Map<String,XmlObject> result = new HashMap<String,XmlObject>();
285    	
286       try
287 		{
288 			XmlOptions options = new XmlOptions();
289 			options.setCompileNoValidation();
290 			options.setSaveUseOpenFrag();
291 			options.setErrorListener( errorList );
292 			options.setSaveSyntheticDocumentElement(new QName( Constants.XSD_NS, "schema"));
293 
294 			XmlObject xmlObject = loader.loadXmlObject(wsdlUrl, options);
295 
296 			Document dom = (Document) xmlObject.getDomNode();
297 			Node domNode = dom.getDocumentElement();
298 			if (domNode.getLocalName().equals("schema")
299 					&& domNode.getNamespaceURI().equals(
300 							Constants.XSD_NS))
301 			{
302 				result.put(wsdlUrl, xmlObject);
303 			}
304 			else
305 			{
306 				XmlObject[] schemas = xmlObject
307 						.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:schema");
308 
309 				for (int i = 0; i < schemas.length; i++)
310 				{
311 					XmlCursor xmlCursor = schemas[i].newCursor();
312 					String xmlText = xmlCursor.getObject().xmlText(options);
313 					schemas[i] = XmlObject.Factory.parse(xmlText, options);
314 					schemas[i].documentProperties().setSourceName(wsdlUrl);
315 					
316 					result.put(wsdlUrl + "@" + (i+1), schemas[i]);
317 				}
318 
319 				XmlObject[] wsdlImports = xmlObject
320 						.selectPath("declare namespace s='" + Constants.WSDL11_NS + "' .//s:import/@location");
321 				for (int i = 0; i < wsdlImports.length; i++)
322 				{
323 					String location = ((SimpleValue) wsdlImports[i]).getStringValue();
324 					if (location != null)
325 					{
326 						if (location.startsWith("file:")
327 								|| location.indexOf("://") > 0)
328 						{
329 							getSchemas(location, existing, loader);
330 						}
331 						else
332 						{
333 							getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader);
334 						}
335 					}
336 				}
337 			}
338 
339 			existing.putAll( result );
340 			
341 			XmlObject[] schemas = result.values().toArray(
342 					new XmlObject[result.size()]);
343 
344 			for (int c = 0; c < schemas.length; c++)
345 			{
346 				xmlObject = schemas[c];
347 
348 				XmlObject[] schemaImports = xmlObject
349 						.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import/@schemaLocation");
350 				for (int i = 0; i < schemaImports.length; i++)
351 				{
352 					String location = ((SimpleValue) schemaImports[i])
353 							.getStringValue();
354 					if (location != null)
355 					{
356 						if (location.startsWith("file:")
357 								|| location.indexOf("://") > 0)
358 						{
359 							getSchemas(location, existing, loader);
360 						}
361 						else
362 						{
363 							getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader);
364 						}
365 					}
366 				}
367 
368 				XmlObject[] schemaIncludes = xmlObject
369 						.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include/@schemaLocation");
370 				for (int i = 0; i < schemaIncludes.length; i++)
371 				{
372 					String location = ((SimpleValue) schemaIncludes[i])
373 							.getStringValue();
374 					if (location != null)
375 					{
376 						if (location.startsWith("file:")
377 								|| location.indexOf("://") > 0)
378 						{
379 							getSchemas(location, existing, loader);
380 						}
381 						else
382 						{
383 							getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader);
384 						}
385 					}
386 				}
387 			}
388 		}
389 		catch (Exception e)
390 		{
391 			e.printStackTrace();
392 			throw new SchemaException( e, errorList );
393 		}
394    }
395    
396    /***
397     * Returns a map mapping urls to corresponding XmlObjects for the specified wsdlUrl
398     */
399    
400    public static Map<String,XmlObject> getDefinitionParts( WsdlLoader loader ) throws Exception
401    {
402    	HashMap<String, XmlObject> result = new HashMap<String,XmlObject>();
403 		getDefinitionParts( loader.getBaseURI(), result, loader );
404 		return result;
405    }
406    
407    public static void getDefinitionParts( String wsdlUrl, Map<String,XmlObject> existing, WsdlLoader loader ) throws Exception
408    {
409    	if( existing.containsKey( wsdlUrl ))
410    		return;
411    	
412       XmlObject xmlObject = loader.loadXmlObject( wsdlUrl, null );
413       existing.put( wsdlUrl, xmlObject );
414 
415     	XmlObject [] wsdlImports = xmlObject.selectPath("declare namespace s='" + Constants.WSDL11_NS + "' .//s:import");
416       for (int i = 0; i < wsdlImports.length; i++)
417       {
418          String location = wsdlImports[i].getDomNode().getAttributes().getNamedItem( "location" ).getNodeValue();
419          if( location != null )
420          {
421             if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
422             {
423             	getDefinitionParts( location, existing, loader );
424             }
425             else
426             {
427             	getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
428             }
429          }
430       }
431       
432       XmlObject[] schemaImports = xmlObject.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import/@schemaLocation");
433       for (int i = 0; i < schemaImports.length; i++)
434       {
435          String location = ((SimpleValue)schemaImports[i]).getStringValue();
436          if( location != null )
437          {
438             if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
439             {
440             	getDefinitionParts( location, existing, loader );
441             }
442             else
443             {
444             	getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
445             }
446          }
447       }
448       
449       XmlObject[] schemaIncludes = xmlObject.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include/@schemaLocation");
450       for (int i = 0; i < schemaIncludes.length; i++)
451       {
452          String location = ((SimpleValue)schemaIncludes[i]).getStringValue();
453          if( location != null  )
454          {
455             if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
456             {
457             	getDefinitionParts( location, existing, loader );
458             }
459             else
460             {
461             	getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
462             }
463          }
464       }
465    }
466    
467    /***
468     * Extracts namespaces - used in tool integrations for mapping..
469     */
470 
471 	public static Collection<String> extractNamespaces( SchemaTypeSystem schemaTypes )
472 	{
473 		Set<String> namespaces = new HashSet<String>();
474 		SchemaType[] globalTypes = schemaTypes.globalTypes();
475 		for( int c = 0; c < globalTypes.length; c++ )
476 		{
477 			namespaces.add( globalTypes[c].getName().getNamespaceURI() );
478 		}
479 		
480 		namespaces.remove( Constants.SOAP11_ENVELOPE_NS );
481 		namespaces.remove( Constants.SOAP_ENCODING_NS );
482 		
483 		return namespaces;
484 	}
485    
486    /***
487     * Used when creating a TypeSystem from a complete collection of SchemaDocuments so that referenced 
488     * types are not downloaded (again)
489     */
490    
491 	public static void removeImports(XmlObject xmlObject) throws XmlException
492 	{
493 		 XmlObject[] imports = xmlObject
494        	.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import");
495 		 
496 		 for( int c = 0; c < imports.length; c++ )
497 		 {
498 			 XmlCursor cursor = imports[c].newCursor();
499 			 cursor.removeXml();
500 			 cursor.dispose();
501 		 }
502 		 
503 		 XmlObject[] includes = xmlObject
504      		.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include");
505 		 
506 		 for( int c = 0; c < includes.length; c++ )
507 		 {
508 			 XmlCursor cursor = includes[c].newCursor();
509 			 cursor.removeXml();
510 			 cursor.dispose();
511 		 }
512 	}
513 
514 	public static boolean isInstanceOf( SchemaType schemaType, SchemaType baseType )
515 	{
516 		if( schemaType == null )
517 			return false;
518 		return schemaType.equals(baseType) ? true : isInstanceOf( schemaType.getBaseType(), baseType );
519 	}
520 
521 	public static boolean isBinaryType(SchemaType schemaType)
522 	{
523 		return isInstanceOf( schemaType, XmlHexBinary.type ) ||
524 				isInstanceOf( schemaType, XmlBase64Binary.type );
525 	}
526 
527 	public static String getDocumentation( SchemaParticle particle, SchemaType schemaType )
528 	{
529 		String result = null;
530 		
531 		if( particle instanceof SchemaLocalElement )
532 		{
533 			SchemaAnnotation annotation = ((SchemaLocalElement)particle).getAnnotation();
534 			if( annotation != null )
535 			{
536 				XmlObject[] userInformation = annotation.getUserInformation();
537 				if( userInformation != null && userInformation.length > 0 )
538 				{
539 					result = userInformation[0].toString(); // XmlUtils.getElementText( ( Element ) userInformation[0].getDomNode());
540 				}
541 			}
542 		}
543 		
544 		if( schemaType != null && schemaType.getAnnotation() != null )
545 		{
546 			XmlObject[] userInformation = schemaType.getAnnotation().getUserInformation();
547 			if( userInformation != null && userInformation.length > 0 )
548 			{
549 				result = userInformation[0].toString(); // = XmlUtils.getElementText( ( Element ) userInformation[0].getDomNode());
550 			}
551 		}
552 		
553 		if( result != null )
554 		{
555 			result = result.replaceAll( "<br/>", "\n" );
556 			
557 			StringTokenizer st = new StringTokenizer( result, "\r\n" );
558 			StringBuffer buf = new StringBuffer( "<html><body>" );
559 			boolean added = false;
560 			
561 			while( st.hasMoreElements() )
562 			{
563 				if( added )
564 					buf.append( "<br>" );
565 				String str = st.nextToken().trim();
566 				if( str.length() > 0 )
567 				{
568 					buf.append( str );
569 					added = true;
570 				}
571 			}
572 			buf.append( "</body></html>" );
573 			result = buf.toString();
574 		}
575 		
576 		return result;
577 	}
578 
579 	public static String [] getEnumerationValues( SchemaType schemaType, boolean addNull )
580 	{
581 		if( schemaType != null )
582 		{
583 			XmlAnySimpleType[] enumerationValues = schemaType.getEnumerationValues();
584 			if( enumerationValues != null && enumerationValues.length > 0 )
585 			{
586 				if( addNull )
587 				{
588 					String [] values = new String[enumerationValues.length+1];
589 					values[0] = null;
590 					
591 					for( int c = 1; c < values.length; c++ )
592 						values[c] = enumerationValues[c-1].getStringValue();
593 					
594 					return values;
595 				}
596 				else
597 				{
598 					String [] values = new String[enumerationValues.length];
599 					
600 					for( int c = 0; c < values.length; c++ )
601 						values[c] = enumerationValues[c].getStringValue();
602 					
603 					return values;
604 				}
605 			}
606 		}
607 		
608 		return new String[0];
609 	}
610 }