这显然是一个限制西丛Mojo API 内部使用的项目。如果你往里面看MapConverter源代码,您会发现它首先尝试通过尝试将配置解释为字符串(调用fromExpression
),当失败时,查找值的预期类型。但是此方法不检查参数化类型,这就是我们这里的情况(因为地图值的类型是Map<String, String>
)。我提交了错误 498757在该项目的 Bugzilla 上进行跟踪。
使用自定义包装对象
一种解决方法是不使用Map<String, String>
作为值但使用自定义对象:
@Parameter
private Map<String, Converter> converters;
和一个班级Converter
,与 Mojo 位于同一包中,为:
public class Converter {
@Parameter
private Map<String, String> properties;
@Override
public String toString() { return properties.toString(); } // to test
}
然后你可以配置你的 Mojo:
<converters>
<json>
<properties>
<indent>true</indent>
<strict>true</strict>
</properties>
</json>
<yaml>
<properties>
<stripComments>false</stripComments>
</properties>
</yaml>
</converters>
此配置将正确地将值注入内部映射中。它还保留了可变方面:该对象仅作为内部映射的包装器引入。我用一个简单的测试魔力对此进行了测试
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info(converters.toString());
}
输出是预期的{json={indent=true, strict=true}, yaml={stripComments=false}}
.
使用自定义配置器
我还找到了一种方法来保持Map<String, Map<String, String>>
通过使用自定义ComponentConfigurator
.
所以我们想修复MapConverter
通过继承它,麻烦的是如何注册这个新的FixedMapConverter
。默认情况下,Maven 使用BasicComponentConfigurator
配置 Mojo,它依赖于DefaultConverterLookup
查找用于特定类的转换器。在这种情况下,我们想要提供一个自定义转换为Map
这将返回我们的固定版本。因此,我们需要扩展这个基本配置器并注册我们的新转换器。
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
public class CustomBasicComponentConfigurator extends BasicComponentConfigurator {
@Override
public void configureComponent(final Object component, final PlexusConfiguration configuration,
final ExpressionEvaluator evaluator, final ClassRealm realm, final ConfigurationListener listener)
throws ComponentConfigurationException {
converterLookup.registerConverter(new FixedMapConverter());
super.configureComponent(component, configuration, evaluator, realm, listener);
}
}
然后我们需要告诉 Maven 使用这个新的配置器而不是基本的配置器。这是一个 2 步过程:
-
在 Maven 插件中,创建一个文件src/main/resources/META-INF/plexus/components.xml
注册新组件:
<?xml version="1.0" encoding="UTF-8"?>
<component-set>
<components>
<component>
<role>org.codehaus.plexus.component.configurator.ComponentConfigurator</role>
<role-hint>custom-basic</role-hint>
<implementation>package.to.CustomBasicComponentConfigurator</implementation>
</component>
</components>
</component-set>
请注意一些事项:我们声明一个具有提示的新组件"custom-basic"
,这将作为一个 id 来引用它,并且<implementation>
指的是我们的配置器的完全限定类名。
-
告诉我们的 Mojo 使用此配置器configurator的属性@Mojo
注解:
@Mojo(name = "test", configurator = "custom-basic")
这里传递的配置器对应于在components.xml
above.
有了这样的设置,你终于可以声明了
@Parameter
private Map<String, Map<String, String>> converters;
并且所有内容都会正确注入:Maven 将使用我们的自定义配置器,它将注册我们的固定版本的地图转换器并正确转换内部地图。
完整代码FixedMapConverter
(这几乎是复制粘贴MapConverter
因为我们无法覆盖错误的方法):
public class FixedMapConverter extends MapConverter {
public Object fromConfiguration(final ConverterLookup lookup, final PlexusConfiguration configuration,
final Class<?> type, final Type[] typeArguments, final Class<?> enclosingType, final ClassLoader loader,
final ExpressionEvaluator evaluator, final ConfigurationListener listener)
throws ComponentConfigurationException {
final Object value = fromExpression(configuration, evaluator, type);
if (null != value) {
return value;
}
try {
final Map<Object, Object> map = instantiateMap(configuration, type, loader);
final Class<?> elementType = findElementType(typeArguments);
if (Object.class == elementType || String.class == elementType) {
for (int i = 0, size = configuration.getChildCount(); i < size; i++) {
final PlexusConfiguration element = configuration.getChild(i);
map.put(element.getName(), fromExpression(element, evaluator));
}
return map;
}
// handle maps with complex element types...
final ConfigurationConverter converter = lookup.lookupConverterForType(elementType);
for (int i = 0, size = configuration.getChildCount(); i < size; i++) {
Object elementValue;
final PlexusConfiguration element = configuration.getChild(i);
try {
elementValue = converter.fromConfiguration(lookup, element, elementType, enclosingType, //
loader, evaluator, listener);
}
// TEMP: remove when http://jira.codehaus.org/browse/MSHADE-168
// is fixed
catch (final ComponentConfigurationException e) {
elementValue = fromExpression(element, evaluator);
Logs.warn("Map in " + enclosingType + " declares value type as: {} but saw: {} at runtime",
elementType, null != elementValue ? elementValue.getClass() : null);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
map.put(element.getName(), elementValue);
}
return map;
} catch (final ComponentConfigurationException e) {
if (null == e.getFailedConfiguration()) {
e.setFailedConfiguration(configuration);
}
throw e;
}
}
@SuppressWarnings("unchecked")
private Map<Object, Object> instantiateMap(final PlexusConfiguration configuration, final Class<?> type,
final ClassLoader loader) throws ComponentConfigurationException {
final Class<?> implType = getClassForImplementationHint(type, configuration, loader);
if (null == implType || Modifier.isAbstract(implType.getModifiers())) {
return new TreeMap<Object, Object>();
}
final Object impl = instantiateObject(implType);
failIfNotTypeCompatible(impl, type, configuration);
return (Map<Object, Object>) impl;
}
private static Class<?> findElementType( final Type[] typeArguments )
{
if ( null != typeArguments && typeArguments.length > 1 )
{
if ( typeArguments[1] instanceof Class<?> )
{
return (Class<?>) typeArguments[1];
}
// begin fix here
if ( typeArguments[1] instanceof ParameterizedType )
{
return (Class<?>) ((ParameterizedType) typeArguments[1]).getRawType();
}
// end fix here
}
return Object.class;
}
}