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.model.profile;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.WeakHashMap;
30  import java.util.concurrent.ConcurrentHashMap;
31  
32  import org.apache.maven.api.model.Build;
33  import org.apache.maven.api.model.BuildBase;
34  import org.apache.maven.api.model.Model;
35  import org.apache.maven.api.model.ModelBase;
36  import org.apache.maven.api.model.Plugin;
37  import org.apache.maven.api.model.PluginContainer;
38  import org.apache.maven.api.model.PluginExecution;
39  import org.apache.maven.api.model.Profile;
40  import org.apache.maven.api.model.ReportPlugin;
41  import org.apache.maven.api.model.ReportSet;
42  import org.apache.maven.api.model.Reporting;
43  import org.apache.maven.model.building.ModelBuildingRequest;
44  import org.apache.maven.model.building.ModelProblemCollector;
45  import org.apache.maven.model.merge.MavenModelMerger;
46  
47  /**
48   * Handles profile injection into the model.
49   *
50   */
51  @Named
52  @Singleton
53  @SuppressWarnings({"checkstyle:methodname"})
54  public class DefaultProfileInjector implements ProfileInjector {
55  
56      private static final Map<Model, Map<List<Profile>, Model>> CACHE = Collections.synchronizedMap(new WeakHashMap<>());
57  
58      // In order for the weak hash map to work correctly, we must not hold any reference to
59      // the model used as the key.  So we use a dummy model as a placeholder to indicate that
60      // we want to store the model used as they key.
61      private static final Model KEY = Model.newInstance();
62  
63      private ProfileModelMerger merger = new ProfileModelMerger();
64  
65      @Override
66      public void injectProfile(
67              org.apache.maven.model.Model model,
68              org.apache.maven.model.Profile profile,
69              ModelBuildingRequest request,
70              ModelProblemCollector problems) {
71          model.update(
72                  injectProfile(model.getDelegate(), profile != null ? profile.getDelegate() : null, request, problems));
73      }
74  
75      @Override
76      public Model injectProfile(
77              Model model, Profile profile, ModelBuildingRequest request, ModelProblemCollector problems) {
78          return injectProfiles(model, Collections.singletonList(profile), request, problems);
79      }
80  
81      @Override
82      public Model injectProfiles(
83              Model model, List<Profile> profiles, ModelBuildingRequest request, ModelProblemCollector problems) {
84          Model result = CACHE.computeIfAbsent(model, k -> new ConcurrentHashMap<>())
85                  .computeIfAbsent(profiles, l -> doInjectProfiles(model, profiles));
86          return result == KEY ? model : result;
87      }
88  
89      private Model doInjectProfiles(Model model, List<Profile> profiles) {
90          Model orgModel = model;
91          for (Profile profile : profiles) {
92              if (profile != null) {
93                  Model.Builder builder = Model.newBuilder(model);
94                  merger.mergeModelBase(builder, model, profile);
95  
96                  if (profile.getBuild() != null) {
97                      Build build = model.getBuild() != null ? model.getBuild() : Build.newInstance();
98                      Build.Builder bbuilder = Build.newBuilder(build);
99                      merger.mergeBuildBase(bbuilder, build, profile.getBuild());
100                     builder.build(bbuilder.build());
101                 }
102 
103                 model = builder.build();
104             }
105         }
106         return model == orgModel ? KEY : model;
107     }
108 
109     /**
110      * ProfileModelMerger
111      */
112     protected static class ProfileModelMerger extends MavenModelMerger {
113 
114         public void mergeModelBase(ModelBase.Builder builder, ModelBase target, ModelBase source) {
115             mergeModelBase(builder, target, source, true, Collections.emptyMap());
116         }
117 
118         public void mergeBuildBase(BuildBase.Builder builder, BuildBase target, BuildBase source) {
119             mergeBuildBase(builder, target, source, true, Collections.emptyMap());
120         }
121 
122         @Override
123         protected void mergePluginContainer_Plugins(
124                 PluginContainer.Builder builder,
125                 PluginContainer target,
126                 PluginContainer source,
127                 boolean sourceDominant,
128                 Map<Object, Object> context) {
129             List<Plugin> src = source.getPlugins();
130             if (!src.isEmpty()) {
131                 List<Plugin> tgt = target.getPlugins();
132                 Map<Object, Plugin> master = new LinkedHashMap<>(tgt.size() * 2);
133 
134                 for (Plugin element : tgt) {
135                     Object key = getPluginKey().apply(element);
136                     master.put(key, element);
137                 }
138 
139                 Map<Object, List<Plugin>> predecessors = new LinkedHashMap<>();
140                 List<Plugin> pending = new ArrayList<>();
141                 for (Plugin element : src) {
142                     Object key = getPluginKey().apply(element);
143                     Plugin existing = master.get(key);
144                     if (existing != null) {
145                         existing = mergePlugin(existing, element, sourceDominant, context);
146                         master.put(key, existing);
147                         if (!pending.isEmpty()) {
148                             predecessors.put(key, pending);
149                             pending = new ArrayList<>();
150                         }
151                     } else {
152                         pending.add(element);
153                     }
154                 }
155 
156                 List<Plugin> result = new ArrayList<>(src.size() + tgt.size());
157                 for (Map.Entry<Object, Plugin> entry : master.entrySet()) {
158                     List<Plugin> pre = predecessors.get(entry.getKey());
159                     if (pre != null) {
160                         result.addAll(pre);
161                     }
162                     result.add(entry.getValue());
163                 }
164                 result.addAll(pending);
165 
166                 builder.plugins(result);
167             }
168         }
169 
170         @Override
171         protected void mergePlugin_Executions(
172                 Plugin.Builder builder,
173                 Plugin target,
174                 Plugin source,
175                 boolean sourceDominant,
176                 Map<Object, Object> context) {
177             List<PluginExecution> src = source.getExecutions();
178             if (!src.isEmpty()) {
179                 List<PluginExecution> tgt = target.getExecutions();
180                 Map<Object, PluginExecution> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
181 
182                 for (PluginExecution element : tgt) {
183                     Object key = getPluginExecutionKey().apply(element);
184                     merged.put(key, element);
185                 }
186 
187                 for (PluginExecution element : src) {
188                     Object key = getPluginExecutionKey().apply(element);
189                     PluginExecution existing = merged.get(key);
190                     if (existing != null) {
191                         element = mergePluginExecution(existing, element, sourceDominant, context);
192                     }
193                     merged.put(key, element);
194                 }
195 
196                 builder.executions(merged.values());
197             }
198         }
199 
200         @Override
201         protected void mergeReporting_Plugins(
202                 Reporting.Builder builder,
203                 Reporting target,
204                 Reporting source,
205                 boolean sourceDominant,
206                 Map<Object, Object> context) {
207             List<ReportPlugin> src = source.getPlugins();
208             if (!src.isEmpty()) {
209                 List<ReportPlugin> tgt = target.getPlugins();
210                 Map<Object, ReportPlugin> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
211 
212                 for (ReportPlugin element : tgt) {
213                     Object key = getReportPluginKey().apply(element);
214                     merged.put(key, element);
215                 }
216 
217                 for (ReportPlugin element : src) {
218                     Object key = getReportPluginKey().apply(element);
219                     ReportPlugin existing = merged.get(key);
220                     if (existing != null) {
221                         element = mergeReportPlugin(existing, element, sourceDominant, context);
222                     }
223                     merged.put(key, element);
224                 }
225 
226                 builder.plugins(merged.values());
227             }
228         }
229 
230         @Override
231         protected void mergeReportPlugin_ReportSets(
232                 ReportPlugin.Builder builder,
233                 ReportPlugin target,
234                 ReportPlugin source,
235                 boolean sourceDominant,
236                 Map<Object, Object> context) {
237             List<ReportSet> src = source.getReportSets();
238             if (!src.isEmpty()) {
239                 List<ReportSet> tgt = target.getReportSets();
240                 Map<Object, ReportSet> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
241 
242                 for (ReportSet element : tgt) {
243                     Object key = getReportSetKey().apply(element);
244                     merged.put(key, element);
245                 }
246 
247                 for (ReportSet element : src) {
248                     Object key = getReportSetKey().apply(element);
249                     ReportSet existing = merged.get(key);
250                     if (existing != null) {
251                         element = mergeReportSet(existing, element, sourceDominant, context);
252                     }
253                     merged.put(key, element);
254                 }
255 
256                 builder.reportSets(merged.values());
257             }
258         }
259     }
260 }