1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.internal.ws.wscompile;
  27 
  28 import com.sun.codemodel.internal.JCodeModel;
  29 import com.sun.tools.internal.ws.processor.generator.GeneratorExtension;
  30 import com.sun.tools.internal.ws.resources.ConfigurationMessages;
  31 import com.sun.tools.internal.ws.resources.WscompileMessages;
  32 import com.sun.tools.internal.ws.util.ForkEntityResolver;
  33 import com.sun.tools.internal.ws.wsdl.document.jaxws.JAXWSBindingsConstants;
  34 import com.sun.tools.internal.ws.wsdl.document.schema.SchemaConstants;
  35 import com.sun.tools.internal.xjc.api.SchemaCompiler;
  36 import com.sun.tools.internal.xjc.api.SpecVersion;
  37 import com.sun.tools.internal.xjc.api.XJC;
  38 import com.sun.tools.internal.xjc.reader.Util;
  39 import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
  40 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
  41 import com.sun.xml.internal.ws.util.ServiceFinder;
  42 import com.sun.xml.internal.ws.util.JAXWSUtils;
  43 import com.sun.xml.internal.ws.util.xml.XmlUtil;
  44 import org.w3c.dom.Element;
  45 import org.xml.sax.EntityResolver;
  46 import org.xml.sax.InputSource;
  47 import org.xml.sax.helpers.LocatorImpl;
  48 
  49 import javax.xml.namespace.QName;
  50 import javax.xml.stream.XMLStreamReader;
  51 
  52 import java.io.ByteArrayInputStream;
  53 import java.io.ByteArrayOutputStream;
  54 import java.io.File;
  55 import java.io.IOException;
  56 import java.io.InputStream;
  57 import java.io.Reader;
  58 import java.lang.reflect.Array;
  59 import java.net.MalformedURLException;
  60 import java.net.URL;
  61 import java.util.ArrayList;
  62 import java.util.Arrays;
  63 import java.util.List;
  64 import java.util.HashMap;
  65 
  66 /**
  67  * @author Vivek Pandey
  68  */
  69 public class WsimportOptions extends Options {
  70     /**
  71      * -wsdlLocation
  72      */
  73     public String wsdlLocation;
  74 
  75     /**
  76      * Actually stores {@link com.sun.org.apache.xml.internal.resolver.tools.CatalogResolver}, but the field
  77      * type is made to {@link org.xml.sax.EntityResolver} so that XJC can be
  78      * used even if resolver.jar is not available in the classpath.
  79      */
  80     public EntityResolver entityResolver = null;
  81 
  82     /**
  83      * The -p option that should control the default Java package that
  84      * will contain the generated code. Null if unspecified.
  85      */
  86     public String defaultPackage = null;
  87 
  88     /**
  89      * The -clientjar option to package client artifacts as jar
  90      */
  91     public String clientjar = null;
  92 
  93     /**
  94      * -XadditionalHeaders
  95      */
  96     public boolean additionalHeaders;
  97 
  98     /**
  99      * The option indicates the dir where the jwsImpl will be generated.
 100      */
 101     public File implDestDir = null;
 102 
 103     /**
 104      * optional, generated impl file only for the ordered serviceName
 105      * Note: It is a QName string, formatted as: "{" + Namespace URI + "}" + local part
 106      */
 107     public String implServiceName = null;
 108 
 109     /**
 110      * optional, generated impl file only for the ordered portName
 111      * Note: It is a QName string, formatted as: "{" + Namespace URI + "}" + local part
 112      */
 113     public String implPortName = null;
 114 
 115     /**
 116      * optional, if true JWS file is generated
 117      */
 118     public boolean isGenerateJWS = false;
 119 
 120     /**
 121      * Setting disableSSLHostVerification to true disables the SSL Hostname verification while fetching the wsdls.
 122      * -XdisableSSLHostVerification
 123      */
 124     public boolean disableSSLHostnameVerification;
 125 
 126     /**
 127      * Setting useBaseResourceAndURLToLoadWSDL to true causes generated Service classes to load the WSDL file from
 128      * a URL generated from the base resource.
 129      * -XuseBaseResourceAndURLToLoadWSDL
 130      */
 131     public boolean useBaseResourceAndURLToLoadWSDL = false;
 132 
 133     /**
 134      * JAXB's {@link SchemaCompiler} to be used for handling the schema portion.
 135      * This object is also configured through options.
 136      */
 137     private SchemaCompiler schemaCompiler = XJC.createSchemaCompiler();
 138 
 139     /**
 140      * Authentication file
 141      */
 142     public File authFile;
 143 
 144     /**
 145      * Setting disableAuthenticator to true disables the DefaultAuthenticator.
 146      * -XdisableAuthenticator
 147      */
 148     public boolean disableAuthenticator;
 149 
 150     /**
 151      * Additional arguments
 152      */
 153     public HashMap<String, String> extensionOptions = new HashMap<String, String>();
 154 
 155     /**
 156      * All discovered {@link Plugin}s.
 157      * This is lazily parsed, so that we can take '-cp' option into account.
 158      *
 159      * @see #getAllPlugins()
 160      */
 161     private List<Plugin> allPlugins;
 162 
 163     /**
 164      * {@link Plugin}s that are enabled in this compilation.
 165      */
 166     public final List<Plugin> activePlugins = new ArrayList<Plugin>();
 167 
 168     public JCodeModel getCodeModel() {
 169         if(codeModel == null)
 170             codeModel = new JCodeModel();
 171         return codeModel;
 172     }
 173 
 174     public SchemaCompiler getSchemaCompiler() {
 175         schemaCompiler.setTargetVersion(SpecVersion.parse(target.getVersion()));
 176         if(entityResolver != null) {
 177             //set if its not null so as not to override catalog option specified via xjc args
 178             schemaCompiler.setEntityResolver(entityResolver);
 179         }
 180         return schemaCompiler;
 181     }
 182 
 183     public void setCodeModel(JCodeModel codeModel) {
 184         this.codeModel = codeModel;
 185     }
 186 
 187     private JCodeModel codeModel;
 188 
 189     /**
 190      * This captures jars passed on the commandline and passes them to XJC and puts them in the classpath for compilation
 191      */
 192     public List<String> cmdlineJars = new ArrayList<String>();
 193 
 194     /**
 195      * Gets all the {@link Plugin}s discovered so far.
 196      *
 197      * <p>
 198      * A plugins are enumerated when this method is called for the first time,
 199      * by taking {@link #classpath} into account. That means
 200      * "-cp plugin.jar" has to come before you specify options to enable it.
 201      */
 202     public List<Plugin> getAllPlugins() {
 203         if(allPlugins==null) {
 204             allPlugins = new ArrayList<Plugin>();
 205             allPlugins.addAll(Arrays.asList(findServices(Plugin.class, getClassLoader())));
 206         }
 207         return allPlugins;
 208     }
 209 
 210     /**
 211      * Parses arguments and fill fields of this object.
 212      *
 213      * @exception BadCommandLineException
 214      *      thrown when there's a problem in the command-line arguments
 215      */
 216     @Override
 217     public final void parseArguments( String[] args ) throws BadCommandLineException {
 218 
 219         for (int i = 0; i < args.length; i++) {
 220             if(args[i].length()==0)
 221                 throw new BadCommandLineException();
 222             if (args[i].charAt(0) == '-') {
 223                 int j = parseArguments(args,i);
 224                 if(j==0)
 225                     throw new BadCommandLineException(WscompileMessages.WSCOMPILE_INVALID_OPTION(args[i]));
 226                 i += (j-1);
 227             } else {
 228                 if(args[i].endsWith(".jar")) {
 229 
 230                     try {
 231                 cmdlineJars.add(args[i]);
 232                 schemaCompiler.getOptions().scanEpisodeFile(new File(args[i]));
 233 
 234             } catch (com.sun.tools.internal.xjc.BadCommandLineException e) {
 235                 //Driver.usage(jaxbOptions,false);
 236                 throw new BadCommandLineException(e.getMessage(), e);
 237             }
 238                 } else{
 239                     addFile(args[i]);
 240                 }
 241             }
 242         }
 243         if(destDir == null)
 244             destDir = new File(".");
 245         if(sourceDir == null)
 246             sourceDir = destDir;
 247     }
 248 
 249     /** -Xno-addressing-databinding option to disable addressing namespace data binding. This is
 250      * experimental switch and will be working as a temporary workaround till
 251      * jaxb can provide a better way to selelctively disable compiling of an
 252      * schema component.
 253      * **/
 254     public boolean noAddressingBbinding;
 255 
 256     @Override
 257     public int parseArguments(String[] args, int i) throws BadCommandLineException {
 258         int j = super.parseArguments(args ,i);
 259         if(j>0) return j;   // understood by the super class
 260 
 261         if (args[i].equals("-b")) {
 262             addBindings(requireArgument("-b", args, ++i));
 263             return 2;
 264         } else if (args[i].equals("-wsdllocation")) {
 265             wsdlLocation = requireArgument("-wsdllocation", args, ++i);
 266             return 2;
 267         } else if (args[i].equals("-XadditionalHeaders")) {
 268             additionalHeaders = true;
 269             return 1;
 270         } else if (args[i].equals("-XdisableSSLHostnameVerification")) {
 271             disableSSLHostnameVerification = true;
 272             return 1;
 273         } else if (args[i].equals("-p")) {
 274             defaultPackage = requireArgument("-p", args, ++i);
 275             return 2;
 276         } else if (args[i].equals("-catalog")) {
 277             String catalog = requireArgument("-catalog", args, ++i);
 278             try {
 279                 if (entityResolver == null) {
 280                     if (catalog != null && catalog.length() > 0)
 281                         entityResolver = XmlUtil.createEntityResolver(JAXWSUtils.getFileOrURL(JAXWSUtils.absolutize(Util.escapeSpace(catalog))));
 282                 } else if (catalog != null && catalog.length() > 0) {
 283                     EntityResolver er = XmlUtil.createEntityResolver(JAXWSUtils.getFileOrURL(JAXWSUtils.absolutize(Util.escapeSpace(catalog))));
 284                     entityResolver = new ForkEntityResolver(er, entityResolver);
 285                 }
 286             } catch (IOException e) {
 287                 throw new BadCommandLineException(WscompileMessages.WSIMPORT_FAILED_TO_PARSE(catalog, e.getMessage()));
 288             }
 289             return 2;
 290         } else if (args[i].startsWith("-httpproxy:")) {
 291             String value = args[i].substring(11);
 292             if (value.length() == 0) {
 293                 throw new BadCommandLineException(WscompileMessages.WSCOMPILE_INVALID_OPTION(args[i]));
 294             }
 295             int index = value.indexOf(':');
 296             if (index == -1) {
 297                 System.setProperty("proxySet", "true");
 298                 System.setProperty("proxyHost", value);
 299                 System.setProperty("proxyPort", "8080");
 300             } else {
 301                 System.setProperty("proxySet", "true");
 302                 System.setProperty("proxyHost", value.substring(0, index));
 303                 System.setProperty("proxyPort", value.substring(index + 1));
 304             }
 305             return 1;
 306         } else if (args[i].equals("-Xno-addressing-databinding")) {
 307             noAddressingBbinding = true;
 308             return 1;
 309         } else if (args[i].startsWith("-B")) {
 310             // JAXB option pass through.
 311             String[] subCmd = new String[args.length-i];
 312             System.arraycopy(args,i,subCmd,0,subCmd.length);
 313             subCmd[0] = subCmd[0].substring(2); // trim off the first "-B"
 314 
 315             com.sun.tools.internal.xjc.Options jaxbOptions = schemaCompiler.getOptions();
 316             try {
 317                 int r = jaxbOptions.parseArgument(subCmd, 0);
 318                 if(r==0) {
 319                     //Driver.usage(jaxbOptions,false);
 320                     throw new BadCommandLineException(WscompileMessages.WSIMPORT_NO_SUCH_JAXB_OPTION(subCmd[0]));
 321                 }
 322                 return r;
 323             } catch (com.sun.tools.internal.xjc.BadCommandLineException e) {
 324                 //Driver.usage(jaxbOptions,false);
 325                 throw new BadCommandLineException(e.getMessage(),e);
 326             }
 327         } else if (args[i].equals("-Xauthfile")) {
 328             String authfile = requireArgument("-Xauthfile", args, ++i);
 329             authFile = new File(authfile);
 330             return 2;
 331         } else if (args[i].equals("-clientjar")) {
 332             clientjar = requireArgument("-clientjar", args, ++i);
 333             return 2;
 334         } else if (args[i].equals("-implDestDir")) {
 335                         implDestDir = new File(requireArgument("-implDestDir", args, ++i));
 336             if (!implDestDir.exists())
 337               throw new BadCommandLineException(WscompileMessages.WSCOMPILE_NO_SUCH_DIRECTORY(implDestDir.getPath()));
 338                         return 2;
 339         } else if (args[i].equals("-implServiceName")) {
 340                 implServiceName = requireArgument("-implServiceName", args, ++i);
 341           return 2;
 342         } else if (args[i].equals("-implPortName")) {
 343                 implPortName = requireArgument("-implPortName", args, ++i);
 344           return 2;
 345         } else if (args[i].equals("-generateJWS")) {
 346             isGenerateJWS = true;
 347             return 1;
 348         } else if (args[i].equals("-XuseBaseResourceAndURLToLoadWSDL")) {
 349                 useBaseResourceAndURLToLoadWSDL = true;
 350             return 1;
 351         } else if (args[i].equals("-XdisableAuthenticator")) {
 352             disableAuthenticator = true;
 353             return 1;
 354         }
 355 
 356         // handle additional options
 357         for (GeneratorExtension f:ServiceFinder.find(GeneratorExtension.class)) {
 358             if (f.validateOption(args[i])) {
 359                 extensionOptions.put(args[i], requireArgument(args[i], args, ++i));
 360                 return 2;
 361             }
 362         }
 363 
 364         // see if this is one of the extensions
 365         for( Plugin plugin : getAllPlugins() ) {
 366             try {
 367                 if(('-' + plugin.getOptionName()).equals(args[i])) {
 368                     activePlugins.add(plugin);
 369                     plugin.onActivated(this);
 370                     return 1;
 371                 }
 372                 int r = plugin.parseArgument(this, args, i);
 373                 if (r != 0) {
 374                     return r;
 375                 }
 376             } catch (IOException e) {
 377                 throw new BadCommandLineException(e.getMessage(),e);
 378             }
 379         }
 380 
 381         return 0; // what's this option?
 382     }
 383 
 384     public void validate() throws BadCommandLineException {
 385         if (wsdls.isEmpty()) {
 386             throw new BadCommandLineException(WscompileMessages.WSIMPORT_MISSING_FILE());
 387         }
 388 
 389         if(wsdlLocation !=null && clientjar != null) {
 390            throw new BadCommandLineException(WscompileMessages.WSIMPORT_WSDLLOCATION_CLIENTJAR());
 391         }
 392         if(wsdlLocation == null){
 393             wsdlLocation = wsdls.get(0).getSystemId();
 394         }
 395 
 396 
 397     }
 398 
 399     @Override
 400     protected void addFile(String arg) throws BadCommandLineException {
 401         addFile(arg, wsdls, ".wsdl");
 402     }
 403 
 404     private final List<InputSource> wsdls = new ArrayList<InputSource>();
 405     private final List<InputSource> schemas = new ArrayList<InputSource>();
 406     private final List<InputSource> bindingFiles = new ArrayList<InputSource>();
 407     private final List<InputSource> jaxwsCustomBindings = new ArrayList<InputSource>();
 408     private final List<InputSource> jaxbCustomBindings = new ArrayList<InputSource>();
 409     private final List<Element> handlerConfigs = new ArrayList<Element>();
 410 
 411     /**
 412      * There is supposed to be one handler chain per generated SEI.
 413      * TODO: There is possible bug, how to associate a @HandlerChain
 414      * with each port on the generated SEI. For now lets preserve the JAXWS 2.0 FCS
 415      * behaviour and generate only one @HandlerChain on the SEI
 416      */
 417     public Element getHandlerChainConfiguration(){
 418         if(handlerConfigs.size() > 0)
 419             return handlerConfigs.get(0);
 420         return null;
 421     }
 422 
 423     public void addHandlerChainConfiguration(Element config){
 424         handlerConfigs.add(config);
 425     }
 426 
 427     public InputSource[] getWSDLs() {
 428         return wsdls.toArray(new InputSource[wsdls.size()]);
 429     }
 430 
 431     public InputSource[] getSchemas() {
 432         return schemas.toArray(new InputSource[schemas.size()]);
 433     }
 434 
 435     public InputSource[] getWSDLBindings() {
 436         return jaxwsCustomBindings.toArray(new InputSource[jaxwsCustomBindings.size()]);
 437     }
 438 
 439     public InputSource[] getSchemaBindings() {
 440         return jaxbCustomBindings.toArray(new InputSource[jaxbCustomBindings.size()]);
 441     }
 442 
 443     public void addWSDL(File source) {
 444         addWSDL(fileToInputSource(source));
 445     }
 446 
 447     public void addWSDL(InputSource is) {
 448         wsdls.add(absolutize(is));
 449     }
 450 
 451     public void addSchema(File source) {
 452         addSchema(fileToInputSource(source));
 453     }
 454 
 455     public void addSchema(InputSource is) {
 456         schemas.add(is);
 457     }
 458 
 459     private InputSource fileToInputSource(File source) {
 460         try {
 461             String url = source.toURL().toExternalForm();
 462             return new InputSource(Util.escapeSpace(url));
 463         } catch (MalformedURLException e) {
 464             return new InputSource(source.getPath());
 465         }
 466     }
 467 
 468     /**
 469      * Recursively scan directories and add all XSD files in it.
 470      */
 471     public void addGrammarRecursive(File dir) {
 472         addRecursive(dir, ".wsdl", wsdls);
 473         addRecursive(dir, ".xsd", schemas);
 474     }
 475 
 476     /**
 477      * Adds a new input schema.
 478      */
 479     public void addWSDLBindFile(InputSource is) {
 480         jaxwsCustomBindings.add(new RereadInputSource(absolutize(is)));
 481     }
 482 
 483     public void addSchemmaBindFile(InputSource is) {
 484         jaxbCustomBindings.add(new RereadInputSource(absolutize(is)));
 485     }
 486 
 487     private void addRecursive(File dir, String suffix, List<InputSource> result) {
 488         File[] files = dir.listFiles();
 489         if (files == null) return; // work defensively
 490 
 491         for (File f : files) {
 492             if (f.isDirectory())
 493                 addRecursive(f, suffix, result);
 494             else if (f.getPath().endsWith(suffix))
 495                 result.add(absolutize(fileToInputSource(f)));
 496         }
 497     }
 498 
 499     private InputSource absolutize(InputSource is) {
 500         // absolutize all the system IDs in the input,
 501         // so that we can map system IDs to DOM trees.
 502         try {
 503             URL baseURL = new File(".").getCanonicalFile().toURL();
 504             is.setSystemId(new URL(baseURL, is.getSystemId()).toExternalForm());
 505         } catch (IOException e) {
 506             // ignore
 507         }
 508         return is;
 509     }
 510 
 511     public void addBindings(String name) throws BadCommandLineException {
 512         addFile(name, bindingFiles, null);
 513     }
 514 
 515     /**
 516      * Parses a token to a file (or a set of files)
 517      * and add them as {@link InputSource} to the specified list.
 518      *
 519      * @param suffix If the given token is a directory name, we do a recusive search
 520      *               and find all files that have the given suffix.
 521      */
 522     private void addFile(String name, List<InputSource> target, String suffix) throws BadCommandLineException {
 523         Object src;
 524         try {
 525             src = Util.getFileOrURL(name);
 526         } catch (IOException e) {
 527             throw new BadCommandLineException(WscompileMessages.WSIMPORT_NOT_A_FILE_NOR_URL(name));
 528         }
 529         if (src instanceof URL) {
 530             target.add(absolutize(new InputSource(Util.escapeSpace(((URL) src).toExternalForm()))));
 531         } else {
 532             File fsrc = (File) src;
 533             if (fsrc.isDirectory()) {
 534                 addRecursive(fsrc, suffix, target);
 535             } else {
 536                 target.add(absolutize(fileToInputSource(fsrc)));
 537             }
 538         }
 539     }
 540 
 541 
 542     /**
 543      * Exposing it as a public method to allow external tools such as NB to read from wsdl model and work on it.
 544      * TODO: WSDL model needs to be exposed - basically at tool time we need to use the runtimw wsdl model
 545      *
 546      * Binding files could be jaxws or jaxb. This method identifies jaxws and jaxb binding files and keeps them separately. jaxb binding files are given separately
 547      * to JAXB in {@link com.sun.tools.internal.ws.processor.modeler.wsdl.JAXBModelBuilder}
 548      *
 549      * @param receiver {@link ErrorReceiver}
 550      */
 551     public final void parseBindings(ErrorReceiver receiver){
 552         for (InputSource is : bindingFiles) {
 553             XMLStreamReader reader =
 554                     XMLStreamReaderFactory.create(is,true);
 555             XMLStreamReaderUtil.nextElementContent(reader);
 556             if (reader.getName().equals(JAXWSBindingsConstants.JAXWS_BINDINGS)) {
 557                 jaxwsCustomBindings.add(new RereadInputSource(is));
 558             } else if (reader.getName().equals(JAXWSBindingsConstants.JAXB_BINDINGS) ||
 559                     reader.getName().equals(new QName(SchemaConstants.NS_XSD, "schema"))) {
 560                 jaxbCustomBindings.add(new RereadInputSource(is));
 561             } else {
 562                 LocatorImpl locator = new LocatorImpl();
 563                 locator.setSystemId(reader.getLocation().getSystemId());
 564                 locator.setPublicId(reader.getLocation().getPublicId());
 565                 locator.setLineNumber(reader.getLocation().getLineNumber());
 566                 locator.setColumnNumber(reader.getLocation().getColumnNumber());
 567                 receiver.warning(locator, ConfigurationMessages.CONFIGURATION_NOT_BINDING_FILE(is.getSystemId()));
 568             }
 569         }
 570     }
 571 
 572     /**
 573      * Get extension argument
 574      */
 575     public String getExtensionOption(String argument) {
 576         return extensionOptions.get(argument);
 577     }
 578 
 579     /**
 580      * Looks for all "META-INF/services/[className]" files and
 581      * create one instance for each class name found inside this file.
 582      */
 583     private static <T> T[] findServices(Class<T> clazz, ClassLoader classLoader) {
 584         ServiceFinder<T> serviceFinder = ServiceFinder.find(clazz, classLoader);
 585         List<T> r = new ArrayList<T>();
 586         for (T t : serviceFinder) {
 587             r.add(t);
 588         }
 589         return r.toArray((T[]) Array.newInstance(clazz, r.size()));
 590     }
 591 
 592     private static final class ByteStream extends ByteArrayOutputStream {
 593         byte[] getBuffer() {
 594                 return buf;
 595         }
 596     }
 597 
 598     private static final class RereadInputStream extends InputStream {
 599         private InputStream is;
 600         private ByteStream bs;
 601 
 602         RereadInputStream(InputStream is) {
 603                 this.is = is;
 604                 this.bs = new ByteStream();
 605         }
 606 
 607                 @Override
 608                 public int available() throws IOException {
 609                         return is.available();
 610                 }
 611 
 612                 @Override
 613                 public void close() throws IOException {
 614                         if (bs != null) {
 615                                 InputStream i = new ByteArrayInputStream(bs.getBuffer());
 616                                 bs = null;
 617                                 is.close();
 618                                 is = i;
 619                         }
 620                 }
 621 
 622                 @Override
 623                 public synchronized void mark(int readlimit) {
 624                         is.mark(readlimit);
 625                 }
 626 
 627                 @Override
 628                 public boolean markSupported() {
 629                         return is.markSupported();
 630                 }
 631 
 632                 @Override
 633                 public int read() throws IOException {
 634                         int r = is.read();
 635                         if (bs != null)
 636                                 bs.write(r);
 637                         return r;
 638                 }
 639 
 640                 @Override
 641                 public int read(byte[] b, int off, int len) throws IOException {
 642                         int r = is.read(b, off, len);
 643                         if (r > 0 && bs != null)
 644                                 bs.write(b, off, r);
 645                         return r;
 646                 }
 647 
 648                 @Override
 649                 public int read(byte[] b) throws IOException {
 650                         int r = is.read(b);
 651                         if (r > 0 && bs != null)
 652                                 bs.write(b, 0, r);
 653                         return r;
 654                 }
 655 
 656                 @Override
 657                 public synchronized void reset() throws IOException {
 658                         is.reset();
 659                 }
 660     }
 661 
 662     private static final class RereadInputSource extends InputSource {
 663         private InputSource is;
 664 
 665         RereadInputSource(InputSource is) {
 666                 this.is = is;
 667         }
 668 
 669                 @Override
 670                 public InputStream getByteStream() {
 671                         InputStream i = is.getByteStream();
 672                         if (i != null && !(i instanceof RereadInputStream)) {
 673                                 i = new RereadInputStream(i);
 674                                 is.setByteStream(i);
 675                         }
 676                         return i;
 677                 }
 678 
 679                 @Override
 680                 public Reader getCharacterStream() {
 681                         // TODO Auto-generated method stub
 682                         return is.getCharacterStream();
 683                 }
 684 
 685                 @Override
 686                 public String getEncoding() {
 687                         return is.getEncoding();
 688                 }
 689 
 690                 @Override
 691                 public String getPublicId() {
 692                         return is.getPublicId();
 693                 }
 694 
 695                 @Override
 696                 public String getSystemId() {
 697                         return is.getSystemId();
 698                 }
 699 
 700                 @Override
 701                 public void setByteStream(InputStream byteStream) {
 702                         is.setByteStream(byteStream);
 703                 }
 704 
 705                 @Override
 706                 public void setCharacterStream(Reader characterStream) {
 707                         is.setCharacterStream(characterStream);
 708                 }
 709 
 710                 @Override
 711                 public void setEncoding(String encoding) {
 712                         is.setEncoding(encoding);
 713                 }
 714 
 715                 @Override
 716                 public void setPublicId(String publicId) {
 717                         is.setPublicId(publicId);
 718                 }
 719 
 720                 @Override
 721                 public void setSystemId(String systemId) {
 722                         is.setSystemId(systemId);
 723                 }
 724     }
 725 }