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