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.creator;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.Enumeration;
29  import java.util.zip.ZipEntry;
30  import java.util.zip.ZipFile;
31  
32  import org.apache.lucene.document.Document;
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.codehaus.plexus.util.StringUtils;
39  
40  /**
41   * An index creator used to index Java class names from a Maven artifact (JAR, ZIP or WAR for now).
42   * Will open up the file and collect all the class names from it.
43   */
44  @Singleton
45  @Named(JarFileContentsIndexCreator.ID)
46  public class JarFileContentsIndexCreator extends AbstractIndexCreator implements LegacyDocumentUpdater {
47      public static final String ID = "jarContent";
48  
49      public static final IndexerField FLD_CLASSNAMES = new IndexerField(
50              MAVEN.CLASSNAMES,
51              IndexerFieldVersion.V3,
52              "classnames",
53              "Artifact Classes (tokenized)",
54              IndexerField.ANALYZED_NOT_STORED);
55  
56      /**
57       * NexusAnalyzer makes exception with this field only, to keep backward compatibility with old consumers of
58       * nexus-indexer. This field is here for "backward" compat only! The order is important too! FLD_CLASSNAMES must be
59       * registered BEFORE FLD_CLASSNAMES_KW!
60       */
61      public static final IndexerField FLD_CLASSNAMES_KW = new IndexerField(
62              MAVEN.CLASSNAMES,
63              IndexerFieldVersion.V1,
64              "c",
65              "Artifact Classes (tokenized on newlines only)",
66              IndexerField.ANALYZED_STORED);
67  
68      public JarFileContentsIndexCreator() {
69          super(ID);
70      }
71  
72      public void populateArtifactInfo(final ArtifactContext artifactContext) throws IOException {
73          ArtifactInfo ai = artifactContext.getArtifactInfo();
74  
75          File artifactFile = artifactContext.getArtifact();
76  
77          if (artifactFile != null
78                  && artifactFile.isFile()
79                  && (artifactFile.getName().endsWith(".jar")
80                          || artifactFile.getName().endsWith(".war")
81                          || artifactFile.getName().endsWith(".zip"))) {
82              updateArtifactInfo(ai, artifactFile);
83          }
84      }
85  
86      public void updateDocument(final ArtifactInfo ai, final Document doc) {
87          if (ai.getClassNames() != null) {
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          if (ai.getClassNames() != null) {
95              String classNames = ai.getClassNames();
96  
97              // downgrade the classNames if needed
98              if (classNames.length() > 0 && classNames.charAt(0) == '/') {
99                  // conversion from the new format
100                 String[] lines = classNames.split("\\n");
101                 StringBuilder sb = new StringBuilder();
102                 for (String line : lines) {
103                     sb.append(line.substring(1)).append('\n');
104                 }
105 
106                 classNames = sb.toString();
107             }
108 
109             doc.add(FLD_CLASSNAMES_KW.toField(classNames));
110         }
111     }
112 
113     public boolean updateArtifactInfo(final Document doc, final ArtifactInfo artifactInfo) {
114         String names = doc.get(FLD_CLASSNAMES_KW.getKey());
115 
116         if (names != null) {
117             if (names.length() == 0 || names.charAt(0) == '/') {
118                 artifactInfo.setClassNames(names);
119             } else {
120                 // conversion from the old format
121                 String[] lines = names.split("\\n");
122                 StringBuilder sb = new StringBuilder();
123                 for (String line : lines) {
124                     sb.append('/').append(line).append('\n');
125                 }
126                 artifactInfo.setClassNames(sb.toString());
127             }
128 
129             return true;
130         }
131 
132         return false;
133     }
134 
135     private void updateArtifactInfo(final ArtifactInfo ai, final File f) throws IOException {
136         if (f.getName().endsWith(".jar") || f.getName().endsWith(".zip")) {
137             updateArtifactInfo(ai, f, null);
138         } else if (f.getName().endsWith(".war")) {
139             updateArtifactInfo(ai, f, "WEB-INF/classes/");
140         }
141     }
142 
143     private void updateArtifactInfo(final ArtifactInfo ai, final File f, final String strippedPrefix)
144             throws IOException {
145 
146         try (ZipFile zipFile = new ZipFile(f)) {
147             final Enumeration<? extends ZipEntry> entries = zipFile.entries();
148 
149             final StringBuilder sb = new StringBuilder();
150 
151             while (entries.hasMoreElements()) {
152                 String name = entries.nextElement().getName();
153                 if (name.endsWith(".class")) {
154                     // TODO verify if class is public or protected
155                     // TODO skip all inner classes for now
156 
157                     int i = name.indexOf("$");
158 
159                     if (i == -1) {
160                         if (name.charAt(0) != '/') {
161                             sb.append('/');
162                         }
163 
164                         if (StringUtils.isBlank(strippedPrefix)) {
165                             // class name without ".class"
166                             sb.append(name, 0, name.length() - 6).append('\n');
167                         } else if (name.startsWith(strippedPrefix) && (name.length() > (strippedPrefix.length() + 6))) {
168                             // class name without ".class" and stripped prefix
169                             sb.append(name, strippedPrefix.length(), name.length() - 6)
170                                     .append('\n');
171                         }
172                     }
173                 }
174             }
175 
176             final String fieldValue = sb.toString().trim();
177 
178             if (fieldValue.length() != 0) {
179                 ai.setClassNames(fieldValue);
180             } else {
181                 ai.setClassNames(null);
182             }
183         }
184     }
185 
186     @Override
187     public String toString() {
188         return ID;
189     }
190 
191     @Override
192     public Collection<IndexerField> getIndexerFields() {
193         return Arrays.asList(FLD_CLASSNAMES, FLD_CLASSNAMES_KW);
194     }
195 }