| /** |
| * 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); |
| } |
| } |
| } |