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 }