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