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