View Javadoc
1   package org.codehaus.mojo.jaxb2.javageneration;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import com.sun.tools.xjc.Driver;
23  import org.apache.maven.plugin.MojoExecutionException;
24  import org.apache.maven.plugin.MojoFailureException;
25  import org.apache.maven.plugins.annotations.Parameter;
26  import org.apache.maven.settings.Proxy;
27  import org.apache.maven.settings.Settings;
28  import org.codehaus.mojo.jaxb2.AbstractJaxbMojo;
29  import org.codehaus.mojo.jaxb2.NoSchemasException;
30  import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
31  import org.codehaus.mojo.jaxb2.shared.arguments.ArgumentBuilder;
32  import org.codehaus.mojo.jaxb2.shared.environment.EnvironmentFacet;
33  import org.codehaus.mojo.jaxb2.shared.environment.ToolExecutionEnvironment;
34  import org.codehaus.mojo.jaxb2.shared.environment.classloading.ThreadContextClassLoaderBuilder;
35  import org.codehaus.mojo.jaxb2.shared.environment.locale.LocaleFacet;
36  import org.codehaus.mojo.jaxb2.shared.environment.logging.LoggingHandlerEnvironmentFacet;
37  import org.codehaus.mojo.jaxb2.shared.environment.sysprops.SystemPropertyChangeEnvironmentFacet;
38  import org.codehaus.mojo.jaxb2.shared.environment.sysprops.SystemPropertySaveEnvironmentFacet;
39  import org.codehaus.plexus.util.FileUtils;
40  import org.codehaus.plexus.util.IOUtil;
41  
42  import java.io.File;
43  import java.io.FileWriter;
44  import java.net.HttpURLConnection;
45  import java.net.URL;
46  import java.net.URLConnection;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.List;
50  
51  /**
52   * <p>Abstract superclass for Mojos generating Java source or binaries from XML schema(s) by invoking the JAXB XJC
53   * binding compiler. Most of the Configuration options for the AbstractJavaGeneratorMojo are set or copied to the
54   * XJC directly; refer to their documentation in the <a href="https://jaxb.java.net/">JAXB Reference Implementation</a>
55   * site.</p>
56   *
57   * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>
58   * @see <a href="https://jaxb.java.net/">The JAXB Reference Implementation</a>
59   */
60  public abstract class AbstractJavaGeneratorMojo extends AbstractJaxbMojo {
61  
62      private static final List<String> PROXY_PROPERTY_KEYS = Arrays.asList("http.proxyHost", "http.proxyPort", "https.proxyHost", "https.proxyPort");
63  
64      private static final int XJC_COMPLETED_OK = 0;
65  
66      /**
67       * <p>Corresponding XJC parameter: {@code catalog}.</p>
68       * <p>Specify catalog files to resolve external entity references.
69       * Supports TR9401, XCatalog, and OASIS XML Catalog format.</p>
70       */
71      @Parameter
72      protected File catalog;
73  
74      /**
75       * <strong>Deprecated - will be removed in a future release</strong>
76       * <p>From plugin version 2.4, this parameter will not be used.
77       * Instead, episode files are generated by default with all JAXB operations.</p>
78       * <p>Starting with plugin version 2.4, use the parameter {@link #episodeFileName} to provide a custom
79       * name of the generated episode File (or rely on the standard file name {@link #STANDARD_EPISODE_FILENAME}).</p>
80       *
81       * @since 2.0
82       * @deprecated
83       */
84      @Deprecated
85      @Parameter(defaultValue = "true")
86      protected boolean generateEpisode;
87  
88      /**
89       * <p>Corresponding XJC parameter: {@code episode}.</p>
90       * <p>Generate an episode file with the supplied name from this XJC compilation, so that other schemas that rely
91       * on this schema can be compiled later and rely on classes that are generated from this compilation.
92       * The generated episode file is simply a JAXB customization file (but with vendor extensions), normally known
93       * as a <em>binding file</em> with the suffix <code>.xjb</code>.</p>
94       * <p>If the <code>episodeFileName</code> parameter is not given, the episode file name is synthesized on the form
95       * <code>"episode_" + executionID + ".xjb"</code> - typically something like <em>episode_my_xjc.xjb</em>, but
96       * it depends on the actual ID given in the execution element:</p>
97       * <pre>
98       *     <code>
99       * &lt;executions&gt;
100      *     &lt;execution&gt;
101      *         &lt;id&gt;my_xjc&lt;/id&gt;
102      *         &lt;goals&gt;
103      *             &lt;goal&gt;xjc&lt;/goal&gt;
104      *         &lt;/goals&gt;
105      *     &lt;/execution&gt;
106      * &lt;/executions&gt;
107      *      </code>
108      * </pre>
109      *
110      * @see #STANDARD_EPISODE_FILENAME
111      * @since 2.4
112      */
113     @Parameter
114     protected String episodeFileName;
115 
116     /**
117      * <p>Sets the HTTP/HTTPS proxy to be used by the XJC, on the format
118      * {@code [user[:password]@]proxyHost[:proxyPort]}.
119      * All information is retrieved from the active proxy within the standard maven settings file.</p>
120      */
121     @Parameter(defaultValue = "${settings}", readonly = true)
122     protected Settings settings;
123 
124     /**
125      * <p>Defines the content type of sources for the XJC. To simplify usage of the JAXB2 maven plugin,
126      * all source files are assumed to have the same type of content.</p>
127      * <p>This parameter replaces the previous multiple-choice boolean configuration options for the
128      * jaxb2-maven-plugin (i.e. dtd, xmlschema, relaxng, relaxng-compact, wsdl), and
129      * corresponds to setting one of those flags as an XJC argument.</p>
130      *
131      * @since 2.0
132      */
133     @Parameter(defaultValue = "XmlSchema")
134     protected SourceContentType sourceType;
135 
136     /**
137      * <p>Corresponding XJC parameter: {@code npa}.</p>
138      * <p>Suppress the generation of package level annotations into {@code package-info.java}.
139      * Using this switch causes the generated code to internalize those annotations into the other
140      * generated classes.</p>
141      *
142      * @since 2.0
143      */
144     @Parameter(defaultValue = "false")
145     protected boolean noPackageLevelAnnotations;
146 
147     /**
148      * <p>Corresponding XJC parameter: {@code no-header}.</p>
149      * <p>Suppress the generation of a file header comment that includes some note and timestamp.
150      * Using this makes the generated code more diff-friendly.</p>
151      *
152      * @since 2.0
153      */
154     @Parameter(defaultValue = "false")
155     protected boolean noGeneratedHeaderComments;
156 
157     /**
158      * <p>Corresponding XJC parameter: {@code mark-generated}.</p>
159      * <p>This feature causes all of the generated code to have {@code @Generated} annotation.</p>
160      *
161      * @since 2.0
162      */
163     @Parameter(defaultValue = "false")
164     protected boolean addGeneratedAnnotation;
165 
166     /**
167      * <p>Corresponding XJC parameter: {@code nv}.</p>
168      * <p>By default, the XJC binding compiler performs strict validation of the source schema before processing it.
169      * Use this option to disable strict schema validation. This does not mean that the binding compiler will not
170      * perform any validation, it simply means that it will perform less-strict validation.</p>
171      *
172      * @since 2.0
173      */
174     @Parameter(defaultValue = "false")
175     protected boolean laxSchemaValidation;
176 
177     /**
178      * <p>Corresponding XJC parameter: {@code quiet}.</p>
179      * <p>Suppress compiler output, such as progress information and warnings.</p>
180      */
181     @Parameter(defaultValue = "false")
182     protected boolean quiet;
183 
184     /**
185      * <p>Corresponding XJC parameter: {@code verbose}.</p>
186      * <p>Tells XJC to be extra verbose, such as printing informational messages or displaying stack traces.</p>
187      */
188     @Parameter(property = "xjc.verbose", defaultValue = "false")
189     protected boolean verbose;
190 
191     /**
192      * <p>Corresponding XJC parameter: {@code extension}.</p>
193      * <p>By default, the XJC binding compiler strictly enforces the rules outlined in the Compatibility chapter of
194      * the JAXB Specification. Appendix E.2 defines a set of W3C XML Schema features that are not completely
195      * supported by JAXB v1.0. In some cases, you may be allowed to use them in the "-extension" mode enabled by
196      * this switch. In the default (strict) mode, you are also limited to using only the binding customizations
197      * defined in the specification.</p>
198      */
199     @Parameter(defaultValue = "true")
200     protected boolean extension;
201 
202     /**
203      * Fails the Mojo execution if no XSDs/schemas are found.
204      *
205      * @since 1.3
206      */
207     @Parameter(defaultValue = "true")
208     protected boolean failOnNoSchemas;
209 
210     /**
211      * <p>Removes all files from the output directory before running XJC.</p>
212      */
213     @Parameter(defaultValue = "true")
214     protected boolean clearOutputDir;
215 
216     /**
217      * <p>Corresponding XJC parameter: {@code readOnly}.</p>
218      * <p>By default, the XJC binding compiler does not write-protect the Java source files it generates.
219      * Use this option to force the XJC binding compiler to mark the generated Java sources read-only.</p>
220      *
221      * @since 2.0
222      */
223     @Parameter(defaultValue = "false")
224     protected boolean readOnly;
225 
226     /**
227      * <p>List of ordered extra arguments to the XJC command. Each extra argument is interpreted as a word, intended
228      * to be copied verbatim to the XJC argument list with spaces in between:</p>
229      * <pre>
230      * <code>
231      *   &lt;configuration&gt;
232      *   ...
233      *       &lt;arguments&gt;
234      *          &lt;argument&gt;-Xfluent-api&lt;/argument&gt;
235      *          &lt;argument&gt;somefile&lt;/argument&gt;
236      *      &lt;/arguments&gt;
237      *   &lt;/configuration&gt;
238      * </code>
239      * </pre>
240      * <p>The arguments configured above yields the following extra arguments to the XJC command:
241      * <code>-Xfluent-api -episode somefile</code></p>
242      *
243      * @since 2.0
244      * @deprecated This should be removed in the 2.0+ release, as all arguments should be handled by other parameters.
245      */
246     @Parameter(property = "xjc.arguments")
247     protected List<String> arguments;
248 
249     /**
250      * <p>Corresponding XJC parameter: {@code enableIntrospection}.</p>
251      * <p>Enable correct generation of Boolean getters/setters to enable Bean Introspection APIs.</p>
252      *
253      * @since 1.4
254      */
255     @Parameter(defaultValue = "false")
256     private boolean enableIntrospection;
257 
258     /**
259      * <p>Corresponding XJC parameter: {@code p}.</p>
260      * <p>The package under which the source files will be generated. Quoting the XJC documentation:
261      * "Specifying a target package via this command-line option overrides any binding customization for package
262      * name and the default package name algorithm defined in the specification".</p>
263      */
264     @Parameter
265     protected String packageName;
266 
267     /**
268      * <p>Corresponding XJC parameter: {@code target}.</p>
269      * <p>Permitted values: {@code "2.0"} and {@code "2.1"}. Avoid generating code that relies on JAXB newer than the
270      * version given. This will allow the generated code to run with JAXB 2.0 runtime (such as JavaSE 6.)</p>
271      *
272      * @since 1.3
273      */
274     @Parameter
275     protected String target;
276 
277     /**
278      * <p>If provided, this parameter indicates that the XSDs used by XJC to generate Java code should be
279      * copied into the resulting artifact of this project (the JAR, WAR or whichever artifact type is generated).
280      * The value of the {@code xsdPathWithinArtifact} parameter is the relative path within the artifact where
281      * all source XSDs are copied to (hence the name "XSD Path Within Artifact").</p>
282      * <p>The target directory is created within the artifact if it does not already exist.
283      * If the {@code xsdPathWithinArtifact} parameter is not given, the XSDs used to generate Java code are
284      * <em>not</em> included within the project's artifact.</p>
285      * <p><em>Example:</em>Adding the sample configuration below would copy all source XSDs to the given directory
286      * within the resulting JAR (and/or test-JAR). If the directory {@code META-INF/jaxb/xsd} does not exist, it
287      * will be created.</p>
288      * <pre>
289      *     <code>
290      *         &lt;configuration&gt;
291      *             ...
292      *             &lt;xsdPathWithinArtifact&gt;META-INF/jaxb/xsd&lt;/xsdPathWithinArtifact&gt;
293      *         &lt;/configuration&gt;
294      *     </code>
295      * </pre>
296      * <p><strong>Note</strong>: This parameter was previously called {@code includeSchemasOutputPath}
297      * in the 1.x versions of this plugin, but was renamed and re-documented for improved usability and clarity.</p>
298      *
299      * @since 2.0
300      */
301     @Parameter
302     protected String xsdPathWithinArtifact;
303 
304     /**
305      * <p>If set to <code>true</code>, the system property <code>enableExternalEntityProcessing</code> is set for the
306      * duration of the Java generation by this plugin, permitting DTD sources to use external entity URIs such as
307      * <code>file://</code>. Typically, this is used in conjunction with the <code>sourceType</code> similar to the
308      * configuration snippet below where DTDs are used as sourceType and read from the <code>src/main/dtd</code>
309      * directory. This implies a <code>file://</code> URI.</p>
310      * <pre>
311      *      <code>
312      *      &lt;configuration&gt;
313      *          ...
314      *          &lt;sourceType&gt;dtd&lt;/sourceType&gt;
315      *          &lt;sources&gt;
316      *              &lt;source&gt;src/main/dtd&lt;/source&gt;
317      *          &lt;/sources&gt;
318      *          &lt;externalEntityProcessing&gt;true&lt;/externalEntityProcessing&gt;
319      *      &lt;/configuration&gt;
320      *      </code>
321      * </pre>
322      *
323      * @since 2.4
324      */
325     @Parameter(defaultValue = "false")
326     protected boolean externalEntityProcessing;
327 
328     /**
329      * <p>Java generation is required if any of the file products is outdated/stale.</p>
330      * {@inheritDoc}
331      */
332     @Override
333     protected boolean isReGenerationRequired() {
334 
335         //
336         // Use the stale flag method to identify if we should re-generate the java source code from the supplied
337         // Xml Schema. Basically, we should regenerate the JAXB code if:
338         //
339         // a) The staleFile does not exist
340         // b) The staleFile exists and is older than one of the sources (XSD or XJB files).
341         //    "Older" is determined by comparing the modification timestamp of the staleFile and the source files.
342         //
343         final File staleFile = getStaleFile();
344         final String debugPrefix = "StaleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]";
345 
346         boolean stale = !staleFile.exists();
347         if (stale) {
348             getLog().debug(debugPrefix + " not found. JAXB (re-)generation required.");
349         } else {
350 
351             final List<URL> sourceXSDs = getSources();
352             final List<File> sourceXJBs = getSourceXJBs();
353 
354             if (getLog().isDebugEnabled()) {
355                 getLog().debug(debugPrefix + " found. Checking timestamps on source XSD and XJB "
356                         + "files to determine if JAXB (re-)generation is required.");
357             }
358 
359             final long staleFileLastModified = staleFile.lastModified();
360             for (URL current : sourceXSDs) {
361 
362                 final URLConnection sourceXsdConnection;
363                 try {
364                     sourceXsdConnection = current.openConnection();
365                     sourceXsdConnection.connect();
366                 } catch (Exception e) {
367 
368                     // Can't determine if the staleFile is younger than this sourceXSD.
369                     // Re-generate to be on the safe side.
370                     stale = true;
371                     break;
372                 }
373 
374                 try {
375                     if (sourceXsdConnection.getLastModified() > staleFileLastModified) {
376 
377                         if (getLog().isDebugEnabled()) {
378                             getLog().debug(current.toString() + " is newer than the stale flag file.");
379                         }
380                         stale = true;
381                     }
382                 } finally {
383                     if (sourceXsdConnection instanceof HttpURLConnection) {
384                         ((HttpURLConnection) sourceXsdConnection).disconnect();
385                     }
386                 }
387             }
388 
389             for (File current : sourceXJBs) {
390                 if (current.lastModified() > staleFileLastModified) {
391 
392                     if (getLog().isDebugEnabled()) {
393                         getLog().debug(FileSystemUtilities.getCanonicalPath(current)
394                                 + " is newer than the stale flag file.");
395                     }
396 
397                     stale = true;
398                     break;
399                 }
400             }
401         }
402 
403         // All done.
404         return stale;
405     }
406 
407     /**
408      * {@inheritDoc}
409      */
410     @Override
411     protected boolean performExecution() throws MojoExecutionException, MojoFailureException {
412 
413         boolean updateStaleFileTimestamp = false;
414 
415         try {
416 
417             // Setup the Tool's execution environment
418             ToolExecutionEnvironment environment = null;
419             try {
420 
421                 // Create a LocaleFacet if the user has configured an explicit Locale for the tool.
422                 final LocaleFacet localeFacet = locale == null ? null : LocaleFacet.createFor(locale, getLog());
423 
424                 // Create the ToolExecutionEnvironment
425                 environment = new ToolExecutionEnvironment(getLog(),
426                         ThreadContextClassLoaderBuilder.createFor(this.getClass(), getLog(), getEncoding(false))
427                                 .addPaths(getClasspath()),
428                         LoggingHandlerEnvironmentFacet.create(getLog(), getClass(), getEncoding(false)),
429                         localeFacet);
430 
431                 // Add any extra configured EnvironmentFacets, as configured in the POM.
432                 if (extraFacets != null) {
433                     for (EnvironmentFacet current : extraFacets) {
434                         environment.add(current);
435                     }
436                 }
437 
438                 // Handle extended properties?
439                 if (externalEntityProcessing) {
440 
441                     final List<SystemPropertyChangeEnvironmentFacet> sysPropChanges =
442                             SystemPropertyChangeEnvironmentFacet.getBuilder(getLog())
443                                     .addOrChange("enableExternalEntityProcessing", "" + externalEntityProcessing)
444                                     .build();
445 
446                     for (SystemPropertyChangeEnvironmentFacet current : sysPropChanges) {
447                         environment.add(current);
448                     }
449                 }
450 
451                 // XJC overwrites proxy properties if so inclined, so we use this facet to save them
452                 for (String key : PROXY_PROPERTY_KEYS) {
453                     environment.add(new SystemPropertySaveEnvironmentFacet(key, getLog()));
454                 }
455 
456                 // Setup the environment.
457                 environment.setup();
458 
459                 // Compile the XJC arguments
460                 final String[] xjcArguments = getXjcArguments(environment.getClassPathAsArgument(), episodeFileName);
461 
462                 // Ensure that the outputDirectory exists, but only clear it if does not already
463                 FileSystemUtilities.createDirectory(getOutputDirectory(), clearOutputDir);
464 
465                 // Do we need to re-create the episode file's parent directory.
466                 final boolean reCreateEpisodeFileParentDirectory = generateEpisode && clearOutputDir;
467                 if (reCreateEpisodeFileParentDirectory) {
468                     getEpisodeFile(episodeFileName);
469                 }
470 
471                 // Check the system properties.
472                 logSystemPropertiesAndBasedir();
473 
474                 // Fire XJC
475                 if (XJC_COMPLETED_OK != Driver.run(xjcArguments, new XjcLogAdapter(getLog()))) {
476 
477                     final StringBuilder errorMsgBuilder = new StringBuilder();
478                     errorMsgBuilder.append("\n+=================== [XJC Error]\n");
479                     errorMsgBuilder.append("|\n");
480 
481                     final List<URL> sourceXSDs = getSources();
482                     for (int i = 0; i < sourceXSDs.size(); i++) {
483                         errorMsgBuilder.append("| " + i + ": ").append(sourceXSDs.get(i).toString()).append("\n");
484                     }
485 
486                     errorMsgBuilder.append("|\n");
487                     errorMsgBuilder.append("+=================== [End XJC Error]\n");
488                     throw new MojoExecutionException(errorMsgBuilder.toString());
489                 }
490 
491                 // Indicate that the output directory was updated.
492                 getBuildContext().refresh(getOutputDirectory());
493 
494                 // Update the modification timestamp of the staleFile.
495                 updateStaleFileTimestamp = true;
496 
497             } finally {
498 
499                 if (environment != null) {
500                     environment.restore();
501                 }
502             }
503 
504             // Add the generated source root to the project, enabling tooling and other plugins to see them.
505             addGeneratedSourcesToProjectSourceRoot();
506 
507             // Copy all source XSDs to the resulting artifact?
508             if (xsdPathWithinArtifact != null) {
509 
510                 final String buildOutputDirectory = getProject().getBuild().getOutputDirectory();
511                 final File targetXsdDirectory = new File(buildOutputDirectory, xsdPathWithinArtifact);
512                 FileUtils.forceMkdir(targetXsdDirectory);
513 
514                 for (URL current : getSources()) {
515 
516                     String fileName = null;
517                     if ("file".equalsIgnoreCase(current.getProtocol())) {
518                         fileName = new File(current.getPath()).getName();
519                     } else if ("jar".equalsIgnoreCase(current.getProtocol())) {
520 
521                         // Typical JAR path
522                         // jar:file:/path/to/aJar.jar!/some/path/xsd/aResource.xsd
523                         final int bangIndex = current.toString().indexOf("!");
524                         if (bangIndex == -1) {
525                             throw new MojoExecutionException("Illegal JAR URL [" + current.toString()
526                                     + "]: lacks a '!'");
527                         }
528 
529                         final String internalPath = current.toString().substring(bangIndex + 1);
530                         fileName = new File(internalPath).getName();
531                     } else {
532                         throw new MojoExecutionException("Could not extract FileName from URL [" + current + "]");
533                     }
534 
535                     final File targetFile = new File(targetXsdDirectory, fileName);
536                     if (targetFile.exists()) {
537 
538                         // TODO: Should we throw an exception here instead?
539                         getLog().warn("File [" + FileSystemUtilities.getCanonicalPath(targetFile)
540                                 + "] already exists. Not copying XSD file [" + current.getPath() + "] to it.");
541                     }
542                     IOUtil.copy(current.openStream(), new FileWriter(targetFile));
543                 }
544 
545                 // Refresh the BuildContext
546                 getBuildContext().refresh(targetXsdDirectory);
547             }
548         } catch (MojoExecutionException e) {
549             throw e;
550         } catch (NoSchemasException e) {
551             if (failOnNoSchemas) {
552                 throw new MojoExecutionException("", e);
553             }
554         } catch (Exception e) {
555             throw new MojoExecutionException(e.getMessage(), e);
556         }
557 
558         // All done.
559         return updateStaleFileTimestamp;
560     }
561 
562     /**
563      * Override this method to acquire a List holding all URLs to the JAXB sources for which this
564      * AbstractJavaGeneratorMojo should generate Java files. Sources are assumed to be in the form given by
565      * the {@code sourceType} value.
566      *
567      * @return A non-null List holding URLs to sources for the XJC generation.
568      * @see #sourceType
569      */
570     @Override
571     protected abstract List<URL> getSources();
572 
573     /**
574      * Override this method to retrieve a list of Files to all XJB files for which this
575      * AbstractJavaGeneratorMojo should generate Java files.
576      *
577      * @return A non-null List holding binding files.
578      */
579     protected abstract List<File> getSourceXJBs();
580 
581     /**
582      * Adds any directories containing the generated XJC classes to the appropriate Project compilation sources;
583      * either {@code TestCompileSourceRoot} or {@code CompileSourceRoot} depending on the exact Mojo implementation
584      * of this AbstractJavaGeneratorMojo.
585      */
586     protected abstract void addGeneratedSourcesToProjectSourceRoot();
587 
588     //
589     // Private helpers
590     //
591 
592     private String[] getXjcArguments(final String classPath, final String episodeFileNameOrNull)
593             throws MojoExecutionException, NoSchemasException {
594 
595         final ArgumentBuilder builder = new ArgumentBuilder();
596 
597         // Add all flags on the form '-flagName'
598         builder.withFlag(true, sourceType.getXjcArgument());
599         builder.withFlag(noPackageLevelAnnotations, "npa");
600         builder.withFlag(laxSchemaValidation, "nv");
601         builder.withFlag(verbose, "verbose");
602         builder.withFlag(quiet, "quiet");
603         builder.withFlag(enableIntrospection, "enableIntrospection");
604         builder.withFlag(extension, "extension");
605         builder.withFlag(readOnly, "readOnly");
606         builder.withFlag(noGeneratedHeaderComments, "no-header");
607         builder.withFlag(addGeneratedAnnotation, "mark-generated");
608 
609         // Add all arguments on the form '-argumentName argumentValue'
610         // (i.e. in 2 separate elements of the returned String[])
611         builder.withNamedArgument("httpproxy", getProxyString(settings.getActiveProxy()));
612         builder.withNamedArgument("encoding", getEncoding(true));
613         builder.withNamedArgument("p", packageName);
614         builder.withNamedArgument("target", target);
615         builder.withNamedArgument("d", getOutputDirectory().getAbsolutePath());
616         builder.withNamedArgument("classpath", classPath);
617 
618         // We must add the -extension flag in order to generate the episode file.
619         if (!extension) {
620 
621             if (getLog().isInfoEnabled()) {
622                 getLog().info("Adding 'extension' flag to XJC arguments, to generate an episode "
623                         + "file named '" + (episodeFileName == null ? STANDARD_EPISODE_FILENAME : episodeFileName)
624                         + "'. (XJCs 'episode' argument requires that the 'extension' argument is provided).");
625             }
626         }
627         builder.withFlag(true, "extension");
628 
629         final File episodeFile = getEpisodeFile(episodeFileNameOrNull);
630         builder.withNamedArgument("episode", FileSystemUtilities.getCanonicalPath(episodeFile));
631 
632         if (catalog != null) {
633             builder.withNamedArgument("catalog", FileSystemUtilities.getCanonicalPath(catalog));
634         }
635 
636         if (arguments != null) {
637             builder.withPreCompiledArguments(arguments);
638         }
639 
640         for (File current : getSourceXJBs()) {
641 
642             // Shorten the argument?
643             // final String strippedXjbPath = FileSystemUtilities.relativize(
644             //         current.getAbsolutePath(), getProject().getBasedir());
645 
646             // Each XJB must be given as a separate argument.
647             builder.withPreCompiledArguments(Arrays.asList("-b", current.getAbsolutePath()));
648         }
649 
650         final List<URL> sourceXSDs = getSources();
651         if (sourceXSDs.isEmpty()) {
652 
653             // If we have no XSDs, we are not going to be able to run XJC.
654             getLog().warn("No XSD files found. Please check your plugin configuration.");
655             throw new NoSchemasException();
656 
657         } else {
658 
659             final List<String> unwrappedSourceXSDs = new ArrayList<String>();
660             for (URL current : sourceXSDs) {
661 
662                 // Shorten the argument if possible.
663                 if ("file".equalsIgnoreCase(current.getProtocol())) {
664                     unwrappedSourceXSDs.add(FileSystemUtilities.relativize(
665                             current.getPath(),
666                             new File(System.getProperty("user.dir")),
667                             true));
668                 } else {
669                     unwrappedSourceXSDs.add(current.toString());
670                 }
671             }
672 
673             builder.withPreCompiledArguments(unwrappedSourceXSDs);
674         }
675 
676         // All done.
677         return logAndReturnToolArguments(builder.build(), "XJC");
678     }
679 
680     private String getProxyString(final Proxy activeProxy) {
681 
682         // Check sanity
683         if (activeProxy == null) {
684             return null;
685         }
686 
687         // The XJC proxy argument should be on the form
688         // [user[:password]@]proxyHost[:proxyPort]
689         //
690         // builder.withNamedArgument("httpproxy", httpproxy);
691         //
692         final StringBuilder proxyBuilder = new StringBuilder();
693         if (activeProxy.getUsername() != null) {
694 
695             // Start with the username.
696             proxyBuilder.append(activeProxy.getUsername());
697 
698             // Append the password if provided.
699             if (activeProxy.getPassword() != null) {
700                 proxyBuilder.append(":").append(activeProxy.getPassword());
701             }
702 
703             proxyBuilder.append("@");
704         }
705 
706         // Append hostname and port.
707         proxyBuilder.append(activeProxy.getHost()).append(":").append(activeProxy.getPort());
708 
709         // All done.
710         return proxyBuilder.toString();
711     }
712 }