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