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