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