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());
}
}