package ecologylab.bigsemantics.documentcache; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import org.apache.http.HttpResponse; import org.apache.http.util.EntityUtils; import org.apache.http.entity.StringEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpDelete; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.client.ClientProtocolException; import ecologylab.serialization.SimplTypesScope; import ecologylab.serialization.formatenums.StringFormat; import ecologylab.serialization.SIMPLTranslationException; import ecologylab.bigsemantics.collecting.SemanticsSessionScope; import ecologylab.bigsemantics.metadata.builtins.Document; import ecologylab.bigsemantics.metadata.MetadataDeserializationHookStrategy; /** * A Document cache using CouchDB as the backend. * * @author colton * */ public class CouchDBDocumentCache implements DocumentCache<String, Document> { private DefaultHttpClient client; private SemanticsSessionScope sss = null; private SimplTypesScope metadataTypesScope; private static String host = "localhost"; private static String port = "5984"; private static String database = "exampledb"; public static CouchDBDocumentCache simplCouchDBCacheFactory(SemanticsSessionScope sss) { CouchDBDocumentCache cache = new CouchDBDocumentCache(); cache.setSemanticsSessionScope(sss); return cache; } private CouchDBDocumentCache() { client = new DefaultHttpClient(); } /** * Sets the SemanticsSessionScope * * @param sss */ public void setSemanticsSessionScope(SemanticsSessionScope sss) { this.sss = sss; metadataTypesScope = sss.getMetadataTypesScope(); } /** * Returns the SemanticsSessionScope * * @return the SemanticsSessionScope */ public SemanticsSessionScope getSemanticsSessionScope() { return sss; } @Override public boolean containsKey(String key) { boolean isContained = false; HttpResponse response = null; HttpHead headRequest = new HttpHead("http://" + host + ":" + port + "/" + database + "/" + key); try { response = client.execute(headRequest); isContained = (response.getStatusLine().getStatusCode() == 200); } catch (IOException e) { e.printStackTrace(); } finally { EntityUtils.consumeQuietly(response.getEntity()); } return isContained; } @Override public Document get(String key) { String temp; String readerOutput = null; Object obj; Document doc = null; HttpResponse response = null; HttpGet getRequest = new HttpGet("http://" + host + ":" + port + "/" + database + "/" + key); try { response = client.execute(getRequest); if (response.getStatusLine().getStatusCode() != 200) { throw new RuntimeException("ERROR: HTTP status code " + response.getStatusLine().getStatusCode()); } BufferedReader reader = new BufferedReader(new InputStreamReader( (response.getEntity().getContent()))); while ((temp = reader.readLine()) != null) readerOutput += temp; // Stripping off CouchDB ID and revision number readerOutput = "{\"" + readerOutput.split("\"", 10)[9]; MetadataDeserializationHookStrategy deserializationStrategy = new MetadataDeserializationHookStrategy(sss); obj = metadataTypesScope.deserialize(readerOutput, deserializationStrategy, StringFormat.JSON); if (obj instanceof Document) { doc = (Document) obj; } } catch (SIMPLTranslationException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { EntityUtils.consumeQuietly(response.getEntity()); } return doc; } @Override public Document get(String key, String revision) { String temp; String readerOutput = null; Object obj; Document doc = null; HttpResponse response = null; HttpGet getRequest = new HttpGet("http://" + host + ":" + port + "/" + database + "/" + key + "?rev=" + revision); try { response = client.execute(getRequest); if (response.getStatusLine().getStatusCode() != 200) { throw new RuntimeException("ERROR: HTTP status code " + response.getStatusLine().getStatusCode()); } BufferedReader reader = new BufferedReader(new InputStreamReader( (response.getEntity().getContent()))); while ((temp = reader.readLine()) != null) readerOutput += temp; // Stripping off CouchDB ID and revision number readerOutput = "{\"" + readerOutput.split("\"", 10)[9]; MetadataDeserializationHookStrategy deserializationStrategy = new MetadataDeserializationHookStrategy(sss); obj = metadataTypesScope.deserialize(readerOutput, deserializationStrategy, StringFormat.JSON); if (obj instanceof Document) { doc = (Document) obj; } } catch (SIMPLTranslationException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { EntityUtils.consumeQuietly(response.getEntity()); } return doc; } @Override public void put(String key, Document obj) { HttpResponse response = null; HttpPut putRequest = new HttpPut("http://" + host + ":" + port + "/" + database + "/" + key); try { putRequest.setEntity(new StringEntity(SimplTypesScope.serialize( obj, StringFormat.JSON).toString())); response = client.execute(putRequest); if (response.getStatusLine().getStatusCode() != 201) { throw new RuntimeException("ERROR: HTTP status code " + response.getStatusLine().getStatusCode()); } } catch (SIMPLTranslationException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { EntityUtils.consumeQuietly(response.getEntity()); } } @Override public Document putIfAbsent(String key, Document obj) { Document result = null; if (!containsKey(key)) { put(key, obj); result = obj; } return result; } /** * TODO This does not work - need to be able to compare oldObj to object in database Replaces the * object which a key is mapped to, only if the key is mapped to the object also indicated in the * call */ @Override public boolean replace(String key, Document oldObj, Document newObj) { boolean wasReplaced = false; //if (oldObj = object in database @ key) //{ // replace(key, newObj); // wasReplaced = true; //} return wasReplaced; } @Override public Document replace(String key, Document newObj) { boolean wasReplaced = false; HttpResponse response = null; HttpPut putRequest = new HttpPut("http://" + host + ":" + port + "/" + database + "/" + key); if (containsKey(key)) { try { putRequest.setEntity(new StringEntity("{\"_id\":\"" + key + "\",\"_rev\":\"" + getRevisionNumber(key) + "\"," + SimplTypesScope.serialize(newObj, StringFormat.JSON) .toString().substring(1))); response = client.execute(putRequest); if (response.getStatusLine().getStatusCode() != 201) { throw new RuntimeException("ERROR: HTTP status code " + response.getStatusLine().getStatusCode()); } } catch (SIMPLTranslationException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { EntityUtils.consumeQuietly(response.getEntity()); } wasReplaced = true; } return wasReplaced ? get(key) : null; } @Override public void remove(String key) { if (containsKey(key)) { HttpResponse response = null; HttpDelete deleteRequest = new HttpDelete("http://" + host + ":" + port + "/" + database + "/" + key + "?rev=" + getRevisionNumber(key)); try { response = client.execute(deleteRequest); if (response.getStatusLine().getStatusCode() != 200) { throw new RuntimeException("ERROR: HTTP status code " + response.getStatusLine().getStatusCode()); } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { EntityUtils.consumeQuietly(response.getEntity()); } } } @Override public boolean remove(String key, Document oldObj) { boolean wasRemoved = false; if (containsKey(key) && get(key) == oldObj) { remove(key); wasRemoved = true; } return wasRemoved; } /** * Helper method to support instances where CouchDB requires the most recent revision number to * manipulate documents * * @param key * @return the most recent revision number associated with the document linked to the key */ private String getRevisionNumber(String key) { String temp; String readerOutput = null; HttpResponse response = null; HttpGet getRequest = new HttpGet("http://" + host + ":" + port + "/" + database + "/" + key); try { response = client.execute(getRequest); if (response.getStatusLine().getStatusCode() != 200) { throw new RuntimeException("ERROR: HTTP status code " + response.getStatusLine().getStatusCode()); } BufferedReader reader = new BufferedReader(new InputStreamReader( (response.getEntity().getContent()))); while ((temp = reader.readLine()) != null) readerOutput += temp; } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { EntityUtils.consumeQuietly(response.getEntity()); } // Split output on '"', select revision number return readerOutput.split("\"", 9)[7]; } }