1 package org.codehaus.modello.plugin.xsd;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import org.codehaus.modello.ModelloException;
26 import org.codehaus.modello.ModelloParameterConstants;
27 import org.codehaus.modello.model.Model;
28 import org.codehaus.modello.model.ModelAssociation;
29 import org.codehaus.modello.model.ModelClass;
30 import org.codehaus.modello.model.ModelField;
31 import org.codehaus.modello.plugin.xsd.metadata.XsdClassMetadata;
32 import org.codehaus.modello.plugins.xml.AbstractXmlGenerator;
33 import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
34 import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
35 import org.codehaus.plexus.util.StringUtils;
36 import org.codehaus.plexus.util.WriterFactory;
37 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
38 import org.codehaus.plexus.util.xml.XMLWriter;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.Writer;
43 import java.util.HashSet;
44 import java.util.List;
45 import java.util.Properties;
46 import java.util.Set;
47
48
49
50
51 public class XsdGenerator
52 extends AbstractXmlGenerator
53 {
54 public void generate( Model model, Properties parameters )
55 throws ModelloException
56 {
57 initialize( model, parameters );
58
59 try
60 {
61 generateXsd( parameters );
62 }
63 catch ( IOException ex )
64 {
65 throw new ModelloException( "Exception while generating xsd.", ex );
66 }
67 }
68
69 private void generateXsd( Properties parameters )
70 throws IOException, ModelloException
71 {
72 Model objectModel = getModel();
73
74 File directory = getOutputDirectory();
75
76 if ( isPackageWithVersion() )
77 {
78 directory = new File( directory, getGeneratedVersion().toString() );
79 }
80
81 if ( !directory.exists() )
82 {
83 directory.mkdirs();
84 }
85
86
87 String xsdFileName = parameters.getProperty( ModelloParameterConstants.OUTPUT_XSD_FILE_NAME );
88
89 File f = new File( directory, objectModel.getId() + "-" + getGeneratedVersion() + ".xsd" );
90
91 if ( xsdFileName != null )
92 {
93 f = new File( directory, xsdFileName );
94 }
95
96 Writer writer = WriterFactory.newXmlWriter( f );
97
98 try
99 {
100 XMLWriter w = new PrettyPrintXMLWriter( writer );
101
102 writer.write( "<?xml version=\"1.0\"?>\n" );
103
104 initHeader( w );
105
106
107 w.startElement( "xs:schema" );
108 w.addAttribute( "xmlns:xs", "http://www.w3.org/2001/XMLSchema" );
109 w.addAttribute( "elementFormDefault", "qualified" );
110
111 ModelClass root = objectModel.getClass( objectModel.getRoot( getGeneratedVersion() ),
112 getGeneratedVersion() );
113
114 String namespace = XsdModelHelper.getNamespace( root.getModel(), getGeneratedVersion() );
115
116 w.addAttribute( "xmlns", namespace );
117
118 String targetNamespace = XsdModelHelper.getTargetNamespace( root.getModel(), getGeneratedVersion(), namespace );
119
120
121 if ( StringUtils.isNotBlank( targetNamespace ) )
122 {
123 w.addAttribute( "targetNamespace", targetNamespace );
124 }
125
126 w.startElement( "xs:element" );
127 String tagName = resolveTagName( root );
128 w.addAttribute( "name", tagName );
129 w.addAttribute( "type", root.getName() );
130
131 writeClassDocumentation( w, root );
132
133 w.endElement();
134
135
136
137 int initialCapacity = objectModel.getClasses( getGeneratedVersion() ).size();
138 writeComplexTypeDescriptor( w, objectModel, root, new HashSet<ModelClass>( initialCapacity ) );
139
140 w.endElement();
141 }
142 finally
143 {
144 writer.close();
145 }
146 }
147
148 private static void writeClassDocumentation( XMLWriter w, ModelClass modelClass )
149 {
150 writeDocumentation( w, modelClass.getVersionRange().toString(), modelClass.getDescription() );
151 }
152
153 private static void writeFieldDocumentation( XMLWriter w, ModelField field )
154 {
155 writeDocumentation( w, field.getVersionRange().toString(), field.getDescription() );
156 }
157
158 private static void writeDocumentation( XMLWriter w, String version, String description )
159 {
160 if ( version != null || description != null )
161 {
162 w.startElement( "xs:annotation" );
163
164 if ( version != null )
165 {
166 w.startElement( "xs:documentation" );
167 w.addAttribute( "source", "version" );
168 w.writeText( version );
169 w.endElement();
170 }
171
172 if ( description != null )
173 {
174 w.startElement( "xs:documentation" );
175 w.addAttribute( "source", "description" );
176 w.writeText( description );
177 w.endElement();
178 }
179
180 w.endElement();
181 }
182 }
183
184 private void writeComplexTypeDescriptor( XMLWriter w, Model objectModel, ModelClass modelClass,
185 Set<ModelClass> written )
186 {
187 written.add( modelClass );
188
189 w.startElement( "xs:complexType" );
190 w.addAttribute( "name", modelClass.getName() );
191
192 List<ModelField> fields = getFieldsForXml( modelClass, getGeneratedVersion() );
193
194 ModelField contentField = getContentField( fields );
195
196 boolean hasContentField = contentField != null;
197
198 List<ModelField> attributeFields = getXmlAttributeFields( fields );
199
200 fields.removeAll( attributeFields );
201
202 if ( hasContentField )
203 {
204
205 w.startElement( "xs:simpleContent" );
206
207 w.startElement( "xs:extension" );
208
209 w.addAttribute( "base", getXsdType( contentField.getType() ) );
210 }
211
212 writeClassDocumentation( w, modelClass );
213
214 Set<ModelClass> toWrite = new HashSet<ModelClass>();
215
216 if ( fields.size() > 0 )
217 {
218 XsdClassMetadata xsdClassMetadata = (XsdClassMetadata) modelClass.getMetadata( XsdClassMetadata.ID );
219 boolean compositorAll = XsdClassMetadata.COMPOSITOR_ALL.equals( xsdClassMetadata.getCompositor() );
220
221 if ( !hasContentField )
222 {
223 if ( compositorAll )
224 {
225 w.startElement( "xs:all" );
226 }
227 else
228 {
229 w.startElement( "xs:sequence" );
230 }
231 }
232
233 for ( ModelField field : fields )
234 {
235 XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID );
236
237 String fieldTagName = resolveTagName( field, xmlFieldMetadata );
238
239 if ( !hasContentField )
240 {
241 w.startElement( "xs:element" );
242
243
244
245 w.addAttribute( "minOccurs", "0" );
246 }
247
248 String xsdType = getXsdType( field.getType() );
249
250 if ( "Date".equals( field.getType() ) && "long".equals( xmlFieldMetadata.getFormat() ) )
251 {
252 xsdType = getXsdType( "long" );
253 }
254
255 if ( xmlFieldMetadata.isContent() )
256 {
257
258 }
259 else if ( ( xsdType != null ) || "char".equals( field.getType() ) || "Character".equals( field.getType() ) )
260 {
261 w.addAttribute( "name", fieldTagName );
262
263 if ( xsdType != null )
264 {
265
266 w.addAttribute( "type", xsdType );
267 }
268
269 if ( field.getDefaultValue() != null )
270 {
271
272 if ( field.getDefaultValue() != "\0" )
273 {
274 w.addAttribute( "default", field.getDefaultValue() );
275 }
276 }
277
278 writeFieldDocumentation( w, field );
279
280 if ( xsdType == null )
281 {
282 writeCharElement( w );
283 }
284 }
285 else
286 {
287
288 if ( isInnerAssociation( field ) )
289 {
290 ModelAssociation association = (ModelAssociation) field;
291 ModelClass fieldModelClass = objectModel.getClass( association.getTo(), getGeneratedVersion() );
292
293 toWrite.add( fieldModelClass );
294
295 if ( association.isManyMultiplicity() )
296 {
297 XmlAssociationMetadata xmlAssociationMetadata =
298 (XmlAssociationMetadata) association.getAssociationMetadata( XmlAssociationMetadata.ID );
299
300 if ( xmlAssociationMetadata.isWrappedItems() )
301 {
302 w.addAttribute( "name", fieldTagName );
303 writeFieldDocumentation( w, field );
304
305 writeListElement( w, xmlFieldMetadata, xmlAssociationMetadata, field,
306 fieldModelClass.getName() );
307 }
308 else
309 {
310 if ( compositorAll )
311 {
312
313
314 throw new IllegalStateException( field.getName() + " field is declared as xml.listStyle=\"flat\" "
315 + "then class " + modelClass.getName() + " MUST be declared as xsd.compositor=\"sequence\"" );
316 }
317
318 w.addAttribute( "name", resolveTagName( fieldTagName, xmlAssociationMetadata ) );
319
320 w.addAttribute( "type", fieldModelClass.getName() );
321 w.addAttribute( "maxOccurs", "unbounded" );
322
323 writeFieldDocumentation( w, field );
324 }
325 }
326 else
327 {
328
329 w.addAttribute( "name", fieldTagName );
330 w.addAttribute( "type", fieldModelClass.getName() );
331 writeFieldDocumentation( w, field );
332 }
333 }
334 else
335 {
336 w.addAttribute( "name", fieldTagName );
337
338 writeFieldDocumentation( w, field );
339
340 if ( List.class.getName().equals( field.getType() )
341 || Set.class.getName().equals( field.getType() ) )
342 {
343 ModelAssociation association = (ModelAssociation) field;
344
345 XmlAssociationMetadata xmlAssociationMetadata =
346 (XmlAssociationMetadata) association.getAssociationMetadata( XmlAssociationMetadata.ID );
347
348 writeListElement( w, xmlFieldMetadata, xmlAssociationMetadata, field,
349 getXsdType( "String" ) );
350 }
351 else if ( Properties.class.getName().equals( field.getType() )
352 || "DOM".equals( field.getType() ) )
353 {
354 writePropertiesElement( w );
355 }
356 else
357 {
358 throw new IllegalStateException( "Non-association field of a non-primitive type '"
359 + field.getType() + "' for '" + field.getName() + "' in '"
360 + modelClass.getName() + "' model class" );
361 }
362 }
363 }
364 if ( !hasContentField )
365 {
366 w.endElement();
367 }
368 }
369
370 if ( !hasContentField )
371 {
372 w.endElement();
373 }
374 }
375
376 for ( ModelField field : attributeFields )
377 {
378 XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID );
379
380 w.startElement( "xs:attribute" );
381
382 String xsdType = getXsdType( field.getType() );
383
384 String tagName = resolveTagName( field, xmlFieldMetadata );
385
386 w.addAttribute( "name", tagName );
387
388 if ( xsdType != null )
389 {
390 w.addAttribute( "type", xsdType );
391 }
392
393 if ( field.getDefaultValue() != null )
394 {
395 w.addAttribute( "default", field.getDefaultValue() );
396 }
397
398 writeFieldDocumentation( w, field );
399
400 if ( "char".equals( field.getType() ) || "Character".equals( field.getType() ) )
401 {
402 writeCharElement( w );
403 }
404 else if ( xsdType == null )
405 {
406 throw new IllegalStateException( "Attribute field of a non-primitive type '" + field.getType()
407 + "' for '" + field.getName() + "' in '" + modelClass.getName() + "' model class" );
408 }
409
410 w.endElement();
411 }
412
413 if ( hasContentField )
414 {
415 w.endElement();
416
417 w.endElement();
418 }
419
420
421 w.endElement();
422
423 for ( ModelClass fieldModelClass : toWrite )
424 {
425 if ( !written.contains( fieldModelClass ) )
426 {
427 writeComplexTypeDescriptor( w, objectModel, fieldModelClass, written );
428 }
429 }
430 }
431
432 private static void writeCharElement( XMLWriter w )
433 {
434
435 w.startElement( "xs:simpleType" );
436
437 w.startElement( "xs:restriction" );
438 w.addAttribute( "base", "xs:string" );
439
440 w.startElement( "xs:length" );
441 w.addAttribute( "value", "1" );
442 w.addAttribute( "fixed", "true" );
443
444 w.endElement();
445
446 w.endElement();
447
448 w.endElement();
449 }
450
451 private static void writePropertiesElement( XMLWriter w )
452 {
453 w.startElement( "xs:complexType" );
454
455 w.startElement( "xs:sequence" );
456
457 w.startElement( "xs:any" );
458 w.addAttribute( "minOccurs", "0" );
459 w.addAttribute( "maxOccurs", "unbounded" );
460 w.addAttribute( "processContents", "skip" );
461
462 w.endElement();
463
464 w.endElement();
465
466 w.endElement();
467 }
468
469 private void writeListElement( XMLWriter w, XmlFieldMetadata xmlFieldMetadata,
470 XmlAssociationMetadata xmlAssociationMetadata, ModelField field, String type )
471 {
472 String fieldTagName = resolveTagName( field, xmlFieldMetadata );
473
474 String valuesTagName = resolveTagName( fieldTagName, xmlAssociationMetadata );
475
476 w.startElement( "xs:complexType" );
477
478 w.startElement( "xs:sequence" );
479
480 w.startElement( "xs:element" );
481 w.addAttribute( "name", valuesTagName );
482 w.addAttribute( "minOccurs", "0" );
483 w.addAttribute( "maxOccurs", "unbounded" );
484 w.addAttribute( "type", type );
485
486 w.endElement();
487
488 w.endElement();
489
490 w.endElement();
491 }
492
493 private static String getXsdType( String type )
494 {
495 if ( "String".equals( type ) )
496 {
497 return "xs:string";
498 }
499 else if ( "boolean".equals( type ) || "Boolean".equals( type ) )
500 {
501 return "xs:boolean";
502 }
503 else if ( "byte".equals( type ) || "Byte".equals( type ) )
504 {
505 return "xs:byte";
506 }
507 else if ( "short".equals( type ) || "Short".equals( type ) )
508 {
509 return "xs:short";
510 }
511 else if ( "int".equals( type ) || "Integer".equals( type ) )
512 {
513 return "xs:int";
514 }
515 else if ( "long".equals( type ) || "Long".equals( type ) )
516 {
517 return "xs:long";
518 }
519 else if ( "float".equals( type ) || "Float".equals( type ) )
520 {
521 return "xs:float";
522 }
523 else if ( "double".equals( type ) || "Double".equals( type ) )
524 {
525 return "xs:double";
526 }
527 else if ( "Date".equals( type ) )
528 {
529 return "xs:dateTime";
530 }
531 else
532 {
533 return null;
534 }
535 }
536 }