blob: 86a8f22c9d6f4dd1ae2f1754b363153bcbb4ef77 [file] [log] [blame]
/**
* 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);
}
}
}