View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2009 eviware.com
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by
6    *  the Free Software Foundation.
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.wadl.inference.schema.types;
14  
15  import java.util.ArrayList;
16  import java.util.HashMap;
17  import java.util.List;
18  import java.util.Map;
19  
20  import javax.xml.namespace.QName;
21  
22  import org.apache.xmlbeans.SchemaTypeLoader;
23  import org.apache.xmlbeans.SchemaTypeSystem;
24  import org.apache.xmlbeans.XmlBeans;
25  import org.apache.xmlbeans.XmlCursor;
26  import org.apache.xmlbeans.XmlException;
27  import org.apache.xmlbeans.XmlObject;
28  
29  import com.eviware.soapui.impl.wadl.inference.ConflictHandler;
30  import com.eviware.soapui.impl.wadl.inference.schema.Content;
31  import com.eviware.soapui.impl.wadl.inference.schema.Context;
32  import com.eviware.soapui.impl.wadl.inference.schema.Particle;
33  import com.eviware.soapui.impl.wadl.inference.schema.Schema;
34  import com.eviware.soapui.impl.wadl.inference.schema.Settings;
35  import com.eviware.soapui.impl.wadl.inference.schema.Type;
36  import com.eviware.soapui.impl.wadl.inference.schema.content.EmptyContent;
37  import com.eviware.soapui.inferredSchema.ComplexTypeConfig;
38  import com.eviware.soapui.inferredSchema.ParticleConfig;
39  import com.eviware.soapui.inferredSchema.TypeReferenceConfig;
40  
41  /***
42   * ComplexType corresponds to an xs:complexType. It's definition is displayed in
43   * the schema for which it belongs.
44   * 
45   * @author Dain Nilsson
46   */
47  public class ComplexType implements Type
48  {
49  	private String name;
50  	private Schema schema;
51  	private Map<QName, Particle> attributes;
52  	private Content content;
53  	private boolean mixed = false;
54  	private boolean completed = false;
55  
56  	public ComplexType( Schema schema, String name, boolean completed )
57  	{
58  		this.schema = schema;
59  		this.name = name;
60  		this.completed = completed;
61  		content = new EmptyContent( schema, completed );
62  		attributes = new HashMap<QName, Particle>();
63  		schema.addType( this );
64  	}
65  
66  	public ComplexType( ComplexTypeConfig xml, Schema schema )
67  	{
68  		this.schema = schema;
69  		name = xml.getName();
70  		completed = xml.getCompleted();
71  		mixed = xml.getMixed();
72  		content = Content.Factory.parse( xml.getContent(), schema );
73  		attributes = new HashMap<QName, Particle>();
74  		for( ParticleConfig item : xml.getAttributeList() )
75  		{
76  			Particle p = Particle.Factory.parse( item, schema );
77  			attributes.put( new QName( "", p.getName().getLocalPart() ), p );
78  		}
79  		schema.addType( this );
80  	}
81  
82  	public void save( ComplexTypeConfig xml )
83  	{
84  		xml.setName( name );
85  		xml.setCompleted( completed );
86  		xml.setMixed( mixed );
87  		List<ParticleConfig> particleList = new ArrayList<ParticleConfig>();
88  		for( Particle item : attributes.values() )
89  			particleList.add( item.save() );
90  		xml.setAttributeArray( particleList.toArray( new ParticleConfig[0] ) );
91  		xml.setContent( content.save() );
92  	}
93  
94  	public TypeReferenceConfig save()
95  	{
96  		TypeReferenceConfig xml = TypeReferenceConfig.Factory.newInstance();
97  		xml.setReference( new QName( schema.getNamespace(), name ) );
98  		return xml;
99  	}
100 
101 	public void setContent( Content content )
102 	{
103 		this.content = content;
104 	}
105 
106 	public Type validate( Context context ) throws XmlException
107 	{
108 		XmlCursor cursor = context.getCursor();
109 		List<QName> seen = new ArrayList<QName>();
110 		cursor.push();
111 		if( !mixed && isMixed( context ) )
112 		{
113 			// TODO: Check with ConflictHandler
114 			mixed = true;
115 		}
116 		cursor.pop();
117 		cursor.push();
118 		if( cursor.toFirstAttribute() )
119 		{
120 			do
121 			{
122 				QName qname = cursor.getName();
123 				if( attributes.containsKey( qname ) )
124 				{
125 					attributes.get( qname ).validate( context );
126 				}
127 				else if( qname.getNamespaceURI().equals( Settings.xsins ) )
128 				{
129 					// Ignore
130 				}
131 				else if( context.getHandler().callback( ConflictHandler.Event.CREATION, ConflictHandler.Type.ATTRIBUTE,
132 						new QName( schema.getNamespace(), qname.getLocalPart() ), context.getPath(), "Undeclared attribute." ) )
133 				{
134 					if( qname.getNamespaceURI().equals( schema.getNamespace() ) || qname.getNamespaceURI().equals( "" ) )
135 					{
136 						newAttribute( qname ).validate( context );
137 					}
138 					else
139 					{
140 						Schema otherSchema = context.getSchemaSystem().getSchemaForNamespace( qname.getNamespaceURI() );
141 						schema.putPrefixForNamespace( qname.getPrefix(), qname.getNamespaceURI() );
142 						if( otherSchema == null )
143 						{
144 							otherSchema = context.getSchemaSystem().newSchema( qname.getNamespaceURI() );
145 						}
146 						Particle ref = otherSchema.getParticle( qname.getLocalPart() );
147 						if( ref == null )
148 						{
149 							ref = otherSchema.newAttribute( qname.getLocalPart() );
150 						}
151 						if( completed )
152 							ref.setAttribute( "use", "optional" );
153 						Particle newAttribute = Particle.Factory.newReferenceInstance( schema, ref );
154 						attributes.put( qname, newAttribute );
155 						newAttribute.validate( context );
156 					}
157 				}
158 				else
159 					throw new XmlException( "Illegal attribute!" );
160 				seen.add( qname );
161 			}
162 			while( cursor.toNextAttribute() );
163 		}
164 		// Make sure all attributes have been accounted for
165 		for( QName item : attributes.keySet() )
166 		{
167 			if( !seen.contains( item ) && !attributes.get( item ).getAttribute( "use" ).equals( "optional" ) )
168 			{
169 				if( context.getHandler().callback( ConflictHandler.Event.MODIFICATION, ConflictHandler.Type.ATTRIBUTE,
170 						item, context.getPath(), "Required attribute missing." ) )
171 				{
172 					attributes.get( item ).setAttribute( "use", "optional" );
173 				}
174 				else
175 					throw new XmlException( "Required attribute missing!" );
176 			}
177 		}
178 		cursor.pop();
179 		if( !cursor.toFirstChild() )
180 			cursor.toFirstContentToken();
181 		if( !context.getAttribute( "nil" ).equals( "true" ) )
182 			validateContent( context );
183 		completed = true;
184 		return this;
185 	}
186 
187 	private void validateContent( Context context ) throws XmlException
188 	{
189 		context.getCursor().push();
190 		context.putAttribute( "typeName", name );
191 		Content newContent = content.validate( context );
192 		context.clearAttribute( "typeName" );
193 		if( content != newContent )
194 		{
195 			String problem = "Illegal content for complexType '" + name + "'.";
196 			if( context.getHandler().callback( ConflictHandler.Event.MODIFICATION, ConflictHandler.Type.TYPE,
197 					new QName( schema.getNamespace(), name ), context.getPath(), "Illegal complex content." ) )
198 			{
199 				content = newContent;
200 				context.getCursor().pop();
201 				validateContent( context );
202 				return;
203 			}
204 			else
205 				throw new XmlException( problem );
206 		}
207 		context.getCursor().pop();
208 	}
209 
210 	private boolean isMixed( Context context )
211 	{
212 		QName name = context.getCursor().getName();
213 		SchemaTypeSystem sts;
214 		try
215 		{
216 			sts = XmlBeans.compileXsd( new XmlObject[] { XmlObject.Factory
217 					.parse( "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"" + name.getNamespaceURI()
218 							+ "\" targetNamespace=\"" + name.getNamespaceURI() + "\">" + "<xs:element name=\""
219 							+ name.getLocalPart() + "\"><xs:complexType><xs:sequence>"
220 							+ "<xs:any processContents=\"skip\" minOccurs=\"0\" maxOccurs=\"unbounded\" /></xs:sequence>"
221 							+ "<xs:anyAttribute processContents=\"skip\"/></xs:complexType></xs:element></xs:schema>" ) },
222 					XmlBeans.getBuiltinTypeSystem(), null );
223 			SchemaTypeLoader stl = XmlBeans
224 					.typeLoaderUnion( new SchemaTypeLoader[] { sts, XmlBeans.getBuiltinTypeSystem() } );
225 			if( !stl.parse( context.getCursor().xmlText(), null, null ).validate() )
226 				return true;
227 		}
228 		catch( XmlException e )
229 		{
230 			// TODO Auto-generated catch block
231 			e.printStackTrace();
232 		}
233 		return false;
234 	}
235 
236 	public String getName()
237 	{
238 		return name;
239 	}
240 
241 	public Schema getSchema()
242 	{
243 		return schema;
244 	}
245 
246 	public void setSchema( Schema schema )
247 	{
248 		this.schema = schema;
249 	}
250 
251 	@Override
252 	public String toString()
253 	{
254 		String xsdns = schema.getPrefixForNamespace( Settings.xsdns );
255 		StringBuilder s = new StringBuilder( "<" + xsdns + ":complexType name=\"" + name + "\"" );
256 		if( mixed )
257 			s.append( " mixed=\"true\"" );
258 		s.append( ">" );
259 		StringBuilder attrs = new StringBuilder();
260 		for( Particle item : attributes.values() )
261 		{
262 			attrs.append( item );
263 		}
264 		s.append( content.toString( attrs.toString() ) );
265 		s.append( "</" + xsdns + ":complexType>" );
266 		return s.toString();
267 	}
268 
269 	private Particle newAttribute( QName qname )
270 	{
271 		Particle p = Particle.Factory.newAttributeInstance( schema, qname.getLocalPart() );
272 		attributes.put( qname, p );
273 		if( completed )
274 			p.setAttribute( "use", "optional" );
275 		return p;
276 	}
277 
278 }