1 package org.codehaus.mojo.jaxb2.schemageneration;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.MojoExecutionException;
23 import org.apache.maven.plugin.logging.Log;
24 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.NodeProcessor;
25 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.JavaDocRenderer;
26 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.SearchableDocumentation;
27 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.XsdAnnotationProcessor;
28 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.XsdEnumerationAnnotationProcessor;
29 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement.ChangeFilenameProcessor;
30 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement.ChangeNamespacePrefixProcessor;
31 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement.SimpleNamespaceResolver;
32 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement.TransformSchema;
33 import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
34 import org.codehaus.mojo.jaxb2.shared.Validate;
35 import org.codehaus.plexus.util.FileUtils;
36 import org.codehaus.plexus.util.IOUtil;
37 import org.codehaus.plexus.util.StringUtils;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.NamedNodeMap;
40 import org.w3c.dom.Node;
41 import org.w3c.dom.NodeList;
42 import org.xml.sax.InputSource;
43
44 import javax.xml.parsers.DocumentBuilderFactory;
45 import javax.xml.transform.OutputKeys;
46 import javax.xml.transform.Transformer;
47 import javax.xml.transform.TransformerException;
48 import javax.xml.transform.TransformerFactory;
49 import javax.xml.transform.dom.DOMSource;
50 import javax.xml.transform.stream.StreamResult;
51 import java.io.BufferedWriter;
52 import java.io.File;
53 import java.io.FileFilter;
54 import java.io.FileNotFoundException;
55 import java.io.FileReader;
56 import java.io.FileWriter;
57 import java.io.IOException;
58 import java.io.Reader;
59 import java.io.StringWriter;
60 import java.io.Writer;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.TreeMap;
66
67
68
69
70
71
72
73 public final class XsdGeneratorHelper {
74
75
76 private static final String MISCONFIG = "Misconfiguration detected: ";
77 private static TransformerFactory FACTORY;
78 private static final FileFilter RECURSIVE_XSD_FILTER;
79
80
81
82
83 private XsdGeneratorHelper() {
84
85 }
86
87 static {
88
89
90 RECURSIVE_XSD_FILTER = new FileFilter() {
91 @Override
92 public boolean accept(final File toMatch) {
93
94 if (toMatch.exists()) {
95
96
97
98 return toMatch.isDirectory()
99 || AbstractXsdGeneratorMojo.SCHEMAGEN_EMITTED_FILENAME.matcher(toMatch.getName()).matches();
100 }
101
102
103 return false;
104 }
105 };
106 }
107
108
109
110
111
112
113
114
115 public static Map<String, SimpleNamespaceResolver> getFileNameToResolverMap(final File outputDirectory)
116 throws MojoExecutionException {
117
118 final Map<String, SimpleNamespaceResolver> toReturn = new TreeMap<String, SimpleNamespaceResolver>();
119
120
121
122 File[] generatedSchemaFiles = outputDirectory.listFiles(new FileFilter() {
123 public boolean accept(File pathname) {
124 return pathname.getName().startsWith("schema") && pathname.getName().endsWith(".xsd");
125 }
126 });
127
128 for (File current : generatedSchemaFiles) {
129 toReturn.put(current.getName(), new SimpleNamespaceResolver(current));
130 }
131
132 return toReturn;
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146 public static void validateSchemasInPluginConfiguration(final List<TransformSchema> configuredTransformSchemas)
147 throws MojoExecutionException {
148
149 final List<String> uris = new ArrayList<String>();
150 final List<String> prefixes = new ArrayList<String>();
151 final List<String> fileNames = new ArrayList<String>();
152
153 for (int i = 0; i < configuredTransformSchemas.size(); i++) {
154 final TransformSchema current = configuredTransformSchemas.get(i);
155 final String currentURI = current.getUri();
156 final String currentPrefix = current.getToPrefix();
157 final String currentFile = current.getToFile();
158
159
160 if (StringUtils.isEmpty(currentURI)) {
161 throw new MojoExecutionException(MISCONFIG + "Null or empty property 'uri' found in "
162 + "plugin configuration for schema element at index [" + i + "]: " + current);
163 }
164
165
166 if (StringUtils.isEmpty(currentPrefix) && StringUtils.isEmpty(currentFile)) {
167 throw new MojoExecutionException(MISCONFIG + "Null or empty properties 'prefix' "
168 + "and 'file' found within plugin configuration for schema element at index ["
169 + i + "]: " + current);
170 }
171
172
173 if (uris.contains(currentURI)) {
174 throw new MojoExecutionException(getDuplicationErrorMessage("uri", currentURI,
175 uris.indexOf(currentURI), i));
176 }
177 uris.add(currentURI);
178
179
180 if (prefixes.contains(currentPrefix) && !(currentPrefix == null)) {
181 throw new MojoExecutionException(getDuplicationErrorMessage("prefix", currentPrefix,
182 prefixes.indexOf(currentPrefix), i));
183 }
184 prefixes.add(currentPrefix);
185
186
187 if (fileNames.contains(currentFile)) {
188 throw new MojoExecutionException(getDuplicationErrorMessage("file", currentFile,
189 fileNames.indexOf(currentFile), i));
190 }
191 fileNames.add(currentFile);
192 }
193 }
194
195
196
197
198
199
200
201
202
203
204
205 public static int insertJavaDocAsAnnotations(final Log log,
206 final File outputDir,
207 final SearchableDocumentation docs,
208 final JavaDocRenderer renderer) {
209
210
211 Validate.notNull(docs, "docs");
212 Validate.notNull(log, "log");
213 Validate.notNull(outputDir, "outputDir");
214 Validate.isTrue(outputDir.isDirectory(), "'outputDir' must be a Directory.");
215 Validate.notNull(renderer, "renderer");
216
217 int processedXSDs = 0;
218 final List<File> foundFiles = new ArrayList<File>();
219 addRecursively(foundFiles, RECURSIVE_XSD_FILTER, outputDir);
220
221 if (foundFiles.size() > 0) {
222
223
224 final XsdAnnotationProcessor classProcessor = new XsdAnnotationProcessor(docs, renderer);
225 final XsdEnumerationAnnotationProcessor enumProcessor
226 = new XsdEnumerationAnnotationProcessor(docs, renderer);
227
228 for (File current : foundFiles) {
229
230
231 final Document generatedSchemaFileDocument = parseXmlToDocument(current);
232
233
234 process(generatedSchemaFileDocument.getFirstChild(), true, classProcessor);
235 processedXSDs++;
236
237
238 savePrettyPrintedDocument(generatedSchemaFileDocument, current);
239 }
240
241 } else {
242 if (log.isWarnEnabled()) {
243 log.warn("Found no generated 'vanilla' XSD files to process under ["
244 + FileSystemUtilities.getCanonicalPath(outputDir) + "]. Aborting processing.");
245 }
246 }
247
248
249 return processedXSDs;
250 }
251
252
253
254
255
256
257
258
259
260
261 public static void replaceNamespacePrefixes(final Map<String, SimpleNamespaceResolver> resolverMap,
262 final List<TransformSchema> configuredTransformSchemas,
263 final Log mavenLog,
264 final File schemaDirectory) throws MojoExecutionException {
265
266 if (mavenLog.isDebugEnabled()) {
267 mavenLog.debug("Got resolverMap.keySet() [generated filenames]: " + resolverMap.keySet());
268 }
269
270 for (SimpleNamespaceResolver currentResolver : resolverMap.values()) {
271 File generatedSchemaFile = new File(schemaDirectory, currentResolver.getSourceFilename());
272 Document generatedSchemaFileDocument = null;
273
274 for (TransformSchema currentTransformSchema : configuredTransformSchemas) {
275
276 final String newPrefix = currentTransformSchema.getToPrefix();
277 final String currentUri = currentTransformSchema.getUri();
278
279 if (StringUtils.isNotEmpty(newPrefix)) {
280
281 final String oldPrefix = currentResolver.getNamespaceURI2PrefixMap().get(currentUri);
282
283 if (StringUtils.isNotEmpty(oldPrefix)) {
284
285 validatePrefixSubstitutionIsPossible(oldPrefix, newPrefix, currentResolver);
286
287 if (mavenLog.isDebugEnabled()) {
288 mavenLog.debug("Subtituting namespace prefix [" + oldPrefix + "] with [" + newPrefix
289 + "] in file [" + currentResolver.getSourceFilename() + "].");
290 }
291
292
293 if (generatedSchemaFileDocument == null) {
294 generatedSchemaFileDocument = parseXmlToDocument(generatedSchemaFile);
295 }
296
297
298 process(generatedSchemaFileDocument.getFirstChild(), true,
299 new ChangeNamespacePrefixProcessor(oldPrefix, newPrefix));
300 }
301 }
302 }
303
304 if (generatedSchemaFileDocument != null) {
305
306 mavenLog.debug("Overwriting file [" + currentResolver.getSourceFilename() + "] with content ["
307 + getHumanReadableXml(generatedSchemaFileDocument) + "]");
308 savePrettyPrintedDocument(generatedSchemaFileDocument, generatedSchemaFile);
309 } else {
310 mavenLog.debug("No namespace prefix changes to generated schema file ["
311 + generatedSchemaFile.getName() + "]");
312 }
313 }
314 }
315
316
317
318
319
320
321
322
323
324
325 public static void renameGeneratedSchemaFiles(final Map<String, SimpleNamespaceResolver> resolverMap,
326 final List<TransformSchema> configuredTransformSchemas,
327 final Log mavenLog,
328 final File schemaDirectory) {
329
330
331 Map<String, String> namespaceUriToDesiredFilenameMap = new TreeMap<String, String>();
332 for (TransformSchema current : configuredTransformSchemas) {
333 if (StringUtils.isNotEmpty(current.getToFile())) {
334 namespaceUriToDesiredFilenameMap.put(current.getUri(), current.getToFile());
335 }
336 }
337
338
339 for (SimpleNamespaceResolver currentResolver : resolverMap.values()) {
340 File generatedSchemaFile = new File(schemaDirectory, currentResolver.getSourceFilename());
341 Document generatedSchemaFileDocument = parseXmlToDocument(generatedSchemaFile);
342
343
344 process(generatedSchemaFileDocument.getFirstChild(), true,
345 new ChangeFilenameProcessor(namespaceUriToDesiredFilenameMap));
346
347
348 if (mavenLog.isDebugEnabled()) {
349 mavenLog.debug("Changed schemaLocation entries within [" + currentResolver.getSourceFilename() + "]. "
350 + "Result: [" + getHumanReadableXml(generatedSchemaFileDocument) + "]");
351 }
352 savePrettyPrintedDocument(generatedSchemaFileDocument, generatedSchemaFile);
353 }
354
355
356 for (SimpleNamespaceResolver currentResolver : resolverMap.values()) {
357 final String localNamespaceURI = currentResolver.getLocalNamespaceURI();
358
359 if (StringUtils.isEmpty(localNamespaceURI)) {
360 mavenLog.warn("SimpleNamespaceResolver contained no localNamespaceURI; aborting rename.");
361 continue;
362 }
363
364 final String newFilename = namespaceUriToDesiredFilenameMap.get(localNamespaceURI);
365 final File originalFile = new File(schemaDirectory, currentResolver.getSourceFilename());
366
367 if (StringUtils.isNotEmpty(newFilename)) {
368 File renamedFile = FileUtils.resolveFile(schemaDirectory, newFilename);
369 String renameResult = (originalFile.renameTo(renamedFile) ? "Success " : "Failure ");
370
371 if (mavenLog.isDebugEnabled()) {
372 String suffix = "renaming [" + originalFile.getAbsolutePath() + "] to [" + renamedFile + "]";
373 mavenLog.debug(renameResult + suffix);
374 }
375 }
376 }
377 }
378
379
380
381
382
383
384
385
386
387
388 public static void process(final Node node, final boolean recurseToChildren, final NodeProcessor visitor) {
389
390
391 if (visitor.accept(node)) {
392 visitor.process(node);
393 }
394
395 NamedNodeMap attributes = node.getAttributes();
396 for (int i = 0; i < attributes.getLength(); i++) {
397 Node attribute = attributes.item(i);
398
399
400 if (visitor.accept(attribute)) {
401 visitor.process(attribute);
402 }
403 }
404
405 if (recurseToChildren) {
406 NodeList children = node.getChildNodes();
407 for (int i = 0; i < children.getLength(); i++) {
408 Node child = children.item(i);
409
410
411 if (child.getNodeType() == Node.ELEMENT_NODE) {
412 process(child, true, visitor);
413 }
414 }
415 }
416 }
417
418
419
420
421
422
423
424 public static Document parseXmlStream(final Reader xmlStream) {
425
426
427 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
428 factory.setNamespaceAware(true);
429
430 try {
431 return factory.newDocumentBuilder().parse(new InputSource(xmlStream));
432 } catch (Exception e) {
433 throw new IllegalArgumentException("Could not acquire DOM Document", e);
434 }
435 }
436
437
438
439
440
441
442
443 protected static String getHumanReadableXml(final Node node) {
444 StringWriter toReturn = new StringWriter();
445
446 try {
447 Transformer transformer = getFactory().newTransformer();
448 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
449 transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
450 transformer.transform(new DOMSource(node), new StreamResult(toReturn));
451 } catch (TransformerException e) {
452 throw new IllegalStateException("Could not transform node [" + node.getNodeName() + "] to XML", e);
453 }
454
455 return toReturn.toString();
456 }
457
458
459
460
461
462 private static String getDuplicationErrorMessage(final String propertyName, final String propertyValue,
463 final int firstIndex, final int currentIndex) {
464 return MISCONFIG + "Duplicate '" + propertyName + "' property with value [" + propertyValue
465 + "] found in plugin configuration. Correct schema elements index (" + firstIndex + ") and ("
466 + currentIndex + "), to ensure that all '" + propertyName + "' values are unique.";
467 }
468
469
470
471
472
473
474
475
476
477
478
479 private static void validatePrefixSubstitutionIsPossible(final String oldPrefix, final String newPrefix,
480 final SimpleNamespaceResolver currentResolver)
481 throws MojoExecutionException {
482
483 if (currentResolver.getNamespaceURI2PrefixMap().containsValue(newPrefix)) {
484 throw new MojoExecutionException(MISCONFIG + "Namespace prefix [" + newPrefix + "] is already in use."
485 + " Cannot replace namespace prefix [" + oldPrefix + "] with [" + newPrefix + "] in file ["
486 + currentResolver.getSourceFilename() + "].");
487 }
488 }
489
490
491
492
493
494
495
496 private static Document parseXmlToDocument(final File xmlFile) {
497 Document result = null;
498 Reader reader = null;
499 try {
500 reader = new FileReader(xmlFile);
501 result = parseXmlStream(reader);
502 } catch (FileNotFoundException e) {
503
504 } finally {
505 IOUtil.close(reader);
506 }
507
508 return result;
509 }
510
511 private static void savePrettyPrintedDocument(final Document toSave, final File targetFile) {
512 Writer out = null;
513 try {
514 out = new BufferedWriter(new FileWriter(targetFile));
515 out.write(getHumanReadableXml(toSave.getFirstChild()));
516 } catch (IOException e) {
517 throw new IllegalStateException("Could not write to file [" + targetFile.getAbsolutePath() + "]", e);
518 } finally {
519 IOUtil.close(out);
520 }
521 }
522
523 private static void addRecursively(final List<File> toPopulate,
524 final FileFilter fileFilter,
525 final File aDir) {
526
527
528 Validate.notNull(toPopulate, "toPopulate");
529 Validate.notNull(fileFilter, "fileFilter");
530 Validate.notNull(aDir, "aDir");
531
532
533 for (File current : aDir.listFiles(fileFilter)) {
534
535 if (current.isFile()) {
536 toPopulate.add(current);
537 } else if (current.isDirectory()) {
538 addRecursively(toPopulate, fileFilter, current);
539 }
540 }
541 }
542
543 private static TransformerFactory getFactory() {
544
545 if (FACTORY == null) {
546
547 try {
548 FACTORY = TransformerFactory.newInstance();
549
550
551 for (String currentAttributeName : Arrays.asList("indent-number", OutputKeys.INDENT)) {
552 try {
553 FACTORY.setAttribute(currentAttributeName, 2);
554 } catch (IllegalArgumentException ex) {
555
556 }
557 }
558 } catch (Throwable exception) {
559
560
561 throw new IllegalStateException("Could not acquire TransformerFactory implementation.", exception);
562 }
563 }
564
565
566 return FACTORY;
567 }
568 }