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