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