package org.ops4j.pax.construct.lifecycle;
/*
* Copyright 2007 Stuart McCulloch
*
* Licensed 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.File;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.installer.ArtifactInstallationException;
import org.apache.maven.artifact.installer.ArtifactInstaller;
import org.apache.maven.artifact.manager.WagonManager;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.util.IOUtil;
import org.ops4j.pax.construct.util.PomUtils;
import org.ops4j.pax.construct.util.StreamFactory;
/**
* Provision all local and imported bundles onto the selected OSGi framework
*
* <code><pre>
* mvn pax:provision [-Dframework=felix|equinox|kf|concierge] [-Dprofiles=log,war,spring,...]
* </pre></code>
*
* If you don't have Pax-Runner in your local Maven repository this command
* will automatically attempt to download the latest release. It will then
* continue to use this locally installed version of Pax-Runner unless you
* add <code>-U</code> to force it to check online for a later release, or
* <code>-Drunner=version</code> to temporarily use a different version.
*
* @goal provision
* @aggregator true
*
* @requiresProject false
* @requiresDependencyResolution test
*/
public class ProvisionMojo extends AbstractMojo
{
/**
* Maven groupId for the new Pax-Runner
*/
private static final String PAX_RUNNER_GROUP = "org.ops4j.pax.runner";
/**
* Maven artifactId for the new Pax-Runner
*/
private static final String PAX_RUNNER_ARTIFACT = "pax-runner";
/**
* Main entry-point for the new Pax-Runner
*/
private static final String PAX_RUNNER_METHOD = "org.ops4j.pax.runner.Run";
/**
* Accumulated set of bundles to be deployed
*/
private static List m_bundleIds;
/**
* Component for resolving Maven metadata
*
* @component
*/
private ArtifactMetadataSource m_source;
/**
* Component factory for Maven artifacts
*
* @component
*/
private ArtifactFactory m_factory;
/**
* Component for resolving Maven artifacts
*
* @component
*/
private ArtifactResolver m_resolver;
/**
* Component for installing Maven artifacts
*
* @component
*/
private ArtifactInstaller m_installer;
/**
* Component factory for Maven projects
*
* @component
*/
private MavenProjectBuilder m_projectBuilder;
/**
* The local Maven settings.
*
* @parameter expression="${settings}"
* @required
* @readonly
*/
private Settings m_settings;
/**
* List of remote Maven repositories for the containing project.
*
* @parameter expression="${project.remoteArtifactRepositories}"
* @required
* @readonly
*/
private List m_remoteRepos;
/**
* The local Maven repository for the containing project.
*
* @parameter expression="${localRepository}"
* @required
* @readonly
*/
private ArtifactRepository m_localRepo;
/**
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject m_project;
/**
* The current Maven reactor.
*
* @parameter expression="${reactorProjects}"
* @required
* @readonly
*/
private List m_reactorProjects;
/**
* Name of the OSGi framework to deploy onto.
*
* @parameter expression="${framework}"
*/
private String framework;
/**
* When true, start the OSGi framework and deploy the provisioned bundles.
*
* @parameter expression="${deploy}" default-value="true"
*/
private boolean deploy;
/**
* Comma separated list of additional Pax-Runner profiles to deploy.
*
* @parameter expression="${profiles}"
*/
private String profiles;
/**
* URL of file containing additional Pax-Runner arguments.
*
* @parameter expression="${args}"
*/
private String args;
/**
* Ignore bundle dependencies when deploying project.
*
* @parameter expression="${noDeps}"
*/
private boolean noDependencies;
/**
* Comma separated list of additional POMs with bundles as dependencies.
*
* @parameter expression="${deployPoms}"
*/
private String deployPoms;
/**
* Comma separated list of additional bundle URLs to deploy.
*
* @parameter expression="${deployURLs}"
*/
private String deployURLs;
/**
* The version of Pax-Runner to use for provisioning.
*
* @parameter expression="${runner}" default-value="RELEASE"
*/
private String runner;
/**
* A set of provision commands for Pax-Runner.
*
* @parameter expression="${provision}"
*/
private String[] provision;
/**
* Component factory for Maven repositories.
*
* @component
*/
private ArtifactRepositoryFactory m_repoFactory;
/**
* @component roleHint="default"
*/
private ArtifactRepositoryLayout m_defaultLayout;
/**
* Component for calculating mirror details.
*
* @component
*/
private WagonManager m_wagonManager;
/**
* Runtime helper available on Maven 2.0.9 and above.
*/
private Method m_getMirrorRepository;
/**
* {@inheritDoc}
*/
public void execute()
throws MojoExecutionException
{
m_bundleIds = new ArrayList();
if( deployPoms != null )
{
addAdditionalPoms();
}
if( m_project.getFile() != null )
{
for( Iterator i = m_reactorProjects.iterator(); i.hasNext(); )
{
addProjectBundles( (MavenProject) i.next(), false == noDependencies );
}
}
setupRuntimeHelpers();
deployBundles();
}
/**
* Use reflection to find if certain runtime helper methods are available.
*/
private void setupRuntimeHelpers()
{
try
{
m_getMirrorRepository = m_wagonManager.getClass().getMethod( "getMirrorRepository", new Class[]
{
ArtifactRepository.class
} );
}
catch( RuntimeException e )
{
m_getMirrorRepository = null;
}
catch( NoSuchMethodException e )
{
m_getMirrorRepository = null;
}
}
/**
* Does this look like a provisioning POM? ie. artifactId of 'provision', packaging type 'pom', with dependencies
*
* @param project a Maven project
* @return true if this looks like a provisioning POM, otherwise false
*/
public static boolean isProvisioningPom( MavenProject project )
{
// ignore POMs which don't have provision as their artifactId
if( !"provision".equals( project.getArtifactId() ) )
{
return false;
}
// ignore POMs that produce actual artifacts
if( !"pom".equals( project.getPackaging() ) )
{
return false;
}
// ignore POMs with no dependencies at all
List dependencies = project.getDependencies();
if( dependencies == null || dependencies.size() == 0 )
{
return false;
}
return true;
}
/**
* Adds project artifact (if it's a bundle) to the deploy list as well as any non-optional bundle dependencies
*
* @param project a Maven project
* @param checkDependencies when true, check project dependencies for other bundles to provision
*/
private void addProjectBundles( MavenProject project, boolean checkDependencies )
{
if( PomUtils.isBundleProject( project, m_resolver, m_remoteRepos, m_localRepo, true ) )
{
provisionBundle( project.getArtifact() );
}
if( checkDependencies || isProvisioningPom( project ) )
{
addProjectDependencies( project );
}
}
/**
* Adds any non-optional bundle dependencies to the deploy list
*
* @param project a Maven project
*/
private void addProjectDependencies( MavenProject project )
{
Set artifacts = project.getArtifacts();
for( Iterator i = artifacts.iterator(); i.hasNext(); )
{
Artifact artifact = (Artifact) i.next();
if( !artifact.isOptional() && !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
{
provisionBundle( artifact );
}
}
}
/**
* @param bundle potential bundle artifact
*/
private void provisionBundle( Artifact bundle )
{
if( "pom".equals( bundle.getType() ) )
{
return;
}
// force download here, as next check tries to avoid downloading where possible
if( !PomUtils.downloadFile( bundle, m_resolver, m_remoteRepos, m_localRepo ) )
{
getLog().warn( "Skipping missing artifact " + bundle );
return;
}
if( PomUtils.isBundleArtifact( bundle, m_resolver, m_remoteRepos, m_localRepo, true ) )
{
String version = PomUtils.getMetaVersion( bundle );
String id = bundle.getGroupId() + ':' + bundle.getArtifactId() + ':' + version + ':' + bundle.getType();
if( !m_bundleIds.contains( id ) )
{
m_bundleIds.add( id );
}
}
else
{
getLog().warn( "Skipping non-bundle artifact " + bundle );
}
}
/**
* Add user supplied POMs as if they were in the Maven reactor
*/
private void addAdditionalPoms()
{
String[] pomPaths = deployPoms.split( "," );
for( int i = 0; i < pomPaths.length; i++ )
{
File pomFile = new File( pomPaths[i].trim() );
if( pomFile.exists() )
{
try
{
addProjectBundles( m_projectBuilder.build( pomFile, m_localRepo, null ), true );
}
catch( ProjectBuildingException e )
{
getLog().warn( "Unable to build Maven project for " + pomFile );
}
}
}
}
/**
* Create deployment POM and pass it onto Pax-Runner for provisioning
*
* @throws MojoExecutionException
*/
private void deployBundles()
throws MojoExecutionException
{
if( m_bundleIds.size() == 0 )
{
getLog().info( "~~~~~~~~~~~~~~~~~~~" );
getLog().info( " No bundles found! " );
getLog().info( "~~~~~~~~~~~~~~~~~~~" );
}
List bundles = resolveProvisionedBundles();
MavenProject deployProject = createDeploymentProject( bundles );
installDeploymentPom( deployProject );
if( !deploy )
{
getLog().info( "Skipping OSGi deployment" );
return;
}
m_remoteRepos.add( getOps4jRepository() ); // can remove this once runner is on central
String delim = "";
StringBuffer repoListBuilder = new StringBuffer( "+" );
for( Iterator i = m_remoteRepos.iterator(); i.hasNext(); )
{
repoListBuilder.append( delim );
ArtifactRepository repo = (ArtifactRepository) i.next();
repoListBuilder.append( getRepositoryURL( repo ) );
if( repo.getSnapshots().isEnabled() )
{
repoListBuilder.append( "@snapshots" );
}
if( false == repo.getReleases().isEnabled() )
{
repoListBuilder.append( "@noreleases" );
}
delim = ",";
}
if( PomUtils.needReleaseVersion( runner ) )
{
// find the latest release of Pax-Runner by querying the local and remote repos...
Artifact runnerProject = m_factory.createProjectArtifact( PAX_RUNNER_GROUP, PAX_RUNNER_ARTIFACT, runner );
runner = PomUtils.getReleaseVersion( runnerProject, m_source, m_remoteRepos, m_localRepo, null );
}
/*
* Dynamically load the correct Pax-Runner code
*/
Pattern classicVersion = Pattern.compile( "0\\.[1-4]\\.\\d" );
if( classicVersion.matcher( runner ).matches() )
{
Class clazz = loadRunnerClass( "org.ops4j.pax", "runner", PAX_RUNNER_METHOD, false );
deployRunnerClassic( clazz, deployProject, repoListBuilder.toString() );
}
else
{
Class clazz = loadRunnerClass( PAX_RUNNER_GROUP, PAX_RUNNER_ARTIFACT, PAX_RUNNER_METHOD, true );
deployRunnerNG( clazz, deployProject, repoListBuilder.toString() );
}
}
/**
* @param repo remote Maven repository
* @return repository (or mirror) URL
*/
private String getRepositoryURL( ArtifactRepository repo )
{
if( null != m_getMirrorRepository )
{
try
{
ArtifactRepository mirror = (ArtifactRepository) m_getMirrorRepository.invoke( m_wagonManager,
new Object[]
{
repo
} );
if( null != mirror )
{
return mirror.getUrl();
}
}
catch( RuntimeException e )
{
getLog().warn( e );
}
catch( IllegalAccessException e )
{
getLog().warn( e );
}
catch( InvocationTargetException e )
{
getLog().warn( e );
}
}
Mirror mirror = m_settings.getMirrorOf( repo.getId() );
if( null != mirror )
{
return mirror.getUrl();
}
mirror = m_settings.getMirrorOf( "*" );
if( null != mirror )
{
return mirror.getUrl();
}
return repo.getUrl();
}
/**
* Attempt to resolve each provisioned bundle, and warn about any we can't find
*
* @return list of bundles to be deployed (as Maven dependencies)
*/
private List resolveProvisionedBundles()
{
List dependencies = new ArrayList();
for( Iterator i = m_bundleIds.iterator(); i.hasNext(); )
{
String id = (String) i.next();
String[] fields = id.split( ":" );
Dependency dep = new Dependency();
dep.setGroupId( fields[0] );
dep.setArtifactId( fields[1] );
dep.setVersion( fields[2] );
dep.setType( fields[3] );
dep.setScope( Artifact.SCOPE_PROVIDED );
dependencies.add( dep );
}
return dependencies;
}
/**
* Create new POM (based on the root POM) which lists the deployed bundles as dependencies
*
* @param bundles list of bundles to be deployed
* @return deployment project
* @throws MojoExecutionException
*/
private MavenProject createDeploymentProject( List bundles )
throws MojoExecutionException
{
MavenProject deployProject;
if( null == m_project.getFile() )
{
deployProject = new MavenProject();
deployProject.setGroupId( "examples" );
deployProject.setArtifactId( "pax-provision" );
deployProject.setVersion( "1.0-SNAPSHOT" );
}
else
{
deployProject = new MavenProject( m_project );
}
String internalId = PomUtils.getCompoundId( deployProject.getGroupId(), deployProject.getArtifactId() );
deployProject.setGroupId( internalId + ".build" );
deployProject.setArtifactId( "deployment" );
// remove unnecessary cruft
deployProject.setPackaging( "pom" );
deployProject.getModel().setModules( null );
deployProject.getModel().setDependencies( bundles );
deployProject.getModel().setPluginRepositories( null );
deployProject.getModel().setReporting( null );
deployProject.setBuild( null );
File deployFile = new File( deployProject.getBasedir(), "runner/deploy-pom.xml" );
deployFile.getParentFile().mkdirs();
deployProject.setFile( deployFile );
try
{
Writer writer = StreamFactory.newXmlWriter( deployFile );
deployProject.writeModel( writer );
IOUtil.close( writer );
}
catch( IOException e )
{
throw new MojoExecutionException( "Unable to write deployment POM " + deployFile );
}
return deployProject;
}
/**
* Install deployment POM in the local Maven repository
*
* @param project deployment project
* @throws MojoExecutionException
*/
private void installDeploymentPom( MavenProject project )
throws MojoExecutionException
{
String groupId = project.getGroupId();
String artifactId = project.getArtifactId();
String version = project.getVersion();
Artifact pomArtifact = m_factory.createProjectArtifact( groupId, artifactId, version );
try
{
m_installer.install( project.getFile(), pomArtifact, m_localRepo );
}
catch( ArtifactInstallationException e )
{
throw new MojoExecutionException( "Unable to install deployment POM " + pomArtifact );
}
}
/**
* Dynamically resolve and load the Pax-Runner class
*
* @param groupId pax-runner group id
* @param artifactId pax-runner artifact id
* @param mainClass main pax-runner classname
* @param needClassifier classify pax-runner artifact according to current JVM
* @return main pax-runner class
* @throws MojoExecutionException
*/
private Class loadRunnerClass( String groupId, String artifactId, String mainClass, boolean needClassifier )
throws MojoExecutionException
{
String jdk = null;
if( needClassifier && System.getProperty( "java.class.version" ).compareTo( "49.0" ) < 0 )
{
jdk = "jdk14";
}
Artifact jarArtifact = m_factory.createArtifactWithClassifier( groupId, artifactId, runner, "jar", jdk );
if( !PomUtils.downloadFile( jarArtifact, m_resolver, m_remoteRepos, m_localRepo ) )
{
throw new MojoExecutionException( "Unable to find Pax-Runner " + jarArtifact );
}
URL[] urls = new URL[1];
try
{
urls[0] = jarArtifact.getFile().toURI().toURL();
}
catch( MalformedURLException e )
{
throw new MojoExecutionException( "Bad Jar location " + jarArtifact.getFile() );
}
try
{
ClassLoader loader = new URLClassLoader( urls );
Thread.currentThread().setContextClassLoader( loader );
return Class.forName( mainClass, true, loader );
}
catch( ClassNotFoundException e )
{
throw new MojoExecutionException( "Unable to find entry point " + mainClass + " in " + urls[0] );
}
}
/**
* Deploy bundles using the 'classic' Pax-Runner
*
* @param mainClass main Pax-Runner class
* @param project deployment project
* @param repositories comma separated list of Maven repositories
* @throws MojoExecutionException
*/
private void deployRunnerClassic( Class mainClass, MavenProject project, String repositories )
throws MojoExecutionException
{
String workDir = project.getBasedir() + "/runner";
String cachedPomName = project.getArtifactId() + '_' + project.getVersion() + ".pom";
File cachedPomFile = new File( workDir + "/lib/" + cachedPomName );
// Force reload of pom
cachedPomFile.delete();
if( PomUtils.isEmpty( framework ) )
{
framework = "felix";
}
String[] deployAppCmds =
{
"--dir=" + workDir, "--no-md5", "--platform=" + framework, "--profile=default",
"--repository=" + repositories, "--localRepository=" + m_localRepo.getBasedir(), project.getGroupId(),
project.getArtifactId(), project.getVersion()
};
invokePaxRunner( mainClass, deployAppCmds );
}
/**
* This method allows subclasses to specify additional
* commands for provisioning. By default provide no initial
* deploy commands.
* @return A List of Strings that represent the deploy commands.
*/
protected List getDeployCommands()
{
return new ArrayList();
}
/**
* Deploy bundles using the new Pax-Runner codebase
*
* @param mainClass main Pax-Runner class
* @param project deployment project
* @param repositories comma separated list of Maven repositories
* @throws MojoExecutionException
*/
private void deployRunnerNG( Class mainClass, MavenProject project, String repositories )
throws MojoExecutionException
{
List deployAppCmds = getDeployCommands();
// only apply if explicitly configured
if( PomUtils.isNotEmpty( framework ) )
{
deployAppCmds.add( "--platform=" + framework );
}
if( PomUtils.isNotEmpty( profiles ) )
{
deployAppCmds.add( "--profiles=" + profiles );
}
if( PomUtils.isNotEmpty( args ) )
{
try
{
new URL( args ); // check syntax
}
catch( MalformedURLException e )
{
// assume it's a local filename
File argsFile = new File( args );
args = argsFile.toURI().toString();
}
// custom Pax-Runner arguments file
deployAppCmds.add( "--args=" + args );
}
// apply project provision settings before defaults
deployAppCmds.addAll( Arrays.asList( provision ) );
// main deployment pom with project bundles as dependencies
deployAppCmds.add( project.getFile().getAbsolutePath() );
if( PomUtils.isNotEmpty( deployURLs ) )
{
// additional (external) bundle URLs
String[] urls = deployURLs.split( "," );
for( int i = 0; i < urls.length; i++ )
{
deployAppCmds.add( urls[i].trim() );
}
}
// use project settings to access remote/local repositories
deployAppCmds.add( "--localRepository=" + m_localRepo.getBasedir() );
deployAppCmds.add( "--repositories=" + repositories );
deployAppCmds.add( "--overwriteUserBundles" );
getLog().debug( "Starting Pax-Runner " + runner + " with: " + deployAppCmds.toString() );
invokePaxRunner( mainClass, (String[]) deployAppCmds.toArray( new String[deployAppCmds.size()] ) );
}
/**
* Invoke Pax-Runner in-process
*
* @param mainClass main Pax-Runner class
* @param commands array of command-line options
* @throws MojoExecutionException
*/
private void invokePaxRunner( Class mainClass, String[] commands )
throws MojoExecutionException
{
Class[] paramTypes = new Class[1];
paramTypes[0] = String[].class;
Object[] paramValues = new Object[1];
paramValues[0] = commands;
try
{
Method entryPoint = mainClass.getMethod( "main", paramTypes );
entryPoint.invoke( null, paramValues );
}
catch( NoSuchMethodException e )
{
throw new MojoExecutionException( "Unable to find Pax-Runner entry point" );
}
catch( IllegalAccessException e )
{
throw new MojoExecutionException( "Unable to access Pax-Runner entry point" );
}
catch( InvocationTargetException e )
{
throw new MojoExecutionException( "Pax-Runner exception", e );
}
}
/**
* @return backup OPS4J remote repository
*/
ArtifactRepository getOps4jRepository()
{
ArtifactRepositoryPolicy noSnapshots = new ArtifactRepositoryPolicy( false,
ArtifactRepositoryPolicy.UPDATE_POLICY_DAILY, null );
ArtifactRepositoryPolicy releases = new ArtifactRepositoryPolicy( true,
ArtifactRepositoryPolicy.UPDATE_POLICY_NEVER, null );
return m_repoFactory.createArtifactRepository( "ops4j.releases", "http://repository.ops4j.org/maven2/",
m_defaultLayout, noSnapshots, releases );
}
}