blob: cfa53fe39a253e99ce27e29623d848cc032b2ae9 [file] [log] [blame]
/**
* Copyright (c) 2016 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.rest;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.HttpCacheUpdateException;
import org.apache.http.client.cache.Resource;
import org.apache.http.impl.client.cache.HeapResource;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.apache.lucene.analysis.KeywordAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.Fieldable;
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.Query;
import org.apache.lucene.search.ScoreDoc;
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.util.Version;
class LuceneHttpCacheStorage implements HttpCacheStorage, Closeable {
private static final String KEY_FIELD_NAME = "key";
private static final String REQUEST_DATE_FIELD_NAME = "requestDate";
private static final String RESPONSE_DATE_FIELD_NAME = "responseDate";
private static final String STATUS_CODE_FIELD_NAME = "statusLine/statusCode";
private static final String REASON_PHRASE_FIELD_NAME = "statusLine/reasonPhrase";
private static final String PROTOCOL_FIELD_NAME = "statusLine/protocolVersion/protocol";
private static final String MINOR_PROTOCOL_VERSION_FIELD_NAME = "statusLine/protocolVersion/minor";
private static final String MAJOR_PROTOCAL_VERSION_FIELD_NAME = "statusLine/protocolVersion/major";
private static final String HEADER_FIELD_NAMES = "header";
private static final int HEADER_FIELD_NAMES_LENGTH = HEADER_FIELD_NAMES.length();
private static final String BODY_FIELD_NAME = "body";
private static final String VARIANT_FIELD_NAMES = "variant";
private static final int VARIANT_FIELD_NAMES_LENGTH = VARIANT_FIELD_NAMES.length();
private final IndexWriter writer;
private final SearcherManager searcherManager;
LuceneHttpCacheStorage(Directory directory) throws IOException {
IndexWriterConfig writerConfig = new IndexWriterConfig(Version.LUCENE_35, new KeywordAnalyzer());
writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
writer = new IndexWriter(directory, writerConfig);
searcherManager = new SearcherManager(writer, true, null, null);
}
@Override
public HttpCacheEntry getEntry(String key) throws IOException {
searcherManager.maybeReopen();
IndexSearcher searcher = searcherManager.acquire();
try {
Query query = new TermQuery(new Term(KEY_FIELD_NAME, key));
TopDocs topDocs = searcher.search(query, 1);
if (topDocs.totalHits > 1) {
throw new IOException("Corrupt index (cache key is not unique)");
}
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
if (scoreDocs.length > 0) {
ScoreDoc scoreDoc = scoreDocs[0];
Document document = searcher.doc(scoreDoc.doc);
return fromLuceneFields(document.getFields());
} else {
return null;
}
} finally {
searcherManager.release(searcher);
}
}
@Override
public void putEntry(String key, HttpCacheEntry entry) throws IOException {
Document document = toLuceneDocument(key, entry);
synchronized (this) {
writer.updateDocument(new Term(KEY_FIELD_NAME, key), document);
}
}
@Override
public void updateEntry(String key, HttpCacheUpdateCallback callback) throws IOException, HttpCacheUpdateException {
synchronized (this) {
HttpCacheEntry existingEntry = getEntry(key);
HttpCacheEntry newEntry = callback.update(existingEntry);
Document newDocument = toLuceneDocument(key, newEntry);
writer.updateDocument(new Term(KEY_FIELD_NAME, key), newDocument);
}
}
@Override
public void removeEntry(String key) throws IOException {
Query query = new TermQuery(new Term(KEY_FIELD_NAME, key));
synchronized (this) {
writer.deleteDocuments(query);
}
}
@Override
public void close() throws IOException {
searcherManager.close();
writer.close();
}
private Document toLuceneDocument(String key, HttpCacheEntry entry) throws IOException {
Document document = new Document();
document.add(new Field(KEY_FIELD_NAME, key, Store.NO, Index.NOT_ANALYZED_NO_NORMS));
for (Fieldable field : toLuceneFields(entry)) {
document.add(field);
}
return document;
}
private List<Fieldable> toLuceneFields(HttpCacheEntry entry) throws IOException {
List<Fieldable> fields = new ArrayList<>();
fields.add(new Field(REQUEST_DATE_FIELD_NAME, Long.toString(entry.getRequestDate().getTime()), Store.YES, Index.NO));
fields.add(new Field(RESPONSE_DATE_FIELD_NAME, Long.toString(entry.getResponseDate().getTime()), Store.YES, Index.NO));
StatusLine statusLine = entry.getStatusLine();
fields.add(new Field(STATUS_CODE_FIELD_NAME, Integer.toString(statusLine.getStatusCode()), Store.YES, Index.NO));
fields.add(new Field(REASON_PHRASE_FIELD_NAME, statusLine.getReasonPhrase(), Store.YES, Index.NO));
ProtocolVersion protocolVersion = statusLine.getProtocolVersion();
fields.add(new Field(PROTOCOL_FIELD_NAME, protocolVersion.getProtocol(), Store.YES, Index.NO));
fields.add(new Field(MAJOR_PROTOCAL_VERSION_FIELD_NAME, Integer.toString(protocolVersion.getMajor()), Store.YES, Index.NO));
fields.add(new Field(MINOR_PROTOCOL_VERSION_FIELD_NAME, Integer.toString(protocolVersion.getMinor()), Store.YES, Index.NO));
Header[] headers = entry.getAllHeaders();
for (int index = 0; index < headers.length; index++) {
Header header = headers[index];
fields.add(new Field(HEADER_FIELD_NAMES + '/' + index + '/' + header.getName(), header.getValue(), Store.YES, Index.NO));
}
Resource body = entry.getResource();
if (body != null) {
fields.add(new Field(BODY_FIELD_NAME, IOUtils.toByteArray(body.getInputStream())));
}
if (entry.hasVariants()) {
for (Entry<String, String> variant : entry.getVariantMap().entrySet()) {
fields.add(new Field(VARIANT_FIELD_NAMES + '/' + variant.getKey(), variant.getValue(), Store.YES, Index.NO));
}
}
return fields;
}
private HttpCacheEntry fromLuceneFields(List<Fieldable> fields) throws IOException {
Date requestDate = null;
Date responseDate = null;
int statusCode = Integer.MIN_VALUE;
String reasonPhrase = null;
String protocol = null;
int majorProtocolVersion = Integer.MIN_VALUE;
int minorProtocolVersion = Integer.MIN_VALUE;
List<Header> responseHeaders = new ArrayList<>();
Resource body = null;
Map<String, String> variantMap = new HashMap<>();
for (Fieldable field : fields) {
String fieldName = field.name();
if (REQUEST_DATE_FIELD_NAME.equals(fieldName)) {
requestDate = parseDateField(field);
} else if (RESPONSE_DATE_FIELD_NAME.equals(fieldName)) {
responseDate = parseDateField(field);
} else if (STATUS_CODE_FIELD_NAME.equals(fieldName)) {
statusCode = parseIntField(field);
} else if (REASON_PHRASE_FIELD_NAME.equals(fieldName)) {
reasonPhrase = field.stringValue();
} else if (PROTOCOL_FIELD_NAME.equals(fieldName)) {
protocol = field.stringValue();
} else if (MAJOR_PROTOCAL_VERSION_FIELD_NAME.equals(fieldName)) {
majorProtocolVersion = parseIntField(field);
} else if (MINOR_PROTOCOL_VERSION_FIELD_NAME.equals(fieldName)) {
minorProtocolVersion = parseIntField(field);
} else if (fieldName.startsWith(HEADER_FIELD_NAMES)) {
try {
int secondSlash = fieldName.indexOf('/', HEADER_FIELD_NAMES_LENGTH + 1);
String indexString = fieldName.substring(HEADER_FIELD_NAMES_LENGTH + 1, secondSlash);
int index = Integer.parseInt(indexString);
String headerName = fieldName.substring(secondSlash + 1);
String headerValue = field.stringValue();
Header header = new BasicHeader(headerName, headerValue);
responseHeaders.add(index, header);
} catch (NumberFormatException e) {
throw new IOException(e);
}
} else if (BODY_FIELD_NAME.equals(fieldName)) {
body = new HeapResource(field.getBinaryValue());
} else if (fieldName.startsWith(VARIANT_FIELD_NAMES)) {
String variantKey = fieldName.substring(VARIANT_FIELD_NAMES_LENGTH + 1);
String cacheKey = field.stringValue();
variantMap.put(variantKey, cacheKey);
} else {
throw new IOException("Corrupt index (unknown field: " + fieldName + ")");
}
}
try {
ProtocolVersion protocolVersion = new ProtocolVersion(protocol, majorProtocolVersion, minorProtocolVersion);
StatusLine statusLine = new BasicStatusLine(protocolVersion, statusCode, reasonPhrase);
return new HttpCacheEntry(requestDate, responseDate, statusLine, responseHeaders.toArray(new Header[responseHeaders.size()]),
body, variantMap);
} catch (IllegalArgumentException e) {
throw new IOException("Corrupt index", e);
}
}
private Date parseDateField(Fieldable field) throws IOException {
try {
String stringValue = field.stringValue();
long longValue = Long.parseLong(stringValue);
return new Date(longValue);
} catch (NumberFormatException e) {
throw new IOException(field.name(), e);
}
}
private int parseIntField(Fieldable field) throws IOException {
try {
String stringValue = field.stringValue();
return Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
throw new IOException(field.name(), e);
}
}
}