You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by rf...@apache.org on 2019/06/01 18:14:41 UTC
[maven] 01/01: toolchain.xml file should support environment
variables Submitted by: Mike Mol and Martin Kanters
This is an automated email from the ASF dual-hosted git repository.
rfscholte pushed a commit to branch MNG-6665
in repository https://gitbox.apache.org/repos/asf/maven.git
commit ef2a0066e25aa4b16a90ce7f963a0db62a725cd0
Author: Martin Kanters <Ma...@ing.com>
AuthorDate: Fri May 31 16:02:18 2019 +0200
toolchain.xml file should support environment variables Submitted by: Mike Mol and Martin Kanters
---
.../building/DefaultToolchainsBuilder.java | 105 +++++++++++++--
.../toolchain/io/DefaultToolchainsWriter.java | 52 ++++++++
.../maven/toolchain/io/ToolchainsWriter.java | 48 +++++++
.../building/DefaultToolchainsBuilderTest.java | 144 +++++++++++++++++----
4 files changed, 311 insertions(+), 38 deletions(-)
diff --git a/maven-core/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java b/maven-core/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
index 7983388..f9ac801 100644
--- a/maven-core/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
+++ b/maven-core/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
@@ -19,24 +19,31 @@ package org.apache.maven.toolchain.building;
* under the License.
*/
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
import org.apache.maven.building.Problem;
import org.apache.maven.building.ProblemCollector;
import org.apache.maven.building.ProblemCollectorFactory;
import org.apache.maven.building.Source;
+import org.apache.maven.toolchain.io.DefaultToolchainsWriter;
import org.apache.maven.toolchain.io.ToolchainsParseException;
import org.apache.maven.toolchain.io.ToolchainsReader;
+import org.apache.maven.toolchain.io.ToolchainsWriter;
import org.apache.maven.toolchain.merge.MavenToolchainMerger;
import org.apache.maven.toolchain.model.PersistedToolchains;
import org.apache.maven.toolchain.model.TrackableBase;
+import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
+import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
+import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
/**
*
@@ -49,7 +56,9 @@ public class DefaultToolchainsBuilder
implements ToolchainsBuilder
{
private MavenToolchainMerger toolchainsMerger = new MavenToolchainMerger();
-
+
+ private ToolchainsWriter toolchainsWriter = new DefaultToolchainsWriter();
+
@Inject
private ToolchainsReader toolchainsReader;
@@ -66,16 +75,86 @@ public class DefaultToolchainsBuilder
toolchainsMerger.merge( userToolchains, globalToolchains, TrackableBase.GLOBAL_LEVEL );
problems.setSource( "" );
-
+
+ userToolchains = interpolate( userToolchains, problems );
+
if ( hasErrors( problems.getProblems() ) )
{
throw new ToolchainsBuildingException( problems.getProblems() );
}
-
-
+
+
return new DefaultToolchainsBuildingResult( userToolchains, problems.getProblems() );
}
+ private PersistedToolchains interpolate( PersistedToolchains toolchains, ProblemCollector problems )
+ {
+
+ StringWriter stringWriter = new StringWriter( 1024 * 4 );
+ try
+ {
+ toolchainsWriter.write( stringWriter, null, toolchains );
+ }
+ catch ( IOException e )
+ {
+ throw new IllegalStateException( "Failed to serialize toolchains to memory", e );
+ }
+
+ String serializedToolchains = stringWriter.toString();
+
+ RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
+
+ try
+ {
+ interpolator.addValueSource( new EnvarBasedValueSource() );
+ }
+ catch ( IOException e )
+ {
+ problems.add( Problem.Severity.WARNING, "Failed to use environment variables for interpolation: "
+ + e.getMessage(), -1, -1, e );
+ }
+
+ interpolator.addPostProcessor( new InterpolationPostProcessor()
+ {
+ @Override
+ public Object execute( String expression, Object value )
+ {
+ if ( value != null )
+ {
+ // we're going to parse this back in as XML so we need to escape XML markup
+ value = value.toString().replace( "&", "&" ).replace( "<", "<" ).replace( ">", ">" );
+ return value;
+ }
+ return null;
+ }
+ } );
+
+ try
+ {
+ serializedToolchains = interpolator.interpolate( serializedToolchains );
+ }
+ catch ( InterpolationException e )
+ {
+ problems.add( Problem.Severity.ERROR, "Failed to interpolate toolchains: " + e.getMessage(), -1, -1, e );
+ return toolchains;
+ }
+
+ PersistedToolchains result;
+ try
+ {
+ Map<String, ?> options = Collections.singletonMap( ToolchainsReader.IS_STRICT, Boolean.FALSE );
+
+ result = toolchainsReader.read( new StringReader( serializedToolchains ), options );
+ }
+ catch ( IOException e )
+ {
+ problems.add( Problem.Severity.ERROR, "Failed to interpolate toolchains: " + e.getMessage(), -1, -1, e );
+ return toolchains;
+ }
+
+ return result;
+ }
+
private PersistedToolchains readToolchains( Source toolchainsSource, ToolchainsBuildingRequest request,
ProblemCollector problems )
{
diff --git a/maven-core/src/main/java/org/apache/maven/toolchain/io/DefaultToolchainsWriter.java b/maven-core/src/main/java/org/apache/maven/toolchain/io/DefaultToolchainsWriter.java
new file mode 100644
index 0000000..c3d95d8
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/toolchain/io/DefaultToolchainsWriter.java
@@ -0,0 +1,52 @@
+package org.apache.maven.toolchain.io;
+
+/*
+ * 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.toolchain.model.PersistedToolchains;
+import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Writer;
+import org.codehaus.plexus.component.annotations.Component;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Handles serialization of toolchains into the default textual format.
+ *
+ * @author Mike Mol
+ * @author Martin Kanters
+ */
+@Component( role = ToolchainsWriter.class )
+public class DefaultToolchainsWriter implements ToolchainsWriter
+{
+
+ @Override
+ public void write( Writer output, Map<String, Object> options, PersistedToolchains toolchains ) throws IOException
+ {
+ Objects.requireNonNull( output, "output cannot be null" );
+ Objects.requireNonNull( toolchains, "toolchains cannot be null" );
+
+ try ( final Writer out = output )
+ {
+ new MavenToolchainsXpp3Writer().write( out, toolchains );
+ }
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/toolchain/io/ToolchainsWriter.java b/maven-core/src/main/java/org/apache/maven/toolchain/io/ToolchainsWriter.java
new file mode 100644
index 0000000..0b15f34
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/toolchain/io/ToolchainsWriter.java
@@ -0,0 +1,48 @@
+package org.apache.maven.toolchain.io;
+
+/*
+ * 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.toolchain.model.PersistedToolchains;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+/**
+ * Handles serialization of toolchains into some kind of textual format like XML.
+ *
+ * @author Mike Mol
+ * @author Martin Kanters
+ */
+public interface ToolchainsWriter
+{
+
+ /**
+ * Writes the supplied toolchains to the specified character writer. The writer will be automatically closed before
+ * the method returns.
+ *
+ * @param output The writer to serialize the toolchains to, must not be {@code null}.
+ * @param options The options to use for serialization, may be {@code null} to use the default values.
+ * @param toolchains The toolchains to serialize, must not be {@code null}.
+ * @throws IOException If the toolchains could not be serialized.
+ */
+ void write( Writer output, Map<String, Object> options, PersistedToolchains toolchains )
+ throws IOException;
+}
diff --git a/maven-core/src/test/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilderTest.java b/maven-core/src/test/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilderTest.java
index fc530df..4d218d2 100644
--- a/maven-core/src/test/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilderTest.java
+++ b/maven-core/src/test/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilderTest.java
@@ -19,32 +19,37 @@ package org.apache.maven.toolchain.building;
* under the License.
*/
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.io.InputStream;
-
import org.apache.maven.building.StringSource;
+import org.apache.maven.toolchain.io.DefaultToolchainsReader;
import org.apache.maven.toolchain.io.ToolchainsParseException;
-import org.apache.maven.toolchain.io.ToolchainsReader;
import org.apache.maven.toolchain.model.PersistedToolchains;
import org.apache.maven.toolchain.model.ToolchainModel;
+import org.codehaus.plexus.interpolation.os.OperatingSystemUtils;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
public class DefaultToolchainsBuilderTest
{
private static final String LS = System.getProperty( "line.separator" );
-
- @Mock
- private ToolchainsReader toolchainsReader;
+
+ @Spy
+ private DefaultToolchainsReader toolchainsReader;
@InjectMocks
private DefaultToolchainsBuilder toolchainBuilder = new DefaultToolchainsBuilder();
@@ -53,6 +58,11 @@ public class DefaultToolchainsBuilderTest
public void onSetup()
{
MockitoAnnotations.initMocks( this );
+
+ Map<String, String> envVarMap = new HashMap<>();
+ envVarMap.put("testKey", "testValue");
+ envVarMap.put("testSpecialCharactersKey", "<test&Value>");
+ OperatingSystemUtils.setEnvVarSource(new TestEnvVarSource(envVarMap));
}
@Test
@@ -78,7 +88,7 @@ public class DefaultToolchainsBuilderTest
toolchain.setType( "TYPE" );
toolchain.addProvide( "key", "user_value" );
userResult.addToolchain( toolchain );
- when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenReturn( userResult );
+ doReturn(userResult).doReturn(userResult).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
ToolchainsBuildingResult result = toolchainBuilder.build( request );
assertNotNull( result.getEffectiveToolchains() );
@@ -101,7 +111,7 @@ public class DefaultToolchainsBuilderTest
toolchain.setType( "TYPE" );
toolchain.addProvide( "key", "global_value" );
globalResult.addToolchain( toolchain );
- when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenReturn( globalResult );
+ doReturn(globalResult).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
ToolchainsBuildingResult result = toolchainBuilder.build( request );
assertNotNull( result.getEffectiveToolchains() );
@@ -131,7 +141,7 @@ public class DefaultToolchainsBuilderTest
globalToolchain.setType( "TYPE" );
globalToolchain.addProvide( "key", "global_value" );
globalResult.addToolchain( globalToolchain );
- when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenReturn( globalResult ).thenReturn( userResult );
+ doReturn(globalResult).doReturn(userResult).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
ToolchainsBuildingResult result = toolchainBuilder.build( request );
assertNotNull( result.getEffectiveToolchains() );
@@ -143,43 +153,127 @@ public class DefaultToolchainsBuilderTest
assertNotNull( result.getProblems() );
assertEquals( 0, result.getProblems().size() );
}
-
+
@Test
public void testStrictToolchainsParseException() throws Exception
{
ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
request.setGlobalToolchainsSource( new StringSource( "" ) );
ToolchainsParseException parseException = new ToolchainsParseException( "MESSAGE", 4, 2 );
- when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenThrow( parseException );
-
+ doThrow(parseException).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
try
{
toolchainBuilder.build( request );
}
catch ( ToolchainsBuildingException e )
{
- assertEquals( "1 problem was encountered while building the effective toolchains" + LS +
+ assertEquals( "1 problem was encountered while building the effective toolchains" + LS +
"[FATAL] Non-parseable toolchains (memory): MESSAGE @ line 4, column 2" + LS, e.getMessage() );
}
}
-
+
@Test
public void testIOException() throws Exception
{
ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
request.setGlobalToolchainsSource( new StringSource( "", "LOCATION" ) );
IOException ioException = new IOException( "MESSAGE" );
- when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenThrow( ioException );
-
+ doThrow(ioException).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
try
{
toolchainBuilder.build( request );
}
catch ( ToolchainsBuildingException e )
{
- assertEquals( "1 problem was encountered while building the effective toolchains" + LS +
+ assertEquals( "1 problem was encountered while building the effective toolchains" + LS +
"[FATAL] Non-readable toolchains LOCATION: MESSAGE" + LS, e.getMessage() );
}
}
-
+
+ @Test
+ public void testEnvironmentVariablesAreInterpolated()
+ throws Exception
+ {
+ ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
+ request.setUserToolchainsSource( new StringSource( "" ) );
+
+ PersistedToolchains persistedToolchains = new PersistedToolchains();
+ ToolchainModel toolchain = new ToolchainModel();
+ toolchain.setType( "TYPE" );
+ toolchain.addProvide( "key", "${env.testKey}" );
+
+ Xpp3Dom configurationChild = new Xpp3Dom("jdkHome");
+ configurationChild.setValue("${env.testKey}");
+ Xpp3Dom configuration = new Xpp3Dom("configuration");
+ configuration.addChild(configurationChild);
+ toolchain.setConfiguration(configuration);
+ persistedToolchains.addToolchain( toolchain );
+ doReturn(persistedToolchains).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
+ ToolchainsBuildingResult result = toolchainBuilder.build( request );
+ String interpolatedValue = "testValue";
+ assertEquals(interpolatedValue, result.getEffectiveToolchains().getToolchains().get(0).getProvides().getProperty( "key" ) );
+ Xpp3Dom toolchainConfiguration = (Xpp3Dom) result.getEffectiveToolchains().getToolchains().get(0).getConfiguration();
+ assertEquals(interpolatedValue, toolchainConfiguration.getChild("jdkHome").getValue());
+ assertNotNull( result.getProblems() );
+ assertEquals( 0, result.getProblems().size() );
+ }
+
+ @Test
+ public void testNonExistingEnvironmentVariablesAreNotInterpolated()
+ throws Exception
+ {
+ ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
+ request.setUserToolchainsSource( new StringSource( "" ) );
+
+ PersistedToolchains persistedToolchains = new PersistedToolchains();
+ ToolchainModel toolchain = new ToolchainModel();
+ toolchain.setType( "TYPE" );
+ toolchain.addProvide( "key", "${env.testNonExistingKey}" );
+
+ persistedToolchains.addToolchain( toolchain );
+ doReturn(persistedToolchains).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
+ ToolchainsBuildingResult result = toolchainBuilder.build( request );
+ assertEquals("${env.testNonExistingKey}", result.getEffectiveToolchains().getToolchains().get(0).getProvides().getProperty( "key" ) );
+ assertNotNull( result.getProblems() );
+ assertEquals( 0, result.getProblems().size() );
+ }
+
+ @Test
+ public void testEnvironmentVariablesWithSpecialCharactersAreInterpolated()
+ throws Exception
+ {
+ ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
+ request.setUserToolchainsSource( new StringSource( "" ) );
+
+ PersistedToolchains persistedToolchains = new PersistedToolchains();
+ ToolchainModel toolchain = new ToolchainModel();
+ toolchain.setType( "TYPE" );
+ toolchain.addProvide( "key", "${env.testSpecialCharactersKey}" );
+
+ persistedToolchains.addToolchain( toolchain );
+ doReturn(persistedToolchains).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
+ ToolchainsBuildingResult result = toolchainBuilder.build( request );
+ String interpolatedValue = "<test&Value>";
+ assertEquals(interpolatedValue, result.getEffectiveToolchains().getToolchains().get(0).getProvides().getProperty( "key" ) );
+ assertNotNull( result.getProblems() );
+ assertEquals( 0, result.getProblems().size() );
+ }
+
+ static class TestEnvVarSource implements OperatingSystemUtils.EnvVarSource {
+ private final Map<String, String> envVarMap;
+
+ TestEnvVarSource(Map<String, String> envVarMap) {
+ this.envVarMap = envVarMap;
+ }
+
+ public Map<String, String> getEnvMap() {
+ return envVarMap;
+ }
+ }
+
}