maven: fix index context duplication, do not force to download the index for remote...
[idea/community.git] / plugins / maven / maven2-server-impl / src / org / jetbrains / idea / maven / server / embedder / Maven2ServerIndexerImpl.java
1 /*
2  * Copyright 2000-2010 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.idea.maven.server.embedder;
17
18 import com.intellij.openapi.progress.ProcessCanceledException;
19 import com.intellij.openapi.util.ShutDownTracker;
20 import com.intellij.openapi.util.text.StringUtil;
21 import gnu.trove.THashSet;
22 import gnu.trove.TIntObjectHashMap;
23 import org.apache.lucene.document.Document;
24 import org.apache.lucene.index.IndexReader;
25 import org.apache.lucene.search.BooleanQuery;
26 import org.apache.lucene.search.Query;
27 import org.apache.lucene.search.TopDocs;
28 import org.apache.maven.archetype.catalog.Archetype;
29 import org.apache.maven.archetype.catalog.ArchetypeCatalog;
30 import org.apache.maven.archetype.source.ArchetypeDataSource;
31 import org.apache.maven.archetype.source.ArchetypeDataSourceException;
32 import org.apache.maven.artifact.manager.WagonManager;
33 import org.apache.maven.wagon.events.TransferEvent;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 import org.jetbrains.idea.maven.model.MavenArchetype;
37 import org.jetbrains.idea.maven.model.MavenArtifactInfo;
38 import org.jetbrains.idea.maven.model.MavenId;
39 import org.jetbrains.idea.maven.server.*;
40 import org.sonatype.nexus.index.*;
41 import org.sonatype.nexus.index.context.IndexUtils;
42 import org.sonatype.nexus.index.context.IndexingContext;
43 import org.sonatype.nexus.index.updater.IndexUpdateRequest;
44 import org.sonatype.nexus.index.updater.IndexUpdater;
45
46 import java.io.File;
47 import java.io.IOException;
48 import java.lang.reflect.InvocationTargetException;
49 import java.lang.reflect.Method;
50 import java.rmi.RemoteException;
51 import java.util.*;
52
53 public class Maven2ServerIndexerImpl extends MavenRemoteObject implements MavenServerIndexer {
54   private Maven2ServerEmbedderImpl myEmbedder;
55   private final NexusIndexer myIndexer;
56   private final IndexUpdater myUpdater;
57   private final ArtifactContextProducer myArtifactContextProducer;
58
59   private final TIntObjectHashMap<IndexingContext> myIndices = new TIntObjectHashMap<IndexingContext>();
60
61   public Maven2ServerIndexerImpl() throws RemoteException {
62     myEmbedder = Maven2ServerEmbedderImpl.create(new MavenServerSettings());
63
64     myIndexer = myEmbedder.getComponent(NexusIndexer.class);
65     myUpdater = myEmbedder.getComponent(IndexUpdater.class);
66     myArtifactContextProducer = myEmbedder.getComponent(ArtifactContextProducer.class);
67
68     ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
69       @Override
70       public void run() {
71         release();
72       }
73     });
74   }
75
76   public int createIndex(@NotNull String indexId,
77                          @NotNull String repositoryId,
78                          @Nullable File file,
79                          @Nullable String url,
80                          @NotNull File indexDir) throws MavenServerIndexerException {
81     try {
82       IndexingContext context = myIndexer.addIndexingContextForced(indexId,
83                                                                    repositoryId,
84                                                                    file,
85                                                                    indexDir,
86                                                                    url,
87                                                                    null, // repo update url
88                                                                    NexusIndexer.FULL_INDEX);
89       int id = System.identityHashCode(context);
90       myIndices.put(id, context);
91       return id;
92     }
93     catch (Exception e) {
94       throw new MavenServerIndexerException(wrapException(e));
95     }
96   }
97
98   public void releaseIndex(int id) throws MavenServerIndexerException {
99     try {
100       myIndexer.removeIndexingContext(getIndex(id), false);
101     }
102     catch (Exception e) {
103       throw new MavenServerIndexerException(wrapException(e));
104     }
105   }
106
107   @NotNull
108   private IndexingContext getIndex(int id) {
109     IndexingContext index = myIndices.get(id);
110     if (index == null) throw new RuntimeException("Index not found for id: " + id);
111     return index;
112   }
113
114   @Override
115   public boolean indexExists(File dir) throws RemoteException {
116     try {
117       return IndexReader.indexExists(dir);
118     }
119     catch (Exception e) {
120       Maven2ServerGlobals.getLogger().warn(e);
121     }
122     return false;
123   }
124
125   private String getRepositoryPathOrUrl(IndexingContext index) {
126     File file = index.getRepository();
127     return file == null ? index.getRepositoryUrl() : file.getPath();
128   }
129
130   private boolean isLocal(IndexingContext index) {
131     return index.getRepository() != null;
132   }
133
134   public int getIndexCount() {
135     return myIndexer.getIndexingContexts().size();
136   }
137
138   public void updateIndex(int id, MavenServerSettings settings, MavenServerProgressIndicator indicator) throws
139                                                                                                         MavenServerIndexerException,
140                                                                                                         MavenServerProcessCanceledException,
141                                                                                                         RemoteException {
142     IndexingContext index = getIndex(id);
143
144     try {
145       if (isLocal(index)) {
146         File repository = index.getRepository();
147
148         if (repository != null && repository.exists()) {
149           indicator.setIndeterminate(true);
150           try {
151             myIndexer.scan(index, new MyScanningListener(indicator), false);
152           }
153           finally {
154             indicator.setIndeterminate(false);
155           }
156         }
157       }
158       else {
159         IndexUpdateRequest request = new IndexUpdateRequest(index);
160         Maven2ServerEmbedderImpl embedder = Maven2ServerEmbedderImpl.create(settings);
161         try {
162           request.setResourceFetcher(new Maven2ServerIndexFetcher(index.getRepositoryId(),
163                                                                   index.getRepositoryUrl(),
164                                                                   embedder.getComponent(WagonManager.class),
165                                                                   new TransferListenerAdapter(indicator) {
166                                                                     @Override
167                                                                     protected void downloadProgress(long downloaded, long total) {
168                                                                       super.downloadProgress(downloaded, total);
169                                                                       try {
170                                                                         myIndicator.setFraction(((double)downloaded) / total);
171                                                                       }
172                                                                       catch (RemoteException e) {
173                                                                         throw new RuntimeRemoteException(e);
174                                                                       }
175                                                                     }
176
177                                                                     @Override
178                                                                     public void transferCompleted(TransferEvent event) {
179                                                                       super.transferCompleted(event);
180                                                                       try {
181                                                                         myIndicator.setText2("Processing indices...");
182                                                                       }
183                                                                       catch (RemoteException e) {
184                                                                         throw new RuntimeRemoteException(e);
185                                                                       }
186                                                                     }
187                                                                   }));
188           myUpdater.fetchAndUpdateIndex(request);
189         }
190         finally {
191           embedder.release();
192         }
193       }
194     }
195     catch (RuntimeRemoteException e) {
196       throw e.getCause();
197     }
198     catch (ProcessCanceledException e) {
199       throw new MavenServerProcessCanceledException();
200     }
201     catch (Exception e) {
202       throw new MavenServerIndexerException(wrapException(e));
203     }
204   }
205
206   public void processArtifacts(int indexId, MavenServerIndicesProcessor processor) throws MavenServerIndexerException {
207     try {
208       final int CHUNK_SIZE = 10000;
209
210       IndexReader r = getIndex(indexId).getIndexReader();
211       int total = r.numDocs();
212
213       List<MavenId> result = new ArrayList<MavenId>(Math.min(CHUNK_SIZE, total));
214       for (int i = 0; i < total; i++) {
215         if (r.isDeleted(i)) continue;
216
217         Document doc = r.document(i);
218         String uinfo = doc.get(SEARCH_TERM_COORDINATES);
219         if (uinfo == null) continue;
220         List<String> parts = StringUtil.split(uinfo, "|");
221         String groupId = parts.get(0);
222         String artifactId = parts.get(1);
223         String version = parts.get(2);
224         if (groupId == null || artifactId == null || version == null) continue;
225
226         result.add(new MavenId(groupId, artifactId, version));
227
228         if (result.size() == CHUNK_SIZE) {
229           processor.processArtifacts(result);
230           result.clear();
231         }
232       }
233
234       if (!result.isEmpty()) {
235         processor.processArtifacts(result);
236       }
237     }
238     catch (Exception e) {
239       throw new MavenServerIndexerException(wrapException(e));
240     }
241   }
242
243   public MavenId addArtifact(int indexId, File artifactFile) throws MavenServerIndexerException {
244     try {
245       IndexingContext index = getIndex(indexId);
246       ArtifactContext artifactContext = myArtifactContextProducer.getArtifactContext(index, artifactFile);
247       if (artifactContext == null) return null;
248
249       addArtifact(myIndexer, index, artifactContext);
250
251       org.sonatype.nexus.index.ArtifactInfo a = artifactContext.getArtifactInfo();
252       return new MavenId(a.groupId, a.artifactId, a.version);
253     }
254     catch (Exception e) {
255       throw new MavenServerIndexerException(wrapException(e));
256     }
257   }
258
259   public static void addArtifact(NexusIndexer indexer, IndexingContext index, ArtifactContext artifactContext)
260     throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
261     indexer.addArtifactToIndex(artifactContext, index);
262     // this hack is necessary to invalidate searcher's and reader's cache (may not be required then lucene or nexus library change
263     Method m = index.getClass().getDeclaredMethod("closeReaders");
264     m.setAccessible(true);
265     m.invoke(index);
266   }
267
268   public Set<MavenArtifactInfo> search(int indexId, Query query, int maxResult) throws MavenServerIndexerException {
269     try {
270       IndexingContext index = getIndex(indexId);
271
272       TopDocs docs = null;
273       try {
274         BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);
275         docs = index.getIndexSearcher().search(query, null, maxResult);
276       }
277       catch (BooleanQuery.TooManyClauses ignore) {
278         // this exception occurs when too wide wildcard is used on too big data.
279       }
280
281       if (docs == null || docs.scoreDocs.length == 0) return Collections.emptySet();
282
283       Set<MavenArtifactInfo> result = new THashSet<MavenArtifactInfo>();
284
285       for (int i = 0; i < docs.scoreDocs.length; i++) {
286         int docIndex = docs.scoreDocs[i].doc;
287         Document doc = index.getIndexReader().document(docIndex);
288         ArtifactInfo a = IndexUtils.constructArtifactInfo(doc, index);
289         if (a == null) continue;
290
291         a.repository = getRepositoryPathOrUrl(index);
292         result.add(Maven2ModelConverter.convertArtifactInfo(a));
293       }
294       return result;
295     }
296     catch (Exception e) {
297       throw new MavenServerIndexerException(wrapException(e));
298     }
299   }
300
301   public Collection<MavenArchetype> getArchetypes() throws RemoteException {
302     Set<MavenArchetype> result = new THashSet<MavenArchetype>();
303     doCollectArchetypes("internal-catalog", result);
304     doCollectArchetypes("nexus", result);
305     return result;
306   }
307
308   private void doCollectArchetypes(String roleHint, Set<MavenArchetype> result) throws RemoteException {
309     try {
310       ArchetypeDataSource source = myEmbedder.getComponent(ArchetypeDataSource.class, roleHint);
311       ArchetypeCatalog catalog = source.getArchetypeCatalog(new Properties());
312
313       for (Archetype each : (Iterable<? extends Archetype>)catalog.getArchetypes()) {
314         result.add(Maven2ModelConverter.convertArchetype(each));
315       }
316     }
317     catch (ArchetypeDataSourceException e) {
318       Maven2ServerGlobals.getLogger().warn(e);
319     }
320   }
321
322   public void release() {
323     try {
324       myEmbedder.release();
325     }
326     catch (Exception e) {
327       throw rethrowException(e);
328     }
329   }
330
331   private static class MyScanningListener implements ArtifactScanningListener {
332     private final MavenServerProgressIndicator p;
333
334     public MyScanningListener(MavenServerProgressIndicator indicator) {
335       p = indicator;
336     }
337
338     public void scanningStarted(IndexingContext ctx) {
339       try {
340         if (p.isCanceled()) throw new ProcessCanceledException();
341       }
342       catch (RemoteException e) {
343         throw new RuntimeRemoteException(e);
344       }
345     }
346
347     public void scanningFinished(IndexingContext ctx, ScanningResult result) {
348       try {
349         if (p.isCanceled()) throw new ProcessCanceledException();
350       }
351       catch (RemoteException e) {
352         throw new RuntimeRemoteException(e);
353       }
354     }
355
356     public void artifactError(ArtifactContext ac, Exception e) {
357     }
358
359     public void artifactDiscovered(ArtifactContext ac) {
360       try {
361         if (p.isCanceled()) throw new ProcessCanceledException();
362         ArtifactInfo info = ac.getArtifactInfo();
363         p.setText2(info.groupId + ":" + info.artifactId + ":" + info.version);
364       }
365       catch (RemoteException e) {
366         throw new RuntimeRemoteException(e);
367       }
368     }
369   }
370 }