You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by up...@apache.org on 2003/06/04 11:25:53 UTC

cvs commit: cocoon-2.1/src/java/org/apache/cocoon/bean/destination Destination.java DestinationNotFoundException.java FileDestination.java NullDestination.java ThreadedDestination.java

upayavira    2003/06/04 02:25:53

  Modified:    .        cli.xconf
               src/java/org/apache/cocoon Cocoon.java Constants.java
                        Main.java
               src/java/org/apache/cocoon/bean CocoonBean.java
               src/java/org/apache/cocoon/environment/commandline
                        AbstractCommandLineEnvironment.java
  Added:       src/java/org/apache/cocoon/bean/helpers
                        DelayedOutputStream.java
  Removed:     src/java/org/apache/cocoon/bean/destination Destination.java
                        DestinationNotFoundException.java
                        FileDestination.java NullDestination.java
                        ThreadedDestination.java
  Log:
  Added broken link functionality
  Replaced Destination object with ModifiableSources in CLI
  Moved CLI constants into CocoonBean class (as suggested by GP)
  Reworked getConfigurationFile and made it also look into WEB-INF for the config file (see bug 12158)
  
  Revision  Changes    Path
  1.2       +139 -8    cocoon-2.1/cli.xconf
  
  Index: cli.xconf
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/cli.xconf,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- cli.xconf	18 Mar 2003 15:23:28 -0000	1.1
  +++ cli.xconf	4 Jun 2003 09:25:52 -0000	1.2
  @@ -1,22 +1,153 @@
   <?xml version="1.0"?>
  -<!-- all links here are relative to the build context dir, that
  -     in the build.xml file is set to ${build.context} -->
  +<!--+
  +    |  This is the Apache Cocoon command line configuration file. 
  +    |  Here you give the command line interface details of where
  +    |  to find various aspects of your Cocoon installation.
  +    |
  +    |  If you wish, you can also use this file to specify the URIs
  +    |  that you wish to generate.
  +    |
  +    |  The current configuration information in this file is for
  +    |  building the Cocoon documentation. Therefore, all links here 
  +    |  are relative to the build context dir, which, in the build.xml 
  +    |  file, is set to ${build.context} 
  +    |
  +    |  Options:
  +    |    verbose:            increase amount of information presented
  +    |                        to standard output (default: false)
  +    |    follow-links:       whether linked pages should also be 
  +    |                        generated (default: true)
  +    |    precompile-only:    precompile sitemaps and XSP pages, but 
  +    |                        do not generate any pages (default: false)
  +    |    confirm-extensions: check the mime type for the generated page
  +    |                        and adjust filename and links extensions
  +    |                        to match the mime type 
  +    |                        (e.g. text/html->.html)
  +    +-->
  +    
   <cocoon verbose="true"  
           follow-links="true" 
           precompile-only="false" 
  -        confirm-extensions="true">
  +        confirm-extensions="false">
   
  -   <broken-link-file>../brokenlinks.txt</broken-link-file>
  -<!--<load-class>org.firebirdsql.jdbc.Driver</load-class>-->
  +   <!--+
  +       | Broken link reporting options:
  +       |   Report into a text file, one link per line:
  +       |     <broken-links type="text" report="filename"/>
  +       |   Report into an XML file:
  +       |     <broken-links type="xml" report="filename"/>
  +       |   Ignore broken links (default):
  +       |     <broken-links type="none"/>
  +       |   When a page includes an error, should a page be generated?
  +       |     
  +       |   Two attributes to this node specify whether a page should
  +       |   be generated when an error occured. 'generate' specifies 
  +       |   whether a page should be generated (default: true) and
  +       |   extension specifies an extension that should be appended
  +       |   to the generated page's filename (default: none)
  +       |     <broken-links generate="true" extension=".error.txt"/>
  +       |
  +       +-->
  +   <broken-links type="xml" 
  +                 file="../brokenlinks.xml"
  +                 generate="false"
  +                 extension=".error"/>
  +   
  +   <!--+
  +       |  Load classes at startup. This is necessary for generating
  +       |  from sites that use SQL databases and JDBC.
  +       |  The <load-class> element can be repeated if multiple classes
  +       |  are needed.
  +       +-->
  +   <!--
  +   <load-class>org.firebirdsql.jdbc.Driver</load-class>
  +   -->
  +
  +   <!--+
  +       |
  +       +-->
      <logging log-kit="./logkit.xconf" logger="cli" level="ERROR" />
  +
  +   <!--+
  +       |  The context directory is usually the webapp directory
  +       |  containing the sitemap.xmap file.
  +       |
  +       |  The config file is the cocoon.xconf file.
  +       |
  +       |  The work directory is used by Cocoon to store temporary
  +       |  files and cache files.
  +       |  
  +       |  The destination directory is where generated pages will
  +       |  be written (assuming the 'simple' mapper is used)
  +       +-->
      <context-dir>.</context-dir>
      <config-file>cocoon.xconf</config-file>
  -   <dest-dir>../docs</dest-dir>
      <work-dir>../temp/docs</work-dir>
  -<!--<user-agent>xxx</user-agent>-->
  +   <dest-dir>../docs</dest-dir>
  +
  +   <!--+
  +       | Specifies the filename to be appended to URIs that
  +       | refer to a directory (i.e. end with a forward slash).
  +       +-->
  +       
  +   <default-filename>index.html</default-filename>
  +
  +   <!--+
  +       |  Specifies a user agent string to the sitemap when
  +       |  generating the site.
  +       +-->
  +   
  +   <!--
  +   <user-agent>xxx</user-agent>
  +   -->
  +
  +   <!--+
  +       |  Specifies an accept string to the sitemap when generating
  +       |  the site.
  +       +-->
  +       
      <accept>*/*</accept>
  +   
  +   <!--+
  +       |  Specifies the URIs that should be generated (using <uri>
  +       |  elements, and (if necessary) what should be done with the
  +       |  generated pages.
  +       |
  +       |  The old behaviour - appends uri to the specified destination
  +       |  directory (as specified in <dest-dir>):
  +       |
  +       |   <uri>documents/index.html</uri>
  +       |
  +       |  Append: append the generated page's URI to the end of the 
  +       |  source URI:
  +       |
  +       |   <uri type="append" src-prefix="documents/" src="index.html"
  +       |   dest="build/dest/"/>
  +       |
  +       |  Replace: Completely ignore the generated page's URI - just 
  +       |  use the destination URI:
  +       |
  +       |   <uri type="replace" src-prefix="documents/" src="index.html" 
  +       |   dest="build/dest/docs.html"/>
  +       |
  +       |  Insert: Insert generated page's URI into the destination 
  +       |  URI at the point marked with a * (example uses fictional 
  +       |  zip protocol)
  +       |
  +       |   <uri type="insert" src-prefix="documents/" src="index.html" 
  +       |   dest="zip://*.zip/page.html"/>
  +       |
  +       +-->
      <uri>favicon.ico</uri>
  -<!--<uri-file></uri-file>-->
  +
  +   <!--+
  +       |  File containing URIs (plain text, one per
  +       |  line).
  +       +-->
  +  
  +   <!--
  +   <uri-file></uri-file>
  +   -->
      
   </cocoon>
   
  
  
  
  1.8       +4 -1      cocoon-2.1/src/java/org/apache/cocoon/Cocoon.java
  
  Index: Cocoon.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/Cocoon.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- Cocoon.java	23 May 2003 09:18:48 -0000	1.7
  +++ Cocoon.java	4 Jun 2003 09:25:53 -0000	1.8
  @@ -758,5 +758,8 @@
       public int getActiveRequestCount() {
           return activeRequestCount;
       }
  +    public ExcaliburComponentManager getComponentManager() {
  +        return this.componentManager;
  +    }
   }
   
  
  
  
  1.5       +1 -36     cocoon-2.1/src/java/org/apache/cocoon/Constants.java
  
  Index: Constants.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/Constants.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- Constants.java	23 May 2003 09:18:48 -0000	1.4
  +++ Constants.java	4 Jun 2003 09:25:53 -0000	1.5
  @@ -135,34 +135,6 @@
        */
       public static final String ACTION_PARAM_PREFIX = "cocoon-action-";
   
  -    /**
  -     * The directory to use as context root.
  -     *
  -     * FIXME(GP): Isn't this CLI specific?
  -     */
  -    public static final String DEFAULT_CONTEXT_DIR = "./webapp";
  -
  -    /**
  -     * The diretory to use to use for the generated output.
  -     *
  -     * FIXME(GP): Isn't this CLI specific?
  -     */
  -    public static final String DEFAULT_DEST_DIR = "./site";
  -
  -    /**
  -     * The diretory to use for generated files.
  -     *
  -     * FIXME(GP): Isn't this CLI specific?
  -     */
  -    public static final String DEFAULT_WORK_DIR = "./work";
  -
  -    /**
  -     * How a default configuration file is named.
  -     *
  -     * FIXME(GP): Isn't this CLI specific?
  -     */
  -    public static final String DEFAULT_CONF_FILE = "cocoon.xconf";
  -
       /** The name of the property holding the class for a XML parser */
       public static final String PARSER_PROPERTY = "org.apache.excalibur.xml.sax.SAXParser";
   
  @@ -297,13 +269,6 @@
        * The name of a <code>NotifyingObject</code> in the so called objectModel <code>Map</code>.
        */
       public static final String NOTIFYING_OBJECT = "notifying-object";
  -
  -    /**
  -     * Describe variable <code>INDEX_URI</code> here.
  -     *
  -     * FIXME(GP): It seems to be (CLI) Environment specific!
  -     */
  -    public static final String INDEX_URI = "index";
   
       /** The namespace URI for the Error/Exception XML */
       public static final String ERROR_NAMESPACE_URI = "http://apache.org/cocoon/error/" + CONF_VERSION;
  
  
  
  1.3       +176 -146  cocoon-2.1/src/java/org/apache/cocoon/Main.java
  
  Index: Main.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/Main.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- Main.java	18 Mar 2003 15:23:30 -0000	1.2
  +++ Main.java	4 Jun 2003 09:25:53 -0000	1.3
  @@ -47,8 +47,7 @@
    Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
    Software Foundation, please see <http://www.apache.org/>.
   
  -*/
  -package org.apache.cocoon;
  +*/package org.apache.cocoon;
   
   import java.io.BufferedReader;
   import java.io.FileReader;
  @@ -74,10 +73,8 @@
   import org.apache.commons.cli.HelpFormatter;
   
   import org.apache.cocoon.Constants;
  -import org.apache.cocoon.util.NetUtils;
   
   import org.apache.cocoon.bean.CocoonBean;
  -import org.apache.cocoon.bean.destination.FileDestination;
   
   /**
    * Command line entry point. Parses command line, create Cocoon bean and invokes it
  @@ -146,9 +143,14 @@
       private static final String NODE_DEST_DIR = "dest-dir";
       private static final String NODE_WORK_DIR = "work-dir";
       private static final String NODE_CONFIG_FILE = "config-file";
  -    private static final String NODE_BROKEN_LINK_FILE = "broken-link-file";
       private static final String NODE_URI_FILE = "uri-file";
   
  +    private static final String NODE_BROKEN_LINKS = "broken-links";
  +    private static final String ATTR_BROKEN_LINK_REPORT_TYPE = "type";
  +    private static final String ATTR_BROKEN_LINK_REPORT_FILE = "file";
  +    private static final String ATTR_BROKEN_LINK_GENERATE = "generate";
  +    private static final String ATTR_BROKEN_LINK_EXTENSION = "extension";
  +
       private static final String NODE_AGENT = "user-agent";
       private static final String NODE_ACCEPT = "accept";
   
  @@ -157,28 +159,16 @@
       private static final String ATTR_CONFIRM_EXTENSIONS = "confirm-extensions";
       private static final String NODE_LOAD_CLASS = "load-class";
       private static final String NODE_DEFAULT_FILENAME = "default-filename";
  +
  +
       private static final String NODE_URI = "uri";
  +    private static final String ATTR_URI_TYPE = "type";
  +    private static final String ATTR_URI_SOURCEPREFIX = "src-prefix";
  +    private static final String ATTR_URI_SOURCEURI = "src";
  +    private static final String ATTR_URI_DESTURI = "dest";
   
       private static Options options;
   
  -    private static boolean verbose = false;
  -    private static String logKit = null;
  -    private static String logger = null;
  -    private static String logLevel = "ERROR";
  -    private static String contextDir = Constants.DEFAULT_CONTEXT_DIR;
  -    private static String destDir = Constants.DEFAULT_DEST_DIR;
  -    private static String workDir = Constants.DEFAULT_WORK_DIR;
  -    private static String configFile = Constants.DEFAULT_CONF_FILE;
  -    private static String brokenLinkFile = null;
  -    private static String agentOptions = null;
  -    private static String acceptOptions = null;
  -    private static String defaultFilename = Constants.INDEX_URI;
  -    private static boolean precompileOnly = false;
  -    private static boolean followLinks = true;
  -    private static boolean confirmExtensions = true;
  -    private static List loadedClasses = new ArrayList();
  -    private static List targets = new ArrayList();
  -
       private static void setOptions() {
           options = new Options();
   
  @@ -300,6 +290,9 @@
   
           long startTimeMillis = System.currentTimeMillis();
   
  +        CocoonBean cocoon = new CocoonBean();
  +        String destDir = null;
  +
           Main.setOptions();
           CommandLineParser parser = new PosixParser();
           CommandLine line = parser.parse( options, args );
  @@ -307,108 +300,72 @@
           if (line.hasOption(HELP_OPT)) {
                printUsage();
           }
  -
           if (line.hasOption(VERSION_OPT)) {
                printVersion();
           }
  -
           if (line.hasOption(XCONF_OPT)) {
  -            Main.processXConf(line.getOptionValue(XCONF_OPT));
  +            Main.processXConf(cocoon, line.getOptionValue(XCONF_OPT));
           }
           if (line.hasOption(VERBOSE_OPT)) {
  -            verbose = true;
  +            cocoon.setVerbose(true);
           }
           if (line.hasOption(PRECOMPILE_ONLY_OPT)) {
  -            precompileOnly=true;
  +            cocoon.setPrecompileOnly(true);
  +        }
  +        if (line.hasOption(DEST_DIR_OPT)) {
  +            destDir = line.getOptionValue(DEST_DIR_OPT);
  +        }
  +        if (line.hasOption(WORK_DIR_OPT)) {
  +            cocoon.setWorkDir(line.getOptionValue(WORK_DIR_OPT));
  +        }
  +        if (line.hasOption(CONTEXT_DIR_OPT)) {
  +            cocoon.setContextDir(line.getOptionValue(CONTEXT_DIR_OPT));
  +        }
  +        if (line.hasOption(CONFIG_FILE_OPT)) {
  +            cocoon.setConfigFile(line.getOptionValue(CONFIG_FILE_OPT));
  +        }
  +        if (line.hasOption(LOG_KIT_OPT)) {
  +            cocoon.setLogKit(line.getOptionValue(LOG_KIT_OPT));
  +        }
  +        if (line.hasOption(LOGGER_OPT)) {
  +            cocoon.setLogger(line.getOptionValue(LOGGER_OPT));
  +        }
  +        if (line.hasOption(LOG_LEVEL_OPT)) {
  +            cocoon.setLogLevel(line.getOptionValue(LOG_LEVEL_OPT));
  +        }
  +        if (line.hasOption(AGENT_OPT)) {
  +            cocoon.setAgentOptions(line.getOptionValue(AGENT_OPT));
  +        }
  +        if (line.hasOption(ACCEPT_OPT)) {
  +            cocoon.setAcceptOptions(line.getOptionValue(ACCEPT_OPT));
  +        }
  +        if (line.hasOption(DEFAULT_FILENAME_OPT)) {
  +            cocoon.setDefaultFilename(line.getOptionValue(DEFAULT_FILENAME_OPT));
  +        }
  +        if (line.hasOption(BROKEN_LINK_FILE_OPT)) {
  +            cocoon.setBrokenLinkReportFile(line.getOptionValue(BROKEN_LINK_FILE_OPT));
           }
  -        destDir = line.getOptionValue(DEST_DIR_OPT, destDir);
  -        workDir = line.getOptionValue(WORK_DIR_OPT, workDir);
  -        contextDir = line.getOptionValue(CONTEXT_DIR_OPT, contextDir);
  -        configFile = line.getOptionValue(CONFIG_FILE_OPT, configFile);
  -        logKit = line.getOptionValue(LOG_KIT_OPT, logKit);
  -        logger = line.getOptionValue(LOGGER_OPT, logger);
  -        logLevel = line.getOptionValue(LOG_LEVEL_OPT, logLevel);
  -        agentOptions = line.getOptionValue(AGENT_OPT, agentOptions);
  -        acceptOptions = line.getOptionValue(ACCEPT_OPT, acceptOptions);
  -        defaultFilename = line.getOptionValue(DEFAULT_FILENAME_OPT, defaultFilename);
  -        brokenLinkFile = line.getOptionValue(BROKEN_LINK_FILE_OPT, brokenLinkFile);
  -
           if (line.hasOption(URI_FILE_OPT)) {
  -            Main.processURIFile(line.getOptionValue(URI_FILE_OPT), targets);
  +            cocoon.addTargets(processURIFile(line.getOptionValue(URI_FILE_OPT)));
           }
           if (line.hasOption(FOLLOW_LINKS_OPT)) {
  -            followLinks = "yes".equals(line.getOptionValue(FOLLOW_LINKS_OPT, "yes"))
  -                          || "true".equals(line.getOptionValue(FOLLOW_LINKS_OPT, "true"));
  +        cocoon.setFollowLinks(yesno(line.getOptionValue(FOLLOW_LINKS_OPT)));
           }
           if (line.hasOption(CONFIRM_EXTENSIONS_OPT)) {
  -            confirmExtensions = "yes".equals(line.getOptionValue(CONFIRM_EXTENSIONS_OPT, "yes"))
  -                                || "true".equals(line.getOptionValue(CONFIRM_EXTENSIONS_OPT, "true"));
  +            cocoon.setConfirmExtensions(yesno(line.getOptionValue(CONFIRM_EXTENSIONS_OPT, "yes")));
           }
           if (line.hasOption(LOAD_CLASS_OPT)){
  -            loadedClasses.add(Arrays.asList(line.getOptionValues(LOAD_CLASS_OPT)));
  +            cocoon.addLoadedClasses(Arrays.asList(line.getOptionValues(LOAD_CLASS_OPT)));
           }
  -
           for (Iterator i = line.getArgList().iterator(); i.hasNext();) {
  -            targets.add(NetUtils.normalize((String) i.next()));
  -        }
  -
  -        CocoonBean cocoon = new CocoonBean(workDir, contextDir, configFile);
  -        cocoon.setLogKit(logKit);
  -        cocoon.setLogger(logger);
  -        cocoon.setLogLevel(logLevel);
  -        
  -        if (loadedClasses.size()!=0) {
  -            cocoon.setLoadedClasses(loadedClasses);
  -        }
  -
  -        if (agentOptions != null) {
  -            cocoon.setAgentOptions(agentOptions);
  -        }
  -        if (acceptOptions != null) {
  -            cocoon.setAcceptOptions(acceptOptions);
  -        }
  -        if (defaultFilename != null) {
  -            cocoon.setDefaultFilename(defaultFilename);
  -        }        
  -        cocoon.setBrokenLinkFile(brokenLinkFile);
  -        cocoon.setPrecompileOnly(precompileOnly);
  -        cocoon.setFollowLinks(followLinks);
  -        cocoon.setConfirmExtensions(confirmExtensions);
  -        cocoon.setVerbose(verbose);
  -
  -        if (destDir.equals("")) {
  -            String error = "Careful, you must specify a destination dir when using the -d/--destDir argument";
  -            cocoon.getLogger().fatalError(error);
  -            System.out.println(error);
  -            System.exit(1);
  -        }
  -
  -        if (cocoon.getContextDir().equals("")) {
  -            String error = "Careful, you must specify a configuration file when using the -c/--contextDir argument";
  -            cocoon.getLogger().fatalError(error);
  -            System.out.println(error);
  -            System.exit(1);
  -        }
  -
  -        if (cocoon.getWorkDir().equals("")) {
  -            String error = "Careful, you must specify a destination dir when using the -w/--workDir argument";
  -            cocoon.getLogger().fatalError(error);
  -            System.out.println(error);
  -            System.exit(1);
  -        }
  -
  -        if (targets.size() == 0 && !cocoon.isPrecompileOnly()) {
  -            String error = "Please, specify at least one starting URI.";
  -            cocoon.getLogger().fatalError(error);
  -            System.out.println(error);
  -            System.exit(1);
  +            cocoon.addTarget((String)i.next(), destDir);
           }
   
           System.out.println(getProlog());
           
           cocoon.initialize();
           cocoon.warmup();
  -        cocoon.process(targets, new FileDestination(destDir));
  +        cocoon.process();
           cocoon.dispose();
   
           long duration = System.currentTimeMillis() - startTimeMillis;
  @@ -416,13 +373,18 @@
           System.exit(0);
       }
   
  +    private static boolean yesno(String in) {
  +        return "yes".equals(in) || "true".equals(in);
  +    }
  +
       /**
        * <code>processURIFile</code> method.
        *
        * @param filename a <code>String</code> value
  -     * @param uris a <code>List</code> of URIs
  +     * @return uris a <code>List</code> of URIs
        */
  -    public static void processURIFile(String filename, List uris) {
  +    public static List processURIFile(String filename) {
  +        List uris = new ArrayList();
           try {
               BufferedReader uriFile = new BufferedReader(new FileReader(filename));
               boolean eof = false;
  @@ -433,7 +395,7 @@
                   if (null == uri) {
                       eof = true;
                   } else {
  -                    uris.add(NetUtils.normalize(uri.trim()));
  +                    uris.add(uri.trim());
                   }
               }
   
  @@ -441,15 +403,17 @@
           } catch (Exception e) {
               // ignore errors.
           }
  +        return uris;
       }
   
       /**
        * <code>processXConf</code> method. Reads in XML configuration from
        * specified xconf file.
        *
  +     * @param cocoon a <code>CocoonBean</code> that will be configured by the xconf file
        * @param filename a <code>String</code> value
        */
  -    private static void processXConf(String filename) {
  +    private static void processXConf(CocoonBean cocoon, String filename) {
   
           try {
               final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  @@ -462,52 +426,59 @@
                   throw new IllegalArgumentException("Expected root node of "+ NODE_ROOT);
               }
   
  -            verbose = Main.getBooleanAttributeValue(root, ATTR_VERBOSE, verbose);
  -            followLinks = Main.getBooleanAttributeValue(root, ATTR_FOLLOW_LINKS, followLinks);
  -            precompileOnly = Main.getBooleanAttributeValue(root, ATTR_PRECOMPILE_ONLY, precompileOnly);
  -            confirmExtensions = Main.getBooleanAttributeValue(root, ATTR_CONFIRM_EXTENSIONS, confirmExtensions);
  -
  +            if (Main.hasAttribute(root, ATTR_VERBOSE)) {
  +                cocoon.setVerbose(Main.getBooleanAttributeValue(root, ATTR_VERBOSE));
  +            }
  +            if (Main.hasAttribute(root, ATTR_FOLLOW_LINKS)) {
  +                cocoon.setFollowLinks(Main.getBooleanAttributeValue(root, ATTR_FOLLOW_LINKS));
  +            }
  +            if (Main.hasAttribute(root, ATTR_PRECOMPILE_ONLY)) {
  +                cocoon.setPrecompileOnly(Main.getBooleanAttributeValue(root, ATTR_PRECOMPILE_ONLY));
  +            }
  +            if (Main.hasAttribute(root, ATTR_CONFIRM_EXTENSIONS)) {
  +                cocoon.setConfirmExtensions(Main.getBooleanAttributeValue(root, ATTR_CONFIRM_EXTENSIONS));
  +            }
               NodeList nodes = root.getChildNodes();
   
               for (int i=0; i<nodes.getLength();i++) {
                   Node node = nodes.item(i);
                   if (node.getNodeType()== Node.ELEMENT_NODE) {
                       String nodeName = node.getNodeName();
  -                    if (nodeName.equals(NODE_BROKEN_LINK_FILE)) {
  -                        brokenLinkFile = getNodeValue(node);
  +                    if (nodeName.equals(NODE_BROKEN_LINKS)) {
  +                        parseBrokenLinkNode(cocoon, node);
   
                       } else if (nodeName.equals(NODE_LOAD_CLASS)) {
  -                        loadedClasses.add(getNodeValue(node));
  +                        cocoon.addLoadedClass(getNodeValue(node));
   
                       } else if (nodeName.equals(NODE_LOGGING)) {
  -                        parseLoggingNode(node);
  +                        parseLoggingNode(cocoon, node);
   
                       } else if (nodeName.equals(NODE_CONTEXT_DIR)) {
  -                        contextDir = getNodeValue(node);
  +                        cocoon.setContextDir(getNodeValue(node));
   
                       } else if (nodeName.equals(NODE_CONFIG_FILE)) {
  -                        configFile = getNodeValue(node);
  +                        cocoon.setConfigFile(getNodeValue(node));
   
                       } else if (nodeName.equals(NODE_DEST_DIR)) {
  -                        destDir = getNodeValue(node);
  +                        cocoon.setDestDir(getNodeValue(node));
   
                       } else if (nodeName.equals(NODE_WORK_DIR)) {
  -                        workDir = getNodeValue(node);
  +                        cocoon.setWorkDir(getNodeValue(node));
   
                       } else if (nodeName.equals(NODE_AGENT)) {
  -                        agentOptions = getNodeValue(node);
  +                        cocoon.setAgentOptions(getNodeValue(node));
   
                       } else if (nodeName.equals(NODE_ACCEPT)) {
  -                        acceptOptions = getNodeValue(node);
  +                        cocoon.setAcceptOptions(getNodeValue(node));
   
                       } else if (nodeName.equals(NODE_DEFAULT_FILENAME)) {
  -                        defaultFilename = getNodeValue(node);
  +                        cocoon.setDefaultFilename(getNodeValue(node));
   
                       } else if (nodeName.equals(NODE_URI)) {
  -                        targets.add(NetUtils.normalize(getNodeValue(node)));
  +                        Main.parseURINode(cocoon, node);
   
                       } else if (nodeName.equals(NODE_URI_FILE)) {
  -                        Main.processURIFile(getNodeValue(node), targets);
  +                        cocoon.addTargets(Main.processURIFile(getNodeValue(node)));
   
                       } else {
                           throw new IllegalArgumentException("Unknown element: " + nodeName);
  @@ -520,11 +491,75 @@
           }
       }
   
  -    private static void parseLoggingNode(Node node) throws IllegalArgumentException {
  -        logKit = Main.getAttributeValue(node, ATTR_LOG_KIT, logKit);
  -        logger = Main.getAttributeValue(node, ATTR_LOGGER, logger);
  -        logLevel = Main.getAttributeValue(node, ATTR_LOG_LEVEL, logLevel);
  +    private static void parseLoggingNode(CocoonBean cocoon, Node node) throws IllegalArgumentException {
  +        if (Main.hasAttribute(node, ATTR_LOG_KIT)) {
  +            cocoon.setLogKit(Main.getAttributeValue(node, ATTR_LOG_KIT));
  +        }
  +        if (Main.hasAttribute(node, ATTR_LOGGER)) {
  +            cocoon.setLogger(Main.getAttributeValue(node, ATTR_LOGGER));
  +        }
  +        if (Main.hasAttribute(node, ATTR_LOG_LEVEL)) {
  +            cocoon.setLogLevel(Main.getAttributeValue(node, ATTR_LOG_LEVEL));
  +        }
  +        NodeList nodes = node.getChildNodes();
  +        if (nodes.getLength()!=0) {
  +            throw new IllegalArgumentException("Unexpected children of "+NODE_LOGGING+" node");
  +        }
  +    }
   
  +    private static void parseBrokenLinkNode(CocoonBean cocoon, Node node) throws IllegalArgumentException {
  +        if (Main.hasAttribute(node, ATTR_BROKEN_LINK_REPORT_FILE)) {
  +            cocoon.setBrokenLinkReportFile(Main.getAttributeValue(node, ATTR_BROKEN_LINK_REPORT_FILE));
  +        }
  +        if (Main.hasAttribute(node, ATTR_BROKEN_LINK_REPORT_TYPE)) {
  +            cocoon.setBrokenLinkReportType(Main.getAttributeValue(node, ATTR_BROKEN_LINK_REPORT_TYPE));
  +        }
  +        if (Main.hasAttribute(node, ATTR_BROKEN_LINK_GENERATE)) {
  +        cocoon.setBrokenLinkGenerate(Main.getBooleanAttributeValue(node, ATTR_BROKEN_LINK_GENERATE));
  +        }
  +        if (Main.hasAttribute(node, ATTR_BROKEN_LINK_EXTENSION)) {
  +        cocoon.setBrokenLinkExtension(Main.getAttributeValue(node, ATTR_BROKEN_LINK_EXTENSION));
  +        }
  +        NodeList nodes = node.getChildNodes();
  +        if (nodes.getLength()!=0) {
  +            throw new IllegalArgumentException("Unexpected children of "+NODE_LOGGING+" node");
  +        }
  +    }
  +    private static void parseURINode(CocoonBean cocoon, Node node) throws IllegalArgumentException {
  +        String type = null;
  +        String root = null;
  +        String src = null;
  +        String dest = null;
  +
  +        if (node.getAttributes().getLength()==0) {
  +            cocoon.addTarget(getNodeValue(node));
  +            return;
  +        }
  +        if (Main.hasAttribute(node, ATTR_URI_TYPE)) {
  +            type = Main.getAttributeValue(node, ATTR_URI_TYPE);
  +        }
  +        if (Main.hasAttribute(node, ATTR_URI_SOURCEPREFIX)) {
  +            root = Main.getAttributeValue(node, ATTR_URI_SOURCEPREFIX);
  +        }
  +        if (Main.hasAttribute(node, ATTR_URI_DESTURI)) {
  +            dest = Main.getAttributeValue(node, ATTR_URI_DESTURI);
  +        }
  +        if (Main.hasAttribute(node, ATTR_URI_SOURCEURI)) {
  +            src = Main.getAttributeValue(node, ATTR_URI_SOURCEURI);
  +        } else {
  +            throw new IllegalArgumentException("Missing src attribute on uri node");
  +        }
  +        if (src == null) {
  +            throw new IllegalArgumentException("Missing src attribute in <uri> node");
  +        } else if (root != null && type!=null & dest!=null) {
  +            cocoon.addTarget(type, root, src, dest);
  +        } else if (root!=null & dest!=null) {
  +            cocoon.addTarget(root, src, dest);
  +        } else if (dest!=null) {
  +            cocoon.addTarget(src, dest);
  +        } else {
  +            cocoon.addTarget(src);
  +        }
           NodeList nodes = node.getChildNodes();
           if (nodes.getLength()!=0) {
               throw new IllegalArgumentException("Unexpected children of "+NODE_LOGGING+" node");
  @@ -551,7 +586,7 @@
           return s.toString();
       }
   
  -    private static String getAttributeValue(Node node, String attr, String defaultValue) {
  +    private static String getAttributeValue(Node node, String attr) throws IllegalArgumentException {
           NamedNodeMap nodes = node.getAttributes();
           if (nodes != null) {
               Node attribute = nodes.getNamedItem(attr);
  @@ -559,10 +594,18 @@
                   return attribute.getNodeValue();
               }
           }
  -        return defaultValue;
  +        throw new IllegalArgumentException("Missing "+attr+" attribute");
       }
   
  -    private static boolean getBooleanAttributeValue(Node node, String attr, boolean defaultValue) {
  +    private static boolean hasAttribute(Node node, String attr) {
  +        NamedNodeMap nodes = node.getAttributes();
  +        if (nodes != null) {
  +            Node attribute = nodes.getNamedItem(attr);
  +            return (attribute != null);
  +        }
  +        return false;
  +    }
  +    private static boolean getBooleanAttributeValue(Node node, String attr) {
           NamedNodeMap nodes = node.getAttributes();
           if (nodes != null) {
               Node attribute = nodes.getNamedItem(attr);
  @@ -573,7 +616,7 @@
                           || "true".equals(value);
               }
           }
  -        return defaultValue;
  +        return false;
       }
   
       /**
  @@ -598,7 +641,7 @@
           formatter.printHelp("cocoon cli [options] [targets]",
                               getProlog().toString(),
                               options,
  -                            "Note: the context directory defaults to '"+ Constants.DEFAULT_CONTEXT_DIR + "'");
  +                            "Note: the context directory defaults to '"+ CocoonBean.DEFAULT_CONTEXT_DIR + "'");
           System.exit(0);
       }
       
  @@ -610,16 +653,3 @@
           System.exit(0);
       }    
   }
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  
  
  
  1.6       +492 -224  cocoon-2.1/src/java/org/apache/cocoon/bean/CocoonBean.java
  
  Index: CocoonBean.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/bean/CocoonBean.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- CocoonBean.java	27 May 2003 12:09:01 -0000	1.5
  +++ CocoonBean.java	4 Jun 2003 09:25:53 -0000	1.6
  @@ -57,8 +57,8 @@
   import org.apache.cocoon.Constants;
   import org.apache.cocoon.Cocoon;
   import org.apache.cocoon.ResourceNotFoundException;
  -import org.apache.cocoon.Main;
   import org.apache.cocoon.ProcessingException;
  +import org.apache.cocoon.bean.helpers.DelayedOutputStream;
   import org.apache.cocoon.components.notification.SimpleNotifyingBean;
   import org.apache.cocoon.components.notification.Notifier;
   import org.apache.cocoon.components.notification.DefaultNotifyingBuilder;
  @@ -67,7 +67,10 @@
   import org.apache.cocoon.environment.commandline.CommandLineContext;
   import org.apache.cocoon.environment.commandline.LinkSamplingEnvironment;
   import org.apache.cocoon.environment.commandline.FileSavingEnvironment;
  -import org.apache.cocoon.bean.destination.Destination;
  +
  +import org.apache.excalibur.source.ModifiableSource;
  +import org.apache.excalibur.source.SourceResolver;
  +import org.apache.excalibur.source.Source;
   
   import org.apache.avalon.excalibur.logger.DefaultLogKitManager;
   import org.apache.avalon.framework.context.DefaultContext;
  @@ -110,31 +113,63 @@
       protected static final String DEFAULT_USER_AGENT = Constants.COMPLETE_NAME;
       protected static final String DEFAULT_ACCEPT = "text/html, */*";
   
  +    /**
  +     * The directory to use as context root.
  +     */
  +    public static final String DEFAULT_CONTEXT_DIR = "./webapp";
  +
  +    /**
  +     * The diretory to use to use for the generated output.
  +     */
  +    public static final String DEFAULT_DEST_DIR = "./site";
  +
  +    /**
  +     * The diretory to use for generated files.
  +     */
  +    public static final String DEFAULT_WORK_DIR = "./work";
  +
  +    /**
  +     * How a default configuration file is named.
  +     */
  +    public static final String DEFAULT_CONF_FILE = "cocoon.xconf";
  +
  +    /**
  +     * The default URI to be used when a URI requested refers to
  +     * a directory, e.g. http://localhost:8080/site/
  +     */
  +    public static final String INDEX_URI = "index";
  +
       // User Supplied Parameters
  -    private String contextDir = Constants.DEFAULT_CONTEXT_DIR;
  +    private String contextDir = CocoonBean.DEFAULT_CONTEXT_DIR;
       private String configFile = null;
  -    private String brokenLinkFileName = null;
  -    private String workDir = Constants.DEFAULT_WORK_DIR;
  +
  +    private String brokenLinkReportFile = null;
  +    private String brokenLinkReportType = "text";
  +    private boolean brokenLinkGenerate = false;
  +    private String brokenLinkExtension = "";
  +
  +    private String workDir = CocoonBean.DEFAULT_WORK_DIR;
       private String logKit = null;
       private String logger = null;
       private String userAgent = DEFAULT_USER_AGENT;
       private String accept = DEFAULT_ACCEPT;
  -    private String defaultFilename = Constants.INDEX_URI;
  +    private String defaultFilename = CocoonBean.INDEX_URI;
       private boolean followLinks = true;
       private boolean precompileOnly = false;
       private boolean confirmExtension = true;
  -    private List classList = null;
  +    private List classList = new ArrayList();
  +    private List brokenLinks = null;
  +    private List targets = new ArrayList();
   
       // Objects used alongside User Supplied Parameters
       private File context;
       private File work;
       private File conf;
  -    private PrintWriter brokenLinkWriter;
   
       // Internal Objects
       private CommandLineContext cliContext;
       private Cocoon cocoon;
  -    private Destination dest;
  +    private String destDir;
       private static Logger log;
       private Map attributes;
       private HashMap empty;
  @@ -142,38 +177,53 @@
       private Map allTranslatedLinks;
       private boolean initialized;
       private boolean verbose;
  -
  +    SourceResolver sourceResolver;
       //
  -    // CONSTRUCTORS AND INITIALISATION METHODS
  +    // INITIALISATION METHOD
       //
  -    public CocoonBean(String workingDir, String contextDir) throws IOException {
  -        this(workingDir, contextDir, null);
  -    }
   
  -    public CocoonBean(String workingDir, String contextDir, String configFile) throws IOException {
  -        this.contextDir = contextDir;
  -        this.workDir = workingDir;
  -        this.configFile = configFile;
  +    public CocoonBean() {
  +    }
   
  +    public void initialize() throws Exception {
  +        // @todo@ when does the logger get initialised? uv
  +        // @todo@ these should log then throw exceptions back to the caller, not use system.exit()
  +        if (destDir.equals("")) {
  +            String error = "Careful, you must specify a destination dir when using the -d/--destDir argument";
  +            log.fatalError(error);
  +            System.out.println(error);
  +            System.exit(1);
  +        }
  +
  +        if (contextDir.equals("")) {
  +            String error = "Careful, you must specify a configuration file when using the -c/--contextDir argument";
  +            log.fatalError(error);
  +            System.out.println(error);
  +            System.exit(1);
  +        }
  +
  +        if (workDir.equals("")) {
  +            String error = "Careful, you must specify a destination dir when using the -w/--workDir argument";
  +            log.fatalError(error);
  +            System.out.println(error);
  +            System.exit(1);
  +        }
  +
  +        if (targets.size() == 0 && !precompileOnly) {
  +            String error = "Please, specify at least one starting URI.";
  +            log.fatalError(error);
  +            System.out.println(error);
  +            System.exit(1);
  +        }
           setLogLevel("ERROR");
           this.context = getDir(this.contextDir, "context");
           this.work = getDir(workDir, "working");
   
  -        this.conf = null;
  -        if (null == this.configFile) {
  -            this.conf = getConfigurationFile(context);
  -        } else {
  -             this.conf = new File(this.configFile);
  -             if (!conf.exists()) {
  -                 this.conf = new File(context, this.configFile);
  -             }
  -        }
  -    }
  +        this.conf = getConfigurationFile(this.context, this.configFile);
   
  -    public void initialize() throws Exception {
           try {
               DefaultContext appContext = new DefaultContext();
  -            appContext.put(Constants.CONTEXT_CLASS_LOADER, Main.class.getClassLoader());
  +            appContext.put(Constants.CONTEXT_CLASS_LOADER, CocoonBean.class.getClassLoader());
               cliContext = new CommandLineContext(contextDir);
               cliContext.enableLogging(new LogKitLogger(log));
               appContext.put(Constants.CONTEXT_ENVIRONMENT_CONTEXT, cliContext);
  @@ -212,13 +262,15 @@
               cocoon.setLogKitManager(logKitManager);
               cocoon.initialize();
   
  -            if (brokenLinkFileName != null) {
  -                try {
  -                    this.brokenLinkWriter = new PrintWriter(new FileWriter(new File(brokenLinkFileName)), true);
  -                } catch (IOException ioe) {
  -                    log.error("File does not exist: " + brokenLinkFileName);
  -                }
  +            if (brokenLinkReportFile!=null && !(new File(brokenLinkReportFile).exists())) {
  +              log.error("Broken Link Report File does not exist: " + brokenLinkReportFile);
  +            }
  +
  +            if (brokenLinkReportFile!=null) {
  +                brokenLinks= new ArrayList();
               }
  +            this.sourceResolver = (SourceResolver)cocoon.getComponentManager().lookup(SourceResolver.ROLE);
  +
           } catch (Exception e) {
               log.fatalError("Exception caught", e);
               throw e;
  @@ -227,39 +279,52 @@
       }
   
       /**
  -     * Check for a configuration file in a specific directory.
  +     * Look around for the configuration file.
        *
        * @param dir a <code>File</code> where to look for configuration files
        * @return a <code>File</code> representing the configuration
        * @exception IOException if an error occurs
        */
  -    private static File getConfigurationFile(File dir) throws IOException {
  -        if (log.isDebugEnabled()) {
  -            log.debug("Trying configuration file at: " + dir + File.separator + Constants.DEFAULT_CONF_FILE);
  -        }
  -        File f = new File(dir, Constants.DEFAULT_CONF_FILE);
  -        if (f.canRead()) {
  -            return f;
  -        }
  -
  -        if (log.isDebugEnabled()) {
  -            log.debug("Trying configuration file at: " + System.getProperty("user.dir") + File.separator + Constants.DEFAULT_CONF_FILE);
  +    private static File getConfigurationFile(File dir, String configFile) throws IOException {
  +        File conf;
  +        if (configFile == null) {
  +            conf = tryConfigurationFile(dir + File.separator + CocoonBean.DEFAULT_CONF_FILE);
  +            if (conf == null) {
  +                conf = tryConfigurationFile(dir + File.separator + "WEB-INF" + File.separator + CocoonBean.DEFAULT_CONF_FILE);
  +            }
  +            if (conf == null) {
  +                conf = tryConfigurationFile(System.getProperty("user.dir") + File.separator + CocoonBean.DEFAULT_CONF_FILE);
  +            }
  +            if (conf == null) {
  +                conf = tryConfigurationFile("/usr/local/etc/" + CocoonBean.DEFAULT_CONF_FILE);
  +            }
  +        } else {
  +            conf = new File(configFile);
  +            if (!conf.exists()) {
  +                conf = new File(dir, configFile);
  +            }
           }
  -        f = new File(System.getProperty("user.dir"), Constants.DEFAULT_CONF_FILE);
  -        if (f.canRead()) {
  -            return f;
  +        if (conf == null) {
  +            log.error("Could not find the configuration file.");
  +            throw new FileNotFoundException("The configuration file could not be found.");
           }
  +        return conf;
  +    }
   
  +    /**
  +     * Try loading the configuration file from a single location
  +     */
  +    private static File tryConfigurationFile(String filename) throws IOException {
  +        File conf;
           if (log.isDebugEnabled()) {
  -            log.debug("Trying configuration file at: /usr/local/etc/" + Constants.DEFAULT_CONF_FILE);
  +            log.debug("Trying configuration file at: " + filename);
           }
  -        f = new File("/usr/local/etc/", Constants.DEFAULT_CONF_FILE);
  -        if (f.canRead()) {
  -            return f;
  +        conf = new File(filename);
  +        if (conf.canRead()) {
  +            return conf;
  +        } else {
  +            return null;
           }
  -
  -        log.error("Could not find the configuration file.");
  -        throw new FileNotFoundException("The configuration file could not be found.");
       }
   
       /**
  @@ -366,6 +431,14 @@
           this.workDir = workDir;
       }
   
  +    public void setDestDir(String destDir) {
  +        this.destDir = destDir;
  +    }
  +
  +    public void setConfigFile(String configFile) {
  +        this.configFile = configFile;
  +    }
  +
       public void setAgentOptions(String userAgent) {
           this.userAgent = userAgent;
       }
  @@ -373,7 +446,7 @@
       public void setAcceptOptions(String accept) {
           this.accept = accept;
       }
  - 
  +
       public void setDefaultFilename(String filename) {
           defaultFilename = filename;
       }
  @@ -386,8 +459,20 @@
           this.confirmExtension = confirmExtension;
       }
   
  -    public void setBrokenLinkFile(String filename){
  -        this.brokenLinkFileName = filename;
  +    public void setBrokenLinkReportFile(String filename){
  +        this.brokenLinkReportFile = filename;
  +    }
  +
  +    public void setBrokenLinkReportType(String brokenLinkReportType) {
  +        this.brokenLinkReportType = brokenLinkReportType;
  +    }
  +
  +    public void setBrokenLinkGenerate(boolean brokenLinkGenerate) {
  +        this.brokenLinkGenerate = brokenLinkGenerate;
  +    }
  +
  +    public void setBrokenLinkExtension(String brokenLinkExtension) {
  +        this.brokenLinkExtension = brokenLinkExtension;
       }
   
       public void setPrecompileOnly(boolean precompileOnly){
  @@ -397,27 +482,36 @@
       public void setVerbose(boolean verbose){
            this.verbose = verbose;
       }
  -    
  -    public void setLoadedClasses(List classList) {
  -        this.classList = classList;
  -    }
   
  -    public Logger getLogger(){
  -        return CocoonBean.log;
  +    public void addLoadedClass(String className) {
  +        this.classList.add(className);
       }
   
  -    public boolean isPrecompileOnly(){
  -        return this.precompileOnly;
  +    public void addLoadedClasses(List classList) {
  +        this.classList.addAll(classList);
       }
   
  -    public String getContextDir(){
  -        return this.contextDir;
  +    public void addTarget(String sourceURI) {
  +        targets.add(new Target(sourceURI, destDir));
       }
  -
  -    public String getWorkDir(){
  -        return this.workDir;
  +    public void addTarget(String type, String root, String sourceURI, String destURI){
  +        targets.add(new Target(type, root, sourceURI, destURI));
  +    }
  +  
  +    public void addTarget(String type, String sourceURI, String destURI){
  +        targets.add(new Target(type, sourceURI, destURI));
  +    }
  +  
  +    public void addTarget(String sourceURI, String destURI){
  +        targets.add(new Target(sourceURI, destURI));
  +    }
  +    public void addTargets(List uris) {
  +        Iterator i = uris.iterator();
  +        while (i.hasNext()) {
  +            Target target = new Target((String)i.next(), destDir);
  +            targets.add(target);
  +        }
       }
  -
       /**
        * Process single URI into given output stream.
        *
  @@ -425,31 +519,52 @@
        * @param outputStream to write generated contents into
        */
       public void processURI(String uri, OutputStream outputStream) throws Exception {
  +
           if (!initialized) {
               initialize();
           }
  -        attributes = new HashMap();
  -        empty = new HashMap();
  -        allProcessedLinks = new HashMap();
  -        allTranslatedLinks = new HashMap();
  -
           log.info("Processing URI: " + uri);
  -        processURI(uri);
  -    }
   
  -    public void process(List uris, Destination destination) throws Exception {
  -        dest = destination;
  +        // Get parameters, deparameterized URI and path from URI
  +        final TreeMap parameters = new TreeMap();
  +        final String deparameterizedURI = NetUtils.deparameterize(uri, parameters);
  +        final String suri = NetUtils.parameterize(deparameterizedURI, parameters);
  +        parameters.put("user-agent", userAgent);
  +        parameters.put("accept", accept);
   
  -        if (!initialized){
  -            initialize();
  +        // Get file name from URI (without path)
  +        String pageURI = deparameterizedURI;
  +        if (pageURI.indexOf("/") != -1) {
  +            pageURI = pageURI.substring(pageURI.lastIndexOf("/") + 1);
  +            if (pageURI.length() == 0) {
  +                pageURI = "./";
  +            }
           }
  -        attributes = new HashMap();
  -        empty = new HashMap();
  -        allProcessedLinks = new HashMap();
  -        allTranslatedLinks = new HashMap();
   
  -        if (process(uris, precompileOnly) == 0) {
  -            recursivelyPrecompile(context, context);
  +        String filename;
  +        if (confirmExtension) {
  +            filename = (String)allTranslatedLinks.get(suri);
  +            if (filename == null) {
  +                filename = mangle(suri);
  +                final String type = getType(deparameterizedURI, parameters);
  +                final String ext = NetUtils.getExtension(filename);
  +                final String defaultExt = MIMEUtils.getDefaultExtension(type);
  +                if ((ext == null) || (!ext.equals(defaultExt))) {
  +                    filename += defaultExt;
  +                }
  +            }
  +        } else {
  +            filename = suri;
  +        }
  +        if ("".equals(filename)) {
  +            outputStream.close();
  +            return;
  +        }
  +
  +        int status = getPage(deparameterizedURI, parameters, null, null, outputStream);
  +
  +        if (status >=400) {
  +           throw new ProcessingException("Resource not found: "+status);
           }
       }
   
  @@ -474,56 +589,67 @@
   
       /**
        * Process the URI list and process them all independently.
  -     * @param uris a <code>Collection</code> of URIs
  -     * @param precompileOnly a <code>boolean</code> denoting to process XSP only
  -     * @return an <code>int</code> value with the number of links processed
        * @exception Exception if an error occurs
        */
  -    private int process(Collection uris, boolean precompileOnly) throws Exception {
  +    public void process() throws Exception {
  +
  +        if (!initialized){
  +            initialize();
  +        }
  +        attributes = new HashMap();
  +        empty = new HashMap();
  +        allProcessedLinks = new HashMap();
  +        allTranslatedLinks = new HashMap();
   
           int nCount = 0;
  -        ArrayList links = new java.util.ArrayList();
  -        Iterator i = uris.iterator();
  -        String next;
  +        Target target;
  +
  +        HashMap targetMap = new java.util.HashMap();
  +        Iterator i = targets.iterator();
           while (i.hasNext()) {
  -            next = (String)i.next();
  -            if (!links.contains(next)) {
  -                links.add(next);
  +            target = (Target)i.next();
  +            if (!targetMap.containsKey(target.getHashCode())) {
  +                targetMap.put(target.getHashCode(), target);
               }
           }
  -        while (links.size() > 0) {
  -            String url = (String)links.get(0);
   
  +        while (targetMap.size() > 0) {
  +            String hashCode = (String)(new ArrayList(targetMap.keySet()).get(0));
  +            target = (Target)targetMap.get(hashCode);
               try {
  -                if (allProcessedLinks.get(url) == null) {
  +                if (allProcessedLinks.get(hashCode) == null) {
                       if (precompileOnly) {
  -                        this.processXSP(url);
  +                        this.processXSP(target.getSourceURI());
                       } else if (this.followLinks) {
  -                        i = processURI(url).iterator();
  +                        i = processTarget(target).iterator();
                           while (i.hasNext()) {
  -                            next = (String)i.next();
  -                            if (!links.contains(next)) {
  -                                links.add(next);
  +                            target = (Target)i.next();
  +                            if (!targetMap.containsKey(target.getHashCode())) {
  +                                targetMap.put(target.getHashCode(), target);
                               }
                           }
                       } else {
  -                        processURI(url);
  +                        processTarget(target);
                       }
                   }
               } catch (ResourceNotFoundException rnfe) {
  -                printBroken (url, rnfe.getMessage());
  +                printBroken (target.getSourceURI(), rnfe.getMessage());
               }
   
  -            links.remove(url);
  +            targetMap.remove(target.getHashCode());
               nCount++;
   
               if (log.isInfoEnabled()) {
                   log.info("  Memory used: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
  -                log.info("  Processed, Translated & Left: " + allProcessedLinks.size() + ", "  + allTranslatedLinks.size() + ", " + links.size());
  +                log.info("  Processed, Translated & Left: " + allProcessedLinks.size() + ", "  + allTranslatedLinks.size() + ", " + targetMap.size());
               }
           }
   
  -        return nCount;
  +        if (nCount == 0) {
  +            recursivelyPrecompile(context, context);
  +        }
  +        outputBrokenLinks();
  +
       }
   
       /**
  @@ -581,8 +707,10 @@
       }
   
       /**
  -     * Processes the given URI and return all links. The algorithm is the following:
  +     * Processes the given Target and return all links.
        *
  +     * If links are to be followed, and extensions checked then the algorithm is as
  +     * follows:
        * <ul>
        *  <li>file name for the URI is generated. URI MIME type is checked for
        *      consistency with the URI and, if the extension is inconsistent
  @@ -596,13 +724,26 @@
        *  <li>list of absolute URI is returned, for every URI which is not yet
        *      present in list of all translated URIs</li>
        * </ul>
  -     * @param uri a <code>String</code> URI to process
  -     * @return a <code>Collection</code> containing all links found
  +     *
  +     * If links are to be followed, but extensions are not checked, then the
  +     * algorithm will be:
  +     * <ul>
  +     *   <li>The content for the page is generated</li>
  +     *   <li>Whilst generating, all links are gathered by the LinkGatherer</li>
  +     *   <li>Gathered links are added to the unprocessed links list, and
  +     *       processing continues until all processing is complete
  +     *   </li>
  +     * </ul>
  +     * @param target a <code>Target</code> target to process
  +     * @return a <code>Collection</code> containing all links found, as 
  +     * Target objects.
        * @exception Exception if an error occurs
        */
  -    private Collection processURI(String uri) throws Exception {
  +    private Collection processTarget(Target target) throws Exception {
           System.out.print(" * ");
   
  +        String uri = target.getSourceURI();
  +
           // Get parameters, deparameterized URI and path from URI
           final TreeMap parameters = new TreeMap();
           final String deparameterizedURI = NetUtils.deparameterize(uri, parameters);
  @@ -620,10 +761,11 @@
               }
           }
   
  -        String filename = (String)allTranslatedLinks.get(suri);
  -        if (filename == null) {
  -            filename = mangle(suri);
  -            if (confirmExtension) {
  +        String filename;
  +        if (confirmExtension) {
  +            filename = (String)allTranslatedLinks.get(suri);
  +            if (filename == null) {
  +                filename = mangle(suri);
                   final String type = getType(deparameterizedURI, parameters);
                   final String ext = NetUtils.getExtension(filename);
                   final String defaultExt = MIMEUtils.getDefaultExtension(type);
  @@ -632,20 +774,22 @@
                           filename += defaultExt;
                       }
                   }
  +                allTranslatedLinks.put(suri, filename);
               }
  -            allTranslatedLinks.put(suri, filename);
  +        } else {
  +            filename = suri;
           }
           // Store processed URI list to avoid eternal loop
  -        allProcessedLinks.put(suri, filename);
  +        allProcessedLinks.put(target.getHashCode(),target);
   
           if ("".equals(filename)) {
               return new ArrayList();
           }
   
           // Process links
  -        final ArrayList absoluteLinks = new ArrayList();
  +        final List absoluteLinks = new ArrayList();
           final HashMap translatedLinks = new HashMap();
  -        final ArrayList gatheredLinks = new ArrayList();
  +        List gatheredLinks = new ArrayList();
   
           if (followLinks && confirmExtension) {
               final Iterator i = this.getLinks(deparameterizedURI, parameters).iterator();
  @@ -687,11 +831,13 @@
   
           try {
               // Process URI
  -            String file = NetUtils.decodePath(filename);
  -            OutputStream output = dest.getOutputStream(file);
  -
  +            DelayedOutputStream output = new DelayedOutputStream();
               try{
  -                getPage(deparameterizedURI, parameters, confirmExtension ? translatedLinks : null, gatheredLinks, output);
  +                int status = getPage(deparameterizedURI, parameters, confirmExtension ? translatedLinks : null, gatheredLinks, output);
  +
  +                if (status >=400) {
  +                    throw new ProcessingException("Resource not found: "+status);
  +                }
   
                   if (followLinks && !confirmExtension) {
                       for (Iterator it=gatheredLinks.iterator();it.hasNext();){
  @@ -711,10 +857,24 @@
   
                   printlnInfo(uri); // (can also output type returned by getPage)
               } catch(ProcessingException pe) {
  -                printBroken(filename, DefaultNotifyingBuilder.getRootCause(pe).getMessage());
                   output.close();
  -                resourceUnavailable(file, uri);
  +                output = null;
  +                if (brokenLinkGenerate) {
  +                    String brokenFile = NetUtils.decodePath(filename);
  +                    if (brokenLinkExtension!=null){
  +                        brokenFile = brokenFile+brokenLinkExtension;
  +                    }
  +                    resourceUnavailable(target, uri);
  +                }
  +                printBroken(filename, DefaultNotifyingBuilder.getRootCause(pe).getMessage());
               } finally {
  +                if (output != null) {
  +
  +                    OutputStream stream = target.getOutputStream(filename);
  +
  +                    output.setFileOutputStream(stream);
  +                    output.flush();
  +                }
                   try {
                       if (output != null)
                           output.close();
  @@ -728,7 +888,15 @@
               if (verbose) System.out.println("Could not process URI: " + deparameterizedURI);
           }
   
  -        return absoluteLinks;
  +         List targets = new ArrayList();
  +         for (Iterator i = absoluteLinks.iterator() ;i.hasNext();) {
  +             String link = (String)i.next();
  +             Target derivedTarget = target.getDerivedTarget(link);
  +             if (derivedTarget != null) {
  +                 targets.add(target.getDerivedTarget(link));
  +             }
  +         }
  +         return targets;
       }
   
       /**
  @@ -765,12 +933,67 @@
       }
   
       /**
  +      * Print the message of a broken link.
  +      *
  +      * @param url the <code>URL</code> of the broken link
  +      * @param cause of the broken link
  +      */
  +     private void printBroken(String url, String cause) {
  +         int screenWidth = 67;
  +         int causeWidth = screenWidth - 6;
  +
  +         printlnInfo("");
  +         printlnInfo("-> [broken page] " + url +" <- ");
  +         printlnInfo("");
  +         printInfo("     ");
  +
  +         int causeLength = cause.length(), currentStart = -causeWidth, currentEnd = 0;
  +         do {
  +             currentStart += causeWidth;
  +             currentEnd   += causeWidth;
  +
  +             if (currentEnd>causeLength) {
  +                 currentEnd=causeLength;
  +             }
  +
  +             printlnInfo(cause.substring(currentStart, currentEnd));
  +             printInfo("     ");
  +         } while(currentEnd < causeLength);
  +
  +         printlnInfo("");
  +
  +         if (null != this.brokenLinks) {
  +             brokenLinks.add(url);
  +         }
  +     }
  +
  +     /**
  +      * Print an info message.
  +      *
  +      * @param message the message to print
  +      */
  +     private void printlnInfo (String message) {
  +         log.info(message);
  +         if (verbose) System.out.println(message);
  +     }
  +
  +     /**
  +      * Print an info message.
  +      *
  +      * @param message the message to print
  +      */
  +     private void printInfo (String message) {
  +         log.info(message);
  +         if (verbose) System.out.print(message);
  +     }
  +
  +    /**
        * Generate a <code>resourceUnavailable</code> message.
        *
  -     * @param file being unavailable
  +     * @param target being unavailable
        * @exception IOException if an error occurs
        */
  -    private void resourceUnavailable(String file, String uri) throws IOException {
  +    private void resourceUnavailable(Target target, String uri) throws IOException, ProcessingException {
           SimpleNotifyingBean n = new SimpleNotifyingBean(this);
           n.setType("resource-not-found");
           n.setTitle("Resource not Found");
  @@ -778,14 +1001,49 @@
           n.setMessage("Page Not Available.");
           n.setDescription("The requested resource couldn't be found.");
           n.addExtraDescription(Notifying.EXTRA_REQUESTURI, uri);
  -        n.addExtraDescription("missing-file", file.toString());
  +        n.addExtraDescription("missing-file", uri);
   
  -        PrintStream out = new PrintStream(dest.getOutputStream(file));
  +        PrintStream out = new PrintStream(target.getOutputStream(uri));
           Notifier.notify(n, out, "text/html");
           out.flush();
           out.close();
       }
   
  +    private void outputBrokenLinks() {
  +        if (brokenLinkReportFile==null){
  +            return;
  +        } else if ("text".equalsIgnoreCase(brokenLinkReportType)) {
  +            outputBrokenLinksAsText();
  +        } else if ("xml".equalsIgnoreCase(brokenLinkReportType)) {
  +            outputBrokenLinksAsXML();
  +        }
  +    }
  +    private void outputBrokenLinksAsText(){
  +        PrintWriter writer;
  +        try {
  +            writer = new PrintWriter(new FileWriter(new File(brokenLinkReportFile)), true);
  +            for (Iterator i=brokenLinks.iterator();i.hasNext();){
  +                writer.println((String)i.next());
  +            }
  +            writer.close();
  +        } catch (IOException ioe) {
  +            log.error("File does not exist: " + brokenLinkReportFile);
  +        }
  +    }
  +    private void outputBrokenLinksAsXML(){
  +        PrintWriter writer;
  +        try {
  +            writer = new PrintWriter(new FileWriter(new File(brokenLinkReportFile)), true);
  +            writer.println("<broken-links>");
  +            for (Iterator i=brokenLinks.iterator();i.hasNext();){
  +                writer.println("  <link>" + (String)i.next()+"</link>");
  +            }
  +            writer.println("</broken-links>");
  +            writer.close();
  +        } catch (IOException ioe) {
  +            log.error("File does not exist: " + brokenLinkReportFile);
  +        }
  +    }
       /**
        * Mangle a URI.
        *
  @@ -837,7 +1095,7 @@
        * @return a <code>String</code> value for the content
        * @exception Exception if an error occurs
        */
  -    protected String getPage(String deparameterizedURI, Map parameters, Map links, List gatheredLinks, OutputStream stream) throws Exception {
  +    protected int getPage(String deparameterizedURI, Map parameters, Map links, List gatheredLinks, OutputStream stream) throws Exception {
           FileSavingEnvironment env = new FileSavingEnvironment(deparameterizedURI,
                                                                 context,
                                                                 attributes,
  @@ -847,10 +1105,12 @@
                                                                 cliContext,
                                                                 stream,
                                                                 new LogKitLogger(log));
  +
           // Here Cocoon can throw an exception if there are errors in processing the page
           cocoon.process(env);
  +
           // if we get here, the page was created :-)
  -        return env.getContentType();
  +        return env.getStatus();
       }
   
       /** Class <code>NullOutputStream</code> here. */
  @@ -901,61 +1161,6 @@
       }
   
       /**
  -     * Print the message of a broken link.
  -     *
  -     * @param url the <code>URL</code> of the broken link
  -     * @param cause of the broken link
  -     */
  -    private void printBroken(String url, String cause) {
  -        int screenWidth = 67;
  -        int causeWidth = screenWidth - 6;
  -
  -        printlnInfo("");
  -        printlnInfo("-> [broken page] " + url +" <- ");
  -        printlnInfo("");
  -        printInfo("     ");
  -
  -        int causeLength = cause.length(), currentStart = -causeWidth, currentEnd = 0;
  -        do {
  -            currentStart += causeWidth;
  -            currentEnd   += causeWidth;
  -
  -            if (currentEnd>causeLength) {
  -                currentEnd=causeLength;
  -            }
  -
  -            printlnInfo(cause.substring(currentStart, currentEnd));
  -            printInfo("     ");
  -        } while(currentEnd < causeLength);
  -
  -        printlnInfo("");
  -
  -        if (null != this.brokenLinkWriter) {
  -            this.brokenLinkWriter.println(url);
  -        }
  -    }
  -
  -    /**
  -     * Print an info message.
  -     *
  -     * @param message the message to print
  -     */
  -    private void printlnInfo (String message) {
  -        log.info(message);
  -        if (verbose) System.out.println(message);
  -    }
  -
  -    /**
  -     * Print an info message.
  -     *
  -     * @param message the message to print
  -     */
  -    private void printInfo (String message) {
  -        log.info(message);
  -        if (verbose) System.out.print(message);
  -    }
  -
  -    /**
        * This builds the important ClassPath used by this class.  It
        * does so in a neutral way.
        * It iterates in alphabetical order through every file in the
  @@ -995,36 +1200,99 @@
           }
           return buildClassPath.toString();
        }
  -}
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
  -
   
  +    public class Target {
  +        private static final String DEFAULT_TYPE = "default";
  +        private static final String REPLACE_TYPE = "replace";
  +        private static final String APPEND_TYPE = "append";
  +        private static final String INSERT_TYPE = "insert";
  +        private String type;
  +        private String root;
  +        private String sourceURI;
  +        private String destURI;
  +        
  +        public Target(String type, String root, String sourceURI, String destURI){
  +            this.type = type;
  +            this.root = root;
  +            this.sourceURI = NetUtils.normalize(sourceURI);
  +            this.destURI = destURI;
  +        }
  +
  +        public Target(String type, String sourceURI, String destURI){
  +            this(type, "", sourceURI, destURI);
  +        }
  +
  +        public Target(String sourceURI, String destURI){
  +            this(DEFAULT_TYPE, "", sourceURI, destURI);
  +        }
  +
  +        public Target getDerivedTarget(String newURI) throws ProcessingException {
  +            if (!newURI.startsWith(root)) {
  +                return null;
  +            }
  +            newURI = newURI.substring(root.length());
  +            return new Target(this.type, this.root, newURI, this.destURI);
  +        }
  +        
  +        public String getFinalURI(String actualSourceURI) throws ProcessingException {
  +            if (!actualSourceURI.startsWith(root)) {
  +                throw new ProcessingException("Derived target does not share same root: " + actualSourceURI);
  +            }
  +            actualSourceURI = actualSourceURI.substring(root.length());
   
  +            if (DEFAULT_TYPE.equals(this.type)){
  +                return this.getFinalURIWithDefault(actualSourceURI);
  +            }
  +            else if (REPLACE_TYPE.equals(this.type)){
  +                return this.getFinalURIWithReplace(actualSourceURI);
  +            }
  +            else if (APPEND_TYPE.equals(this.type)){
  +                return this.getFinalURIWithAppend(actualSourceURI);
  +            }
  +            else if (INSERT_TYPE.equals(this.type)){
  +                return this.getFinalURIWithInsert(actualSourceURI);
  +            } else {
  +                throw new ProcessingException("Unknown mapper type: " + this.type);
  +            }
  +        }
  +        private String getFinalURIWithDefault(String actualSourceURI){
  +            return destDir + "/" + actualSourceURI;
  +        }
   
  +        private String getFinalURIWithAppend(String actualSourceURI){
  +            return destURI + actualSourceURI;
  +        }
   
  +        private String getFinalURIWithReplace(String actualSourceURI){
  +            return destURI;
  +        }
   
  +        private String getFinalURIWithInsert(String actualSourceURI) throws ProcessingException {
  +            int starPos = destURI.indexOf("*");
  +            if (starPos == -1) {
  +                throw new ProcessingException("Missing * in replace mapper uri");
  +            } else if (starPos == destURI.length()-1) {
  +               return destURI.substring(0,starPos) + actualSourceURI;
  +            } else {
  +                return destURI.substring(0,starPos) + actualSourceURI + destURI.substring(starPos+1);
  +            }
  +        }
  +   
  +        public String getSourceURI() {
  +            return root + sourceURI;
  +        }
  +        // @todo@ this is misusing the 'hashCode' name - hashCodes should be integer it seems, uv
  +        public String getHashCode() {
  +            return type + "|" + root +"|" + sourceURI + "|" + destURI;
  +        }
  +        public OutputStream getOutputStream(String filename) throws IOException, ProcessingException {
  +            final String finalDestinationURI = this.getFinalURI(filename);
  +            Source src = (Source) sourceResolver.resolveURI(finalDestinationURI);
  +            if (!(src instanceof ModifiableSource)) {
  +                throw new ProcessingException("Source is not Modifiable: "+finalDestinationURI);
  +            }
  +            ModifiableSource outputSource = (ModifiableSource) src;
  +            return outputSource.getOutputStream();
  +        }
  +    }
  +}
  
  
  
  1.3       +18 -6     cocoon-2.1/src/java/org/apache/cocoon/environment/commandline/AbstractCommandLineEnvironment.java
  
  Index: AbstractCommandLineEnvironment.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/environment/commandline/AbstractCommandLineEnvironment.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- AbstractCommandLineEnvironment.java	16 May 2003 07:04:55 -0000	1.2
  +++ AbstractCommandLineEnvironment.java	4 Jun 2003 09:25:53 -0000	1.3
  @@ -81,6 +81,7 @@
       protected String contentType;
       protected int contentLength;
       protected boolean hasRedirected = false;
  +    protected int statusCode;
   
       public AbstractCommandLineEnvironment(String uri,
                                             String view,
  @@ -91,6 +92,7 @@
           super(uri, view, context);
           this.enableLogging(log);
           this.outputStream = stream;
  +        this.statusCode = 0;
       }
   
       /**
  @@ -166,6 +168,20 @@
       }
   
       /**
  +     * Set the StatusCode
  +     */
  +    public void setStatus(int statusCode) {
  +        this.statusCode = statusCode;
  +    }
  +
  +    /**
  +     * Get the StatusCode
  +     */
  +    public int getStatus() {
  +        return statusCode;
  +    }
  +
  +    /**
        * Set the ContentType
        */
       public void setContentType(String contentType) {
  @@ -185,8 +201,4 @@
       public String getContentType() {
           return this.contentType;
       }
  -
  -
  -}
  -
  -
  +}
  \ No newline at end of file
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/bean/helpers/DelayedOutputStream.java
  
  Index: DelayedOutputStream.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.bean.helpers;
  
  import java.io.BufferedOutputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.FileDescriptor;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  
  /**
   *   A output stream writing to a ByteArrayOutputStream, until an OutputStream target is defined.
   *
   * @author    huber@apache.org
   * @author    uv@upaya.co.uk
   * @version CVS $Id: DelayedOutputStream.java,v 1.1 2003/06/04 09:25:53 upayavira Exp $
   */
  public class DelayedOutputStream extends OutputStream {
      /**
       * write to baos as long as fos member is still null,
       * create a ByteArrayOutputStream only
       */
      private ByteArrayOutputStream baos;
      /**
       * If fos is defined write into fos, dump content of
       * baos to fos first
       */
      private OutputStream fos;
  
  
      /**
       * Constructor for the DelayedFileOutputStream object,
       * create a ByteArrayOutputStream only
       */
      public DelayedOutputStream() {
          baos = new ByteArrayOutputStream();
          fos = null;
      }
  
      /**
       * Creates a file output stream to write to the file represented by the specified File object.
       *
       * @param  outputStream               The new fileOutputStream value
       * @exception  FileNotFoundException  thrown if creating of FileOutputStream fails
       */
      public void setFileOutputStream(OutputStream outputStream) throws FileNotFoundException {
          if (fos == null) {
              fos = outputStream;
          }
      }
  
      /**
       *   Write into ByteArrayOutputStrem, or FileOutputStream, depending on inner
       *   state of this stream
       *
       * @param  b                Description of Parameter
       * @exception  IOException  thrown iff implicitly flush of baos to fos fails, or writing
       *   of baos, or fos fails
       */
      public void write(int b) throws IOException {
          OutputStream os = getTargetOutputStream();
          os.write(b);
      }
  
  
      /**
       *   Write into ByteArrayOutputStrem, or FileOutputStream, depending on inner
       *   state of this stream
       *
       * @param  b                Description of Parameter
       * @exception  IOException  thrown iff implicitly flush of baos to fos fails, or writing
       *   of baos, or fos fails
       */
      public void write(byte b[]) throws IOException {
          OutputStream os = getTargetOutputStream();
          os.write(b);
      }
  
  
      /**
       *   Write into ByteArrayOutputStrem, or FileOutputStream, depending on inner
       *   state of this stream
       *
       * @param  b                Description of Parameter
       * @param  off              Description of Parameter
       * @param  len              Description of Parameter
       * @exception  IOException  thrown iff implicitly flush of baos to fos fails, or writing
       *   of baos, or fos fails
       */
      public void write(byte b[], int off, int len) throws IOException {
          OutputStream os = getTargetOutputStream();
          os.write(b, off, len);
      }
  
  
      /**
       *   Close ByteArrayOutputStrem, and FileOutputStream, depending on inner
       *   state of this stream
       *
       * @exception  IOException  thrown iff implicitly flush of baos to fos fails, or closing
       *   of baos, or fos fails
       */
      public void close() throws IOException {
          IOException ioexception = null;
  
          getTargetOutputStream();
  
          // close baos
          try {
              if (baos != null) {
                  baos.close();
              }
          } catch (IOException ioe) {
              if (ioexception == null) {
                  ioexception = ioe;
              }
          } finally {
              baos = null;
          }
  
          // close fos
          try {
              if (fos != null) {
                  fos.close();
              }
          } catch (IOException ioe) {
              if (ioexception == null) {
                  ioexception = ioe;
              }
          } finally {
              fos = null;
          }
  
          if (ioexception != null) {
              throw ioexception;
          }
      }
  
  
      /**
       *   Flush ByteArrayOutputStrem, writing content to FileOutputStream,
       *   flush FileOutputStream
       *
       * @exception  IOException  thrown iff implicitly flush of baos to fos fails, or flushing
       *   of baos, or fos fails
       */
      public void flush() throws IOException {
          IOException ioexception = null;
  
          // flush baos, writing to fos, if neccessary
          getTargetOutputStream();
  
          // flush baos
          try {
              if (baos != null) {
                  baos.flush();
              }
          } catch (IOException ioe) {
              if (ioexception == null) {
                  ioexception = ioe;
              }
          }
  
          // flush fos
          try {
              if (fos != null) {
                  fos.flush();
              }
          } catch (IOException ioe) {
              if (ioexception == null) {
                  ioexception = ioe;
              }
          }
          if (ioexception != null) {
              throw ioexception;
          }
      }
  
  
      /**
       *   Gets the targetOutputStream attribute of the DelayedFileOutputStream object
       *
       * @return                  The targetOutputStream value
       * @exception  IOException  thrown iff implicitly flush of baos to fos fails
       */
      private OutputStream getTargetOutputStream() throws IOException {
          if (baos != null && fos == null) {
  
              // no fos is defined, just write to baos in the mean time
              return baos;
          } else if (baos != null && fos != null) {
              // fos is defined, flush boas to fos, and destroy baos
              IOException ioexception = null;
  
              try {
                  baos.flush();
                  baos.writeTo(fos);
                  baos.close();
              } catch (IOException ioe) {
                  ioexception = ioe;
              } finally {
                  baos = null;
              }
  
              if (ioexception != null) {
                  throw ioexception;
              }
  
              return fos;
          } else if (baos == null && fos != null) {
              // no more temporary baos writing, write directly to fos
              return fos;
          } else {
              // neither baos, nor fos are valid
              throw new IOException("No outputstream available!");
          }
      }
  }