blob: 97c77407537cde9735f61e4194bdef98fb8d1bdc [file] [log] [blame]
package org.apache.maven.archetype.mojos;
/*
* 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 org.apache.maven.archetype.ArchetypeCreationRequest;
import org.apache.maven.archetype.ArchetypeCreationResult;
import org.apache.maven.archetype.ArchetypeManager;
import org.apache.maven.archetype.common.Constants;
import org.apache.maven.archetype.ui.creation.ArchetypeCreationConfigurator;
import org.apache.maven.artifact.repository.ArtifactRepository;
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.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.PropertyUtils;
import org.codehaus.plexus.util.StringUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
/**
* <p>
* Creates an archetype project from the current project.
* </p>
* <p>
* This goal reads your source and resource files, the values of its parameters,
* and properties you specify in a <code>.property</code> file, and uses them to
* create a Maven archetype project using the maven-archetype packaging.
* If you build the resulting project, it will create the archetype. You can then
* use this archetype to create new projects that resemble the original.
* </p>
* <p>
* The maven-archetype-plugin uses Velocity to expand template files, and this documentation
* talks about 'Velocity Properties', which are values substituted into Velocity templates.
* See <a href="http://velocity.apache.org/engine/devel/user-guide.html">The Velocity User's Guide</a>
* for more information.
* </p>
* <p>
* This goal modifies the text of the files of the current project to form the Velocity template files
* that make up the archetype.
* </p>
* <dl>
* <dt>GAV</dt><dd>The GAV values for the current project are replaced by properties: groupId, artifactId, and version.
* The user chooses new values for these when generating a project from the archetype.</dd>
* <dt>package</dt><dd>All the files under one specified Java (or cognate) package are relocated to a project
* that the user chooses when generating a project. References to the class name are replaced by a property reference. For
* example, if the current project's sources are in the package <code>org.apache.saltedpeanuts</code>, then
* any example of the string <code>org.apache.saltedpeanuts</code> is replaced with the Velocity property
* reference <code>${packageName}</code>. When the user generates a project, this is in turn replaced by
* his or her choice of a package.
* </dd>
* <dt>custom properties</dt><dd>You may identify additional strings that should be replaced by parameters.
* To add custom properties, you must use the <code>propertyFile</code> parameter to specify a property file.
* See the documentation for <code>propertyFile</code> for the details.
* </dd>
* <dt>integration tests</dt><dd>You may also specify a set of integration tests to be executed right after
* you create archetype from the project. Each test consists of a separate folder under src/it/projects folder and
* in there you specify <code>archetype.properties</code>, <code>goal.txt</code> and <code>verify.groovy</code> files.
* The <code>archetype.properties</code> file is the file used to generate a new project from the newly created archetype
* and the <code>goal.txt</code> a single-line file to specify the maven goal to invoke after generation of the test-project.
* Finally the <code>verify.groovy</code> is a groovy file that you can use to specify your assertions on the generated project.
* </dd>
* </dl>
* <p>
* Note that you may need to edit the results of this goal. This goal has no way to exclude unwanted files,
* or add copyright notices to the Velocity templates, or add more complex elements to the archetype metadata file.
* </p>
* <p>
* This goal also generates a simple integration-test that exercises the generated archetype.
* </p>
*
* @author rafale
*/
@Mojo( name = "create-from-project", requiresProject = true, aggregator = true )
@Execute( phase = LifecyclePhase.GENERATE_SOURCES )
public class CreateArchetypeFromProjectMojo
extends AbstractMojo
{
@Component
private ArchetypeCreationConfigurator configurator;
/**
* Enable the interactive mode to define the archetype from the project.
*/
@Parameter( property = "interactive", defaultValue = "false" )
private boolean interactive;
@Component
private ArchetypeManager manager;
/**
* File extensions which are checked for project's text files (vs binary files).
*/
@Parameter( property = "archetype.filteredExtentions" )
private String archetypeFilteredExtentions;
/**
* Directory names which are checked for project's sources main package.
*/
@Parameter( property = "archetype.languages" )
private String archetypeLanguages;
/**
* Velocity templates encoding.
*/
@Parameter( property = "archetype.encoding", defaultValue = "UTF-8" )
private String defaultEncoding;
/**
* Create a partial archetype.
*/
@Parameter( property = "archetype.partialArchetype" )
private boolean partialArchetype = false;
/**
* Create pom's velocity templates with CDATA preservation. This uses the <code>String.replaceAll()</code>
* method and risks to have some overly replacement capabilities (beware of '1.0' value).
*/
@Parameter( property = "archetype.preserveCData" )
private boolean preserveCData = false;
@Parameter( defaultValue = "${localRepository}", readonly = true )
private ArtifactRepository localRepository;
/**
* POMs in archetype are created with their initial parent.
* This property is ignored when preserveCData is true.
*/
@Parameter( property = "archetype.keepParent" )
private boolean keepParent = true;
/**
* The Maven project to create an archetype from.
*/
@Parameter( defaultValue = "${project}", readonly = true, required = true )
private MavenProject project;
/**
* The property file that holds the plugin configuration. If this is provided, then
* the plugin reads properties from here. The properties in here can be standard
* properties listed below or custom properties for this archetype. The standard properties
* are below. Several of them overlap parameters of this goal; it's better to just
* set the parameter.
* <p/>
* <dl><dt>package</dt><dd>See the packageName parameter.</dd>
* <dt>archetype.languages</dt><dd>See the archetypeLanguages parameter.</dd>
* <dt>groupId</dt><dd>The default groupId of the generated project.</dd>
* <dt>artifactId</dt><dd>The default artifactId of the generated project.</dd>
* <dt>version</dt><dd>The default version of the generated project.</dd>
* <dt>excludePatterns</dt><dd>A comma-separated list of paths that will not be included in the resulting
* archetype.</dd>
* <dt>archetype.filteredExtensions</dt><dd>See the filteredExensions parameter.</dd>
* </dl>
* <strong>Custom Properties</strong>
* <p>
* Custom properties allow you to replace some constant values in the project's files
* with Velocity macro references. When a user generates a project from your archetype
* he or she gets the opportunity to replace the value from the source project.
* </p>
* <p>
* Custom property names <strong>may not contain the '.' character</strong>.
* </p>
* <p>
* For example, if you include a line like the following in your property file:
* <pre>cxf-version=2.5.1-SNAPSHOT</pre>
* the plugin will search your files for the <code>2.5.1-SNAPSHOT</code> string and
* replace them with references to a velocity macro <code>cxf-version</cpde>. It will
* then list <code>cxf-version</code> as a <code>requiredProperty</code> in the
* <code>archetype-metadata.xml</code>, with <code>2.5.1-SNAPSHOT</code> as the default value.
* </p>
*/
@Parameter( property = "archetype.properties" )
private File propertyFile;
/**
* The property telling which phase to call on the generated archetype.
* Interesting values are: <code>package</code>, <code>integration-test</code>, <code>install</code> and <code>deploy</code>.
*/
@Parameter( property = "archetype.postPhase", defaultValue = "package" )
private String archetypePostPhase;
/**
* The directory where the archetype should be created.
*/
@Parameter( defaultValue = "${project.build.directory}/generated-sources/archetype" )
private File outputDirectory;
@Parameter( property = "testMode" )
private boolean testMode;
/**
* The package name for Java source files to be incorporated in the archetype and
* and relocated to the package that the user selects.
*/
@Parameter( property = "packageName" )
private String packageName; //Find a better way to resolve the package!!! enforce usage of the configurator
@Parameter( defaultValue = "${session}", readonly = true, required = true )
private MavenSession session;
public void execute()
throws MojoExecutionException, MojoFailureException
{
Properties executionProperties = session.getExecutionProperties();
try
{
if ( propertyFile != null )
{
propertyFile.getParentFile().mkdirs();
}
List<String> languages = getLanguages( archetypeLanguages, propertyFile );
Properties properties =
configurator.configureArchetypeCreation( project, Boolean.valueOf( interactive ), executionProperties,
propertyFile, languages );
List<String> filtereds = getFilteredExtensions( archetypeFilteredExtentions, propertyFile );
ArchetypeCreationRequest request =
new ArchetypeCreationRequest().setDefaultEncoding( defaultEncoding ).setProject( project )
/* Used when in interactive mode */.setProperties( properties ).setLanguages( languages )
/* Should be refactored to use some ant patterns */.setFiltereds( filtereds )
/* This should be correctly handled */.setPreserveCData( preserveCData ).setKeepParent(
keepParent ).setPartialArchetype( partialArchetype )
.setLocalRepository( localRepository ).setProjectBuildingRequest( session.getProjectBuildingRequest() )
/* this should be resolved and asked for user to verify */.setPackageName( packageName ).setPostPhase(
archetypePostPhase ).setOutputDirectory( outputDirectory );
ArchetypeCreationResult result = manager.createArchetypeFromProject( request );
if ( result.getCause() != null )
{
throw new MojoFailureException( result.getCause(), result.getCause().getMessage(),
result.getCause().getMessage() );
}
getLog().info( "Archetype project created in " + outputDirectory );
if ( testMode )
{
// Now here a properties file would be useful to write so that we could automate
// some functional tests where we string together an:
//
// archetype create from project -> deploy it into a test repo
// project create from archetype -> use the repository we deployed to archetype to
// generate
// test the output
//
// This of course would be strung together from the outside.
}
}
catch ( MojoFailureException ex )
{
throw ex;
}
catch ( Exception ex )
{
throw new MojoFailureException( ex, ex.getMessage(), ex.getMessage() );
}
}
private List<String> getFilteredExtensions( String archetypeFilteredExtentions, File propertyFile )
{
List<String> filteredExtensions = new ArrayList<String>();
if ( StringUtils.isNotEmpty( archetypeFilteredExtentions ) )
{
filteredExtensions.addAll( Arrays.asList( StringUtils.split( archetypeFilteredExtentions, "," ) ) );
getLog().debug( "Found in command line extensions = " + filteredExtensions );
}
if ( filteredExtensions.isEmpty() && propertyFile != null && propertyFile.exists() )
{
Properties properties = PropertyUtils.loadProperties( propertyFile );
String extensions = properties.getProperty( Constants.ARCHETYPE_FILTERED_EXTENSIONS );
if ( StringUtils.isNotEmpty( extensions ) )
{
filteredExtensions.addAll( Arrays.asList( StringUtils.split( extensions, "," ) ) );
}
getLog().debug( "Found in propertyFile " + propertyFile.getName() + " extensions = " + filteredExtensions );
}
if ( filteredExtensions.isEmpty() )
{
filteredExtensions.addAll( Constants.DEFAULT_FILTERED_EXTENSIONS );
getLog().debug( "Using default extensions = " + filteredExtensions );
}
return filteredExtensions;
}
private List<String> getLanguages( String archetypeLanguages, File propertyFile )
{
List<String> resultingLanguages = new ArrayList<String>();
if ( StringUtils.isNotEmpty( archetypeLanguages ) )
{
resultingLanguages.addAll( Arrays.asList( StringUtils.split( archetypeLanguages, "," ) ) );
getLog().debug( "Found in command line languages = " + resultingLanguages );
}
if ( resultingLanguages.isEmpty() && propertyFile != null && propertyFile.exists() )
{
Properties properties = PropertyUtils.loadProperties( propertyFile );
String languages = properties.getProperty( Constants.ARCHETYPE_LANGUAGES );
if ( StringUtils.isNotEmpty( languages ) )
{
resultingLanguages.addAll( Arrays.asList( StringUtils.split( languages, "," ) ) );
}
getLog().debug( "Found in propertyFile " + propertyFile.getName() + " languages = " + resultingLanguages );
}
if ( resultingLanguages.isEmpty() )
{
resultingLanguages.addAll( Constants.DEFAULT_LANGUAGES );
getLog().debug( "Using default languages = " + resultingLanguages );
}
return resultingLanguages;
}
}