| 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.lang3.ArrayUtils; |
| import org.apache.commons.lang3.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> |
| * @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 |
| { |
| return locateDocumentFiles( siteRenderingContext, false ); |
| } |
| |
| /** {@inheritDoc} */ |
| public Map<String, DocumentRenderer> locateDocumentFiles( SiteRenderingContext siteRenderingContext, |
| boolean editable ) |
| 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( siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files, |
| editable ); |
| } |
| } |
| } |
| |
| // 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( siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes, |
| files, editable ); |
| } |
| 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 rootDir, File moduleBasedir, ParserModule module, String excludes, |
| Map<String, DocumentRenderer> files, boolean editable ) |
| throws IOException, RendererException |
| { |
| if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) ) |
| { |
| return; |
| } |
| |
| String moduleRelativePath = |
| PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() ); |
| |
| 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, moduleRelativePath, doc, |
| module.getParserId(), extension, editable ); |
| |
| // 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 docRenderingContext, SiteRenderingContext siteContext ) |
| throws RendererException, FileNotFoundException, UnsupportedEncodingException |
| { |
| SiteRendererSink sink = new SiteRendererSink( docRenderingContext ); |
| |
| File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() ); |
| |
| Reader reader = null; |
| try |
| { |
| String resource = doc.getAbsolutePath(); |
| |
| Parser parser = doxia.getParser( docRenderingContext.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 ( docRenderingContext.getAttribute( "velocity" ) != null ) |
| { |
| getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() ); |
| try |
| { |
| Context vc = createDocumentVelocityContext( docRenderingContext, 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 |
| saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent ); |
| } |
| |
| reader = new StringReader( doxiaContent ); |
| } |
| catch ( VelocityException e ) |
| { |
| throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath() |
| + " as a Velocity template: " + e.getMessage(), e ); |
| } |
| |
| 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() ) ); |
| |
| doxia.parse( reader, docRenderingContext.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 ); |
| } |
| |
| mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext ); |
| } |
| |
| private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext, |
| String doxiaContent ) |
| throws IOException |
| { |
| if ( !siteContext.getProcessedContentOutput().exists() ) |
| { |
| siteContext.getProcessedContentOutput().mkdirs(); |
| } |
| |
| String input = docRenderingContext.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 ); |
| } |
| |
| /** |
| * 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 content the document content to be merged into the template |
| * @param siteRenderingContext the site rendering context |
| * @return |
| */ |
| protected Context createSiteTemplateVelocityContext( DocumentContent content, |
| SiteRenderingContext siteRenderingContext ) |
| { |
| // first get the context from document |
| Context context = createDocumentVelocityContext( content.getRenderingContext(), siteRenderingContext ); |
| |
| // then add data objects from rendered document |
| |
| // Add infos from document |
| context.put( "authors", content.getAuthors() ); |
| |
| context.put( "shortTitle", content.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 += " – "; // Symbol Name: En Dash, Html Entity: – |
| } |
| title += content.getTitle(); |
| |
| context.put( "title", title ); |
| |
| context.put( "headContent", content.getHead() ); |
| |
| context.put( "bodyContent", content.getBody() ); |
| |
| // document date (got from Doxia Sink date() API) |
| String documentDate = content.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 " |
| + content.getRenderingContext().getInputName() |
| + " (expected yyyy-MM-dd format), ignoring!" ); |
| } |
| } |
| |
| // document rendering context, to get eventual inputName |
| context.put( "docRenderingContext", content.getRenderingContext() ); |
| |
| return context; |
| } |
| |
| /** {@inheritDoc} */ |
| public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext ) |
| throws RendererException |
| { |
| mergeDocumentIntoSite( writer, sink, siteRenderingContext ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void mergeDocumentIntoSite( Writer writer, DocumentContent content, |
| SiteRenderingContext siteRenderingContext ) |
| throws RendererException |
| { |
| String templateName = siteRenderingContext.getTemplateName(); |
| |
| getLogger().debug( "Processing Velocity for template " + templateName + " on " |
| + content.getRenderingContext().getInputName() ); |
| |
| Context context = createSiteTemplateVelocityContext( content, 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 |
| } |
| } |
| } |