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.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.LinkedHashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.stream.Collectors;
29  
30  import org.apache.maven.api.Packaging;
31  import org.apache.maven.api.di.Inject;
32  import org.apache.maven.api.di.Named;
33  import org.apache.maven.api.di.Singleton;
34  import org.apache.maven.api.model.Build;
35  import org.apache.maven.api.model.Model;
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.PluginManagement;
40  import org.apache.maven.api.services.BuilderProblem.Severity;
41  import org.apache.maven.api.services.LifecycleRegistry;
42  import org.apache.maven.api.services.ModelBuilderRequest;
43  import org.apache.maven.api.services.ModelProblem.Version;
44  import org.apache.maven.api.services.ModelProblemCollector;
45  import org.apache.maven.api.services.PackagingRegistry;
46  import org.apache.maven.api.services.model.*;
47  
48  /**
49   * Handles injection of plugin executions induced by the lifecycle bindings for a packaging.
50   *
51   */
52  @Named
53  @Singleton
54  public class DefaultLifecycleBindingsInjector implements LifecycleBindingsInjector {
55  
56      private final LifecycleBindingsMerger merger = new LifecycleBindingsMerger();
57  
58      private final LifecycleRegistry lifecycleRegistry;
59      private final PackagingRegistry packagingRegistry;
60  
61      @Inject
62      public DefaultLifecycleBindingsInjector(LifecycleRegistry lifecycleRegistry, PackagingRegistry packagingRegistry) {
63          this.lifecycleRegistry = lifecycleRegistry;
64          this.packagingRegistry = packagingRegistry;
65      }
66  
67      public Model injectLifecycleBindings(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
68          String packagingId = model.getPackaging();
69          Packaging packaging = packagingRegistry.lookup(packagingId).orElse(null);
70          if (packaging == null) {
71              problems.add(
72                      Severity.ERROR, Version.BASE, "Unknown packaging: " + packagingId, model.getLocation("packaging"));
73              return model;
74          } else {
75              Map<String, PluginContainer> plugins = new HashMap<>(packaging.plugins());
76              lifecycleRegistry.stream()
77                      .filter(lf -> !plugins.containsKey(lf.id()))
78                      .forEach(lf -> plugins.put(
79                              lf.id(),
80                              PluginContainer.newBuilder()
81                                      .plugins(lf.phases().stream()
82                                              .flatMap(phase -> phase.plugins().stream())
83                                              .toList())
84                                      .build()));
85              Map<Plugin, Plugin> allPlugins = new LinkedHashMap<>();
86              plugins.values().stream().flatMap(pc -> pc.getPlugins().stream()).forEach(p -> addPlugin(allPlugins, p));
87              Model lifecycleModel = Model.newBuilder()
88                      .build(Build.newBuilder().plugins(allPlugins.values()).build())
89                      .build();
90              return merger.merge(model, lifecycleModel);
91          }
92      }
93  
94      private void addPlugin(Map<Plugin, Plugin> plugins, Plugin plugin) {
95          Plugin cur = plugins.putIfAbsent(plugin, plugin);
96          if (cur != null) {
97              Map<String, PluginExecution> execs = new LinkedHashMap<>();
98              cur.getExecutions().forEach(e -> execs.put(e.getId(), e));
99              plugin.getExecutions().forEach(e -> {
100                 int i = 0;
101                 String id = e.getId();
102                 while (execs.putIfAbsent(id, e.withId(id)) != null) {
103                     id = e.getId() + "-" + (++i);
104                 }
105             });
106             Plugin merged = cur.withExecutions(execs.values());
107             plugins.put(merged, merged);
108         }
109     }
110 
111     private static String getExecutionId(Plugin plugin, String goal) {
112         Set<String> existingIds = plugin != null
113                 ? plugin.getExecutions().stream().map(PluginExecution::getId).collect(Collectors.toSet())
114                 : Set.of();
115         String base = "default-" + goal;
116         String id = base;
117         for (int index = 1; existingIds.contains(id); index++) {
118             id = base + '-' + index;
119         }
120         return id;
121     }
122 
123     /**
124      *  The domain-specific model merger for lifecycle bindings
125      */
126     protected static class LifecycleBindingsMerger extends MavenModelMerger {
127 
128         private static final String PLUGIN_MANAGEMENT = "plugin-management";
129 
130         public Model merge(Model target, Model source) {
131             Build targetBuild = target.getBuild();
132             if (targetBuild == null) {
133                 targetBuild = Build.newInstance();
134             }
135 
136             Map<Object, Object> context =
137                     Collections.singletonMap(PLUGIN_MANAGEMENT, targetBuild.getPluginManagement());
138 
139             Build.Builder builder = Build.newBuilder(targetBuild);
140             mergePluginContainer_Plugins(builder, targetBuild, source.getBuild(), false, context);
141 
142             return target.withBuild(builder.build());
143         }
144 
145         @SuppressWarnings({"checkstyle:methodname"})
146         @Override
147         protected void mergePluginContainer_Plugins(
148                 PluginContainer.Builder builder,
149                 PluginContainer target,
150                 PluginContainer source,
151                 boolean sourceDominant,
152                 Map<Object, Object> context) {
153             List<Plugin> src = source.getPlugins();
154             if (!src.isEmpty()) {
155                 List<Plugin> tgt = target.getPlugins();
156 
157                 Map<Object, Plugin> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
158 
159                 for (Plugin element : tgt) {
160                     Object key = getPluginKey().apply(element);
161                     merged.put(key, element);
162                 }
163 
164                 Map<Object, Plugin> added = new LinkedHashMap<>();
165 
166                 for (Plugin element : src) {
167                     Object key = getPluginKey().apply(element);
168                     Plugin existing = merged.get(key);
169                     if (existing != null) {
170                         element = mergePlugin(existing, element, sourceDominant, context);
171                     } else {
172                         added.put(key, element);
173                     }
174                     merged.put(key, element);
175                 }
176 
177                 if (!added.isEmpty()) {
178                     PluginManagement pluginMgmt = (PluginManagement) context.get(PLUGIN_MANAGEMENT);
179                     if (pluginMgmt != null) {
180                         for (Plugin managedPlugin : pluginMgmt.getPlugins()) {
181                             Object key = getPluginKey().apply(managedPlugin);
182                             Plugin addedPlugin = added.get(key);
183                             if (addedPlugin != null) {
184                                 Plugin plugin =
185                                         mergePlugin(managedPlugin, addedPlugin, sourceDominant, Collections.emptyMap());
186                                 merged.put(key, plugin);
187                             }
188                         }
189                     }
190                 }
191 
192                 List<Plugin> result = new ArrayList<>(merged.values());
193 
194                 builder.plugins(result);
195             }
196         }
197 
198         @Override
199         protected void mergePluginExecution_Priority(
200                 PluginExecution.Builder builder,
201                 PluginExecution target,
202                 PluginExecution source,
203                 boolean sourceDominant,
204                 Map<Object, Object> context) {
205             if (target.getPriority() > source.getPriority()) {
206                 builder.priority(source.getPriority());
207                 builder.location("priority", source.getLocation("priority"));
208             }
209         }
210         // mergePluginExecution_Priority( builder, target, source, sourceDominant, context );
211     }
212 }