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