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;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Optional;
31  import java.util.Set;
32  import java.util.stream.Collectors;
33  
34  import org.apache.maven.api.Packaging;
35  import org.apache.maven.api.Type;
36  import org.apache.maven.api.model.Dependency;
37  import org.apache.maven.api.model.InputLocation;
38  import org.apache.maven.api.model.InputSource;
39  import org.apache.maven.api.model.Plugin;
40  import org.apache.maven.api.model.PluginContainer;
41  import org.apache.maven.api.model.PluginExecution;
42  import org.apache.maven.api.services.Lookup;
43  import org.apache.maven.api.services.PackagingRegistry;
44  import org.apache.maven.api.services.TypeRegistry;
45  import org.apache.maven.api.spi.PackagingProvider;
46  import org.apache.maven.lifecycle.internal.DefaultLifecyclePluginAnalyzer;
47  import org.apache.maven.lifecycle.mapping.LifecycleMapping;
48  import org.apache.maven.lifecycle.mapping.LifecycleMojo;
49  import org.apache.maven.lifecycle.mapping.LifecyclePhase;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  /**
54   * TODO: this is session scoped as SPI can contribute.
55   */
56  @Named
57  @Singleton
58  public class DefaultPackagingRegistry
59          extends ExtensibleEnumRegistries.DefaultExtensibleEnumRegistry<Packaging, PackagingProvider>
60          implements PackagingRegistry {
61  
62      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPackagingRegistry.class);
63  
64      private final Lookup lookup;
65  
66      private final TypeRegistry typeRegistry;
67  
68      @Inject
69      public DefaultPackagingRegistry(Lookup lookup, TypeRegistry typeRegistry, List<PackagingProvider> providers) {
70          super(providers);
71          this.lookup = lookup;
72          this.typeRegistry = typeRegistry;
73      }
74  
75      @Override
76      public Optional<Packaging> lookup(String id) {
77          id = id.toLowerCase(Locale.ROOT);
78          // TODO: we should be able to inject a Map<String, LifecycleMapping> directly,
79          // however, SISU visibility filtering can only happen when an explicit
80          // lookup is performed. The whole problem here is caused by "project extensions"
81          // which are bound to a project's classloader, without any clear definition
82          // of a "project scope"
83          LifecycleMapping lifecycleMapping =
84                  lookup.lookupOptional(LifecycleMapping.class, id).orElse(null);
85          if (lifecycleMapping == null) {
86              return Optional.empty();
87          }
88          Type type = typeRegistry.lookup(id).orElse(null);
89          if (type == null) {
90              return Optional.empty();
91          }
92          return Optional.of(new DefaultPackaging(id, type, getPlugins(lifecycleMapping)));
93      }
94  
95      private Map<String, PluginContainer> getPlugins(LifecycleMapping lifecycleMapping) {
96          Map<String, PluginContainer> lfs = new HashMap<>();
97          lifecycleMapping.getLifecycles().forEach((id, lifecycle) -> {
98              Map<String, Plugin> plugins = new HashMap<>();
99              lifecycle
100                     .getLifecyclePhases()
101                     .forEach((phase, lifecyclePhase) -> parseLifecyclePhaseDefinitions(plugins, phase, lifecyclePhase));
102             lfs.put(id, PluginContainer.newBuilder().plugins(plugins.values()).build());
103         });
104         return lfs;
105     }
106 
107     static void parseLifecyclePhaseDefinitions(Map<String, Plugin> plugins, String phase, LifecyclePhase goals) {
108         InputSource inputSource =
109                 new InputSource(DefaultLifecyclePluginAnalyzer.DEFAULTLIFECYCLEBINDINGS_MODELID, null);
110         InputLocation location = new InputLocation(-1, -1, inputSource, 0);
111 
112         List<LifecycleMojo> mojos = goals.getMojos();
113         if (mojos != null) {
114             for (int i = 0; i < mojos.size(); i++) {
115                 LifecycleMojo mojo = mojos.get(i);
116 
117                 // Compute goal coordinates
118                 String groupId, artifactId, version, goal;
119                 String[] p = mojo.getGoal().trim().split(":");
120                 if (p.length == 3) {
121                     // <groupId>:<artifactId>:<goal>
122                     groupId = p[0];
123                     artifactId = p[1];
124                     version = null;
125                     goal = p[2];
126                 } else if (p.length == 4) {
127                     // <groupId>:<artifactId>:<version>:<goal>
128                     groupId = p[0];
129                     artifactId = p[1];
130                     version = p[2];
131                     goal = p[3];
132                 } else {
133                     // invalid
134                     LOGGER.warn(
135                             "Ignored invalid goal specification '{}' from lifecycle mapping for phase {}",
136                             mojo.getGoal(),
137                             phase);
138                     continue;
139                 }
140 
141                 String key = groupId + ":" + artifactId;
142 
143                 // Build plugin
144                 List<PluginExecution> execs = new ArrayList<>();
145                 List<Dependency> deps = new ArrayList<>();
146 
147                 Plugin existing = plugins.get(key);
148                 if (existing != null) {
149                     if (version == null) {
150                         version = existing.getVersion();
151                     }
152                     execs.addAll(existing.getExecutions());
153                     deps.addAll(existing.getDependencies());
154                 }
155 
156                 PluginExecution execution = PluginExecution.newBuilder()
157                         .id(getExecutionId(existing, goal))
158                         .priority(i - mojos.size())
159                         .phase(phase)
160                         .goals(List.of(goal))
161                         .configuration(mojo.getConfiguration())
162                         .location("", location)
163                         .location("id", location)
164                         .location("phase", location)
165                         .location("goals", location)
166                         .build();
167                 execs.add(execution);
168 
169                 if (mojo.getDependencies() != null) {
170                     mojo.getDependencies().forEach(d -> deps.add(d.getDelegate()));
171                 }
172 
173                 Plugin plugin = Plugin.newBuilder()
174                         .groupId(groupId)
175                         .artifactId(artifactId)
176                         .version(version)
177                         .location("", location)
178                         .location("groupId", location)
179                         .location("artifactId", location)
180                         .location("version", location)
181                         .executions(execs)
182                         .dependencies(deps)
183                         .build();
184 
185                 plugins.put(key, plugin);
186             }
187         }
188     }
189 
190     private static String getExecutionId(Plugin plugin, String goal) {
191         Set<String> existingIds = plugin != null
192                 ? plugin.getExecutions().stream().map(PluginExecution::getId).collect(Collectors.toSet())
193                 : Set.of();
194         String base = "default-" + goal;
195         String id = base;
196         for (int index = 1; existingIds.contains(id); index++) {
197             id = base + '-' + index;
198         }
199         return id;
200     }
201 
202     private record DefaultPackaging(String id, Type type, Map<String, PluginContainer> plugins) implements Packaging {}
203 }