[MNG-4484] Create a Maven API for component configuration

git-svn-id: https://svn.apache.org/repos/asf/maven/maven-3/trunk@997005 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Benjamin Bentmann 2010-09-14 17:30:14 +00:00
parent b0a5753ad9
commit 8a3c4a0d77
9 changed files with 820 additions and 0 deletions

View File

@ -0,0 +1,71 @@
package org.apache.maven.configuration;
/*
* 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.File;
/**
* A path translator that resolves relative paths against a specific base directory.
*
* @author Benjamin Bentmann
*/
public class BasedirBeanConfigurationPathTranslator
implements BeanConfigurationPathTranslator
{
private final File basedir;
/**
* Creates a new path translator using the specified base directory.
*
* @param basedir The base directory to resolve relative paths against, may be {@code null} to disable path
* translation.
*/
public BasedirBeanConfigurationPathTranslator( File basedir )
{
this.basedir = basedir;
}
public File translatePath( File path )
{
File result = path;
if ( path != null && basedir != null )
{
if ( path.isAbsolute() )
{
// path is already absolute, we're done
}
else if ( path.getPath().startsWith( File.separator ) )
{
// drive-relative Windows path, don't align with base dir but with drive root
result = path.getAbsoluteFile();
}
else
{
// an ordinary relative path, align with base dir
result = new File( new File( basedir, path.getPath() ).toURI().normalize() ).getAbsoluteFile();
}
}
return result;
}
}

View File

@ -0,0 +1,41 @@
package org.apache.maven.configuration;
/*
* 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.
*/
/**
* Thrown when a bean couldn't be configured.
*
* @author Benjamin Bentmann
*/
public class BeanConfigurationException
extends Exception
{
public BeanConfigurationException( String message )
{
super( message );
}
public BeanConfigurationException( String message, Throwable cause )
{
super( message, cause );
}
}

View File

@ -0,0 +1,41 @@
package org.apache.maven.configuration;
/*
* 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.File;
/**
* Postprocesses filesystem paths. For instance, a path translator might want to resolve relative paths given in the
* bean configuration against some base directory.
*
* @author Benjamin Bentmann
*/
public interface BeanConfigurationPathTranslator
{
/**
* Translates the specified path.
*
* @param path The path to translate, may be {@code null}.
* @return The translated path or {@code null} if none.
*/
File translatePath( File path );
}

View File

@ -0,0 +1,108 @@
package org.apache.maven.configuration;
/*
* 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.
*/
/**
* A request to configure a bean from some configuration in the POM or similar.
*
* @author Benjamin Bentmann
*/
public interface BeanConfigurationRequest
{
/**
* Gets the bean to configure. Eventually, a valid request must have a bean set.
*
* @return The bean to configure, or {@code null} if none.
*/
Object getBean();
/**
* Sets the bean to configure. Eventually, a valid request must have a bean set.
*
* @param bean The bean to configure, may be {@code null}.
* @return This request for chaining, never {@code null}.
*/
BeanConfigurationRequest setBean( Object bean );
/**
* Gets the configuration to unmarshal into the bean.
*
* @return The configuration to unmarshal into the bean or {@code null} if none.
*/
Object getConfiguration();
/**
* Sets the configuration to unmarshal into the bean. The configuration should be taken from
* {@link org.apache.maven.model.ConfigurationContainer#getConfiguration()} or a similar source.
*
* @param configuration The configuration to unmarshal, may be {@code null}.
* @return This request for chaining, never {@code null}.
*/
BeanConfigurationRequest setConfiguration( Object configuration );
/**
* Gets the class loader from which to load any types referenced by the configuration. If unset, the class loader of
* the bean class will be used.
*
* @return The class loader to load referenced types from or {@code null} if unset.
*/
ClassLoader getClassLoader();
/**
* Sets the class loader from which to load any types referenced by the configuration. If unset, the class loader of
* the bean class will be used.
*
* @param classLoader The class loader to load referenced types from, may be {@code null}.
* @return This request for chaining, never {@code null}.
*/
BeanConfigurationRequest setClassLoader( ClassLoader classLoader );
/**
* Gets the optional preprocessor for configuration values.
*
* @return The preprocessor for configuration values or {@code null} if none.
*/
BeanConfigurationValuePreprocessor getValuePreprocessor();
/**
* Sets the optional preprocessor for configuration values.
*
* @param valuePreprocessor The preprocessor for configuration values, may be {@code null} if unneeded.
* @return This request for chaining, never {@code null}.
*/
BeanConfigurationRequest setValuePreprocessor( BeanConfigurationValuePreprocessor valuePreprocessor );
/**
* Gets the optional path translator for configuration values unmarshalled to files.
*
* @return The path translator for files or {@code null} if none.
*/
BeanConfigurationPathTranslator getPathTranslator();
/**
* Sets the optional path translator for configuration values unmarshalled to files.
*
* @param pathTranslator The path translator for files, may be {@code null} if unneeded.
* @return This request for chaining, never {@code null}.
*/
BeanConfigurationRequest setPathTranslator( BeanConfigurationPathTranslator pathTranslator );
}

View File

@ -0,0 +1,45 @@
package org.apache.maven.configuration;
/*
* 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.
*/
/**
* Preprocesses a value from a bean configuration before the bean configurator unmarshals it into a bean property. A
* common use case for such preprocessing is the evaluation of variables within the configuration value.
*
* @author Benjamin Bentmann
*/
public interface BeanConfigurationValuePreprocessor
{
/**
* Preprocesses the specified bean configuration value. The optional type provided to this method is a hint (not a
* requirement) for the preprocessor to resolve the value to a compatible value or a (string) value than can be
* unmarshalled into that type. The preprocessor is not required to perform any type conversion but should rather
* filter out incompatible values from its result.
*
* @param value The configuration value to preprocess, must not be {@code null}.
* @param type The target type of the value, may be {@code null}.
* @return The processed configuration value or {@code null} if none.
* @throws BeanConfigurationException If an error occurred while preprocessing the value.
*/
Object preprocessValue( String value, Class<?> type )
throws BeanConfigurationException;
}

View File

@ -0,0 +1,44 @@
package org.apache.maven.configuration;
/*
* 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.
*/
/**
* Unmarshals some textual configuration from the POM or similar into the properties of a bean. This component works
* similar to the way Maven configures plugins from the POM, i.e. some configuration like {@code <param>value</param>}
* is mapped to an equally named property of the bean and converted. The properties of the bean are supposed to either
* have a public setter or be backed by an equally named field (of any visibility).
*
* @since 3.0
* @author Benjamin Bentmann
*/
public interface BeanConfigurator
{
/**
* Performs the specified bean configuration.
*
* @param request The configuration request that specifies the bean and the configuration to process, must not be
* {@code null}.
* @throws BeanConfigurationException If the bean configuration could not be successfully processed.
*/
void configureBean( BeanConfigurationRequest request )
throws BeanConfigurationException;
}

View File

@ -0,0 +1,182 @@
package org.apache.maven.configuration;
/*
* 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.model.Build;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.PluginManagement;
import org.codehaus.plexus.util.StringUtils;
/**
* A basic bean configuration request.
*
* @author Benjamin Bentmann
*/
public class DefaultBeanConfigurationRequest
implements BeanConfigurationRequest
{
private Object bean;
private Object configuration;
private ClassLoader classLoader;
private BeanConfigurationValuePreprocessor valuePreprocessor;
private BeanConfigurationPathTranslator pathTranslator;
public Object getBean()
{
return bean;
}
public DefaultBeanConfigurationRequest setBean( Object bean )
{
this.bean = bean;
return this;
}
public Object getConfiguration()
{
return configuration;
}
public DefaultBeanConfigurationRequest setConfiguration( Object configuration )
{
this.configuration = configuration;
return this;
}
/**
* Sets the configuration to the configuration taken from the specified build plugin in the POM. First, the build
* plugins will be searched for the specified plugin, if that fails, the plugin management section will be searched.
*
* @param model The POM to extract the plugin configuration from, may be {@code null}.
* @param pluginGroupId The group id of the plugin whose configuration should be used, must not be {@code null} or
* empty.
* @param pluginArtifactId The artifact id of the plugin whose configuration should be used, must not be
* {@code null} or empty.
* @param pluginExecutionId The id of a plugin execution whose configuration should be used, may be {@code null} or
* empty to use the general plugin configuration.
* @return This request for chaining, never {@code null}.
*/
public DefaultBeanConfigurationRequest setConfiguration( Model model, String pluginGroupId,
String pluginArtifactId, String pluginExecutionId )
{
Plugin plugin = findPlugin( model, pluginGroupId, pluginArtifactId );
if ( plugin != null )
{
if ( StringUtils.isNotEmpty( pluginExecutionId ) )
{
for ( PluginExecution execution : plugin.getExecutions() )
{
if ( pluginExecutionId.equals( execution.getId() ) )
{
setConfiguration( execution.getConfiguration() );
break;
}
}
}
else
{
setConfiguration( plugin.getConfiguration() );
}
}
return this;
}
private Plugin findPlugin( Model model, String groupId, String artifactId )
{
if ( StringUtils.isEmpty( groupId ) )
{
throw new IllegalArgumentException( "group id for plugin has not been specified" );
}
if ( StringUtils.isEmpty( artifactId ) )
{
throw new IllegalArgumentException( "artifact id for plugin has not been specified" );
}
if ( model != null )
{
Build build = model.getBuild();
if ( build != null )
{
for ( Plugin plugin : build.getPlugins() )
{
if ( groupId.equals( plugin.getGroupId() ) && artifactId.equals( plugin.getArtifactId() ) )
{
return plugin;
}
}
PluginManagement mngt = build.getPluginManagement();
if ( mngt != null )
{
for ( Plugin plugin : mngt.getPlugins() )
{
if ( groupId.equals( plugin.getGroupId() ) && artifactId.equals( plugin.getArtifactId() ) )
{
return plugin;
}
}
}
}
}
return null;
}
public ClassLoader getClassLoader()
{
return classLoader;
}
public DefaultBeanConfigurationRequest setClassLoader( ClassLoader classLoader )
{
this.classLoader = classLoader;
return this;
}
public BeanConfigurationValuePreprocessor getValuePreprocessor()
{
return valuePreprocessor;
}
public DefaultBeanConfigurationRequest setValuePreprocessor( BeanConfigurationValuePreprocessor valuePreprocessor )
{
this.valuePreprocessor = valuePreprocessor;
return this;
}
public BeanConfigurationPathTranslator getPathTranslator()
{
return pathTranslator;
}
public DefaultBeanConfigurationRequest setPathTranslator( BeanConfigurationPathTranslator pathTranslator )
{
this.pathTranslator = pathTranslator;
return this;
}
}

View File

@ -0,0 +1,155 @@
package org.apache.maven.configuration.internal;
/*
* 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.File;
import org.apache.maven.configuration.BeanConfigurationException;
import org.apache.maven.configuration.BeanConfigurationPathTranslator;
import org.apache.maven.configuration.BeanConfigurationRequest;
import org.apache.maven.configuration.BeanConfigurationValuePreprocessor;
import org.apache.maven.configuration.BeanConfigurator;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.converters.composite.ObjectWithFieldsConverter;
import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.util.xml.Xpp3Dom;
/**
* <strong>Warning:</strong> This is an internal class that is only public for technical reasons, it is not part of the
* public API. In particular, this class can be changed or deleted without prior notice.
*
* @author Benjamin Bentmann
*/
@Component( role = BeanConfigurator.class )
public class DefaultBeanConfigurator
implements BeanConfigurator
{
private final ConverterLookup converterLookup = new DefaultConverterLookup();
public void configureBean( BeanConfigurationRequest request )
throws BeanConfigurationException
{
if ( request == null )
{
throw new IllegalArgumentException( "bean configuration request not specified" );
}
if ( request.getBean() == null )
{
throw new IllegalArgumentException( "bean to be configured not specified" );
}
Object configuration = request.getConfiguration();
if ( configuration == null )
{
return;
}
PlexusConfiguration plexusConfig = null;
if ( configuration instanceof PlexusConfiguration )
{
plexusConfig = (PlexusConfiguration) configuration;
}
else if ( configuration instanceof Xpp3Dom )
{
plexusConfig = new XmlPlexusConfiguration( (Xpp3Dom) configuration );
}
else
{
throw new BeanConfigurationException( "unsupported bean configuration source ("
+ configuration.getClass().getName() + ")" );
}
ClassLoader classLoader = request.getClassLoader();
if ( classLoader == null )
{
classLoader = request.getBean().getClass().getClassLoader();
}
BeanExpressionEvaluator evaluator = new BeanExpressionEvaluator( request );
ObjectWithFieldsConverter converter = new ObjectWithFieldsConverter();
try
{
converter.processConfiguration( converterLookup, request.getBean(), classLoader, plexusConfig, evaluator );
}
catch ( ComponentConfigurationException e )
{
throw new BeanConfigurationException( e.getMessage(), e );
}
}
static class BeanExpressionEvaluator
implements TypeAwareExpressionEvaluator
{
private final BeanConfigurationValuePreprocessor preprocessor;
private final BeanConfigurationPathTranslator translator;
public BeanExpressionEvaluator( BeanConfigurationRequest request )
{
preprocessor = request.getValuePreprocessor();
translator = request.getPathTranslator();
}
public Object evaluate( String expression, Class<?> type )
throws ExpressionEvaluationException
{
if ( preprocessor != null )
{
try
{
return preprocessor.preprocessValue( expression, type );
}
catch ( BeanConfigurationException e )
{
throw new ExpressionEvaluationException( e.getMessage(), e );
}
}
return expression;
}
public Object evaluate( String expression )
throws ExpressionEvaluationException
{
return evaluate( expression, null );
}
public File alignToBaseDirectory( File file )
{
if ( translator != null )
{
return translator.translatePath( file );
}
return file;
}
}
}

View File

@ -0,0 +1,133 @@
package org.apache.maven.configuration;
/*
* 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.File;
import java.io.IOException;
import java.io.StringReader;
import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* @author Benjamin Bentmann
*/
public class DefaultBeanConfiguratorTest
extends PlexusTestCase
{
private BeanConfigurator configurator;
@Override
protected void setUp()
throws Exception
{
super.setUp();
configurator = lookup( BeanConfigurator.class );
}
@Override
protected void tearDown()
throws Exception
{
configurator = null;
super.tearDown();
}
private Xpp3Dom toConfig( String xml )
{
try
{
return Xpp3DomBuilder.build( new StringReader( "<configuration>" + xml + "</configuration>" ) );
}
catch ( XmlPullParserException e )
{
throw new IllegalArgumentException( e );
}
catch ( IOException e )
{
throw new IllegalArgumentException( e );
}
}
public void testMinimal()
throws BeanConfigurationException
{
SomeBean bean = new SomeBean();
Xpp3Dom config = toConfig( "<file>test</file>" );
DefaultBeanConfigurationRequest request = new DefaultBeanConfigurationRequest();
request.setBean( bean ).setConfiguration( config );
configurator.configureBean( request );
assertEquals( new File( "test" ), bean.file );
}
public void testPreAndPostProcessing()
throws BeanConfigurationException
{
SomeBean bean = new SomeBean();
Xpp3Dom config = toConfig( "<file>${test}</file>" );
BeanConfigurationValuePreprocessor preprocessor = new BeanConfigurationValuePreprocessor()
{
public Object preprocessValue( String value, Class<?> type )
throws BeanConfigurationException
{
if ( value != null && value.startsWith( "${" ) && value.endsWith( "}" ) )
{
return value.substring( 2, value.length() - 1 );
}
return value;
}
};
BeanConfigurationPathTranslator translator = new BeanConfigurationPathTranslator()
{
public File translatePath( File path )
{
return new File( "base", path.getPath() ).getAbsoluteFile();
}
};
DefaultBeanConfigurationRequest request = new DefaultBeanConfigurationRequest();
request.setBean( bean ).setConfiguration( config );
request.setValuePreprocessor( preprocessor ).setPathTranslator( translator );
configurator.configureBean( request );
assertEquals( new File( "base/test" ).getAbsoluteFile(), bean.file );
}
static class SomeBean
{
File file;
}
}