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