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.archetype.ui.generation;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.util.HashMap;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.maven.archetype.ArchetypeGenerationRequest;
32  import org.apache.maven.archetype.ArchetypeManager;
33  import org.apache.maven.archetype.catalog.Archetype;
34  import org.apache.maven.archetype.exception.ArchetypeSelectionFailure;
35  import org.apache.maven.archetype.ui.ArchetypeDefinition;
36  import org.codehaus.plexus.components.interactivity.PrompterException;
37  import org.eclipse.aether.RepositorySystemSession;
38  import org.eclipse.aether.repository.RemoteRepository;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  @Named("default")
43  @Singleton
44  public class DefaultArchetypeSelector implements ArchetypeSelector {
45      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultArchetypeSelector.class);
46      static final String DEFAULT_ARCHETYPE_GROUPID = "org.apache.maven.archetypes";
47  
48      static final String DEFAULT_ARCHETYPE_VERSION = "1.0";
49  
50      static final String DEFAULT_ARCHETYPE_ARTIFACTID = "maven-archetype-quickstart";
51  
52      private ArchetypeSelectionQueryer archetypeSelectionQueryer;
53  
54      private ArchetypeManager archetypeManager;
55  
56      @Inject
57      public DefaultArchetypeSelector(
58              ArchetypeSelectionQueryer archetypeSelectionQueryer, ArchetypeManager archetypeManager) {
59          this.archetypeManager = archetypeManager;
60          this.archetypeSelectionQueryer = archetypeSelectionQueryer;
61      }
62  
63      @Override
64      public void selectArchetype(ArchetypeGenerationRequest request, Boolean interactiveMode, String catalogs)
65              throws PrompterException, ArchetypeSelectionFailure {
66          ArchetypeDefinition definition = new ArchetypeDefinition(request);
67  
68          if (definition.isDefined() && StringUtils.isNotEmpty(request.getArchetypeRepository())) {
69              LOGGER.info("Archetype defined by properties");
70              return;
71          }
72  
73          Map<String, List<Archetype>> archetypes =
74                  getArchetypesByCatalog(request.getRepositorySession(), request.getRemoteRepositories(), catalogs);
75  
76          if (StringUtils.isNotBlank(request.getFilter())) {
77              // applying some filtering depending on filter parameter
78              archetypes = ArchetypeSelectorUtils.getFilteredArchetypesByCatalog(archetypes, request.getFilter());
79              if (archetypes.isEmpty()) {
80                  LOGGER.info("Your filter doesn't match any archetype, so try again with another value.");
81                  return;
82              }
83          }
84  
85          if (definition.isDefined()) {
86              Map.Entry<String, Archetype> found =
87                      findArchetype(archetypes, request.getArchetypeGroupId(), request.getArchetypeArtifactId());
88  
89              if (found != null) {
90                  String catalogKey = found.getKey();
91                  Archetype archetype = found.getValue();
92  
93                  updateRepository(definition, archetype);
94  
95                  LOGGER.info("Archetype repository not defined. Using the one from " + archetype + " found in catalog "
96                          + catalogKey);
97              } else {
98                  LOGGER.warn("Archetype not found in any catalog. Falling back to central repository.");
99                  LOGGER.warn(
100                         "Add a repository with id 'archetype' in your settings.xml if archetype's repository is elsewhere.");
101             }
102         } else if (definition.isPartiallyDefined()) {
103             Map.Entry<String, Archetype> found =
104                     findArchetype(archetypes, request.getArchetypeGroupId(), request.getArchetypeArtifactId());
105 
106             if (found != null) {
107                 String catalogKey = found.getKey();
108                 Archetype archetype = found.getValue();
109 
110                 updateDefinition(definition, archetype);
111 
112                 LOGGER.info("Archetype " + archetype + " found in catalog " + catalogKey);
113             } else {
114                 LOGGER.warn("Specified archetype not found.");
115                 if (interactiveMode.booleanValue()) {
116                     definition.setVersion(null);
117                     definition.setGroupId(null);
118                     definition.setArtifactId(null);
119                 }
120             }
121         }
122 
123         // set the defaults - only group and version can be auto-defaulted
124         if (definition.getGroupId() == null) {
125             definition.setGroupId(DEFAULT_ARCHETYPE_GROUPID);
126         }
127         if (definition.getVersion() == null) {
128             definition.setVersion(DEFAULT_ARCHETYPE_VERSION);
129         }
130 
131         if (!definition.isPartiallyDefined()) {
132             // if artifact ID is set to its default, we still prompt to confirm
133             if (definition.getArtifactId() == null) {
134                 LOGGER.info("No archetype defined. Using " + DEFAULT_ARCHETYPE_ARTIFACTID + " ("
135                         + definition.getGroupId() + ":" + DEFAULT_ARCHETYPE_ARTIFACTID + ":"
136                         + definition.getVersion() + ")");
137                 definition.setArtifactId(DEFAULT_ARCHETYPE_ARTIFACTID);
138             }
139 
140             if (interactiveMode.booleanValue() && (!archetypes.isEmpty())) {
141                 Archetype selectedArchetype = archetypeSelectionQueryer.selectArchetype(archetypes, definition);
142 
143                 updateDefinition(definition, selectedArchetype);
144             }
145 
146             // Make sure the groupId and artifactId are valid, the version may just default to
147             // the latest release.
148             if (!definition.isPartiallyDefined()) {
149                 throw new ArchetypeSelectionFailure("No valid archetypes could be found to choose.");
150             }
151         }
152 
153         // finally update the request with gathered information
154         definition.updateRequest(request);
155     }
156 
157     private Map<String, List<Archetype>> getArchetypesByCatalog(
158             RepositorySystemSession repositorySession, List<RemoteRepository> remoteRepositories, String catalogs) {
159         if (catalogs == null) {
160             throw new NullPointerException("Catalogs cannot be null");
161         }
162 
163         Map<String, List<Archetype>> archetypes = new LinkedHashMap<>();
164 
165         for (String catalog : StringUtils.split(catalogs, ",")) {
166             if ("internal".equalsIgnoreCase(catalog)) {
167                 archetypes.put("internal", archetypeManager.getInternalCatalog().getArchetypes());
168             } else if ("local".equalsIgnoreCase(catalog)) {
169                 archetypes.put(
170                         "local",
171                         archetypeManager.getLocalCatalog(repositorySession).getArchetypes());
172             } else if ("remote".equalsIgnoreCase(catalog)) {
173                 List<Archetype> archetypesFromRemote = archetypeManager
174                         .getRemoteCatalog(repositorySession, remoteRepositories)
175                         .getArchetypes();
176 
177                 if (!archetypesFromRemote.isEmpty()) {
178                     archetypes.put("remote", archetypesFromRemote);
179                 } else {
180                     LOGGER.warn("No archetype found in remote catalog. Defaulting to internal catalog");
181                     archetypes.put(
182                             "internal", archetypeManager.getInternalCatalog().getArchetypes());
183                 }
184             } else {
185                 throw new IllegalArgumentException("archetypeCatalog '" + catalog + "' is not supported anymore. "
186                         + "Please read the plugin documentation for details.");
187             }
188         }
189 
190         if (archetypes.isEmpty()) {
191             LOGGER.info("No catalog defined. Using internal catalog");
192 
193             archetypes.put("internal", archetypeManager.getInternalCatalog().getArchetypes());
194         }
195         return archetypes;
196     }
197 
198     private void updateRepository(ArchetypeDefinition definition, Archetype archetype) {
199         String repository = archetype.getRepository();
200         if (repository != null && !repository.isEmpty()) {
201             definition.setRepository(repository);
202         }
203     }
204 
205     private void updateDefinition(ArchetypeDefinition definition, Archetype archetype) {
206         definition.setGroupId(archetype.getGroupId());
207         definition.setArtifactId(archetype.getArtifactId());
208         definition.setVersion(archetype.getVersion());
209         definition.setName(archetype.getArtifactId());
210         updateRepository(definition, archetype);
211         definition.setGoals(StringUtils.join(archetype.getGoals().iterator(), ","));
212     }
213 
214     public void setArchetypeSelectionQueryer(ArchetypeSelectionQueryer archetypeSelectionQueryer) {
215         this.archetypeSelectionQueryer = archetypeSelectionQueryer;
216     }
217 
218     private Map.Entry<String, Archetype> findArchetype(
219             Map<String, List<Archetype>> archetypes, String groupId, String artifactId) {
220         Archetype example = new Archetype();
221         example.setGroupId(groupId);
222         example.setArtifactId(artifactId);
223 
224         for (Map.Entry<String, List<Archetype>> entry : archetypes.entrySet()) {
225             List<Archetype> catalog = entry.getValue();
226 
227             if (catalog.contains(example)) {
228                 Archetype archetype = catalog.get(catalog.indexOf(example));
229 
230                 return newMapEntry(entry.getKey(), archetype);
231             }
232         }
233 
234         return null;
235     }
236 
237     private static <K, V> Map.Entry<K, V> newMapEntry(K key, V value) {
238         Map<K, V> map = new HashMap<>(1);
239         map.put(key, value);
240 
241         return map.entrySet().iterator().next();
242     }
243 }