| /** |
| * Copyright (c) 2015 Codetrails GmbH. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| */ |
| package org.eclipse.epp.internal.logging.aeri.ide.server; |
| |
| import static com.google.common.base.Preconditions.*; |
| import static org.apache.commons.lang3.StringUtils.abbreviateMiddle; |
| import static org.eclipse.epp.internal.logging.aeri.ide.l10n.LogMessages.*; |
| import static org.eclipse.epp.logging.aeri.core.SystemControl.isDebug; |
| import static org.eclipse.epp.logging.aeri.core.util.Logs.log; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.annotation.PostConstruct; |
| import javax.annotation.PreDestroy; |
| |
| import org.apache.lucene.analysis.core.KeywordAnalyzer; |
| import org.apache.lucene.document.Document; |
| import org.apache.lucene.document.Field; |
| import org.apache.lucene.document.Field.Store; |
| import org.apache.lucene.document.StoredField; |
| import org.apache.lucene.document.StringField; |
| import org.apache.lucene.index.CorruptIndexException; |
| import org.apache.lucene.index.IndexWriter; |
| import org.apache.lucene.index.IndexWriterConfig; |
| import org.apache.lucene.index.IndexWriterConfig.OpenMode; |
| import org.apache.lucene.index.Term; |
| import org.apache.lucene.search.IndexSearcher; |
| import org.apache.lucene.search.SearcherManager; |
| import org.apache.lucene.search.TermQuery; |
| import org.apache.lucene.search.TopDocs; |
| import org.apache.lucene.store.Directory; |
| import org.apache.lucene.store.FSDirectory; |
| import org.apache.lucene.store.LockObtainFailedException; |
| import org.apache.lucene.util.IOUtils; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.epp.internal.logging.aeri.ide.l10n.Messages; |
| import org.eclipse.epp.logging.aeri.core.SystemControl; |
| import org.eclipse.epp.logging.aeri.core.util.Statuses; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Predicate; |
| import com.google.common.util.concurrent.AbstractIdleService; |
| |
| /** |
| * A history of all problems this client sent before. |
| */ |
| public class LocalReportsHistory extends AbstractIdleService { |
| |
| private static final String F_VERSION = "version"; //$NON-NLS-1$ |
| private static final String VERSION = "0.6"; //$NON-NLS-1$ |
| private static final String F_IDENTITY = "identity"; //$NON-NLS-1$ |
| private File stateLocation; |
| private Directory index; |
| private IndexWriter writer; |
| private SearcherManager manager; |
| |
| public LocalReportsHistory(File stateLocation) { |
| this.stateLocation = stateLocation; |
| } |
| |
| @PostConstruct |
| private void e4Start() { |
| startAsync(); |
| } |
| |
| @Override |
| protected void startUp() throws Exception { |
| // field assignment for testing |
| index = createIndexDirectory(); |
| createWriter(); |
| createSearchManager(); |
| } |
| |
| public boolean seen(IStatus status) { |
| checkNotNull(status); |
| checkState(isRunning()); |
| String fingerprint = computeHistoryFingerprint(status); |
| boolean seen = seen(fingerprint); |
| return seen; |
| } |
| |
| private boolean seen(String fingerprint) { |
| TermQuery query = new TermQuery(new Term(F_IDENTITY, fingerprint)); |
| IndexSearcher searcher = null; |
| try { |
| searcher = manager.acquire(); |
| TopDocs results = searcher.search(query, 1); |
| boolean foundIdenticalReport = results.totalHits.value > 0; |
| return foundIdenticalReport; |
| } catch (Exception e) { |
| log(WARN_HISTORY_NOT_AVAILABLE, e); |
| return false; |
| } finally { |
| try { |
| if (searcher != null) { |
| manager.release(searcher); |
| searcher = null; |
| } |
| } catch (IOException e) { |
| log(WARN_HISTORY_NOT_AVAILABLE, e); |
| } |
| } |
| } |
| |
| private static String computeHistoryFingerprint(IStatus status) { |
| IStatus relevant = Statuses.findRelevantStatus(status); |
| Throwable exception = checkNotNull(relevant.getException()); |
| StackTraceElement[] normalizes = Statuses.normalize(exception); |
| StackTraceElement[] truncated = Statuses.truncate(normalizes); |
| String fingerprint = Statuses.newFingerprint(truncated, false); |
| return fingerprint; |
| } |
| |
| public void remember(IStatus status) { |
| checkNotNull(status); |
| checkState(isRunning()); |
| doRemember(status); |
| reopen(); |
| } |
| |
| private void doRemember(IStatus status) { |
| String fingerprint = computeHistoryFingerprint(status); |
| if (seen(fingerprint)) { |
| return; |
| } |
| Document doc = new Document(); |
| Field field = new StringField(F_IDENTITY, fingerprint, Store.NO); |
| doc.add(field); |
| try { |
| writer.addDocument(doc); |
| writer.commit(); |
| } catch (Exception e) { |
| log(WARN_HISTORY_NOT_AVAILABLE, e); |
| } |
| if (isDebug()) { |
| log(DEBUG_HISTORY_REMEMBER_STATUS, abbreviateMiddle(status.getMessage(), Messages.LOG_HISTORY_ABBREVIATION, 60), fingerprint, |
| abbreviateMiddle(stateLocation.toString(), Messages.LOG_HISTORY_ABBREVIATION, 60)); |
| } |
| } |
| |
| private void reopen() { |
| try { |
| manager.maybeRefresh(); |
| } catch (IOException e) { |
| log(WARN_HISTORY_NOT_AVAILABLE, e); |
| } |
| } |
| |
| @VisibleForTesting |
| protected Directory createIndexDirectory() throws IOException { |
| stateLocation.mkdirs(); |
| index = FSDirectory.open(stateLocation.toPath()); |
| return index; |
| } |
| |
| private void createWriter() throws CorruptIndexException, LockObtainFailedException, IOException { |
| IndexWriterConfig conf = new IndexWriterConfig(new KeywordAnalyzer()); |
| conf.setOpenMode(OpenMode.CREATE_OR_APPEND); |
| writer = new IndexWriter(index, conf); |
| // to build an initial index if empty: |
| IndexWriter.DocStats stats = writer.getDocStats(); |
| if (stats.numDocs == 0) { |
| buildInitialIndex(); |
| } |
| } |
| |
| private void buildInitialIndex() throws CorruptIndexException, IOException { |
| Document meta = new Document(); |
| meta.add(new StoredField(F_VERSION, VERSION)); |
| writer.addDocument(meta); |
| writer.commit(); |
| } |
| |
| private void createSearchManager() throws IOException { |
| manager = new SearcherManager(index, null); |
| } |
| |
| @PreDestroy |
| private void e4Stop() { |
| try { |
| stopAsync().awaitTerminated(2, TimeUnit.SECONDS); |
| } catch (Exception e) { |
| log(WARN_HISTORY_STOP_FAILED, e); |
| } |
| } |
| |
| @Override |
| protected void shutDown() throws Exception { |
| IOUtils.close(writer, index); |
| manager.close(); |
| } |
| |
| public static class LocalHistorySeenFilter implements Predicate<IStatus> { |
| |
| private LocalReportsHistory history; |
| |
| public LocalHistorySeenFilter(LocalReportsHistory history) { |
| this.history = history; |
| } |
| |
| @Override |
| public boolean apply(IStatus input) { |
| if (SystemControl.isDebug()) { |
| return true; |
| } |
| if (!history.isRunning()) { |
| return false; |
| } |
| return !history.seen(input); |
| } |
| } |
| } |