/*
* Copyright (C) 2010 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.wikbook.maven;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.wikbook.core.ResourceType;
import org.wikbook.core.ValidationMode;
import org.wikbook.core.xml.XML;
import org.wikbook.xwiki.AbstractXDOMDocbookBuilderContext;
import org.wikbook.xwiki.Format;
import org.wikbook.xwiki.WikbookConverter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
* @goal transform
* @phase compile
* @requiresDependencyResolution
*/
public class WikBookMojo extends AbstractMojo
{
/**
* The source directory.
*
* @parameter
*/
private File sourceDirectory;
/**
* The extra source directory.
*
* @parameter
*/
private File extraSourceDirectory;
/**
* The source file name.
*
* @parameter
*/
private String sourceFileName;
/**
* The syntax id.
*
* @parameter
*/
private String syntaxId;
/**
* The destination directory.
*
* @parameter
*/
private File destinationDirectory;
/**
* The destination file name.
*
* @parameter
*/
private String destinationFileName;
/**
* Turn on/off code highlighting
*
* @parameter default-value="true"
*/
private boolean highlightCode;
/**
* Turn on/off code doctype generation of the DocBook document.
*
* @parameter default-value="true"
*/
private boolean emitDoctype;
/**
* The output format of the document: book or chapter.
*
* @parameter default-value="book"
*/
private String format;
/**
* The wikbook validation mode, can either be <code>lax</code> or <code>strict</code>.
*
* @parameter default-value="lax"
*/
private String validationMode;
/**
* Any XML that should be inserted before the book body.
*
* @parameter default-value=""
*/
private String beforeBookBodyXML;
/**
* Any XML that should be inserted after the book body.
*
* @parameter default-value=""
*/
private String afterBookBodyXML;
/**
* The charset name when reading wiki files, the default value is the UTF-8 charset.
*
* @parameter default-value="UTF-8"
*/
private String charset;
/**
* The book id, when specified it modifies the XML element <code>book</code> produced to add an <code>id</code> attribute
* with the specified value.
*
* @parameter default-value="UTF-8"
*/
private String bookId;
/**
* INTERNAL : The representation of the maven execution.
*
* @parameter expression="${session}"
* @required
* @readonly
*/
private MavenSession session;
/**
* @parameter expression="${plugin.artifacts}"
* @required
* @readonly
*/
private List pluginArtifacts;
/**
* The source directories containing the sources to be compiled.
*
* @parameter default-value="${project.compileSourceRoots}"
* @required
* @readonly
*/
private List<String> compileSourceRoots;
/**
* Project classpath.
*
* @parameter default-value="${project.compileClasspathElements}"
* @required
* @readonly
*/
private List<String> compileClasspathElements;
/**
* The source directories containing the sources to be compiled.
*
* @parameter default-value="${project.testCompileSourceRoots}"
* @required
* @readonly
*/
private List<String> testCompileSourceRoots;
/**
* Project classpath.
*
* @parameter default-value="${project.testClasspathElements}"
* @required
* @readonly
*/
private List<String> testClasspathElements;
/** The source roots. */
private List<File> roots = null;
private Iterable<File> getRoots()
{
if (roots == null)
{
List<File> roots = new ArrayList<File>();
if (sourceDirectory != null)
{
roots.add(sourceDirectory);
}
if (extraSourceDirectory != null)
{
roots.add(extraSourceDirectory);
}
this.roots = roots;
}
return roots;
}
public void execute() throws MojoExecutionException, MojoFailureException
{
// Determine initial file
File src = null;
for (File root : getRoots())
{
File f = new File(root, sourceFileName);
if (f.exists() && f.isFile())
{
src = f;
break;
}
}
//
if (src == null)
{
throw new MojoFailureException("Source file " + sourceFileName + " is not valid");
}
//
if (destinationDirectory.exists())
{
if (!destinationDirectory.isDirectory())
{
throw new MojoFailureException("Destination directory " + destinationDirectory.getAbsolutePath() + " is not a directory");
}
}
else
{
if (!destinationDirectory.mkdirs())
{
throw new MojoFailureException("Could not create destination directory " + destinationDirectory.getAbsolutePath());
}
}
//
WikbookConverter converter;
try
{
converter = new WikbookConverter(context);
}
catch (Exception e)
{
throw new MojoFailureException("", e);
}
// Set the format
if (format != null)
{
converter.setFormat(Format.valueOf(format.toUpperCase().trim()));
}
//
converter.setEmitDoctype(emitDoctype);
converter.setSyntaxId(syntaxId);
converter.setBookId(bookId);
//
File destination = new File(destinationDirectory, destinationFileName);
//
Writer out = null;
try
{
if (hasTrimmedContent(beforeBookBodyXML))
{
DocumentFragment elt = load(beforeBookBodyXML);
converter.setBeforeBookBodyXML(elt);
}
//
if (hasTrimmedContent(afterBookBodyXML))
{
DocumentFragment elt = load(afterBookBodyXML);
converter.setAfterBookBodyXML(elt);
}
//
out = new OutputStreamWriter(new FileOutputStream(destination), charset);
StreamResult result = new StreamResult(out);
converter.convert(sourceFileName, result);
}
catch (Exception e)
{
MojoFailureException mfe = new MojoFailureException("Could not create destination file " + e.getMessage());
mfe.initCause(e);
throw mfe;
}
finally
{
if (out != null)
{
try
{
out.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
private static boolean hasTrimmedContent(String xml)
{
return xml != null && xml.trim().length() > 0;
}
private static DocumentFragment load(String xml) throws ParserConfigurationException, IOException, SAXException
{
xml = "<root>" + xml + "</root>";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setXIncludeAware(false);
dbf.setValidating(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xml)));
DocumentFragment fragment = doc.createDocumentFragment();
XML.copyStandaloneNodes(doc.getDocumentElement(), fragment);
return fragment;
}
private final AbstractXDOMDocbookBuilderContext context = new AbstractXDOMDocbookBuilderContext()
{
public void log(String msg)
{
getLog().info(msg);
}
public boolean getHighlightCode()
{
return highlightCode;
}
public ValidationMode getValidationMode()
{
if ("strict".equalsIgnoreCase(validationMode))
{
return ValidationMode.STRICT;
}
else
{
return ValidationMode.LAX;
}
}
@Override
public List<URL> resolveResources(ResourceType type, Iterable<String> path, String id) throws IOException
{
List<URL> urls = Collections.emptyList();
if (id.length() > 0)
{
switch (type)
{
case WIKI:
// Explanations (or not to self since I was not able to remember what it did and wasted some time)
// The Iterable<String> path when it is not empty is the list of included documents, for instance
// if "book.wiki" includes "a/b.wiki" which includes "c/d.wiki" then the path should be the
// list {"a/b.wiki","c/d.wiki"}.
// The path is therefore a path of included documents used to find a resource, so the code below
// if "d.wiki" includes "e.wiki" in the same directory should follow:
// 1/ current = "/" (the root)
// 2/ current = "/a"
// 3/ current = "/a/c"
// 4/ resolved = "/a/c/e.wiki"
for (File root : getRoots())
{
File current = root;
for (String segment : path)
{
File relative = new File(current, segment);
current = relative.getParentFile();
}
File resolved = new File(current, id);
if (resolved.exists() && resolved.isFile())
{
if (urls.isEmpty())
{
urls = new ArrayList<URL>();
}
urls.add(resolved.toURI().toURL());
}
}
break;
case XML:
case JAVA:
case DEFAULT:
//
List<String> dirs = new ArrayList<String>();
dirs.addAll(compileSourceRoots);
dirs.addAll(compileClasspathElements);
dirs.addAll(testCompileSourceRoots);
dirs.addAll(testClasspathElements);
//
LinkedHashSet<URL> urlSet = new LinkedHashSet<URL>();
//
for (String elt : dirs)
{
File eltFile = new File(elt);
if (eltFile.exists())
{
urlSet.add(eltFile.toURI().toURL());
}
}
//
for (Artifact artifact : (Set<Artifact>)session.getCurrentProject().getDependencyArtifacts())
{
urlSet.add(artifact.getFile().toURI().toURL());
}
// Remove trailing '/' if any
if (id.startsWith("/"))
{
id = id.substring(1);
}
//
ClassLoader cl = new URLClassLoader(urlSet.toArray(new URL[urlSet.size()]), ClassLoader.getSystemClassLoader());
urls =Collections.list(cl.getResources(id));
}
}
//
return urls;
}
public String getProperty(String propertyName)
{
Properties properties = session.getCurrentProject().getProperties();
return properties.getProperty(propertyName);
}
@Override
protected String getCharsetName()
{
return charset;
}
};
}