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.internal.impl.model;
20  
21  import java.nio.file.Files;
22  import java.nio.file.Path;
23  import java.util.ArrayList;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.concurrent.ConcurrentHashMap;
29  
30  import org.apache.maven.api.model.Model;
31  import org.apache.maven.api.services.ModelBuilderException;
32  import org.apache.maven.api.services.ModelBuilderRequest;
33  import org.apache.maven.api.services.ModelProblem;
34  import org.apache.maven.api.services.ModelProblemCollector;
35  import org.apache.maven.api.services.ModelSource;
36  import org.apache.maven.api.services.ModelTransformerContext;
37  import org.apache.maven.api.services.ModelTransformerContextBuilder;
38  import org.apache.maven.internal.impl.model.DefaultModelTransformerContext.GAKey;
39  import org.apache.maven.internal.impl.model.DefaultModelTransformerContext.Holder;
40  
41  /**
42   * Builds up the transformer context.
43   * After the buildplan is ready, the build()-method returns the immutable context useful during distribution.
44   * This is an inner class, as it must be able to call readRawModel()
45   *
46   * @since 4.0.0
47   */
48  class DefaultModelTransformerContextBuilder implements ModelTransformerContextBuilder {
49      private final Graph dag = new Graph();
50      private final DefaultModelBuilder defaultModelBuilder;
51      private final DefaultModelTransformerContext context;
52  
53      private final Map<String, Set<ModelSource>> mappedSources = new ConcurrentHashMap<>(64);
54  
55      private volatile boolean fullReactorLoaded;
56  
57      DefaultModelTransformerContextBuilder(DefaultModelBuilder defaultModelBuilder) {
58          this.defaultModelBuilder = defaultModelBuilder;
59          this.context = new DefaultModelTransformerContext(defaultModelBuilder.getModelProcessor());
60      }
61  
62      /**
63       * If an interface could be extracted, DefaultModelProblemCollector should be ModelProblemCollectorExt
64       */
65      @Override
66      public ModelTransformerContext initialize(ModelBuilderRequest request, ModelProblemCollector collector) {
67          // We must assume the TransformerContext was created using this.newTransformerContextBuilder()
68          DefaultModelProblemCollector problems = (DefaultModelProblemCollector) collector;
69          return new ModelTransformerContext() {
70  
71              @Override
72              public Path locate(Path path) {
73                  return context.locate(path);
74              }
75  
76              @Override
77              public String getUserProperty(String key) {
78                  return context.userProperties.computeIfAbsent(
79                          key, k -> request.getUserProperties().get(key));
80              }
81  
82              @Override
83              public Model getRawModel(Path from, String gId, String aId) {
84                  Model model = findRawModel(from, gId, aId);
85                  if (model != null) {
86                      context.modelByGA.put(new GAKey(gId, aId), new Holder(model));
87                      context.modelByPath.put(model.getPomFile(), new Holder(model));
88                  }
89                  return model;
90              }
91  
92              @Override
93              public Model getRawModel(Path from, Path path) {
94                  Model model = findRawModel(from, path);
95                  if (model != null) {
96                      String groupId = DefaultModelBuilder.getGroupId(model);
97                      context.modelByGA.put(
98                              new DefaultModelTransformerContext.GAKey(groupId, model.getArtifactId()),
99                              new Holder(model));
100                     context.modelByPath.put(path, new Holder(model));
101                 }
102                 return model;
103             }
104 
105             private Model findRawModel(Path from, String groupId, String artifactId) {
106                 ModelSource source = getSource(groupId, artifactId);
107                 if (source == null) {
108                     // we need to check the whole reactor in case it's a dependency
109                     loadFullReactor();
110                     source = getSource(groupId, artifactId);
111                 }
112                 if (source != null) {
113                     if (!addEdge(from, source.getPath(), problems)) {
114                         return null;
115                     }
116                     try {
117                         ModelBuilderRequest gaBuildingRequest = ModelBuilderRequest.build(request, source);
118                         return defaultModelBuilder.readRawModel(gaBuildingRequest, problems);
119                     } catch (ModelBuilderException e) {
120                         // gathered with problem collector
121                     }
122                 }
123                 return null;
124             }
125 
126             private void loadFullReactor() {
127                 if (!fullReactorLoaded) {
128                     synchronized (DefaultModelTransformerContextBuilder.this) {
129                         if (!fullReactorLoaded) {
130                             doLoadFullReactor();
131                             fullReactorLoaded = true;
132                         }
133                     }
134                 }
135             }
136 
137             private void doLoadFullReactor() {
138                 Path rootDirectory;
139                 try {
140                     rootDirectory = request.getSession().getRootDirectory();
141                 } catch (IllegalStateException e) {
142                     // if no root directory, bail out
143                     return;
144                 }
145                 List<Path> toLoad = new ArrayList<>();
146                 Path root = defaultModelBuilder.getModelProcessor().locateExistingPom(rootDirectory);
147                 toLoad.add(root);
148                 while (!toLoad.isEmpty()) {
149                     Path pom = toLoad.remove(0);
150                     try {
151                         ModelBuilderRequest gaBuildingRequest =
152                                 ModelBuilderRequest.build(request, ModelSource.fromPath(pom));
153                         Model rawModel = defaultModelBuilder.readFileModel(gaBuildingRequest, problems);
154                         for (String module : rawModel.getModules()) {
155                             Path moduleFile = defaultModelBuilder
156                                     .getModelProcessor()
157                                     .locateExistingPom(pom.getParent().resolve(module));
158                             if (moduleFile != null) {
159                                 toLoad.add(moduleFile);
160                             }
161                         }
162                     } catch (ModelBuilderException e) {
163                         // gathered with problem collector
164                     }
165                 }
166             }
167 
168             private Model findRawModel(Path from, Path p) {
169                 if (!Files.isRegularFile(p)) {
170                     throw new IllegalArgumentException("Not a regular file: " + p);
171                 }
172 
173                 if (!addEdge(from, p, problems)) {
174                     return null;
175                 }
176 
177                 ModelBuilderRequest req = ModelBuilderRequest.build(request, ModelSource.fromPath(p));
178 
179                 try {
180                     return defaultModelBuilder.readRawModel(req, problems);
181                 } catch (ModelBuilderException e) {
182                     // gathered with problem collector
183                 }
184                 return null;
185             }
186         };
187     }
188 
189     private boolean addEdge(Path from, Path p, DefaultModelProblemCollector problems) {
190         try {
191             dag.addEdge(from.toString(), p.toString());
192             return true;
193         } catch (Graph.CycleDetectedException e) {
194             problems.add(new DefaultModelProblem(
195                     "Cycle detected between models at " + from + " and " + p,
196                     ModelProblem.Severity.FATAL,
197                     null,
198                     null,
199                     0,
200                     0,
201                     null,
202                     e));
203             return false;
204         }
205     }
206 
207     @Override
208     public ModelTransformerContext build() {
209         return context;
210     }
211 
212     public ModelSource getSource(String groupId, String artifactId) {
213         Set<ModelSource> sources = mappedSources.get(groupId + ":" + artifactId);
214         if (sources == null) {
215             return null;
216         }
217         return sources.stream()
218                 .reduce((a, b) -> {
219                     throw new IllegalStateException(String.format(
220                             "No unique Source for %s:%s: %s and %s",
221                             groupId, artifactId, a.getLocation(), b.getLocation()));
222                 })
223                 .orElse(null);
224     }
225 
226     public void putSource(String groupId, String artifactId, ModelSource source) {
227         mappedSources
228                 .computeIfAbsent(groupId + ":" + artifactId, k -> new HashSet<>())
229                 .add(source);
230     }
231 }