View Javadoc

1   package org.apache.maven.index.treeview;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0    
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.lucene.search.BooleanClause;
29  import org.apache.lucene.search.BooleanQuery;
30  import org.apache.lucene.search.Query;
31  import org.apache.maven.index.ArtifactInfo;
32  import org.apache.maven.index.Field;
33  import org.apache.maven.index.Indexer;
34  import org.apache.maven.index.IteratorSearchRequest;
35  import org.apache.maven.index.IteratorSearchResponse;
36  import org.apache.maven.index.MAVEN;
37  import org.apache.maven.index.expr.SourcedSearchExpression;
38  import org.apache.maven.index.treeview.TreeNode.Type;
39  import org.codehaus.plexus.component.annotations.Component;
40  import org.codehaus.plexus.component.annotations.Requirement;
41  import org.codehaus.plexus.logging.AbstractLogEnabled;
42  import org.codehaus.plexus.util.StringUtils;
43  
44  @Component( role = IndexTreeView.class )
45  public class DefaultIndexTreeView
46      extends AbstractLogEnabled
47      implements IndexTreeView
48  {
49      @Requirement
50      private Indexer indexer;
51  
52      protected Indexer getIndexer()
53      {
54          return indexer;
55      }
56  
57      public TreeNode listNodes( TreeViewRequest request )
58          throws IOException
59      {
60          // get the last path elem
61          String name = null;
62  
63          if ( !"/".equals( request.getPath() ) )
64          {
65  
66              if ( request.getPath().endsWith( "/" ) )
67              {
68                  name = request.getPath().substring( 0, request.getPath().length() - 1 );
69              }
70              else
71              {
72                  name = request.getPath();
73              }
74  
75              name = name.substring( name.lastIndexOf( '/' ) + 1, name.length() );
76  
77              // root is "/"
78              if ( !name.equals( "/" ) && name.endsWith( "/" ) )
79              {
80                  name = name.substring( 0, name.length() - 1 );
81              }
82  
83          }
84          else
85          {
86              name = "/";
87          }
88  
89          // the root node depends on request we have, so let's see
90          TreeNode result = request.getFactory().createGNode( this, request, request.getPath(), name );
91  
92          if ( request.hasFieldHints() )
93          {
94              listChildren( result, request, null );
95          }
96          else
97          {
98              // non hinted way, the "old" way
99              if ( "/".equals( request.getPath() ) )
100             {
101                 // get root groups and finish
102                 Set<String> rootGroups = request.getIndexingContext().getRootGroups();
103 
104                 for ( String group : rootGroups )
105                 {
106                     if ( group.length() > 0 )
107                     {
108                         result.getChildren().add(
109                             request.getFactory().createGNode( this, request, request.getPath() + group + "/", group ) );
110                     }
111                 }
112             }
113             else
114             {
115                 Set<String> allGroups = request.getIndexingContext().getAllGroups();
116 
117                 listChildren( result, request, allGroups );
118             }
119         }
120 
121         return result;
122     }
123 
124     /**
125      * @param root
126      * @param factory
127      * @param allGroups
128      * @throws IOException
129      */
130     protected void listChildren( TreeNode root, TreeViewRequest request, Set<String> allGroups )
131         throws IOException
132     {
133         String path = root.getPath();
134 
135         Map<String, TreeNode> folders = new HashMap<String, TreeNode>();
136 
137         String rootPartialGroupId = StringUtils.strip( root.getPath().replaceAll( "/", "." ), "." );
138 
139         folders.put( Type.G + ":" + rootPartialGroupId, root );
140 
141         IteratorSearchResponse artifacts = getArtifacts( root, request );
142 
143         try
144         {
145             for ( ArtifactInfo ai : artifacts )
146             {
147                 String versionKey = Type.V + ":" + ai.artifactId + ":" + ai.version;
148 
149                 TreeNode versionResource = folders.get( versionKey );
150 
151                 if ( versionResource == null )
152                 {
153                     String artifactKey = Type.A + ":" + ai.artifactId;
154 
155                     TreeNode artifactResource = folders.get( artifactKey );
156 
157                     if ( artifactResource == null )
158                     {
159                         TreeNode groupParentResource = root;
160 
161                         TreeNode groupResource = root;
162 
163                         // here comes the twist: we have to search for parent G node
164                         String partialGroupId = null;
165 
166                         String[] groupIdElems = ai.groupId.split( "\\." );
167 
168                         for ( String groupIdElem : groupIdElems )
169                         {
170                             if ( partialGroupId == null )
171                             {
172                                 partialGroupId = groupIdElem;
173                             }
174                             else
175                             {
176                                 partialGroupId = partialGroupId + "." + groupIdElem;
177                             }
178 
179                             String groupKey = Type.G + ":" + partialGroupId;
180 
181                             groupResource = folders.get( groupKey );
182 
183                             // it needs to be created only if not found (is null) and is _below_ groupParentResource
184                             if ( groupResource == null
185                                 && groupParentResource.getPath().length() < getPathForAi( ai, MAVEN.GROUP_ID ).length() )
186                             {
187                                 String gNodeName =
188                                     partialGroupId.lastIndexOf( '.' ) > -1 ? partialGroupId.substring(
189                                         partialGroupId.lastIndexOf( '.' ) + 1, partialGroupId.length() )
190                                         : partialGroupId;
191 
192                                 groupResource =
193                                     request.getFactory().createGNode( this, request,
194                                         "/" + partialGroupId.replaceAll( "\\.", "/" ) + "/", gNodeName );
195 
196                                 groupParentResource.getChildren().add( groupResource );
197 
198                                 folders.put( groupKey, groupResource );
199 
200                                 groupParentResource = groupResource;
201                             }
202                             else if ( groupResource != null )
203                             {
204                                 // we found it as already existing, break if this is the node we want
205                                 if ( groupResource.getPath().equals( getPathForAi( ai, MAVEN.GROUP_ID ) ) )
206                                 {
207                                     break;
208                                 }
209 
210                                 groupParentResource = groupResource;
211                             }
212                         }
213 
214                         artifactResource =
215                             request.getFactory().createANode( this, request, ai, getPathForAi( ai, MAVEN.ARTIFACT_ID ) );
216 
217                         groupParentResource.getChildren().add( artifactResource );
218 
219                         folders.put( artifactKey, artifactResource );
220                     }
221 
222                     versionResource =
223                         request.getFactory().createVNode( this, request, ai, getPathForAi( ai, MAVEN.VERSION ) );
224 
225                     artifactResource.getChildren().add( versionResource );
226 
227                     folders.put( versionKey, versionResource );
228                 }
229 
230                 String nodePath = getPathForAi( ai, null );
231 
232                 versionResource.getChildren().add(
233                     request.getFactory().createArtifactNode( this, request, ai, nodePath ) );
234             }
235         }
236         finally
237         {
238             artifacts.close();
239         }
240 
241         if ( !request.hasFieldHints() )
242         {
243             Set<String> groups = getGroups( path, allGroups );
244 
245             for ( String group : groups )
246             {
247                 TreeNode groupResource = root.findChildByPath( path + group + "/", Type.G );
248 
249                 if ( groupResource == null )
250                 {
251                     groupResource = request.getFactory().createGNode( this, request, path + group + "/", group );
252 
253                     root.getChildren().add( groupResource );
254                 }
255                 else
256                 {
257                     // if the folder has been created as an artifact name,
258                     // we need to check for possible nested groups as well
259                     listChildren( groupResource, request, allGroups );
260                 }
261             }
262         }
263     }
264 
265     /**
266      * Builds a path out from ArtifactInfo. The field parameter controls "how deep" the path goes. Possible values are
267      * MAVEN.GROUP_ID (builds a path from groupId only), MAVEN.ARTIFACT_ID (builds a path from groupId + artifactId),
268      * MAVEN.VERSION (builds a path up to version) or anything else (including null) will build "full" artifact path.
269      * 
270      * @param ai
271      * @param field
272      * @return path
273      */
274     protected String getPathForAi( ArtifactInfo ai, Field field )
275     {
276         StringBuilder sb = new StringBuilder( "/" );
277 
278         sb.append( ai.groupId.replaceAll( "\\.", "/" ) );
279 
280         if ( MAVEN.GROUP_ID.equals( field ) )
281         {
282             // stop here
283             return sb.append( "/" ).toString();
284         }
285 
286         sb.append( "/" ).append( ai.artifactId );
287 
288         if ( MAVEN.ARTIFACT_ID.equals( field ) )
289         {
290             // stop here
291             return sb.append( "/" ).toString();
292         }
293 
294         sb.append( "/" ).append( ai.version );
295 
296         if ( MAVEN.VERSION.equals( field ) )
297         {
298             // stop here
299             return sb.append( "/" ).toString();
300         }
301 
302         sb.append( "/" ).append( ai.artifactId ).append( "-" ).append( ai.version );
303 
304         if ( ai.classifier != null )
305         {
306             sb.append( "-" ).append( ai.classifier );
307         }
308 
309         sb.append( "." ).append( ai.fextension == null ? "jar" : ai.fextension );
310 
311         return sb.toString();
312     }
313 
314     protected Set<String> getGroups( String path, Set<String> allGroups )
315     {
316         path = path.substring( 1 ).replace( '/', '.' );
317 
318         int n = path.length();
319 
320         Set<String> result = new HashSet<String>();
321 
322         for ( String group : allGroups )
323         {
324             if ( group.startsWith( path ) )
325             {
326                 group = group.substring( n );
327 
328                 int nextDot = group.indexOf( '.' );
329 
330                 if ( nextDot > -1 )
331                 {
332                     group = group.substring( 0, nextDot );
333                 }
334 
335                 if ( group.length() > 0 && !result.contains( group ) )
336                 {
337                     result.add( group );
338                 }
339             }
340         }
341 
342         return result;
343     }
344 
345     protected IteratorSearchResponse getArtifacts( TreeNode root, TreeViewRequest request )
346         throws IOException
347     {
348         if ( request.hasFieldHints() )
349         {
350             return getHintedArtifacts( root, request );
351         }
352 
353         String path = root.getPath();
354 
355         IteratorSearchResponse result = null;
356 
357         String g = null;
358 
359         String a = null;
360 
361         String v = null;
362 
363         // "working copy" of path
364         String wp = null;
365 
366         // remove last / from path
367         if ( path.endsWith( "/" ) )
368         {
369             path = path.substring( 0, path.length() - 1 );
370         }
371 
372         // 1st try, let's consider path is a group
373 
374         // reset wp
375         wp = path;
376 
377         g = wp.substring( 1 ).replace( '/', '.' );
378 
379         result = getArtifactsByG( g, request );
380 
381         if ( result.getTotalHitsCount() > 0 )
382         {
383             return result;
384         }
385         else
386         {
387             result.close();
388         }
389 
390         // 2nd try, lets consider path a group + artifactId, we must ensure there is at least one / but not as root
391 
392         if ( path.lastIndexOf( '/' ) > 0 )
393         {
394             // reset wp
395             wp = path;
396 
397             a = wp.substring( wp.lastIndexOf( '/' ) + 1, wp.length() );
398 
399             g = wp.substring( 1, wp.lastIndexOf( '/' ) ).replace( '/', '.' );
400 
401             result = getArtifactsByGA( g, a, request );
402 
403             if ( result.getTotalHitsCount() > 0 )
404             {
405                 return result;
406             }
407             else
408             {
409                 result.close();
410             }
411 
412             // 3rd try, let's consider path a group + artifactId + version. There is no 100% way to detect this!
413 
414             try
415             {
416                 // reset wp
417                 wp = path;
418 
419                 v = wp.substring( wp.lastIndexOf( '/' ) + 1, wp.length() );
420 
421                 wp = wp.substring( 0, wp.lastIndexOf( '/' ) );
422 
423                 a = wp.substring( wp.lastIndexOf( '/' ) + 1, wp.length() );
424 
425                 g = wp.substring( 1, wp.lastIndexOf( '/' ) ).replace( '/', '.' );
426 
427                 result = getArtifactsByGAV( g, a, v, request );
428 
429                 if ( result.getTotalHitsCount() > 0 )
430                 {
431                     return result;
432                 }
433                 else
434                 {
435                     result.close();
436                 }
437             }
438             catch ( StringIndexOutOfBoundsException e )
439             {
440                 // nothing
441             }
442         }
443 
444         // if we are here, no hits found
445         return IteratorSearchResponse.empty( result.getQuery() );
446     }
447 
448     protected IteratorSearchResponse getHintedArtifacts( TreeNode root, TreeViewRequest request )
449         throws IOException
450     {
451         // we know that hints are there: G hint, GA hint or GAV hint
452         if ( request.hasFieldHint( MAVEN.GROUP_ID, MAVEN.ARTIFACT_ID, MAVEN.VERSION ) )
453         {
454             return getArtifactsByGAV( request.getFieldHint( MAVEN.GROUP_ID ),
455                 request.getFieldHint( MAVEN.ARTIFACT_ID ), request.getFieldHint( MAVEN.VERSION ), request );
456         }
457         else if ( request.hasFieldHint( MAVEN.GROUP_ID, MAVEN.ARTIFACT_ID ) )
458         {
459             return getArtifactsByGA( request.getFieldHint( MAVEN.GROUP_ID ), request.getFieldHint( MAVEN.ARTIFACT_ID ),
460                 request );
461         }
462         else if ( request.hasFieldHint( MAVEN.GROUP_ID ) )
463         {
464             return getArtifactsByG( request.getFieldHint( MAVEN.GROUP_ID ), request );
465         }
466         else
467         {
468             // if we are here, no hits found or something horribly went wrong?
469             return IteratorSearchResponse.empty( null );
470         }
471     }
472 
473     protected IteratorSearchResponse getArtifactsByG( String g, TreeViewRequest request )
474         throws IOException
475     {
476         return getArtifactsByGAVField( g, null, null, request );
477     }
478 
479     protected IteratorSearchResponse getArtifactsByGA( String g, String a, TreeViewRequest request )
480         throws IOException
481     {
482         return getArtifactsByGAVField( g, a, null, request );
483     }
484 
485     protected IteratorSearchResponse getArtifactsByGAV( String g, String a, String v, TreeViewRequest request )
486         throws IOException
487     {
488         return getArtifactsByGAVField( g, a, v, request );
489     }
490 
491     protected IteratorSearchResponse getArtifactsByGAVField( String g, String a, String v, TreeViewRequest request )
492         throws IOException
493     {
494         assert g != null;
495 
496         Query groupIdQ = null;
497         Query artifactIdQ = null;
498         Query versionQ = null;
499 
500         // minimum must have
501         groupIdQ = getIndexer().constructQuery( MAVEN.GROUP_ID, new SourcedSearchExpression( g ) );
502 
503         if ( StringUtils.isNotBlank( a ) )
504         {
505             artifactIdQ = getIndexer().constructQuery( MAVEN.ARTIFACT_ID, new SourcedSearchExpression( a ) );
506         }
507 
508         if ( StringUtils.isNotBlank( v ) )
509         {
510             versionQ = getIndexer().constructQuery( MAVEN.VERSION, new SourcedSearchExpression( v ) );
511         }
512 
513         BooleanQuery q = new BooleanQuery();
514 
515         q.add( new BooleanClause( groupIdQ, BooleanClause.Occur.MUST ) );
516 
517         if ( artifactIdQ != null )
518         {
519             q.add( new BooleanClause( artifactIdQ, BooleanClause.Occur.MUST ) );
520         }
521 
522         if ( versionQ != null )
523         {
524             q.add( new BooleanClause( versionQ, BooleanClause.Occur.MUST ) );
525         }
526 
527         IteratorSearchRequest searchRequest = new IteratorSearchRequest( q, request.getArtifactInfoFilter() );
528 
529         searchRequest.getContexts().add( request.getIndexingContext() );
530 
531         IteratorSearchResponse result = getIndexer().searchIterator( searchRequest );
532 
533         return result;
534     }
535 }