blob: 121c015465bf629dec1271e07f38467e7bbb6553 [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.core.KeywordAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.IndexableField;
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;
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(new KeywordAnalyzer());
writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
writer = new IndexWriter(directory, writerConfig);
searcherManager = new SearcherManager(writer, true, false, null);
}
@Override
public HttpCacheEntry getEntry(String key) throws IOException {
searcherManager.maybeRefresh();
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 StringField(KEY_FIELD_NAME, key, Store.NO));
for (IndexableField field : toLuceneFields(entry)) {
document.add(field);
}
return document;
}
private List<IndexableField> toLuceneFields(HttpCacheEntry entry) throws IOException {
List<IndexableField> fields = new ArrayList<>();
fields.add(new StoredField(REQUEST_DATE_FIELD_NAME, Long.toString(entry.getRequestDate().getTime())));
fields.add(new StoredField(RESPONSE_DATE_FIELD_NAME, Long.toString(entry.getResponseDate().getTime())));
StatusLine statusLine = entry.getStatusLine();
fields.add(new StoredField(STATUS_CODE_FIELD_NAME, Integer.toString(statusLine.getStatusCode())));
fields.add(new StoredField(REASON_PHRASE_FIELD_NAME, statusLine.getReasonPhrase()));
ProtocolVersion protocolVersion = statusLine.getProtocolVersion();
fields.add(new StoredField(PROTOCOL_FIELD_NAME, protocolVersion.getProtocol()));
fields.add(new StoredField(MAJOR_PROTOCAL_VERSION_FIELD_NAME, Integer.toString(protocolVersion.getMajor())));
fields.add(new StoredField(MINOR_PROTOCOL_VERSION_FIELD_NAME, Integer.toString(protocolVersion.getMinor())));
Header[] headers = entry.getAllHeaders();
for (int index = 0; index < headers.length; index++) {
Header header = headers[index];
fields.add(new StoredField(HEADER_FIELD_NAMES + '/' + index + '/' + header.getName(), header.getValue()));
}
Resource body = entry.getResource();
if (body != null) {
fields.add(new StoredField(BODY_FIELD_NAME, IOUtils.toByteArray(body.getInputStream())));
}
if (entry.hasVariants()) {
for (Entry<String, String> variant : entry.getVariantMap().entrySet()) {
fields.add(new StoredField(VARIANT_FIELD_NAMES + '/' + variant.getKey(), variant.getValue()));
}
}
return fields;
}
private HttpCacheEntry fromLuceneFields(List<IndexableField> 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 (IndexableField 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.binaryValue().bytes);
} 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(IndexableField 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(IndexableField field) throws IOException {
try {
String stringValue = field.stringValue();
return Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
throw new IOException(field.name(), e);
}
}
}