View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 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.content;
14  
15  import java.util.ArrayList;
16  import java.util.HashMap;
17  import java.util.LinkedHashMap;
18  import java.util.List;
19  import java.util.Map;
20  
21  import javax.xml.namespace.QName;
22  
23  import org.apache.xmlbeans.XmlCursor;
24  import org.apache.xmlbeans.XmlException;
25  
26  import com.eviware.soapui.impl.wadl.inference.ConflictHandler;
27  import com.eviware.soapui.impl.wadl.inference.schema.Content;
28  import com.eviware.soapui.impl.wadl.inference.schema.Context;
29  import com.eviware.soapui.impl.wadl.inference.schema.Particle;
30  import com.eviware.soapui.impl.wadl.inference.schema.Schema;
31  import com.eviware.soapui.impl.wadl.inference.schema.Settings;
32  import com.eviware.soapui.inferredSchema.ParticleConfig;
33  import com.eviware.soapui.inferredSchema.SequenceContentConfig;
34  import com.eviware.soapui.inferredSchema.SequenceContentConfig.ComesBefore;
35  
36  /***
37   * SequenceContent represents an xs:sequence, xs:choice, or xs:all. It infers ordering and occurrences
38   * of its children.
39   * 
40   * @author Dain Nilsson
41   */
42  public class SequenceContent implements Content
43  {
44  	private Schema schema;
45  	private Map<QName, Particle> particles;
46  	private HashMap<QName, List<QName>> comesBefore;
47  	private boolean completed;
48  
49  	public SequenceContent( Schema schema, boolean completed )
50  	{
51  		this.schema = schema;
52  		this.completed = completed;
53  		particles = new LinkedHashMap<QName, Particle>(); // LinkedHashMap
54  		// preserves order.
55  		comesBefore = new HashMap<QName, List<QName>>();
56  	}
57  
58  	public SequenceContent( SequenceContentConfig xml, Schema schema )
59  	{
60  		this.schema = schema;
61  		completed = xml.getCompleted();
62  		particles = new LinkedHashMap<QName, Particle>(); // LinkedHashMap
63  		// preserves order.
64  		for( ParticleConfig particleXml : xml.getParticleList() )
65  		{
66  			Particle p = Particle.Factory.parse( particleXml, schema );
67  			// TODO: Fix namespace!
68  			particles.put( p.getName(), p );
69  		}
70  		comesBefore = new HashMap<QName, List<QName>>();
71  		for( ComesBefore item : xml.getComesBeforeList() )
72  		{
73  			List<QName> others = new ArrayList<QName>();
74  			for( QName item2 : item.getOtherList() )
75  				others.add( item2 );
76  			comesBefore.put( item.getQname(), others );
77  		}
78  	}
79  
80  	public SequenceContentConfig save()
81  	{
82  		SequenceContentConfig xml = SequenceContentConfig.Factory.newInstance();
83  		xml.setCompleted( completed );
84  		List<ParticleConfig> particleList = new ArrayList<ParticleConfig>();
85  		for( Particle item : particles.values() )
86  			particleList.add( item.save() );
87  		xml.setParticleArray( particleList.toArray( new ParticleConfig[0] ) );
88  		for( Map.Entry<QName, List<QName>> entry : comesBefore.entrySet() )
89  		{
90  			ComesBefore comesBeforeEntry = xml.addNewComesBefore();
91  			comesBeforeEntry.setQname( entry.getKey() );
92  			for( QName item : entry.getValue() )
93  				comesBeforeEntry.addOther( item );
94  		}
95  		return xml;
96  	}
97  
98  	public Content validate( Context context ) throws XmlException
99  	{
100 		XmlCursor cursor = context.getCursor();
101 
102 		// Find element order
103 		List<QName> orderSet = new ArrayList<QName>();
104 		List<QName> orderList = new ArrayList<QName>();
105 		if( !cursor.isEnd() )
106 		{
107 			cursor.push();
108 			do
109 			{
110 				QName qname = cursor.getName();
111 				if( qname == null )
112 					break;
113 				if( orderSet.contains( qname ) )
114 				{
115 					if( !orderSet.get( orderSet.size() - 1 ).equals( qname ) )
116 					{
117 						// Same element occurs more an once but not in a sequence!
118 						cursor.pop();
119 						throw new XmlException( "Same element occurs multiple times in sequence!" );
120 					}
121 				}
122 				else
123 					orderSet.add( qname );
124 				orderList.add( qname );
125 			}
126 			while( cursor.toNextSibling() );
127 			cursor.pop();
128 		}
129 		// Check element order against schema
130 		if( validateOrder( context, orderSet ) && validateOccurances( context, orderList ) )
131 		{
132 			// Validate elements
133 			for( QName item : orderList )
134 			{
135 				cursor.push();
136 				particles.get( item ).validate( context );
137 				cursor.pop();
138 				cursor.toNextSibling();
139 			}
140 		}
141 		else
142 			throw new XmlException( "Sequence validation" );
143 		completed = true;
144 		return this;
145 	}
146 
147 	public String toString( String attrs )
148 	{
149 		if( particles.size() == 0 )
150 			return attrs;
151 		fixOrder();
152 		String type = isChoice() ? ":choice" : ( isAll() ? ":all" : ":sequence" );
153 		StringBuilder s = new StringBuilder( "<" + schema.getPrefixForNamespace( Settings.xsdns ) + type + ">" );
154 		for( Particle item : particles.values() )
155 			s.append( item );
156 		s.append( "</" + schema.getPrefixForNamespace( Settings.xsdns ) + type + ">" + attrs );
157 		return s.toString();
158 	}
159 
160 	private void fixOrder()
161 	{
162 		List<QName> order = new ArrayList<QName>();
163 		for( QName item : particles.keySet() )
164 		{
165 			int i;
166 			for( i = order.size(); !canAppend( order.subList( 0, i ), item ); i-- )
167 				;
168 			order.add( i, item );
169 		}
170 		LinkedHashMap<QName, Particle> fixedParticles = new LinkedHashMap<QName, Particle>();
171 		for( QName item : order )
172 			fixedParticles.put( item, particles.get( item ) );
173 		particles = fixedParticles;
174 	}
175 	
176 	private boolean verifyOrder()
177 	{
178 		List<QName> order = new ArrayList<QName>();
179 		order.addAll( particles.keySet() );
180 		for(int i=1; i<particles.size(); i++)
181 			if(!canAppend(order.subList(0,i), order.get( i )))
182 				return false;
183 		return true;
184 	}
185 
186 	private boolean canAppend( List<QName> before, QName item )
187 	{
188 		for( QName item2 : before )
189 		{
190 			if( comesBefore.get( item ).contains( item2 ) )
191 				return false;
192 		}
193 		return true;
194 	}
195 
196 	private boolean validateOccurances( Context context, List<QName> sequence ) throws XmlException
197 	{
198 		Map<QName, Integer> seen = new HashMap<QName, Integer>();
199 		for( QName item : particles.keySet() )
200 			seen.put( item, 0 );
201 		for( QName item : sequence )
202 			seen.put( item, seen.get( item ) + 1 );
203 		for( Map.Entry<QName, Integer> entry : seen.entrySet() )
204 		{
205 			Particle particle = particles.get( entry.getKey() );
206 			if( Integer.parseInt( particle.getAttribute( "minOccurs" ) ) > entry.getValue() )
207 			{
208 				if( context.getHandler().callback( ConflictHandler.Event.MODIFICATION, ConflictHandler.Type.ELEMENT,
209 						entry.getKey(), context.getPath(), "Element occurs less times than required." ) )
210 				{
211 					particle.setAttribute( "minOccurs", entry.getValue().toString() );
212 				}
213 				else
214 					throw new XmlException( "Element '" + entry.getKey().getLocalPart()
215 							+ "' required at least minOccurs times!" );
216 			}
217 			if( !particle.getAttribute( "maxOccurs" ).equals( "unbounded" )
218 					&& Integer.parseInt( particle.getAttribute( "maxOccurs" ) ) < entry.getValue() )
219 			{
220 				if( context.getHandler().callback( ConflictHandler.Event.MODIFICATION, ConflictHandler.Type.TYPE,
221 						new QName( schema.getNamespace(), context.getAttribute( "typeName" ) ), context.getPath(),
222 						"Element occurs more times than allowed." ) )
223 				{
224 					particle.setAttribute( "maxOccurs", entry.getValue().toString() );
225 				}
226 				else
227 					throw new XmlException( "Element '" + entry.getKey().getLocalPart()
228 							+ "' must not occur more than maxOccurs times!" );
229 			}
230 		}
231 		return true;
232 	}
233 
234 	@SuppressWarnings( "unchecked" )
235 	private boolean validateOrder( Context context, List<QName> sequence )
236 	{
237 		List<QName> seen = new ArrayList<QName>();
238 		HashMap<QName, List<QName>> comesBefore = ( HashMap<QName, List<QName>> )this.comesBefore.clone();
239 		for( QName item : sequence )
240 		{
241 			if( !particles.containsKey( item ) )
242 			{
243 				if( context.getHandler().callback( ConflictHandler.Event.CREATION, ConflictHandler.Type.ELEMENT, item,
244 						context.getPath() + "/" + item.getLocalPart(), "Element has undeclared child element." ) )
245 				{
246 					if( item.getNamespaceURI().equals( schema.getNamespace() ) )
247 					{
248 						Particle element = Particle.Factory.newElementInstance( schema, item.getLocalPart() );
249 						if( completed )
250 							element.setAttribute( "minOccurs", "0" );
251 						particles.put( item, element );
252 					}
253 					else
254 					{
255 						Schema otherSchema = context.getSchemaSystem().getSchemaForNamespace( item.getNamespaceURI() );
256 						schema.putPrefixForNamespace( item.getPrefix(), item.getNamespaceURI() );
257 						if( otherSchema == null )
258 						{
259 							otherSchema = context.getSchemaSystem().newSchema( item.getNamespaceURI() );
260 						}
261 						Particle ref = otherSchema.getParticle( item.getLocalPart() );
262 						if( ref == null )
263 						{
264 							ref = otherSchema.newElement( item.getLocalPart() );
265 						}
266 						if( completed )
267 							ref.setAttribute( "minOccurs", "0" );
268 						particles.put( item, Particle.Factory.newReferenceInstance( schema, ref ) );
269 					}
270 				}
271 				else
272 					return false;
273 			}
274 			if( comesBefore.containsKey( item ) )
275 			{
276 				for( QName item2 : comesBefore.get( item ) )
277 				{
278 					if( seen.contains( item2 ) )
279 						return false;
280 				}
281 			}
282 			else
283 			{
284 				comesBefore.put( item, new ArrayList<QName>() );
285 			}
286 			for( QName item2 : seen )
287 			{
288 				if( !comesBefore.get( item2 ).contains( item ) )
289 					comesBefore.get( item2 ).add( item );
290 			}
291 			seen.add( item );
292 		}
293 		this.comesBefore = comesBefore;
294 		return true;
295 	}
296 
297 	private boolean isChoice()
298 	{
299 		for( Particle e : particles.values() )
300 		{
301 			if( !( "0".equals( e.getAttribute( "minOccurs" ) ) && "1".equals( e.getAttribute( "maxOccurs" ) ) && comesBefore
302 					.get( e.getName() ).size() == 0 ) )
303 				return false;
304 		}
305 		return true;
306 	}
307 
308 	private boolean isAll()
309 	{
310 		for( Particle e : particles.values() )
311 		{
312 			if( Integer.parseInt( e.getAttribute( "maxOccurs" ) ) > 1 )
313 				return false;
314 		}
315 		return !verifyOrder();
316 	}
317 
318 }