package org.infinispan.server.test.client.rest; import static org.infinispan.server.test.client.rest.RESTHelper.KEY_A; import static org.infinispan.server.test.client.rest.RESTHelper.KEY_B; import static org.infinispan.server.test.client.rest.RESTHelper.KEY_C; import static org.infinispan.server.test.client.rest.RESTHelper.addDay; import static org.infinispan.server.test.util.ITestUtils.sleepForSecs; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.URI; import java.net.URLEncoder; import java.util.Arrays; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.util.EntityUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * Tests for the REST client. Subclasses must implement the addRestServer, which has to setup the RESTHelper * by calling RESTHelper.addServer method. * * @author <a href="mailto:jvilkola@redhat.com">Jozef Vilkolak</a> * @author <a href="mailto:mlinhard@redhat.com">Michal Linhard</a> */ public abstract class AbstractRESTClientIT { protected abstract void addRestServer(); protected RESTHelper rest; protected static final String REST_NAMED_CACHE = "restNamedCache"; public static class TestSerializable implements Serializable { private String content; public TestSerializable(String content) { super(); this.content = content; } public String getContent() { return content; } } @Before public void setUp() throws Exception { rest = new RESTHelper(); addRestServer(); cleanUpEntries(); rest.head(rest.fullPathKey(KEY_A), HttpStatus.SC_NOT_FOUND); rest.head(rest.fullPathKey(KEY_B), HttpStatus.SC_NOT_FOUND); rest.head(rest.fullPathKey(KEY_C), HttpStatus.SC_NOT_FOUND); rest.head(rest.fullPathKey(REST_NAMED_CACHE, KEY_A), HttpStatus.SC_NOT_FOUND); } private void cleanUpEntries() throws Exception { rest.delete(rest.fullPathKey(KEY_A)); rest.delete(rest.fullPathKey(KEY_B)); rest.delete(rest.fullPathKey(KEY_C)); rest.delete(rest.fullPathKey(REST_NAMED_CACHE, KEY_A)); } @After public void tearDown() throws Exception { cleanUpEntries(); rest.clearServers(); } @Test public void testBasicOperation() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); String initialXML = "<hey>ho</hey>"; HttpResponse insert = rest.put(fullPathKey, initialXML, "application/octet-stream"); assertEquals(0, insert.getEntity().getContentLength()); HttpResponse get = rest.get(fullPathKey, initialXML); assertEquals("application/octet-stream", get.getHeaders("Content-Type")[0].getValue()); rest.delete(fullPathKey); rest.get(fullPathKey, HttpStatus.SC_NOT_FOUND); rest.put(fullPathKey, initialXML, "application/octet-stream"); rest.get(fullPathKey, initialXML); rest.delete(rest.fullPathKey(null)); rest.get(fullPathKey, HttpStatus.SC_NOT_FOUND); ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bout); oo.writeObject(new TestSerializable("CONTENT")); oo.flush(); byte[] byteData = bout.toByteArray(); rest.put(fullPathKey, byteData, "application/octet-stream"); HttpResponse resp = rest.getWithoutClose(fullPathKey); ObjectInputStream oin = new ObjectInputStream(resp.getEntity().getContent()); TestSerializable ts = (TestSerializable) oin.readObject(); EntityUtils.consume(resp.getEntity()); assertEquals("CONTENT", ts.getContent()); } @Test public void testEmptyGet() throws Exception { rest.get(rest.fullPathKey("nodata"), HttpStatus.SC_NOT_FOUND); } @Test public void testGet() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.post(fullPathKey, "data", "application/text"); HttpResponse resp = rest.get(fullPathKey, "data"); assertNotNull(resp.getHeaders("ETag")[0].getValue()); assertNotNull(resp.getHeaders("Last-Modified")[0].getValue()); assertEquals("application/text", resp.getHeaders("Content-Type")[0].getValue()); } @Test public void testGetNamedCache() throws Exception { URI fullPathKey = rest.fullPathKey(REST_NAMED_CACHE, KEY_A); rest.post(fullPathKey, "data", "application/text"); HttpResponse resp = rest.get(fullPathKey, "data"); assertNotNull(resp.getHeaders("ETag")[0].getValue()); assertNotNull(resp.getHeaders("Last-Modified")[0].getValue()); assertEquals("application/text", resp.getHeaders("Content-Type")[0].getValue()); } @Test public void testHead() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.post(fullPathKey, "data", "application/text"); HttpResponse resp = null; try { resp = rest.headWithoutClose(fullPathKey); assertNotNull(resp.getHeaders("ETag")[0].getValue()); assertNotNull(resp.getHeaders("Last-Modified")[0].getValue()); assertEquals("application/text", resp.getHeaders("Content-Type")[0].getValue()); assertNull(resp.getEntity()); } finally { EntityUtils.consume(resp.getEntity()); } } @Test public void testPostDuplicate() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.post(fullPathKey, "data", "application/text"); // second post, returns 409 rest.post(fullPathKey, "data", "application/text", HttpStatus.SC_CONFLICT); // Should be all ok as its a put rest.put(fullPathKey, "data", "application/text"); } @Test public void testPutDataWithTimeToLive() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.post(fullPathKey, "data", "application/text", HttpStatus.SC_OK, // headers "Content-Type", "application/text", "timeToLiveSeconds", "2"); rest.get(fullPathKey, "data"); sleepForSecs(2.1); // should be evicted rest.head(fullPathKey, HttpStatus.SC_NOT_FOUND); } @Test public void testPutDataWithMaxIdleTime() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.post(fullPathKey, "data", "application/text", HttpStatus.SC_OK, // headers "Content-Type", "application/text", "maxIdleTimeSeconds", "2"); rest.get(fullPathKey, "data"); // data is not idle for next 3 seconds for (int i = 1; i < 3; i++) { sleepForSecs(1); rest.head(fullPathKey); } // idle for 2 seconds sleepForSecs(2.1); // should be evicted rest.head(fullPathKey, HttpStatus.SC_NOT_FOUND); } @Test public void testPutDataTTLMaxIdleCombo1() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.post(fullPathKey, "data", "application/text", HttpStatus.SC_OK, // headers "Content-Type", "application/text", "timeToLiveSeconds", "10", "maxIdleTimeSeconds", "2"); rest.get(fullPathKey, "data"); // data is not idle for next 3 seconds for (int i = 1; i < 3; i++) { sleepForSecs(1); rest.head(fullPathKey); } // idle for 2 seconds sleepForSecs(2.1); // should be evicted rest.head(fullPathKey, HttpStatus.SC_NOT_FOUND); } @Test public void testPutDataTTLMaxIdleCombo2() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.post(fullPathKey, "data", "application/text", HttpStatus.SC_OK, // headers "Content-Type", "application/text", "timeToLiveSeconds", "2", "maxIdleTimeSeconds", "10"); rest.get(fullPathKey, "data"); sleepForSecs(2.1); // should be evicted rest.head(fullPathKey, HttpStatus.SC_NOT_FOUND); } @Test public void testRemoveEntry() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.post(fullPathKey, "data", "application/text"); rest.head(fullPathKey); rest.delete(fullPathKey); rest.head(fullPathKey, HttpStatus.SC_NOT_FOUND); } @Test public void testWipeCacheBucket() throws Exception { rest.post(rest.fullPathKey(KEY_A), "data", "application/text"); rest.post(rest.fullPathKey(KEY_B), "data", "application/text"); rest.head(rest.fullPathKey(KEY_A)); rest.head(rest.fullPathKey(KEY_B)); rest.delete(rest.fullPathKey(null)); rest.head(rest.fullPathKey(KEY_A), HttpStatus.SC_NOT_FOUND); rest.head(rest.fullPathKey(KEY_B), HttpStatus.SC_NOT_FOUND); } @Test public void testPutUnknownClass() throws Exception { URI fullPathKey = rest.fullPathKey("x"); ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bout); oo.writeObject(new TestSerializable("CONTENT")); oo.flush(); byte[] byteData = bout.toByteArray(); rest.put(fullPathKey, byteData, "application/x-java-serialized-object"); HttpResponse resp = rest.get(fullPathKey, null, HttpStatus.SC_OK, false, "Accept", "application/x-java-serialized-object"); ObjectInputStream oin = new ObjectInputStream(resp.getEntity().getContent()); TestSerializable ts = (TestSerializable) oin.readObject(); EntityUtils.consume(resp.getEntity()); assertEquals("CONTENT", ts.getContent()); } @Test public void testPutKnownClass() throws Exception { URI fullPathKey = rest.fullPathKey("y"); ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bout); Integer i1 = 42; oo.writeObject(i1); oo.flush(); byte[] byteData = bout.toByteArray(); rest.put(fullPathKey, byteData, "application/x-java-serialized-object"); HttpResponse resp = rest.get(fullPathKey, null, HttpStatus.SC_OK, false, "Accept", "application/x-java-serialized-object"); ObjectInputStream oin = new ObjectInputStream(resp.getEntity().getContent()); Integer i2 = (Integer) oin.readObject(); EntityUtils.consume(resp.getEntity()); assertEquals(i1, i2); } @Test public void testETagChanges() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.put(fullPathKey, "data1", "application/text"); String eTagFirst = rest.get(fullPathKey).getHeaders("ETag")[0].getValue(); // second get should get the same ETag assertEquals(eTagFirst, rest.get(fullPathKey).getHeaders("ETag")[0].getValue()); // do second PUT rest.put(fullPathKey, "data2", "application/text"); // get ETag again assertFalse(eTagFirst.equals(rest.get(fullPathKey).getHeaders("ETag")[0].getValue())); } @Test public void testXJavaSerializedObjectPutAndDelete() throws Exception { //show that "application/text" works for delete URI fullPathKey1 = rest.fullPathKey("j"); rest.put(fullPathKey1, "data1", "application/text"); rest.head(fullPathKey1, HttpStatus.SC_OK); rest.delete(fullPathKey1); rest.head(fullPathKey1, HttpStatus.SC_NOT_FOUND); URI fullPathKey2 = rest.fullPathKey("k"); ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bout); Integer i1 = 42; oo.writeObject(i1); oo.flush(); byte[] byteData = bout.toByteArray(); rest.put(fullPathKey2, byteData, "application/x-java-serialized-object"); rest.head(fullPathKey2, HttpStatus.SC_OK); rest.delete(fullPathKey2); rest.head(fullPathKey2, HttpStatus.SC_NOT_FOUND); } @Test public void testIfModifiedSince() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.put(fullPathKey, "data", "application/text"); HttpResponse resp = rest.get(fullPathKey); String dateLast = resp.getHeaders("Last-Modified")[0].getValue(); String dateMinus = addDay(dateLast, -1); String datePlus = addDay(dateLast, 1); rest.get(fullPathKey, "data", HttpStatus.SC_OK, true, // resource has been modified since "If-Modified-Since", dateMinus); rest.get(fullPathKey, null, HttpStatus.SC_NOT_MODIFIED, true, // exact same date as stored one "If-Modified-Since", dateLast); rest.get(fullPathKey, null, HttpStatus.SC_NOT_MODIFIED, true, // resource hasn't been modified since "If-Modified-Since", datePlus); } @Test public void testIfUnmodifiedSince() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.put(fullPathKey, "data", "application/text"); HttpResponse resp = rest.get(fullPathKey); String dateLast = resp.getHeaders("Last-Modified")[0].getValue(); String dateMinus = addDay(dateLast, -1); String datePlus = addDay(dateLast, 1); rest.get(fullPathKey, "data", HttpStatus.SC_OK, true, "If-Unmodified-Since", dateLast); rest.get(fullPathKey, "data", HttpStatus.SC_OK, true, "If-Unmodified-Since", datePlus); rest.get(fullPathKey, null, HttpStatus.SC_PRECONDITION_FAILED, true, "If-Unmodified-Since", dateMinus); } @Test public void testIfNoneMatch() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.put(fullPathKey, "data", "application/text"); HttpResponse resp = rest.get(fullPathKey); String eTag = resp.getHeaders("ETag")[0].getValue(); rest.get(fullPathKey, null, HttpStatus.SC_NOT_MODIFIED, true, "If-None-Match", eTag); rest.get(fullPathKey, "data", HttpStatus.SC_OK, true, "If-None-Match", eTag + "garbage"); } @Test public void testIfMatch() throws Exception { URI fullPathKey = rest.fullPathKey(KEY_A); rest.put(fullPathKey, "data", "application/text"); HttpResponse resp = rest.get(fullPathKey); String eTag = resp.getHeaders("ETag")[0].getValue(); // test GET with If-Match behaviour rest.get(fullPathKey, "data", HttpStatus.SC_OK, true, "If-Match", eTag); rest.get(fullPathKey, null, HttpStatus.SC_PRECONDITION_FAILED, true, "If-Match", eTag + "garbage"); // test HEAD with If-Match behaviour rest.head(fullPathKey, HttpStatus.SC_OK, new String[][]{{"If-Match", eTag}}); rest.head(fullPathKey, HttpStatus.SC_PRECONDITION_FAILED, new String[][]{{"If-Match", eTag + "garbage"}}); } @Test public void testNonExistentCache() throws Exception { rest.head(rest.fullPathKey("nonexistentcache", "nodata"), HttpStatus.SC_NOT_FOUND); rest.get(rest.fullPathKey("nonexistentcache", "nodata"), HttpStatus.SC_NOT_FOUND); rest.put(rest.fullPathKey("nonexistentcache", "nodata"), "data", "application/text", HttpStatus.SC_NOT_FOUND); rest.delete(rest.fullPathKey("nonexistentcache", "nodata"), HttpStatus.SC_NOT_FOUND); } @Test public void testByteArrayStorage() throws Exception { final String KEY_Z = "z"; byte[] data = "data".getBytes("UTF-8"); ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bout); oo.writeObject(data); oo.flush(); byte[] serializedData = bout.toByteArray(); rest.put(rest.fullPathKey(0, KEY_Z), serializedData, "application/x-java-serialized-object"); HttpResponse resp = rest.get(rest.fullPathKey(0, KEY_Z), null, HttpStatus.SC_OK, false, "Accept", "application/x-java-serialized-object"); ObjectInputStream oin = new ObjectInputStream(resp.getEntity().getContent()); byte[] dataBack = (byte[]) oin.readObject(); EntityUtils.consume(resp.getEntity()); assertTrue(Arrays.equals(data, dataBack)); } @Test public void testStoreBigObject() throws Exception { int SIZE = 3000000; byte[] bytes = new byte[SIZE]; for (int i = 0; i < SIZE; i++) { bytes[i] = (byte) (i % 10); } rest.put(rest.fullPathKey("object"), bytes, "application/octet-stream"); HttpResponse resp = rest.getWithoutClose(rest.fullPathKey("object")); InputStream responseStream = resp.getEntity().getContent(); byte[] response = new byte[SIZE]; byte data; int j = 0; while ((data = (byte) responseStream.read()) != -1) { response[j] = data; j++; } boolean correct = true; for (int i = 0; i < SIZE; i++) { if (bytes[i] != response[i]) { correct = false; } } EntityUtils.consume(resp.getEntity()); assertTrue(correct); } //the following property is passed to the server at startup so that this test passes: //-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true @Test public void testKeyIncludingSlashURLEncoded() throws Exception { String encodedSlashKey = URLEncoder.encode("x/y", "UTF-8"); rest.post(rest.fullPathKey(encodedSlashKey), "data", "application/text"); HttpResponse get = rest.get(rest.fullPathKey(encodedSlashKey), "data"); assertNotNull(get.getHeaders("ETag")[0].getValue()); assertNotNull(get.getHeaders("Last-Modified")[0].getValue()); assertEquals("application/text", get.getHeaders("Content-Type")[0].getValue()); } }