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