blob: cd6081331f91bca753916bc27af546b962839a13 [file] [log] [blame]
package org.apache.maven.doxia.siterenderer;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.Restriction;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.doxia.Doxia;
import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
import org.apache.maven.doxia.parser.ParseException;
import org.apache.maven.doxia.parser.Parser;
import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
import org.apache.maven.doxia.site.decoration.DecorationModel;
import org.apache.maven.doxia.site.decoration.PublishDate;
import org.apache.maven.doxia.site.skin.SkinModel;
import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
import org.apache.maven.doxia.parser.module.ParserModule;
import org.apache.maven.doxia.parser.module.ParserModuleManager;
import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
import org.apache.maven.doxia.util.XmlValidator;
import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.config.ConfigurationUtils;
import org.apache.velocity.tools.config.EasyFactoryConfiguration;
import org.apache.velocity.tools.config.FactoryConfiguration;
import org.apache.velocity.tools.generic.AlternatorTool;
import org.apache.velocity.tools.generic.ClassTool;
import org.apache.velocity.tools.generic.ComparisonDateTool;
import org.apache.velocity.tools.generic.ContextTool;
import org.apache.velocity.tools.generic.ConversionTool;
import org.apache.velocity.tools.generic.DisplayTool;
import org.apache.velocity.tools.generic.EscapeTool;
import org.apache.velocity.tools.generic.FieldTool;
import org.apache.velocity.tools.generic.LinkTool;
import org.apache.velocity.tools.generic.LoopTool;
import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.apache.velocity.tools.generic.RenderTool;
import org.apache.velocity.tools.generic.ResourceTool;
import org.apache.velocity.tools.generic.SortTool;
import org.apache.velocity.tools.generic.XmlTool;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.PathTool;
import org.codehaus.plexus.util.PropertyUtils;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.WriterFactory;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.codehaus.plexus.velocity.VelocityComponent;
/**
* <p>DefaultSiteRenderer class.</p>
*
* @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
* @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
* @version $Id$
* @since 1.0
*/
@Component( role = Renderer.class )
public class DefaultSiteRenderer
extends AbstractLogEnabled
implements Renderer
{
// ----------------------------------------------------------------------
// Requirements
// ----------------------------------------------------------------------
@Requirement
private VelocityComponent velocity;
@Requirement
private ParserModuleManager parserModuleManager;
@Requirement
private Doxia doxia;
@Requirement
private I18N i18n;
@Requirement
private PlexusContainer plexus;
private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
// ----------------------------------------------------------------------
// Renderer implementation
// ----------------------------------------------------------------------
/** {@inheritDoc} */
public Map<String, DocumentRenderer> locateDocumentFiles( SiteRenderingContext siteRenderingContext )
throws IOException, RendererException
{
Map<String, DocumentRenderer> files = new LinkedHashMap<String, DocumentRenderer>();
Map<String, String> moduleExcludes = siteRenderingContext.getModuleExcludes();
// look in every site directory (in general src/site or target/generated-site)
for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
{
if ( siteDirectory.exists() )
{
Collection<ParserModule> modules = parserModuleManager.getParserModules();
// use every Doxia parser module
for ( ParserModule module : modules )
{
File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
addModuleFiles( moduleBasedir, module, excludes, files );
}
}
}
// look in specific modules directories (used for old Maven 1.x site layout: xdoc and fml docs in /xdocs)
for ( ExtraDoxiaModuleReference module : siteRenderingContext.getModules() )
{
try
{
ParserModule parserModule = parserModuleManager.getParserModule( module.getParserId() );
String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
addModuleFiles( module.getBasedir(), parserModule, excludes, files );
}
catch ( ParserModuleNotFoundException e )
{
throw new RendererException( "Unable to find module: " + e.getMessage(), e );
}
}
return files;
}
private List<String> filterExtensionIgnoreCase( List<String> fileNames, String extension )
{
List<String> filtered = new LinkedList<String>( fileNames );
for ( Iterator<String> it = filtered.iterator(); it.hasNext(); )
{
String name = it.next();
// Take care of extension case
if ( !endsWithIgnoreCase( name, extension ) )
{
it.remove();
}
}
return filtered;
}
private void addModuleFiles( File moduleBasedir, ParserModule module, String excludes,
Map<String, DocumentRenderer> files )
throws IOException, RendererException
{
if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
{
return;
}
List<String> allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
for ( String extension : module.getExtensions() )
{
String fullExtension = "." + extension;
List<String> docs = filterExtensionIgnoreCase( allFiles, fullExtension );
// *.<extension>.vm
List<String> velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
docs.addAll( velocityFiles );
for ( String doc : docs )
{
RenderingContext context =
new RenderingContext( moduleBasedir, doc, module.getParserId(), extension );
// TODO: DOXIA-111: we need a general filter here that knows how to alter the context
if ( endsWithIgnoreCase( doc, ".vm" ) )
{
context.setAttribute( "velocity", "true" );
}
String key = context.getOutputName();
key = StringUtils.replace( key, "\\", "/" );
if ( files.containsKey( key ) )
{
DocumentRenderer renderer = files.get( key );
RenderingContext originalContext = renderer.getRenderingContext();
File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
+ "' clashes with existing '" + originalDoc + "'." );
}
// -----------------------------------------------------------------------
// Handle key without case differences
// -----------------------------------------------------------------------
for ( Map.Entry<String, DocumentRenderer> entry : files.entrySet() )
{
if ( entry.getKey().equalsIgnoreCase( key ) )
{
RenderingContext originalContext = entry.getValue().getRenderingContext();
File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
{
throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
+ doc + "' clashes with existing '" + originalDoc + "'." );
}
if ( getLogger().isWarnEnabled() )
{
getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
+ "' could clash with existing '" + originalDoc + "'." );
}
}
}
files.put( key, new DoxiaDocumentRenderer( context ) );
}
}
}
/** {@inheritDoc} */
public void render( Collection<DocumentRenderer> documents, SiteRenderingContext siteRenderingContext,
File outputDirectory )
throws RendererException, IOException
{
for ( DocumentRenderer docRenderer : documents )
{
RenderingContext renderingContext = docRenderer.getRenderingContext();
File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
|| ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
if ( modified || docRenderer.isOverwrite() )
{
if ( !outputFile.getParentFile().exists() )
{
outputFile.getParentFile().mkdirs();
}
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "Generating " + outputFile );
}
Writer writer = null;
try
{
if ( !docRenderer.isExternalReport() )
{
writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
}
docRenderer.renderDocument( writer, this, siteRenderingContext );
}
finally
{
IOUtil.close( writer );
}
}
else
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( inputFile + " unchanged, not regenerating..." );
}
}
}
}
/** {@inheritDoc} */
public void renderDocument( Writer writer, RenderingContext renderingContext, SiteRenderingContext siteContext )
throws RendererException, FileNotFoundException, UnsupportedEncodingException
{
SiteRendererSink sink = new SiteRendererSink( renderingContext );
File doc = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
Reader reader = null;
try
{
String resource = doc.getAbsolutePath();
Parser parser = doxia.getParser( renderingContext.getParserId() );
// DOXIASITETOOLS-146 don't render comments from source markup
parser.setEmitComments( false );
// TODO: DOXIA-111: the filter used here must be checked generally.
if ( renderingContext.getAttribute( "velocity" ) != null )
{
getLogger().debug( "Processing Velocity for " + renderingContext.getInputName() );
try
{
Context vc = createDocumentVelocityContext( renderingContext, siteContext );
StringWriter sw = new StringWriter();
velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
String doxiaContent = sw.toString();
if ( siteContext.getProcessedContentOutput() != null )
{
// save Velocity processing result, ie the Doxia content that will be parsed after
if ( !siteContext.getProcessedContentOutput().exists() )
{
siteContext.getProcessedContentOutput().mkdirs();
}
String input = renderingContext.getInputName();
File outputFile = new File( siteContext.getProcessedContentOutput(),
input.substring( 0, input.length() - 3 ) );
File outputParent = outputFile.getParentFile();
if ( !outputParent.exists() )
{
outputParent.mkdirs();
}
FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
}
reader = new StringReader( doxiaContent );
}
catch ( Exception e )
{
if ( getLogger().isDebugEnabled() )
{
getLogger().error( "Error parsing " + resource + " as a velocity template, using as text.", e );
}
else
{
getLogger().error( "Error parsing " + resource + " as a velocity template, using as text." );
}
}
if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
{
reader = validate( reader, resource );
}
}
else
{
switch ( parser.getType() )
{
case Parser.XML_TYPE:
reader = ReaderFactory.newXmlReader( doc );
if ( siteContext.isValidate() )
{
reader = validate( reader, resource );
}
break;
case Parser.TXT_TYPE:
case Parser.UNKNOWN_TYPE:
default:
reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
}
}
sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
if ( reader == null ) // can happen if velocity throws above
{
throw new RendererException( "Error getting a parser for '" + doc + "'" );
}
doxia.parse( reader, renderingContext.getParserId(), sink );
}
catch ( ParserNotFoundException e )
{
throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
}
catch ( ParseException e )
{
throw new RendererException( "Error parsing '"
+ doc + "': line [" + e.getLineNumber() + "] " + e.getMessage(), e );
}
catch ( IOException e )
{
throw new RendererException( "IOException when processing '" + doc + "'", e );
}
finally
{
sink.flush();
sink.close();
IOUtil.close( reader );
}
generateDocument( writer, sink, siteContext );
}
/**
* Creates a Velocity Context with all generic tools configured wit the site rendering context.
*
* @param siteRenderingContext the site rendering context
* @return a Velocity tools managed context
*/
protected Context createToolManagedVelocityContext( SiteRenderingContext siteRenderingContext )
{
Locale locale = siteRenderingContext.getLocale();
String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
EasyFactoryConfiguration config = new EasyFactoryConfiguration( false );
config.property( "safeMode", Boolean.FALSE );
config.toolbox( Scope.REQUEST )
.tool( ContextTool.class )
.tool( LinkTool.class )
.tool( LoopTool.class )
.tool( RenderTool.class );
config.toolbox( Scope.APPLICATION ).property( "locale", locale )
.tool( AlternatorTool.class )
.tool( ClassTool.class )
.tool( ComparisonDateTool.class ).property( "format", dateFormat )
.tool( ConversionTool.class ).property( "dateFormat", dateFormat )
.tool( DisplayTool.class )
.tool( EscapeTool.class )
.tool( FieldTool.class )
.tool( MathTool.class )
.tool( NumberTool.class )
.tool( ResourceTool.class ).property( "bundles", new String[] { "site-renderer" } )
.tool( SortTool.class )
.tool( XmlTool.class );
FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath( TOOLS_LOCATION );
if ( customConfig != null )
{
config.addConfiguration( customConfig );
}
ToolManager manager = new ToolManager( false, false );
manager.configure( config );
return manager.createContext();
}
/**
* Create a Velocity Context for a Doxia document, containing every information about rendered document.
*
* @param sink the site renderer sink for the document
* @param siteRenderingContext the site rendering context
* @return
*/
protected Context createDocumentVelocityContext( RenderingContext renderingContext,
SiteRenderingContext siteRenderingContext )
{
Context context = createToolManagedVelocityContext( siteRenderingContext );
// ----------------------------------------------------------------------
// Data objects
// ----------------------------------------------------------------------
context.put( "relativePath", renderingContext.getRelativePath() );
String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
context.put( "currentFileName", currentFileName );
context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
context.put( "decoration", siteRenderingContext.getDecoration() );
Locale locale = siteRenderingContext.getLocale();
context.put( "locale", locale );
context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
context.put( "currentDate", new Date() );
SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
context.put( "dateRevision", sdf.format( new Date() ) );
context.put( "publishDate", siteRenderingContext.getPublishDate() );
PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
context.put( "dateFormat", dateFormat );
// doxiaSiteRendererVersion
InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
+ "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
Properties properties = PropertyUtils.loadProperties( inputStream );
if ( inputStream == null )
{
getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
}
else if ( properties == null )
{
getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
+ " in the Velocity context." );
}
else
{
context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
}
// Add user properties
Map<String, ?> templateProperties = siteRenderingContext.getTemplateProperties();
if ( templateProperties != null )
{
for ( Map.Entry<String, ?> entry : templateProperties.entrySet() )
{
context.put( entry.getKey(), entry.getValue() );
}
}
// ----------------------------------------------------------------------
// Tools
// ----------------------------------------------------------------------
context.put( "PathTool", new PathTool() );
context.put( "FileUtils", new FileUtils() );
context.put( "StringUtils", new StringUtils() );
context.put( "i18n", i18n );
context.put( "plexus", plexus );
return context;
}
/**
* Create a Velocity Context for the site template decorating the document. In addition to all the informations
* from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
*
* @param siteRendererSink the site renderer sink for the document
* @param siteRenderingContext the site rendering context
* @return
*/
protected Context createSiteTemplateVelocityContext( SiteRendererSink siteRendererSink,
SiteRenderingContext siteRenderingContext )
{
// first get the context from document
Context context = createDocumentVelocityContext( siteRendererSink.getRenderingContext(), siteRenderingContext );
// then add data objects from rendered document
// Add infos from document
context.put( "authors", siteRendererSink.getAuthors() );
context.put( "shortTitle", siteRendererSink.getTitle() );
// DOXIASITETOOLS-70: Prepend the project name to the title, if any
String title = "";
if ( siteRenderingContext.getDecoration() != null
&& siteRenderingContext.getDecoration().getName() != null )
{
title = siteRenderingContext.getDecoration().getName();
}
else if ( siteRenderingContext.getDefaultWindowTitle() != null )
{
title = siteRenderingContext.getDefaultWindowTitle();
}
if ( title.length() > 0 )
{
title += " &#x2013; "; // Symbol Name: En Dash, Html Entity: &ndash;
}
title += siteRendererSink.getTitle();
context.put( "title", title );
context.put( "headContent", siteRendererSink.getHead() );
context.put( "bodyContent", siteRendererSink.getBody() );
// document date (got from Doxia Sink date() API)
String documentDate = siteRendererSink.getDate();
if ( StringUtils.isNotEmpty( documentDate ) )
{
context.put( "documentDate", documentDate );
// deprecated variables that rework the document date, suppose one semantics over others
// (ie creation date, while it may be last modification date if the document writer decided so)
// see DOXIASITETOOLS-20 for the beginning and DOXIASITETOOLS-164 for the end of this story
try
{
// we support only ISO 8601 date
Date creationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( documentDate );
context.put( "creationDate", creationDate );
SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
context.put( "dateCreation", sdf.format( creationDate ) );
}
catch ( java.text.ParseException e )
{
getLogger().warn( "Could not parse date '" + documentDate + "' from "
+ siteRendererSink.getRenderingContext().getInputName()
+ " (expected yyyy-MM-dd format), ignoring!" );
}
}
return context;
}
/** {@inheritDoc} */
public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
throws RendererException
{
String templateName = siteRenderingContext.getTemplateName();
getLogger().debug( "Processing Velocity for template " + templateName + " on "
+ sink.getRenderingContext().getInputName() );
Context context = createSiteTemplateVelocityContext( sink, siteRenderingContext );
ClassLoader old = null;
if ( siteRenderingContext.getTemplateClassLoader() != null )
{
// -------------------------------------------------------------------------
// If no template classloader was set we'll just use the context classloader
// -------------------------------------------------------------------------
old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader( siteRenderingContext.getTemplateClassLoader() );
}
try
{
Template template;
Artifact skin = siteRenderingContext.getSkin();
try
{
SkinModel skinModel = siteRenderingContext.getSkinModel();
String encoding = ( skinModel == null ) ? null : skinModel.getEncoding();
template = ( encoding == null ) ? velocity.getEngine().getTemplate( templateName )
: velocity.getEngine().getTemplate( templateName, encoding );
}
catch ( ParseErrorException pee )
{
throw new RendererException( "Velocity parsing error while reading the site decoration template "
+ ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
pee );
}
catch ( ResourceNotFoundException rnfe )
{
throw new RendererException( "Could not find the site decoration template "
+ ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
rnfe );
}
try
{
StringWriter sw = new StringWriter();
template.merge( context, sw );
writer.write( sw.toString().replaceAll( "\r?\n", SystemUtils.LINE_SEPARATOR ) );
}
catch ( VelocityException ve )
{
throw new RendererException( "Velocity error while merging site decoration template.", ve );
}
catch ( IOException ioe )
{
throw new RendererException( "IO exception while merging site decoration template.", ioe );
}
}
finally
{
IOUtil.close( writer );
if ( old != null )
{
Thread.currentThread().setContextClassLoader( old );
}
}
}
private SiteRenderingContext createSiteRenderingContext( Map<String, ?> attributes, DecorationModel decoration,
String defaultWindowTitle, Locale locale )
{
SiteRenderingContext context = new SiteRenderingContext();
context.setTemplateProperties( attributes );
context.setLocale( locale );
context.setDecoration( decoration );
context.setDefaultWindowTitle( defaultWindowTitle );
return context;
}
/** {@inheritDoc} */
public SiteRenderingContext createContextForSkin( Artifact skin, Map<String, ?> attributes,
DecorationModel decoration, String defaultWindowTitle,
Locale locale )
throws IOException, RendererException
{
SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
context.setSkin( skin );
ZipFile zipFile = getZipFile( skin.getFile() );
InputStream in = null;
try
{
if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
{
context.setTemplateName( SKIN_TEMPLATE_LOCATION );
context.setTemplateClassLoader( new URLClassLoader( new URL[]{skin.getFile().toURI().toURL()} ) );
}
else
{
context.setTemplateName( DEFAULT_TEMPLATE );
context.setTemplateClassLoader( getClass().getClassLoader() );
context.setUsingDefaultTemplate( true );
}
ZipEntry skinDescriptorEntry = zipFile.getEntry( SkinModel.SKIN_DESCRIPTOR_LOCATION );
if ( skinDescriptorEntry != null )
{
in = zipFile.getInputStream( skinDescriptorEntry );
SkinModel skinModel = new SkinXpp3Reader().read( in );
context.setSkinModel( skinModel );
String toolsPrerequisite =
skinModel.getPrerequisites() == null ? null : skinModel.getPrerequisites().getDoxiaSitetools();
Package p = DefaultSiteRenderer.class.getPackage();
String current = ( p == null ) ? null : p.getImplementationVersion();
if ( StringUtils.isNotBlank( toolsPrerequisite ) && ( current != null )
&& !matchVersion( current, toolsPrerequisite ) )
{
throw new RendererException( "Cannot use skin: has " + toolsPrerequisite
+ " Doxia Sitetools prerequisite, but current is " + current );
}
}
}
catch ( XmlPullParserException e )
{
throw new RendererException( "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION
+ " skin descriptor from " + skin.getId() + " skin", e );
}
finally
{
IOUtil.close( in );
closeZipFile( zipFile );
}
return context;
}
boolean matchVersion( String current, String prerequisite )
throws RendererException
{
try
{
ArtifactVersion v = new DefaultArtifactVersion( current );
VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
boolean matched = false;
ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
if ( recommendedVersion == null )
{
List<Restriction> restrictions = vr.getRestrictions();
for ( Restriction restriction : restrictions )
{
if ( restriction.containsVersion( v ) )
{
matched = true;
break;
}
}
}
else
{
// only singular versions ever have a recommendedVersion
@SuppressWarnings( "unchecked" )
int compareTo = recommendedVersion.compareTo( v );
matched = ( compareTo <= 0 );
}
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
+ ", matched = " + matched );
}
return matched;
}
catch ( InvalidVersionSpecificationException e )
{
throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
}
}
/** {@inheritDoc} */
@Deprecated
public SiteRenderingContext createContextForTemplate( File templateFile, Map<String, ?> attributes,
DecorationModel decoration, String defaultWindowTitle,
Locale locale )
throws MalformedURLException
{
SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
context.setTemplateName( templateFile.getName() );
context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
return context;
}
/** {@inheritDoc} */
public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
File outputDirectory )
throws IOException
{
throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
}
/** {@inheritDoc} */
public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
throws IOException
{
if ( siteRenderingContext.getSkin() != null )
{
ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
try
{
for ( Enumeration<? extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
{
ZipEntry entry = e.nextElement();
if ( !entry.getName().startsWith( "META-INF/" ) )
{
File destFile = new File( outputDirectory, entry.getName() );
if ( !entry.isDirectory() )
{
if ( destFile.exists() )
{
// don't override existing content: avoids extra rewrite with same content or extra site
// resource
continue;
}
destFile.getParentFile().mkdirs();
copyFileFromZip( file, entry, destFile );
}
else
{
destFile.mkdirs();
}
}
}
}
finally
{
closeZipFile( file );
}
}
if ( siteRenderingContext.isUsingDefaultTemplate() )
{
InputStream resourceList = getClass().getClassLoader()
.getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
if ( resourceList != null )
{
Reader r = null;
LineNumberReader reader = null;
try
{
r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
reader = new LineNumberReader( r );
String line;
while ( ( line = reader.readLine() ) != null )
{
if ( line.startsWith( "#" ) || line.trim().length() == 0 )
{
continue;
}
InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
if ( is == null )
{
throw new IOException( "The resource " + line + " doesn't exist." );
}
File outputFile = new File( outputDirectory, line );
if ( outputFile.exists() )
{
// don't override existing content: avoids extra rewrite with same content or extra site
// resource
continue;
}
if ( !outputFile.getParentFile().exists() )
{
outputFile.getParentFile().mkdirs();
}
OutputStream os = null;
try
{
// for the images
os = new FileOutputStream( outputFile );
IOUtil.copy( is, os );
}
finally
{
IOUtil.close( os );
}
IOUtil.close( is );
}
}
finally
{
IOUtil.close( reader );
IOUtil.close( r );
}
}
}
// Copy extra site resources
for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
{
File resourcesDirectory = new File( siteDirectory, "resources" );
if ( resourcesDirectory != null && resourcesDirectory.exists() )
{
copyDirectory( resourcesDirectory, outputDirectory );
}
}
// Check for the existence of /css/site.css
File siteCssFile = new File( outputDirectory, "/css/site.css" );
if ( !siteCssFile.exists() )
{
// Create the subdirectory css if it doesn't exist, DOXIA-151
File cssDirectory = new File( outputDirectory, "/css/" );
boolean created = cssDirectory.mkdirs();
if ( created && getLogger().isDebugEnabled() )
{
getLogger().debug(
"The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
}
// If the file is not there - create an empty file, DOXIA-86
if ( getLogger().isDebugEnabled() )
{
getLogger().debug(
"The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
}
Writer writer = null;
try
{
writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
//DOXIA-290...the file should not be 0 bytes.
writer.write( "/* You can override this file with your own styles */" );
}
finally
{
IOUtil.close( writer );
}
}
}
private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
throws IOException
{
FileOutputStream fos = new FileOutputStream( destFile );
try
{
IOUtil.copy( file.getInputStream( entry ), fos );
}
finally
{
IOUtil.close( fos );
}
}
/**
* Copy the directory
*
* @param source source file to be copied
* @param destination destination file
* @throws java.io.IOException if any
*/
protected void copyDirectory( File source, File destination )
throws IOException
{
if ( source.exists() )
{
DirectoryScanner scanner = new DirectoryScanner();
String[] includedResources = {"**/**"};
scanner.setIncludes( includedResources );
scanner.addDefaultExcludes();
scanner.setBasedir( source );
scanner.scan();
List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() );
for ( String name : includedFiles )
{
File sourceFile = new File( source, name );
File destinationFile = new File( destination, name );
FileUtils.copyFile( sourceFile, destinationFile );
}
}
}
private Reader validate( Reader source, String resource )
throws ParseException, IOException
{
getLogger().debug( "Validating: " + resource );
try
{
String content = IOUtil.toString( new BufferedReader( source ) );
new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
return new StringReader( content );
}
finally
{
IOUtil.close( source );
}
}
// TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
static boolean endsWithIgnoreCase( String str, String searchStr )
{
if ( str.length() < searchStr.length() )
{
return false;
}
return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
}
private static ZipFile getZipFile( File file )
throws IOException
{
if ( file == null )
{
throw new IOException( "Error opening ZipFile: null" );
}
try
{
// TODO: plexus-archiver, if it could do the excludes
return new ZipFile( file );
}
catch ( ZipException ex )
{
IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
ioe.initCause( ex );
throw ioe;
}
}
private static void closeZipFile( ZipFile zipFile )
{
// TODO: move to plexus utils
try
{
zipFile.close();
}
catch ( IOException e )
{
// ignore
}
}
}