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