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 com.sun.tools.jxc.SchemaGenerator;
23 import com.thoughtworks.qdox.JavaProjectBuilder;
24 import com.thoughtworks.qdox.model.JavaClass;
25 import com.thoughtworks.qdox.model.JavaPackage;
26 import com.thoughtworks.qdox.model.JavaSource;
27 import org.apache.maven.plugin.MojoExecutionException;
28 import org.apache.maven.plugin.MojoFailureException;
29 import org.apache.maven.plugins.annotations.Parameter;
30 import org.codehaus.mojo.jaxb2.AbstractJaxbMojo;
31 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.DefaultJavaDocRenderer;
32 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.JavaDocExtractor;
33 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.JavaDocRenderer;
34 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.SearchableDocumentation;
35 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement.SimpleNamespaceResolver;
36 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement.TransformSchema;
37 import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
38 import org.codehaus.mojo.jaxb2.shared.arguments.ArgumentBuilder;
39 import org.codehaus.mojo.jaxb2.shared.environment.EnvironmentFacet;
40 import org.codehaus.mojo.jaxb2.shared.environment.ToolExecutionEnvironment;
41 import org.codehaus.mojo.jaxb2.shared.environment.classloading.ThreadContextClassLoaderBuilder;
42 import org.codehaus.mojo.jaxb2.shared.environment.locale.LocaleFacet;
43 import org.codehaus.mojo.jaxb2.shared.environment.logging.LoggingHandlerEnvironmentFacet;
44 import org.codehaus.mojo.jaxb2.shared.filters.Filter;
45 import org.codehaus.mojo.jaxb2.shared.filters.pattern.PatternFileFilter;
46 import org.codehaus.plexus.classworlds.realm.ClassRealm;
47 import org.codehaus.plexus.util.FileUtils;
48
49 import javax.tools.ToolProvider;
50 import java.io.File;
51 import java.io.IOException;
52 import java.net.HttpURLConnection;
53 import java.net.URL;
54 import java.net.URLConnection;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Collection;
58 import java.util.Collections;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.SortedMap;
62 import java.util.TreeMap;
63 import java.util.regex.Pattern;
64
65
66
67
68
69
70
71
72
73
74 public abstract class AbstractXsdGeneratorMojo extends AbstractJaxbMojo {
75
76
77
78
79
80
81 public static final Pattern SCHEMAGEN_EMITTED_FILENAME = Pattern.compile("schema\\p{javaDigit}+.xsd");
82
83
84
85
86
87
88
89 public static final JavaDocRenderer STANDARD_JAVADOC_RENDERER = new DefaultJavaDocRenderer();
90
91
92
93
94
95 public static final List<Filter<File>> STANDARD_BYTECODE_EXCLUDE_FILTERS;
96
97
98
99
100 public static final List<Filter<File>> CLASS_INCLUDE_FILTERS;
101
102
103
104
105
106
107
108
109 public static final List<String> SYSTEM_TOOLS_CLASSLOADER_PACKAGES = Arrays.asList(
110 "com.sun.source.util",
111 "com.sun.source.tree");
112
113 static {
114
115 final List<Filter<File>> schemagenTmp = new ArrayList<Filter<File>>();
116 schemagenTmp.addAll(AbstractJaxbMojo.STANDARD_EXCLUDE_FILTERS);
117 schemagenTmp.add(new PatternFileFilter(Arrays.asList("\\.java", "\\.scala", "\\.mdo"), false));
118 STANDARD_BYTECODE_EXCLUDE_FILTERS = Collections.unmodifiableList(schemagenTmp);
119
120 CLASS_INCLUDE_FILTERS = new ArrayList<Filter<File>>();
121 CLASS_INCLUDE_FILTERS.add(new PatternFileFilter(Arrays.asList("\\.class"), true));
122 }
123
124
125 private static final int SCHEMAGEN_INCORRECT_OPTIONS = -1;
126 private static final int SCHEMAGEN_COMPLETED_OK = 0;
127 private static final int SCHEMAGEN_JAXB_ERRORS = 1;
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 @Parameter
163 private List<TransformSchema> transformSchemas;
164
165
166
167
168
169
170
171
172
173
174
175
176 @Parameter(defaultValue = "true")
177 protected boolean generateEpisode;
178
179
180
181
182
183
184
185
186 @Parameter(defaultValue = "true")
187 protected boolean createJavaDocAnnotations;
188
189
190
191
192
193
194
195
196
197 @Parameter
198 protected JavaDocRenderer javaDocRenderer;
199
200
201
202
203
204
205 @Parameter(defaultValue = "true")
206 protected boolean clearOutputDir;
207
208
209
210
211
212 @Override
213 protected boolean shouldExecutionBeSkipped() {
214
215 boolean toReturn = false;
216
217 if ("pom".equalsIgnoreCase(getProject().getPackaging())) {
218 warnAboutIncorrectPluginConfiguration("packaging", "POM-packaged projects should not generate XSDs.");
219 toReturn = true;
220 }
221
222 if (getSources().isEmpty()) {
223 warnAboutIncorrectPluginConfiguration("sources", "At least one Java Source file has to be included.");
224 toReturn = true;
225 }
226
227
228 return toReturn;
229 }
230
231
232
233
234 @Override
235 protected boolean isReGenerationRequired() {
236
237
238
239
240
241
242
243
244
245 final File staleFile = getStaleFile();
246 final String debugPrefix = "StaleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]";
247
248 boolean stale = !staleFile.exists();
249 if (stale) {
250 getLog().debug(debugPrefix + " not found. XML Schema (re-)generation required.");
251 } else {
252
253 final List<URL> sources = getSources();
254
255 if (getLog().isDebugEnabled()) {
256 getLog().debug(debugPrefix + " found. Checking timestamps on source Java "
257 + "files to determine if XML Schema (re-)generation is required.");
258 }
259
260 final long staleFileLastModified = staleFile.lastModified();
261 for (URL current : sources) {
262
263 final URLConnection sourceFileConnection;
264 try {
265 sourceFileConnection = current.openConnection();
266 sourceFileConnection.connect();
267 } catch (Exception e) {
268
269 if (getLog().isDebugEnabled()) {
270 getLog().debug("Could not open a sourceFileConnection to [" + current + "]", e);
271 }
272
273
274
275 stale = true;
276 break;
277 }
278
279 try {
280 if (sourceFileConnection.getLastModified() > staleFileLastModified) {
281
282 if (getLog().isDebugEnabled()) {
283 getLog().debug(current.toString() + " is newer than the stale flag file.");
284 }
285 stale = true;
286 }
287 } finally {
288 if (sourceFileConnection instanceof HttpURLConnection) {
289 ((HttpURLConnection) sourceFileConnection).disconnect();
290 }
291 }
292 }
293 }
294
295
296 return stale;
297 }
298
299
300
301
302 @Override
303 protected boolean performExecution() throws MojoExecutionException, MojoFailureException {
304
305 boolean updateStaleFileTimestamp = false;
306 ToolExecutionEnvironment environment = null;
307
308 try {
309
310
311
312
313
314
315 final ClassRealm localRealm = (ClassRealm) getClass().getClassLoader();
316 for (String current : SYSTEM_TOOLS_CLASSLOADER_PACKAGES) {
317 localRealm.importFrom(ToolProvider.getSystemToolClassLoader(), current);
318 }
319
320
321 final ThreadContextClassLoaderBuilder classLoaderBuilder = ThreadContextClassLoaderBuilder
322 .createFor(this.getClass(), getLog())
323 .addPaths(getClasspath())
324 .addPaths(getProject().getCompileSourceRoots());
325
326 final LocaleFacet localeFacet = locale == null ? null : LocaleFacet.createFor(locale, getLog());
327
328
329 environment = new ToolExecutionEnvironment(
330 getLog(),
331 classLoaderBuilder,
332 LoggingHandlerEnvironmentFacet.create(getLog(), getClass(), getEncoding(false)),
333 localeFacet);
334 final String projectBasedirPath = FileSystemUtilities.getCanonicalPath(getProject().getBasedir());
335
336
337 if (extraFacets != null) {
338 for (EnvironmentFacet current : extraFacets) {
339 environment.add(current);
340 }
341 }
342
343
344 environment.setup();
345
346
347 final List<URL> sources = getSources();
348 final String[] schemaGenArguments = getSchemaGenArguments(
349 environment.getClassPathAsArgument(),
350 STANDARD_EPISODE_FILENAME,
351 sources);
352
353
354
355 FileSystemUtilities.createDirectory(getOutputDirectory(), clearOutputDir);
356 FileSystemUtilities.createDirectory(getWorkDirectory(), clearOutputDir);
357
358
359 final boolean reCreateEpisodeFileParentDirectory = generateEpisode && clearOutputDir;
360 if (reCreateEpisodeFileParentDirectory) {
361 getEpisodeFile(STANDARD_EPISODE_FILENAME);
362 }
363
364 try {
365
366
367
368
369
370 final int result = SchemaGenerator.run(
371 schemaGenArguments,
372 Thread.currentThread().getContextClassLoader());
373
374 if (SCHEMAGEN_INCORRECT_OPTIONS == result) {
375 printSchemaGenCommandAndThrowException(projectBasedirPath,
376 sources,
377 schemaGenArguments,
378 result,
379 null);
380 } else if (SCHEMAGEN_JAXB_ERRORS == result) {
381
382
383 throw new MojoExecutionException("JAXB errors arose while SchemaGen compiled sources to XML.");
384 }
385
386
387
388 final List<Filter<File>> exclusionFilters = PatternFileFilter.createIncludeFilterList(
389 getLog(), "\\.class");
390
391 final List<File> toCopy = FileSystemUtilities.resolveRecursively(
392 Arrays.asList(getWorkDirectory()),
393 exclusionFilters, getLog());
394 for (File current : toCopy) {
395
396
397 final String currentPath = FileSystemUtilities.getCanonicalPath(current.getAbsoluteFile());
398 final File target = new File(getOutputDirectory(),
399 FileSystemUtilities.relativize(currentPath, getWorkDirectory()));
400
401
402 FileSystemUtilities.createDirectory(target.getParentFile(), false);
403 FileUtils.copyFile(current, target);
404 }
405
406
407
408
409
410
411
412
413
414 final boolean performPostProcessing = createJavaDocAnnotations || transformSchemas != null;
415 if (performPostProcessing) {
416
417
418
419 final Map<String, SimpleNamespaceResolver> resolverMap =
420 XsdGeneratorHelper.getFileNameToResolverMap(getOutputDirectory());
421
422 if (createJavaDocAnnotations) {
423
424 if (getLog().isInfoEnabled()) {
425 getLog().info("XSD post-processing: Adding JavaDoc annotations in generated XSDs.");
426 }
427
428
429 final List<File> fileSources = new ArrayList<File>();
430 for (URL current : sources) {
431 if ("file".equalsIgnoreCase(current.getProtocol())) {
432 final File toAdd = new File(current.getPath());
433 if (toAdd.exists()) {
434 fileSources.add(toAdd);
435 } else {
436 if (getLog().isWarnEnabled()) {
437 getLog().warn("Ignoring URL [" + current + "] as it is a nonexistent file.");
438 }
439 }
440 }
441 }
442
443 final List<File> files = FileSystemUtilities.resolveRecursively(
444 fileSources, null, getLog());
445
446
447 final JavaDocExtractor extractor = new JavaDocExtractor(getLog()).addSourceFiles(files);
448 final SearchableDocumentation javaDocs = extractor.process();
449
450
451 final JavaDocRenderer renderer = javaDocRenderer == null
452 ? STANDARD_JAVADOC_RENDERER
453 : javaDocRenderer;
454 final int numProcessedFiles = XsdGeneratorHelper.insertJavaDocAsAnnotations(getLog(),
455 getOutputDirectory(),
456 javaDocs,
457 renderer);
458
459 if (getLog().isDebugEnabled()) {
460 getLog().info("XSD post-processing: " + numProcessedFiles + " files processed.");
461 }
462 }
463
464 if (transformSchemas != null) {
465
466 if (getLog().isInfoEnabled()) {
467 getLog().info("XSD post-processing: Renaming and converting XSDs.");
468 }
469
470
471 XsdGeneratorHelper.replaceNamespacePrefixes(resolverMap,
472 transformSchemas,
473 getLog(),
474 getOutputDirectory());
475
476
477 XsdGeneratorHelper.renameGeneratedSchemaFiles(resolverMap,
478 transformSchemas,
479 getLog(),
480 getOutputDirectory());
481 }
482 }
483
484 } catch (MojoExecutionException e) {
485 throw e;
486 } catch (Exception e) {
487
488
489
490
491 Throwable current = e.getCause();
492 while (current.getCause() != null) {
493 current = current.getCause();
494 }
495
496 getLog().error("Execution failed.");
497
498
499
500
501 StringBuilder rootCauseBuilder = new StringBuilder();
502 rootCauseBuilder.append("\n");
503 rootCauseBuilder.append("[Exception]: " + current.getClass().getName() + "\n");
504 rootCauseBuilder.append("[Message]: " + current.getMessage() + "\n");
505 for (StackTraceElement el : current.getStackTrace()) {
506 rootCauseBuilder.append(" " + el.toString()).append("\n");
507 }
508 getLog().error(rootCauseBuilder.toString().replaceAll("[\r\n]+", "\n"));
509
510 printSchemaGenCommandAndThrowException(projectBasedirPath,
511 sources,
512 schemaGenArguments,
513 -1,
514 current);
515
516 }
517
518
519 getBuildContext().refresh(getOutputDirectory());
520
521
522 updateStaleFileTimestamp = true;
523
524 } finally {
525
526
527 if (environment != null) {
528 environment.restore();
529 }
530 }
531
532
533 return updateStaleFileTimestamp;
534 }
535
536
537
538
539
540 protected abstract File getWorkDirectory();
541
542
543
544
545
546
547
548
549
550
551 protected abstract List<URL> getCompiledClassNames();
552
553
554
555
556
557
558
559 @Override
560 protected abstract List<URL> getSources();
561
562
563
564
565
566 private String[] getSchemaGenArguments(final String classPath,
567 final String episodeFileNameOrNull,
568 final List<URL> sources)
569 throws MojoExecutionException {
570
571 final ArgumentBuilder builder = new ArgumentBuilder();
572
573
574
575
576
577
578 builder.withNamedArgument("encoding", getEncoding(true));
579 builder.withNamedArgument("d", getWorkDirectory().getAbsolutePath());
580 builder.withNamedArgument("classpath", classPath);
581
582 if (episodeFileNameOrNull != null) {
583 final File episodeFile = getEpisodeFile(episodeFileNameOrNull);
584 builder.withNamedArgument("episode", FileSystemUtilities.getCanonicalPath(episodeFile));
585 }
586
587 try {
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607 builder.withPreCompiledArguments(getSchemaGeneratorSourceFiles(sources));
608 } catch (IOException e) {
609 throw new MojoExecutionException("Could not compile source paths for the SchemaGenerator", e);
610 }
611
612
613 return logAndReturnToolArguments(builder.build(), "SchemaGen");
614 }
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643 private List<String> getSchemaGeneratorSourceFiles(final List<URL> sources)
644 throws IOException, MojoExecutionException {
645
646 final SortedMap<String, String> className2SourcePath = new TreeMap<String, String>();
647 final File baseDir = getProject().getBasedir();
648 final File userDir = new File(System.getProperty("user.dir"));
649 final String encoding = getEncoding(true);
650
651
652 for (URL current : sources) {
653
654 final File sourceCodeFile = FileSystemUtilities.getFileFor(current, encoding);
655
656
657 final String relativePath = FileSystemUtilities.relativize(
658 FileSystemUtilities.getCanonicalPath(sourceCodeFile),
659 userDir);
660
661 if (getLog().isDebugEnabled()) {
662 getLog().debug("SourceCodeFile ["
663 + FileSystemUtilities.getCanonicalPath(sourceCodeFile)
664 + "] and userDir [" + FileSystemUtilities.getCanonicalPath(userDir)
665 + "] ==> relativePath: "
666 + relativePath
667 + ". (baseDir: " + FileSystemUtilities.getCanonicalPath(baseDir) + "]");
668 }
669
670
671 final JavaProjectBuilder builder = new JavaProjectBuilder();
672 builder.setEncoding(encoding);
673
674
675
676
677 if (sourceCodeFile.getName().trim().equalsIgnoreCase(PACKAGE_INFO_FILENAME)) {
678
679
680 builder.addSource(current);
681 final Collection<JavaPackage> packages = builder.getPackages();
682 if (packages.size() != 1) {
683 throw new MojoExecutionException("Exactly one package should be present in file ["
684 + sourceCodeFile.getPath() + "]");
685 }
686
687
688 final JavaPackage javaPackage = packages.iterator().next();
689 className2SourcePath.put("package-info for (" + javaPackage.getName() + ")", relativePath);
690 continue;
691 }
692
693
694 builder.addSource(sourceCodeFile);
695
696
697 for (JavaSource currentJavaSource : builder.getSources()) {
698 for (JavaClass currentJavaClass : currentJavaSource.getClasses()) {
699
700 final String className = currentJavaClass.getFullyQualifiedName();
701 if (className2SourcePath.containsKey(className)) {
702 if (getLog().isWarnEnabled()) {
703 getLog().warn("Already mapped. Source class [" + className + "] within ["
704 + className2SourcePath.get(className)
705 + "]. Not overwriting with [" + relativePath + "]");
706 }
707 } else {
708 className2SourcePath.put(className, relativePath);
709 }
710 }
711 }
712 }
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832 if (getLog().isDebugEnabled()) {
833
834 final int size = className2SourcePath.size();
835 getLog().debug("[ClassName-2-SourcePath Map (size: " + size + ")] ...");
836
837 int i = 0;
838 for (Map.Entry<String, String> current : className2SourcePath.entrySet()) {
839 getLog().debug(" " + (++i) + "/" + size + ": [" + current.getKey() + "]: "
840 + current.getValue());
841 }
842 getLog().debug("... End [ClassName-2-SourcePath Map]");
843 }
844
845
846 final ArrayList<String> toReturn = new ArrayList<String>(className2SourcePath.values());
847 Collections.sort(toReturn);
848
849
850 return toReturn;
851 }
852
853 private void printSchemaGenCommandAndThrowException(final String projectBasedirPath,
854 final List<URL> sources,
855 final String[] schemaGenArguments,
856 final int result,
857 final Throwable cause) throws MojoExecutionException {
858
859 final StringBuilder errorMsgBuilder = new StringBuilder();
860 errorMsgBuilder.append("\n+=================== [SchemaGenerator Error '"
861 + (result == -1 ? "<unknown>" : result) + "']\n");
862 errorMsgBuilder.append("|\n");
863 errorMsgBuilder.append("| SchemaGen did not complete its operation correctly.\n");
864 errorMsgBuilder.append("|\n");
865 errorMsgBuilder.append("| To re-create the error (and get a proper error message), cd to:\n");
866 errorMsgBuilder.append("| ").append(projectBasedirPath).append("\n");
867 errorMsgBuilder.append("| ... and fire the following on a command line/in a shell:\n");
868 errorMsgBuilder.append("|\n");
869
870 final StringBuilder builder = new StringBuilder("schemagen ");
871 for (String current : schemaGenArguments) {
872 builder.append(current).append(" ");
873 }
874
875 errorMsgBuilder.append("| " + builder.toString() + "\n");
876 errorMsgBuilder.append("|\n");
877 errorMsgBuilder.append("| The following source files should be processed by schemagen:\n");
878
879 for (int i = 0; i < sources.size(); i++) {
880 errorMsgBuilder.append("| " + i + ": ").append(sources.get(i).toString()).append("\n");
881 }
882
883 errorMsgBuilder.append("|\n");
884 errorMsgBuilder.append("+=================== [End SchemaGenerator Error]\n");
885
886 final String msg = errorMsgBuilder.toString().replaceAll("[\r\n]+", "\n");
887 if (cause != null) {
888 throw new MojoExecutionException(msg, cause);
889 } else {
890 throw new MojoExecutionException(msg);
891 }
892 }
893 }