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