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