View Javadoc
1   package org.apache.maven.index.creator;
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.Named;
23  import javax.inject.Singleton;
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.List;
29  
30  import org.apache.lucene.document.Document;
31  import org.apache.lucene.document.Field.Index;
32  import org.apache.lucene.document.Field.Store;
33  import org.apache.maven.index.ArtifactContext;
34  import org.apache.maven.index.ArtifactInfo;
35  import org.apache.maven.index.IndexerField;
36  import org.apache.maven.index.IndexerFieldVersion;
37  import org.apache.maven.index.MAVEN;
38  import org.apache.maven.index.util.zip.ZipFacade;
39  import org.apache.maven.index.util.zip.ZipHandle;
40  import org.codehaus.plexus.util.StringUtils;
41  
42  /**
43   * An index creator used to index Java class names from a Maven artifact (JAR or WAR for now). Will open up the file and
44   * collect all the class names from it.
45   */
46  @Singleton
47  @Named( JarFileContentsIndexCreator.ID )
48  public class JarFileContentsIndexCreator
49      extends AbstractIndexCreator
50      implements LegacyDocumentUpdater
51  {
52      public static final String ID = "jarContent";
53  
54      public static final IndexerField FLD_CLASSNAMES = new IndexerField( MAVEN.CLASSNAMES, IndexerFieldVersion.V3,
55          "classnames", "Artifact Classes (tokenized)", Store.NO, Index.ANALYZED );
56  
57      /**
58       * NexusAnalyzer makes exception with this field only, to keep backward compatibility with old consumers of
59       * nexus-indexer. This field is here for "backward" compat only! The order is important too! FLD_CLASSNAMES must be
60       * registered BEFORE FLD_CLASSNAMES_KW!
61       */
62      public static final IndexerField FLD_CLASSNAMES_KW = new IndexerField( MAVEN.CLASSNAMES, IndexerFieldVersion.V1,
63          "c", "Artifact Classes (tokenized on newlines only)", Store.YES, Index.ANALYZED );
64  
65      public JarFileContentsIndexCreator()
66      {
67          super( ID );
68      }
69  
70      public void populateArtifactInfo( final ArtifactContext artifactContext )
71          throws IOException
72      {
73          ArtifactInfo ai = artifactContext.getArtifactInfo();
74  
75          File artifactFile = artifactContext.getArtifact();
76  
77          if ( artifactFile != null && artifactFile.isFile()
78              && ( artifactFile.getName().endsWith( ".jar" ) || artifactFile.getName().endsWith( ".war" ) ) )
79          {
80              updateArtifactInfo( ai, artifactFile );
81          }
82      }
83  
84      public void updateDocument( final ArtifactInfo ai, final Document doc )
85      {
86          if ( ai.getClassNames() != null )
87          {
88              doc.add( FLD_CLASSNAMES_KW.toField( ai.getClassNames() ) );
89              doc.add( FLD_CLASSNAMES.toField( ai.getClassNames() ) );
90          }
91      }
92  
93      public void updateLegacyDocument( final ArtifactInfo ai, final Document doc )
94      {
95          if ( ai.getClassNames() != null )
96          {
97              String classNames = ai.getClassNames();
98  
99              // downgrade the classNames if needed
100             if ( classNames.length() > 0 && classNames.charAt( 0 ) == '/' )
101             {
102                 // conversion from the new format
103                 String[] lines = classNames.split( "\\n" );
104                 StringBuilder sb = new StringBuilder();
105                 for ( String line : lines )
106                 {
107                     sb.append( line.substring( 1 ) ).append( '\n' );
108                 }
109 
110                 classNames = sb.toString();
111             }
112 
113             doc.add( FLD_CLASSNAMES_KW.toField( classNames ) );
114         }
115     }
116 
117     public boolean updateArtifactInfo( final Document doc, final ArtifactInfo artifactInfo )
118     {
119         String names = doc.get( FLD_CLASSNAMES_KW.getKey() );
120 
121         if ( names != null )
122         {
123             if ( names.length() == 0 || names.charAt( 0 ) == '/' )
124             {
125                 artifactInfo.setClassNames( names );
126             }
127             else
128             {
129                 // conversion from the old format
130                 String[] lines = names.split( "\\n" );
131                 StringBuilder sb = new StringBuilder();
132                 for ( String line : lines )
133                 {
134                     sb.append( '/' ).append( line ).append( '\n' );
135                 }
136                 artifactInfo.setClassNames( sb.toString() );
137             }
138 
139             return true;
140         }
141 
142         return false;
143     }
144 
145     private void updateArtifactInfo( final ArtifactInfo ai, final File f )
146         throws IOException
147     {
148         if ( f.getName().endsWith( ".jar" ) )
149         {
150             updateArtifactInfo( ai, f, null );
151         }
152         else if ( f.getName().endsWith( ".war" ) )
153         {
154             updateArtifactInfo( ai, f, "WEB-INF/classes/" );
155         }
156     }
157 
158     private void updateArtifactInfo( final ArtifactInfo ai, final File f, final String strippedPrefix )
159         throws IOException
160     {
161         ZipHandle handle = null;
162 
163         try
164         {
165             handle = ZipFacade.getZipHandle( f );
166 
167             final List<String> entries = handle.getEntries();
168 
169             final StringBuilder sb = new StringBuilder();
170 
171             for ( String name : entries )
172             {
173                 if ( name.endsWith( ".class" ) )
174                 {
175                     // TODO verify if class is public or protected
176                     // TODO skip all inner classes for now
177 
178                     int i = name.indexOf( "$" );
179 
180                     if ( i == -1 )
181                     {
182                         if ( name.charAt( 0 ) != '/' )
183                         {
184                             sb.append( '/' );
185                         }
186 
187                         if ( StringUtils.isBlank( strippedPrefix ) )
188                         {
189                             // class name without ".class"
190                             sb.append( name.substring( 0, name.length() - 6 ) ).append( '\n' );
191                         }
192                         else if ( name.startsWith( strippedPrefix )
193                             && ( name.length() > ( strippedPrefix.length() + 6 ) ) )
194                         {
195                             // class name without ".class" and stripped prefix
196                             sb.append( name.substring( strippedPrefix.length(), name.length() - 6 ) ).append( '\n' );
197                         }
198                     }
199                 }
200             }
201 
202             final String fieldValue = sb.toString().trim();
203 
204             if ( fieldValue.length() != 0 )
205             {
206                 ai.setClassNames( fieldValue );
207             }
208             else
209             {
210                 ai.setClassNames( null );
211             }
212         }
213         finally
214         {
215             try
216             {
217                 ZipFacade.close( handle );
218             }
219             catch ( Exception e )
220             {
221                 getLogger().error( "Could not close jar file properly.", e );
222             }
223         }
224     }
225 
226     @Override
227     public String toString()
228     {
229         return ID;
230     }
231 
232     @Override
233     public Collection<IndexerField> getIndexerFields()
234     {
235         return Arrays.asList( FLD_CLASSNAMES, FLD_CLASSNAMES_KW );
236     }
237 }