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