Sunday, June 19, 2011

Playing with Apache Cocoon 3.0 - Part 2

As couple of people requested a complete example, I uploaded the full source file, as well as some sample input files, everything but the jar files here.


About the upload

  • The uploaded file is called dist.zip (MD5: d930916c8506bdc563925385e7aa1162) and contains 2 folders : src and www
  • The src folder contains the Java source file 
  • The www folders contains all the files needed to generate a simple website. I didn't provide the exact files that I used for privacy reasons.

Dependencies


You can grab the required Jar dependencies from Maven repositories(click on the hyperlinks below) :



Running the application
  • Create an Java project with the IDE of your choice and add the source file and the required dependencies(jar files).
  • In the main method of the class StaticSiteGenerator, change the variable wwwRoot to point the the folder www folder extracted from the dist.zip archive.
  • Run the class and the output will be generated in the www/out folder in HTML format.

Saturday, April 09, 2011

Playing with Apache Cocoon 3.0

This morning, I needed to generate quickly a simple website without learning some template engine or recalling it.

I was thinking about couple of years ago when I was working a lot with Apache Cocoon and XML processing in general.

I downloaded the latest build of Cocoon 3.0 alpha 2 and gave it a shot. Below are my settings for the site generation :

  • Input folder containg XML files (root element and html content)
  • Toc file for site navigation (xml + html markup)
  • Resources to include 
  • An XSL stylesheet for the website generation (dynamic include of the TOC)
The site was ready after few minutes and minor adjustments. It looks like there's a bug in the XIncludeTransformer so I couldn't use it with the alpha 2 version.

Below is some quick and dirty code(all in one class, without other files used) written after downloading it. There are some errors(generics and invalid content) in the code below because of the code formatter that I'm using on this blog.
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.transform.OutputKeys;

import org.apache.cocoon.pipeline.NonCachingPipeline;
import org.apache.cocoon.pipeline.Pipeline;
import org.apache.cocoon.sax.SAXPipelineComponent;
import org.apache.cocoon.sax.component.XMLGenerator;
import org.apache.cocoon.sax.component.XMLSerializer;
import org.apache.cocoon.sax.component.XSLTTransformer;

// Simple website generation demo with Apache Cocoon 3.0 alpha2
public final class StaticSiteGenerator {

    // Generator configuration parameters
    private static enum SiteConfiguration {
        INPUT_FOLDER_LOCATION,
        OUTPUT_FOLDER_LOCATION,
        WEBSITE_XSL_STYLESHEET_LOCATION,
        RESOURCES_FOLDER_LOCATION,
        TOC_FILE_LOCATION,
        INPUT_FILES_EXTENSION
    }
    
    // Error thrown when the configuration appears to be invalid
    private static class InvalidConfiguration extends RuntimeException {
        private static final long serialVersionUID = -8661543651471092797L;

        public InvalidConfiguration(String message) {
            super(message);
        }
    }

    // Parameter passed to the XSL stylesheet to include a navigation bar
    private static final String TOC_FILE_XSLT_PARAMETER_NAME = "tocURL";

    // Current link in the TOC
    private static final String TOC_FILE_CURRENT_LINK = "currentLink";    

    // Copy a file to a folder
    static void copyToFolder(File in, File outputFolder) throws IOException {
        File out = new File(outputFolder, in.getName());
        
        FileChannel inChannel = new FileInputStream(in).getChannel();
        FileChannel outChannel = new FileOutputStream(out).getChannel();
        
        try {
            inChannel.transferTo(0, inChannel.size(), outChannel);            
        } 
        catch (IOException e) {
            throw e;
        }
        finally {
            if (inChannel != null)  inChannel.close();
            if (outChannel != null) outChannel.close();
        }
    }

    // Generator configuration parameters
    private EnumMap configuration;
    
    
    // Creates a new StaticSiteGenerator with a given configuration
    StaticSiteGenerator(EnumMap configuration) {
        validateConfiguration(configuration);
        this.configuration = configuration;
    }
    
    // Simple configuration validation
    private void validateConfiguration(EnumMap configuration) throws InvalidConfiguration {
        if (configuration == null) {
            throw new IllegalArgumentException("You must provide a valid configuration");
        }
        
        Collection allConfigurations = new ArrayList(Arrays.asList(SiteConfiguration.values()));
        Collection providedConfigurations = new ArrayList(configuration.keySet());
        allConfigurations.removeAll(providedConfigurations);
        
        if (!allConfigurations.isEmpty()) {
            StringBuilder sb = new StringBuilder(allConfigurations.size() * 3);
            sb.append("Some configuration parameters were not found:");
            SiteConfiguration[] missingConfigurations = allConfigurations.toArray(new SiteConfiguration[allConfigurations.size()]);
            sb.append(missingConfigurations[0]);
            for (int i = 1; i < missingConfigurations.length; i++) {
                sb.append(", ").append(missingConfigurations[i]);
            }
            throw new InvalidConfiguration(sb.toString());
        }
    }

    private void clean(File outputFolder) {
        for (File outputFile : outputFolder.listFiles()) {
            outputFile.delete();
        }
        
    }
    // Generates the static web site
    private void execute() throws Exception {        
        final File inputFilesFolder = new File(configuration.get(SiteConfiguration.INPUT_FOLDER_LOCATION).toString());
        final File outputFolder = new File(configuration.get(SiteConfiguration.OUTPUT_FOLDER_LOCATION).toString());
        final File resourceFolder = new File(configuration.get(SiteConfiguration.RESOURCES_FOLDER_LOCATION).toString());
        final File xslStylesheet = new File(configuration.get(SiteConfiguration.WEBSITE_XSL_STYLESHEET_LOCATION).toString());
        final String inputFilesExtension = configuration.get(SiteConfiguration.INPUT_FILES_EXTENSION).toString();
        final File tocFile = new File(configuration.get(SiteConfiguration.TOC_FILE_LOCATION).toString()); 
        
        clean(outputFolder);
        
        final String tocURL = tocFile.getAbsolutePath();
        
        // Get the list of XML files for each page of the website
        File[] inputFiles = inputFilesFolder.listFiles(new FileFilter() {
            public boolean accept(File pathname) {
                return pathname.getName().endsWith(inputFilesExtension) && pathname.isFile();
            }
        });
        
        File[] resources = resourceFolder.listFiles();

        // Copy resources to the output folder
        for (File resource : resources) {
            copyToFolder(resource, outputFolder);
        }

        // Loop through the input files
        for (File inputFile : inputFiles) {
            OutputStream outputFileStream = null;

            String msg = "Transforming '%s' to '%s'";

            try {
                String outputFilename = inputFile.getName().replaceAll(inputFilesExtension, ".html");

                File outputFile = new File(outputFolder, outputFilename);

                Logger.getAnonymousLogger().log(Level.INFO, String.format(msg, inputFile.getAbsolutePath(), outputFile.getAbsolutePath()));
                
                outputFileStream = new FileOutputStream(outputFile);
                
                Pipeline pipeline = new NonCachingPipeline();
                
                pipeline.addComponent(new XMLGenerator(inputFile.toURI().toURL()));
                pipeline.addComponent(newXSLTransformer(xslStylesheet, tocURL, outputFilename));
                pipeline.addComponent(newXMLSerializer());

                pipeline.setup(outputFileStream);
                pipeline.execute();
            }
            finally {
                if (outputFileStream != null)  outputFileStream.close();
            }           
        }        

    }
    
    private XMLSerializer newXMLSerializer() {
        Properties format = new Properties();
        
        format.put(OutputKeys.METHOD, "xml");
        format.put(OutputKeys.INDENT, "yes");
        
        return new XMLSerializer(format);
        
    }
    
    private XSLTTransformer newXSLTransformer(File stylesheet, String tocFileURL, String currentLink) throws MalformedURLException {
        Map parameters = new HashMap(1);
        parameters.put(StaticSiteGenerator.TOC_FILE_XSLT_PARAMETER_NAME, tocFileURL);
        parameters.put(StaticSiteGenerator.TOC_FILE_CURRENT_LINK, currentLink);

        XSLTTransformer transformer = new XSLTTransformer(stylesheet.toURI().toURL());
        transformer.setParameters(parameters);

        return transformer;
    }

   public static void main(String[] args) throws Exception {
        String wwwRoot = "/Users/yves/Desktop/www"; 
        
        EnumMap configuration;
        configuration = new EnumMap(StaticSiteGenerator.SiteConfiguration.class);

        configuration.put(SiteConfiguration.INPUT_FOLDER_LOCATION, wwwRoot + "/in");
        configuration.put(SiteConfiguration.INPUT_FILES_EXTENSION, ".xml");
        configuration.put(SiteConfiguration.TOC_FILE_LOCATION, wwwRoot + "/toc/toc.xml");
        configuration.put(SiteConfiguration.OUTPUT_FOLDER_LOCATION, wwwRoot + "/out");
        configuration.put(SiteConfiguration.WEBSITE_XSL_STYLESHEET_LOCATION, wwwRoot + "/resources/xsl/site.xsl");
        configuration.put(SiteConfiguration.RESOURCES_FOLDER_LOCATION, wwwRoot + "/resources/files");
        
        StaticSiteGenerator generator = new StaticSiteGenerator(configuration);
        generator.execute();
    }

}