View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.toolchain.building;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.IOException;
26  import java.io.StringReader;
27  import java.io.StringWriter;
28  import java.util.Collections;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.maven.building.Problem;
33  import org.apache.maven.building.ProblemCollector;
34  import org.apache.maven.building.ProblemCollectorFactory;
35  import org.apache.maven.building.Source;
36  import org.apache.maven.toolchain.io.ToolchainsParseException;
37  import org.apache.maven.toolchain.io.ToolchainsReader;
38  import org.apache.maven.toolchain.io.ToolchainsWriter;
39  import org.apache.maven.toolchain.merge.MavenToolchainMerger;
40  import org.apache.maven.toolchain.model.PersistedToolchains;
41  import org.apache.maven.toolchain.model.TrackableBase;
42  import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
43  import org.codehaus.plexus.interpolation.InterpolationException;
44  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
45  
46  /**
47   *
48   * @since 3.3.0
49   */
50  @Named
51  @Singleton
52  public class DefaultToolchainsBuilder implements ToolchainsBuilder {
53      private final MavenToolchainMerger toolchainsMerger = new MavenToolchainMerger();
54      private final ToolchainsWriter toolchainsWriter;
55      private final ToolchainsReader toolchainsReader;
56  
57      @Inject
58      public DefaultToolchainsBuilder(ToolchainsWriter toolchainsWriter, ToolchainsReader toolchainsReader) {
59          this.toolchainsWriter = toolchainsWriter;
60          this.toolchainsReader = toolchainsReader;
61      }
62  
63      @Override
64      public ToolchainsBuildingResult build(ToolchainsBuildingRequest request) throws ToolchainsBuildingException {
65          ProblemCollector problems = ProblemCollectorFactory.newInstance(null);
66  
67          PersistedToolchains globalToolchains = readToolchains(request.getGlobalToolchainsSource(), request, problems);
68  
69          PersistedToolchains userToolchains = readToolchains(request.getUserToolchainsSource(), request, problems);
70  
71          toolchainsMerger.merge(userToolchains, globalToolchains, TrackableBase.GLOBAL_LEVEL);
72  
73          problems.setSource("");
74  
75          userToolchains = interpolate(userToolchains, problems);
76  
77          if (hasErrors(problems.getProblems())) {
78              throw new ToolchainsBuildingException(problems.getProblems());
79          }
80  
81          return new DefaultToolchainsBuildingResult(userToolchains, problems.getProblems());
82      }
83  
84      private PersistedToolchains interpolate(PersistedToolchains toolchains, ProblemCollector problems) {
85  
86          StringWriter stringWriter = new StringWriter(1024 * 4);
87          try {
88              toolchainsWriter.write(stringWriter, null, toolchains);
89          } catch (IOException e) {
90              throw new IllegalStateException("Failed to serialize toolchains to memory", e);
91          }
92  
93          String serializedToolchains = stringWriter.toString();
94  
95          RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
96  
97          try {
98              interpolator.addValueSource(new EnvarBasedValueSource());
99          } catch (IOException e) {
100             problems.add(
101                     Problem.Severity.WARNING,
102                     "Failed to use environment variables for interpolation: " + e.getMessage(),
103                     -1,
104                     -1,
105                     e);
106         }
107 
108         interpolator.addPostProcessor((expression, value) -> {
109             if (value != null) {
110                 // we're going to parse this back in as XML so we need to escape XML markup
111                 value = value.toString()
112                         .replace("&", "&")
113                         .replace("<", "&lt;")
114                         .replace(">", "&gt;");
115                 return value;
116             }
117             return null;
118         });
119 
120         try {
121             serializedToolchains = interpolator.interpolate(serializedToolchains);
122         } catch (InterpolationException e) {
123             problems.add(Problem.Severity.ERROR, "Failed to interpolate toolchains: " + e.getMessage(), -1, -1, e);
124             return toolchains;
125         }
126 
127         PersistedToolchains result;
128         try {
129             Map<String, ?> options = Collections.singletonMap(ToolchainsReader.IS_STRICT, Boolean.FALSE);
130 
131             result = toolchainsReader.read(new StringReader(serializedToolchains), options);
132         } catch (IOException e) {
133             problems.add(Problem.Severity.ERROR, "Failed to interpolate toolchains: " + e.getMessage(), -1, -1, e);
134             return toolchains;
135         }
136 
137         return result;
138     }
139 
140     private PersistedToolchains readToolchains(
141             Source toolchainsSource, ToolchainsBuildingRequest request, ProblemCollector problems) {
142         if (toolchainsSource == null) {
143             return new PersistedToolchains();
144         }
145 
146         PersistedToolchains toolchains;
147 
148         try {
149             Map<String, ?> options = Collections.singletonMap(ToolchainsReader.IS_STRICT, Boolean.TRUE);
150 
151             try {
152                 toolchains = toolchainsReader.read(toolchainsSource.getInputStream(), options);
153             } catch (ToolchainsParseException e) {
154                 options = Collections.singletonMap(ToolchainsReader.IS_STRICT, Boolean.FALSE);
155 
156                 toolchains = toolchainsReader.read(toolchainsSource.getInputStream(), options);
157 
158                 problems.add(Problem.Severity.WARNING, e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
159             }
160         } catch (ToolchainsParseException e) {
161             problems.add(
162                     Problem.Severity.FATAL,
163                     "Non-parseable toolchains " + toolchainsSource.getLocation() + ": " + e.getMessage(),
164                     e.getLineNumber(),
165                     e.getColumnNumber(),
166                     e);
167             return new PersistedToolchains();
168         } catch (IOException e) {
169             problems.add(
170                     Problem.Severity.FATAL,
171                     "Non-readable toolchains " + toolchainsSource.getLocation() + ": " + e.getMessage(),
172                     -1,
173                     -1,
174                     e);
175             return new PersistedToolchains();
176         }
177 
178         return toolchains;
179     }
180 
181     private boolean hasErrors(List<Problem> problems) {
182         if (problems != null) {
183             for (Problem problem : problems) {
184                 if (Problem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
185                     return true;
186                 }
187             }
188         }
189 
190         return false;
191     }
192 }