blob: 64fb0f0846f6b548cfaf0ccce9b24d5970672ddd [file] [log] [blame]
/*
* 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.
*/
package org.apache.myfaces.buildtools.maven2.plugin.builder;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.myfaces.buildtools.maven2.plugin.builder.model.Model;
import org.apache.myfaces.buildtools.maven2.plugin.builder.utils.BuildException;
import org.apache.myfaces.buildtools.maven2.plugin.builder.utils.MavenPluginConsoleLogSystem;
import org.apache.myfaces.buildtools.maven2.plugin.builder.utils.MyfacesUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.Xpp3DomWriter;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* Creates taglib (tld) and faces-config files.
* <p>
* This Mojo actually provides a generic goal that will run a configurable
* velocity template file and generate a single output file. The template is
* passed the full metadata model object, and can select from the model whatever
* data it wants.
* </p>
* <p>
* The execution can be configured with:
* <ul>
* <li>The name of the input template to be executed</li>
* <li>The name of the output file to be created</li>
* <li>Any number of variables which are accessable from the template
* (string values only)</li>
* </ul>
* </p>
* <p>
* The executed template has the following variables available to it:
* <ul>
* <li>model: the full metadata model for this project</li>
* <li>modelIds: specifies which items from the model should be processed</li>
* <li>baseContent: the full text of the contents of the "xmlBaseFile" (if any)</li>
* <li>utils: an instance of MyfacesUtils that provides static helper methods</li>
* <li>and whatever (name,value) pairs were configured via the "params" property
* of this mojo.</li>
* </ul>
* </p>
*
* @requiresDependencyResolution compile
* @goal make-config
* @phase generate-sources
*/
public class MakeConfigMojo extends AbstractBuilderMojo
{
/**
* The current maven project (auto-injected by Maven).
*
* @parameter expression="${project}"
* @readonly
*/
private MavenProject project;
/**
* Defines the directory where the metadata file (META-INF/myfaces-metadata.xml) is loaded,
* and the generated file named by xmlFile parameter is created.
*
* @parameter expression="${project.build.directory}/generated-resources/myfaces-builder-plugin"
*/
private File buildDirectory;
/**
* Defines the directory where the resource will be output.
*
* @since 1.0.4
* @parameter expression="${project.build.directory}/generated-resources/myfaces-builder-plugin"
*/
private File outputDirectory;
/**
* Name of the file from which the metadata model passed to the
* template will be read. This file is typically generated by an earlier
* execution of the BuildMetaDataMojo goal for the same maven project.
*
* @parameter
*/
private String metadataFile = "META-INF/myfaces-metadata.xml";
/**
* Specifies the name of the output file to be created.
*
* @parameter
*/
private String xmlFile = "META-INF/faces-config.xml";
/**
* A list of metadata model ids.
* <p>
* If not defined, then this defaults to a list containing just one string whose
* value is the same as the current maven project's artifactId.
* </p>
* <p>
* The complete metadata model is passed to the velocity template. This contains
* data about not just the model items defined in the calling project, but also
* about items imported from other projects. Each item in the model is
* labelled with a "model id" indicating which project it was defined by.
* </p>
* <p>
* This list of model ids is also passed to the template. The template can
* then use that data to select the relevant items from the full model.
* </p>
*
* @parameter
*/
private List modelIds;
/**
* The base directory used when resolving references to external files from
* velocity #include and #parse commands within the executed template.
*
* @parameter expression="src/main/resources/META-INF"
*/
private File templateSourceDirectory;
/**
* When defined, specifies an xml file whose top-level elements are to be copied
* directly into the created file.
* <p>
* This allows a hand-written file to be created containing data that cannot be
* automatically generated. All content beneath the root element of the specified
* file (but not the root element itself) is placed into a Velocity variable
* named "baseContent" that the template can reference.
* </p>
* <p>
* The template can do whatever it wants with this variable, but it is expected
* that the template will simply output this immediately after writing the
* root element of the output xml file.
* file.
* </p>
* <p>
* Note that any attributes or namespaces defined on the root element of the
* xmlBaseFile are ignored.
* </p>
*
* @parameter expression="src/main/conf/META-INF/faces-config-base.xml"
*/
private File xmlBaseFile;
/**
* Specifies the Velocity template file to be executed.
*
* @parameter expression="faces-config11.vm"
*/
private String templateFile;
/**
* A map of (name, value) pairs to be made available as Velocity variables
* for the executed template to access.
*
* @parameter
*/
private Map params;
/**
* Execute the Mojo.
* <p>
* The metadata model is loaded, and the specified template is executed with
* any template output being written to the specified output file.
* </p>
*/
public void execute() throws MojoExecutionException
{
try
{
if (modelIds == null)
{
modelIds = new ArrayList();
modelIds.add(project.getArtifactId());
}
// Load the metadata file from an xml file (presumably generated
// by an earlier execution of the build-metadata goal.
File mdFile = new File(buildDirectory, metadataFile);
Model model = IOUtils.loadModel(mdFile);
// Flatten the model so that the template can access every property
// of each model item directly, even when the property is actually
// defined on an ancestor class or interface.
new Flattener(model).flatten();
Properties cacheInfo = new Properties();
loadCache(cacheInfo);
generateConfigFromVelocity(model, cacheInfo, mdFile.lastModified() );
storeCache(cacheInfo);
}
catch (IOException e)
{
throw new MojoExecutionException("Error during config generation", e);
}
catch (BuildException e)
{
throw new MojoExecutionException("Error during config generation", e);
}
}
private void generateConfigFromVelocity(Model model,
Properties cachedInfo, long lastModifiedMetadata) throws IOException,
MojoExecutionException
{
Writer writer = null;
File outFile = null;
File tf = new File(templateSourceDirectory, templateFile);
try
{
outFile = new File(outputDirectory, xmlFile);
if ( !outFile.getParentFile().exists() )
{
outFile.getParentFile().mkdirs();
}
if (isCachingEnabled() && outFile.exists())
{
boolean upToDate = isFileUpToDate(cachedInfo, lastModifiedMetadata, outFile);
if (upToDate && xmlBaseFile != null && xmlBaseFile.exists())
{
upToDate = isFileUpToDate(cachedInfo, xmlBaseFile);
}
if (upToDate && tf != null && tf.exists())
{
upToDate = isFileUpToDate(cachedInfo, tf);
}
if (upToDate)
{
getLog().info("generated file " +outFile.getName()+ " is up to date");
return;
}
}
VelocityEngine velocityEngine = initVelocity();
VelocityContext baseContext = new VelocityContext();
baseContext.put("utils", new MyfacesUtils());
String baseContent = "";
if (xmlBaseFile != null && xmlBaseFile.exists())
{
getLog().info("using base content file: "+xmlBaseFile.getPath());
Reader reader = null;
try
{
reader = new FileReader(xmlBaseFile);
Xpp3Dom root = Xpp3DomBuilder.build(reader);
StringWriter swriter = new StringWriter();
Xpp3Dom [] children = root.getChildren();
for (int i = 0; i< children.length; i++)
{
Xpp3Dom dom = children[i];
Xpp3DomWriter.write(swriter, dom);
swriter.write('\n');
}
baseContent = swriter.toString();
swriter.close();
}
catch (XmlPullParserException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
reader.close();
}
}
baseContext.put("baseContent", baseContent);
baseContext.put("model", model);
baseContext.put("modelIds", modelIds);
if (params != null)
{
//Load all parameters to the context, so the template can
//load it. This allow to generate any config file we want
//(faces-config, tld, facelet,....)
for (Iterator it = params.keySet().iterator(); it.hasNext();)
{
String key = (String) it.next();
baseContext.put(key,params.get(key));
}
}
writer = new OutputStreamWriter(new FileOutputStream(outFile));
Template template = velocityEngine.getTemplate(templateFile);
template.merge(baseContext, writer);
writer.flush();
if (isCachingEnabled())
{
cachedInfo.put(outFile.getAbsolutePath(), Long.toString(lastModifiedMetadata));
if (xmlBaseFile != null && xmlBaseFile.exists())
{
cachedInfo.put(xmlBaseFile.getAbsolutePath(), Long.toString(xmlBaseFile.lastModified()));
}
if (tf != null && tf.exists())
{
cachedInfo.put(tf.getAbsolutePath(), Long.toString(tf.lastModified()));
}
}
}
catch (ResourceNotFoundException e)
{
throw new MojoExecutionException(
"Error merging velocity templates: " + e.getMessage(), e);
}
catch (ParseErrorException e)
{
throw new MojoExecutionException(
"Error merging velocity templates: " + e.getMessage(), e);
}
catch (Exception e)
{
throw new MojoExecutionException(
"Error merging velocity templates: " + e.getMessage(), e);
}
finally
{
IOUtil.close(writer);
writer = null;
}
}
private VelocityEngine initVelocity() throws MojoExecutionException
{
VelocityEngine velocityEngine = new VelocityEngine();
try
{
velocityEngine.setProperty( "resource.loader", "file, class" );
velocityEngine.setProperty( "file.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.FileResourceLoader");
velocityEngine.setProperty( "file.resource.loader.path", templateSourceDirectory.getPath());
velocityEngine.setProperty( "class.resource.loader.class",
"org.apache.myfaces.buildtools.maven2.plugin.builder.utils.RelativeClasspathResourceLoader" );
velocityEngine.setProperty( "class.resource.loader.path", "META-INF");
velocityEngine.setProperty( "velocimacro.library", "xmlMacros.vm");
velocityEngine.setProperty( "velocimacro.permissions.allow.inline","true");
velocityEngine.setProperty( "velocimacro.permissions.allow.inline.local.scope", "true");
velocityEngine.setProperty( "directive.foreach.counter.initial.value","0");
//velocityEngine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
//"org.apache.myfaces.buildtools.maven2.plugin.builder.utils.ConsoleLogSystem" );
velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM,
new MavenPluginConsoleLogSystem(this.getLog()));
velocityEngine.init();
}
catch (Exception e)
{
throw new MojoExecutionException("Error creating VelocityEngine", e);
}
return velocityEngine;
}
}