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.index.treeview;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.IOException;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.apache.lucene.search.BooleanClause;
32  import org.apache.lucene.search.BooleanQuery;
33  import org.apache.lucene.search.Query;
34  import org.apache.maven.index.ArtifactInfo;
35  import org.apache.maven.index.Field;
36  import org.apache.maven.index.Indexer;
37  import org.apache.maven.index.IteratorSearchRequest;
38  import org.apache.maven.index.IteratorSearchResponse;
39  import org.apache.maven.index.MAVEN;
40  import org.apache.maven.index.expr.SourcedSearchExpression;
41  import org.apache.maven.index.treeview.TreeNode.Type;
42  import org.codehaus.plexus.util.StringUtils;
43  
44  @Singleton
45  @Named
46  public class DefaultIndexTreeView implements IndexTreeView {
47  
48      private final Indexer indexer;
49  
50      @Inject
51      public DefaultIndexTreeView(Indexer indexer) {
52          this.indexer = indexer;
53      }
54  
55      protected Indexer getIndexer() {
56          return indexer;
57      }
58  
59      public TreeNode listNodes(TreeViewRequest request) throws IOException {
60          // get the last path elem
61          String name;
62  
63          if (!"/".equals(request.getPath())) {
64  
65              if (request.getPath().endsWith("/")) {
66                  name = request.getPath().substring(0, request.getPath().length() - 1);
67              } else {
68                  name = request.getPath();
69              }
70  
71              name = name.substring(name.lastIndexOf('/') + 1);
72  
73              // root is "/"
74              if (!name.equals("/") && name.endsWith("/")) {
75                  name = name.substring(0, name.length() - 1);
76              }
77  
78          } else {
79              name = "/";
80          }
81  
82          // the root node depends on request we have, so let's see
83          TreeNode result = request.getFactory().createGNode(this, request, request.getPath(), name);
84  
85          if (request.hasFieldHints()) {
86              listChildren(result, request, null);
87          } else {
88              // non hinted way, the "old" way
89              if ("/".equals(request.getPath())) {
90                  // get root groups and finish
91                  Set<String> rootGroups = request.getIndexingContext().getRootGroups();
92  
93                  for (String group : rootGroups) {
94                      if (group.length() > 0) {
95                          result.getChildren()
96                                  .add(request.getFactory()
97                                          .createGNode(this, request, request.getPath() + group + "/", group));
98                      }
99                  }
100             } else {
101                 Set<String> allGroups = request.getIndexingContext().getAllGroups();
102 
103                 listChildren(result, request, allGroups);
104             }
105         }
106 
107         return result;
108     }
109 
110     /**
111      * @param root
112      * @param request
113      * @param allGroups
114      * @throws IOException
115      */
116     protected void listChildren(TreeNode root, TreeViewRequest request, Set<String> allGroups) throws IOException {
117         String path = root.getPath();
118 
119         Map<String, TreeNode> folders = new HashMap<>();
120 
121         String rootPartialGroupId = StringUtils.strip(root.getPath().replaceAll("/", "."), ".");
122 
123         folders.put(Type.G + ":" + rootPartialGroupId, root);
124 
125         try (IteratorSearchResponse artifacts = getArtifacts(root, request)) {
126             for (ArtifactInfo ai : artifacts) {
127                 String versionKey = Type.V + ":" + ai.getArtifactId() + ":" + ai.getVersion();
128 
129                 TreeNode versionResource = folders.get(versionKey);
130 
131                 if (versionResource == null) {
132                     String artifactKey = Type.A + ":" + ai.getArtifactId();
133 
134                     TreeNode artifactResource = folders.get(artifactKey);
135 
136                     if (artifactResource == null) {
137                         TreeNode groupParentResource = root;
138 
139                         TreeNode groupResource;
140 
141                         // here comes the twist: we have to search for parent G node
142                         String partialGroupId = null;
143 
144                         String[] groupIdElems = ai.getGroupId().split("\\.");
145 
146                         for (String groupIdElem : groupIdElems) {
147                             if (partialGroupId == null) {
148                                 partialGroupId = groupIdElem;
149                             } else {
150                                 partialGroupId = partialGroupId + "." + groupIdElem;
151                             }
152 
153                             String groupKey = Type.G + ":" + partialGroupId;
154 
155                             groupResource = folders.get(groupKey);
156 
157                             // it needs to be created only if not found (is null) and is _below_ groupParentResource
158                             if (groupResource == null
159                                     && groupParentResource.getPath().length()
160                                             < getPathForAi(ai, MAVEN.GROUP_ID).length()) {
161                                 String gNodeName = partialGroupId.lastIndexOf('.') > -1
162                                         ? partialGroupId.substring(partialGroupId.lastIndexOf('.') + 1)
163                                         : partialGroupId;
164 
165                                 groupResource = request.getFactory()
166                                         .createGNode(
167                                                 this,
168                                                 request,
169                                                 "/" + partialGroupId.replaceAll("\\.", "/") + "/",
170                                                 gNodeName);
171 
172                                 groupParentResource.getChildren().add(groupResource);
173 
174                                 folders.put(groupKey, groupResource);
175 
176                                 groupParentResource = groupResource;
177                             } else if (groupResource != null) {
178                                 // we found it as already existing, break if this is the node we want
179                                 if (groupResource.getPath().equals(getPathForAi(ai, MAVEN.GROUP_ID))) {
180                                     break;
181                                 }
182 
183                                 groupParentResource = groupResource;
184                             }
185                         }
186 
187                         artifactResource = request.getFactory()
188                                 .createANode(this, request, ai, getPathForAi(ai, MAVEN.ARTIFACT_ID));
189 
190                         groupParentResource.getChildren().add(artifactResource);
191 
192                         folders.put(artifactKey, artifactResource);
193                     }
194 
195                     versionResource =
196                             request.getFactory().createVNode(this, request, ai, getPathForAi(ai, MAVEN.VERSION));
197 
198                     artifactResource.getChildren().add(versionResource);
199 
200                     folders.put(versionKey, versionResource);
201                 }
202 
203                 String nodePath = getPathForAi(ai, null);
204 
205                 versionResource.getChildren().add(request.getFactory().createArtifactNode(this, request, ai, nodePath));
206             }
207         }
208 
209         if (!request.hasFieldHints()) {
210             Set<String> groups = getGroups(path, allGroups);
211 
212             for (String group : groups) {
213                 TreeNode groupResource = root.findChildByPath(path + group + "/", Type.G);
214 
215                 if (groupResource == null) {
216                     groupResource = request.getFactory().createGNode(this, request, path + group + "/", group);
217 
218                     root.getChildren().add(groupResource);
219                 } else {
220                     // if the folder has been created as an artifact name,
221                     // we need to check for possible nested groups as well
222                     listChildren(groupResource, request, allGroups);
223                 }
224             }
225         }
226     }
227 
228     /**
229      * Builds a path out from ArtifactInfo. The field parameter controls "how deep" the path goes. Possible values are
230      * MAVEN.GROUP_ID (builds a path from groupId only), MAVEN.ARTIFACT_ID (builds a path from groupId + artifactId),
231      * MAVEN.VERSION (builds a path up to version) or anything else (including null) will build "full" artifact path.
232      *
233      * @param ai
234      * @param field
235      * @return path
236      */
237     protected String getPathForAi(ArtifactInfo ai, Field field) {
238         StringBuilder sb = new StringBuilder("/");
239 
240         sb.append(ai.getGroupId().replaceAll("\\.", "/"));
241 
242         if (MAVEN.GROUP_ID.equals(field)) {
243             // stop here
244             return sb.append("/").toString();
245         }
246 
247         sb.append("/").append(ai.getArtifactId());
248 
249         if (MAVEN.ARTIFACT_ID.equals(field)) {
250             // stop here
251             return sb.append("/").toString();
252         }
253 
254         sb.append("/").append(ai.getVersion());
255 
256         if (MAVEN.VERSION.equals(field)) {
257             // stop here
258             return sb.append("/").toString();
259         }
260 
261         sb.append("/").append(ai.getArtifactId()).append("-").append(ai.getVersion());
262 
263         if (ai.getClassifier() != null) {
264             sb.append("-").append(ai.getClassifier());
265         }
266 
267         sb.append(".").append(ai.getFileExtension() == null ? "jar" : ai.getFileExtension());
268 
269         return sb.toString();
270     }
271 
272     protected Set<String> getGroups(String path, Set<String> allGroups) {
273         path = path.substring(1).replace('/', '.');
274 
275         int n = path.length();
276 
277         Set<String> result = new HashSet<>();
278 
279         for (String group : allGroups) {
280             if (group.startsWith(path)) {
281                 group = group.substring(n);
282 
283                 int nextDot = group.indexOf('.');
284 
285                 if (nextDot > -1) {
286                     group = group.substring(0, nextDot);
287                 }
288 
289                 if (group.length() > 0) {
290                     result.add(group);
291                 }
292             }
293         }
294 
295         return result;
296     }
297 
298     protected IteratorSearchResponse getArtifacts(TreeNode root, TreeViewRequest request) throws IOException {
299         if (request.hasFieldHints()) {
300             return getHintedArtifacts(root, request);
301         }
302 
303         String path = root.getPath();
304 
305         IteratorSearchResponse result;
306 
307         String g;
308 
309         String a;
310 
311         String v;
312 
313         // "working copy" of path
314         String wp;
315 
316         // remove last / from path
317         if (path.endsWith("/")) {
318             path = path.substring(0, path.length() - 1);
319         }
320 
321         // 1st try, let's consider path is a group
322 
323         // reset wp
324         wp = path;
325 
326         g = wp.substring(1).replace('/', '.');
327 
328         result = getArtifactsByG(g, request);
329 
330         if (result.getTotalHitsCount() > 0) {
331             return result;
332         } else {
333             result.close();
334         }
335 
336         // 2nd try, lets consider path a group + artifactId, we must ensure there is at least one / but not as root
337 
338         if (path.lastIndexOf('/') > 0) {
339             // reset wp
340             wp = path;
341 
342             a = wp.substring(wp.lastIndexOf('/') + 1);
343 
344             g = wp.substring(1, wp.lastIndexOf('/')).replace('/', '.');
345 
346             result = getArtifactsByGA(g, a, request);
347 
348             if (result.getTotalHitsCount() > 0) {
349                 return result;
350             } else {
351                 result.close();
352             }
353 
354             // 3rd try, let's consider path a group + artifactId + version. There is no 100% way to detect this!
355 
356             try {
357                 // reset wp
358                 wp = path;
359 
360                 v = wp.substring(wp.lastIndexOf('/') + 1);
361 
362                 wp = wp.substring(0, wp.lastIndexOf('/'));
363 
364                 a = wp.substring(wp.lastIndexOf('/') + 1);
365 
366                 g = wp.substring(1, wp.lastIndexOf('/')).replace('/', '.');
367 
368                 result = getArtifactsByGAV(g, a, v, request);
369 
370                 if (result.getTotalHitsCount() > 0) {
371                     return result;
372                 } else {
373                     result.close();
374                 }
375             } catch (StringIndexOutOfBoundsException e) {
376                 // nothing
377             }
378         }
379 
380         // if we are here, no hits found
381         return IteratorSearchResponse.empty(result.getQuery());
382     }
383 
384     protected IteratorSearchResponse getHintedArtifacts(TreeNode root, TreeViewRequest request) throws IOException {
385         // we know that hints are there: G hint, GA hint or GAV hint
386         if (request.hasFieldHint(MAVEN.GROUP_ID, MAVEN.ARTIFACT_ID, MAVEN.VERSION)) {
387             return getArtifactsByGAV(
388                     request.getFieldHint(MAVEN.GROUP_ID),
389                     request.getFieldHint(MAVEN.ARTIFACT_ID),
390                     request.getFieldHint(MAVEN.VERSION),
391                     request);
392         } else if (request.hasFieldHint(MAVEN.GROUP_ID, MAVEN.ARTIFACT_ID)) {
393             return getArtifactsByGA(
394                     request.getFieldHint(MAVEN.GROUP_ID), request.getFieldHint(MAVEN.ARTIFACT_ID), request);
395         } else if (request.hasFieldHint(MAVEN.GROUP_ID)) {
396             return getArtifactsByG(request.getFieldHint(MAVEN.GROUP_ID), request);
397         } else {
398             // if we are here, no hits found or something horribly went wrong?
399             return IteratorSearchResponse.empty(null);
400         }
401     }
402 
403     protected IteratorSearchResponse getArtifactsByG(String g, TreeViewRequest request) throws IOException {
404         return getArtifactsByGAVField(g, null, null, request);
405     }
406 
407     protected IteratorSearchResponse getArtifactsByGA(String g, String a, TreeViewRequest request) throws IOException {
408         return getArtifactsByGAVField(g, a, null, request);
409     }
410 
411     protected IteratorSearchResponse getArtifactsByGAV(String g, String a, String v, TreeViewRequest request)
412             throws IOException {
413         return getArtifactsByGAVField(g, a, v, request);
414     }
415 
416     protected IteratorSearchResponse getArtifactsByGAVField(String g, String a, String v, TreeViewRequest request)
417             throws IOException {
418         assert g != null;
419 
420         Query groupIdQ;
421         Query artifactIdQ = null;
422         Query versionQ = null;
423 
424         // minimum must have
425         groupIdQ = getIndexer().constructQuery(MAVEN.GROUP_ID, new SourcedSearchExpression(g));
426 
427         if (StringUtils.isNotBlank(a)) {
428             artifactIdQ = getIndexer().constructQuery(MAVEN.ARTIFACT_ID, new SourcedSearchExpression(a));
429         }
430 
431         if (StringUtils.isNotBlank(v)) {
432             versionQ = getIndexer().constructQuery(MAVEN.VERSION, new SourcedSearchExpression(v));
433         }
434 
435         BooleanQuery.Builder qb = new BooleanQuery.Builder().add(new BooleanClause(groupIdQ, BooleanClause.Occur.MUST));
436 
437         if (artifactIdQ != null) {
438             qb.add(new BooleanClause(artifactIdQ, BooleanClause.Occur.MUST));
439         }
440 
441         if (versionQ != null) {
442             qb.add(new BooleanClause(versionQ, BooleanClause.Occur.MUST));
443         }
444 
445         IteratorSearchRequest searchRequest = new IteratorSearchRequest(qb.build(), request.getArtifactInfoFilter());
446 
447         searchRequest.getContexts().add(request.getIndexingContext());
448 
449         IteratorSearchResponse result = getIndexer().searchIterator(searchRequest);
450 
451         return result;
452     }
453 }