blob: 1bf0e940f0e255b87f6c5a4ba6a520a728963fd3 [file] [log] [blame]
/*
* Copyright (c) 2015 Eike Stepper (Berlin, Germany) and others.
* 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
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.userstorage.tests;
import static org.hamcrest.MatcherAssert.assertThat;
import org.eclipse.userstorage.IBlob;
import org.eclipse.userstorage.IStorage;
import org.eclipse.userstorage.IStorageService;
import org.eclipse.userstorage.internal.Activator;
import org.eclipse.userstorage.internal.Session;
import org.eclipse.userstorage.internal.util.IOUtil;
import org.eclipse.userstorage.internal.util.StringUtil;
import org.eclipse.userstorage.spi.StorageFactory;
import org.eclipse.userstorage.tests.util.FixedCredentialsProvider;
import org.eclipse.userstorage.tests.util.USSServer;
import org.eclipse.userstorage.tests.util.USSServer.NOOPLogger;
import org.eclipse.userstorage.tests.util.USSServer.User;
import org.eclipse.userstorage.util.BadApplicationTokenException;
import org.eclipse.userstorage.util.BadKeyException;
import org.eclipse.userstorage.util.ConflictException;
import org.eclipse.userstorage.util.FileStorageCache;
import org.eclipse.userstorage.util.ProtocolException;
import org.eclipse.jetty.util.log.Log;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
/**
* @author Eike Stepper
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public final class StorageTests extends AbstractTest
{
private static final boolean REMOTE = Boolean.getBoolean(StorageTests.class.getName() + ".remote");
private static final File SERVER = new File(System.getProperty("java.io.tmpdir"), "uss-tests/server");
private static final File CACHE = new File(System.getProperty("java.io.tmpdir"), "uss-tests/cache");
private static final String APPLICATION_TOKEN = "cNhDr0INs8T109P8h6E1r_GvU3I";
private static final String KEY = "test_blob";
private USSServer server;
private User user;
private IStorageService.Dynamic service;
private StorageFactory factory;
private TestCache cache;
@BeforeClass
public static void disableLogging()
{
Log.setLog(new NOOPLogger());
}
@Override
public void setUp() throws Exception
{
super.setUp();
Activator.start();
if (REMOTE)
{
factory = StorageFactory.DEFAULT;
}
else
{
IOUtil.deleteFiles(SERVER);
server = new USSServer(8080, SERVER);
user = server.addUser(FixedCredentialsProvider.DEFAULT_CREDENTIALS);
server.getApplicationTokens().add(APPLICATION_TOKEN);
int port = server.start();
final String serviceURI = "http://localhost:" + port;
service = IStorageService.Registry.INSTANCE.addService("Test Service", StringUtil.newURI(serviceURI));
factory = new StorageFactory()
{
@Override
protected String getPreferredServiceURI(String applicationToken)
{
return serviceURI;
}
};
}
IOUtil.deleteFiles(CACHE);
cache = new TestCache(CACHE);
}
@Override
public void tearDown() throws Exception
{
factory = null;
cache = null;
if (hasLocalServer())
{
service.remove();
service = null;
server.stop();
server = null;
user = null;
}
Activator.stop();
super.tearDown();
}
@Test
public void testApplicationToken() throws Exception
{
try
{
factory.create(null);
fail("BadApplicationTokenException expected");
}
catch (BadApplicationTokenException expected)
{
// SUCCESS
}
try
{
factory.create("aaaa"); // Too short.
fail("BadApplicationTokenException expected");
}
catch (BadApplicationTokenException expected)
{
// SUCCESS
}
try
{
factory.create("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // Too long.
fail("BadApplicationTokenException expected");
}
catch (BadApplicationTokenException expected)
{
// SUCCESS
}
try
{
factory.create("1aaaaaaaaa"); // Too long.
fail("BadApplicationTokenException expected");
}
catch (BadApplicationTokenException expected)
{
// SUCCESS
}
factory.create("aaaaa"); // Just short enough.
factory.create("aaaaaaaaaaaaaaaaaaaaaaaaaaa"); // Just long enough.
}
@Test
public void testKey() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
try
{
storage.getBlob(null);
fail("BadKeyException expected");
}
catch (BadKeyException expected)
{
// SUCCESS
}
try
{
storage.getBlob("aaaa"); // Too short.
fail("BadKeyException expected");
}
catch (BadKeyException expected)
{
// SUCCESS
}
try
{
storage.getBlob("aaaaaaaaaaaaaaaaaaaaaaaaaaa"); // Too long.
fail("BadKeyException expected");
}
catch (BadKeyException expected)
{
// SUCCESS
}
try
{
storage.getBlob("1aaaaaaaaa"); // Too long.
fail("BadKeyException expected");
}
catch (BadKeyException expected)
{
// SUCCESS
}
storage.getBlob("aaaaa"); // Just short enough.
storage.getBlob("aaaaaaaaaaaaaaaaaaaaaaaaa"); // Just long enough.
}
@Test
public void testUserAgent() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
System.setProperty(Session.USER_AGENT_PROPERTY, "malicious/client");
try
{
IBlob blob = storage.getBlob("any_blob");
blob.getContents();
fail("ProtocolException expected");
}
catch (ProtocolException expected)
{
assertThat(expected.getStatusCode(), is(403)); // Forbidden.
}
finally
{
System.clearProperty(Session.USER_AGENT_PROPERTY);
}
}
@Test
public void testUpdate() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob(makeKey());
String value = "A short UTF-8 string value";
assertThat(blob.setContentsUTF(value), is(true));
BlobInfo blobInfo = readServer(blob);
assertThat(blobInfo.contents, is(value));
assertThat(blobInfo.eTag, is(blob.getETag()));
}
@Test
public void testUpdateWithCache() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN, cache);
IBlob blob = storage.getBlob(makeKey());
String value = "A short UTF-8 string value";
assertThat(blob.setContentsUTF(value), is(true));
assertThat(readCache(blob.getKey(), null), is(value));
assertThat(readCache(blob.getKey(), ".properties"), containsString("etag=" + blob.getETag()));
}
@Test
public void testUpdateMulti() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob(makeKey());
blob.setContentsUTF("Text 1");
blob.setContentsUTF("Text 2");
blob.setContentsUTF("Text 3");
}
@Test
public void testRetrieveNotFound() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob("aaaaaaaaaa");
assertThat(blob.getContents(), isNull());
}
@Test
public void testRetrieve() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob(makeKey());
String value = "A short UTF-8 string value";
blob.setContentsUTF(value);
assertThat(blob.getContentsUTF(), is(value));
BlobInfo blobInfo = readServer(blob);
assertThat(blobInfo.contents, is(value));
assertThat(blobInfo.eTag, is(blob.getETag()));
}
@Test
public void testRetrieveWithCache() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN, cache);
IBlob blob = storage.getBlob(makeKey());
String value = "A short UTF-8 string value";
blob.setContentsUTF(value);
assertThat(blob.getContentsUTF(), is(value));
assertThat(readCache(blob.getKey(), null), is(value));
assertThat(readCache(blob.getKey(), ".properties"), containsString("etag=" + blob.getETag()));
}
@Test
public void testRetrieveMulti() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob(makeKey());
blob.setContentsUTF("A short UTF-8 string value");
blob.getContents();
blob.getContents();
blob.getContents();
blob.getContents();
blob.getContents();
}
@Test
public void testConflict() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob(makeKey());
String value1 = "A short UTF-8 string value";
blob.setContentsUTF(value1);
String eTag1 = blob.getETag();
// Prepare the conflict.
String value2 = "Different content";
String eTag2 = writeServer(blob, value2);
String value3 = "And now a conflicting string";
try
{
blob.setContentsUTF(value3);
fail("ConflictException expected");
}
catch (ConflictException expected)
{
assertThat(expected.getStatusCode(), is(409)); // Conflict.
assertThat(expected.getETag(), isNull());
}
assertThat(blob.getETag(), is(eTag1));
BlobInfo blobInfo = readServer(blob);
assertThat(blobInfo.contents, is(value2));
assertThat(blobInfo.eTag, is(eTag2));
}
@Test
public void testConflictWithCache() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN, cache);
IBlob blob = storage.getBlob(makeKey());
String value1 = "A short UTF-8 string value";
blob.setContentsUTF(value1);
String eTag1 = blob.getETag();
// Prepare the conflict.
writeServer(blob, "Different content");
String value3 = "And now a conflicting string";
try
{
blob.setContentsUTF(value3);
fail("ConflictException expected");
}
catch (ConflictException expected)
{
assertThat(expected.getStatusCode(), is(409)); // Conflict.
assertThat(expected.getETag(), isNull());
}
// It's okay for the cache to have the new value. The old ETag (see below) will cause cache refresh...
assertThat(readCache(blob.getKey(), null), is(value3));
// Cache and blob ETags must still be in old state.
assertThat(readCache(blob.getKey(), ".properties"), containsString("etag=" + eTag1));
assertThat(blob.getETag(), is(eTag1));
}
@Test
public void testConflictResolution1() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob(makeKey());
String value1 = "A short UTF-8 string value";
blob.setContentsUTF(value1);
// Prepare the conflict.
String value2 = "Different content";
String eTag2 = writeServer(blob, value2);
assertThat(blob.getContentsUTF(), is(value2));
assertThat(blob.getETag(), is(eTag2));
String value3 = "And now a non-conflicting string";
blob.setContentsUTF(value3);
BlobInfo blobInfo = readServer(blob);
assertThat(blobInfo.contents, is(value3));
assertThat(blobInfo.eTag, is(blob.getETag()));
}
@Test
public void testConflictResolution1WithCache() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN, cache);
IBlob blob = storage.getBlob(makeKey());
String value1 = "A short UTF-8 string value";
blob.setContentsUTF(value1);
// Prepare the conflict.
String value2 = "Different content";
String eTag2 = writeServer(blob, value2);
assertThat(blob.getContentsUTF(), is(value2));
assertThat(blob.getETag(), is(eTag2));
String value3 = "And now a non-conflicting string";
blob.setContentsUTF(value3);
assertThat(readCache(blob.getKey(), null), is(value3));
assertThat(readCache(blob.getKey(), ".properties"), containsString("etag=" + blob.getETag()));
}
@Test
public void testConflictResolution2() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob(makeKey());
String value1 = "A short UTF-8 string value";
blob.setContentsUTF(value1);
// Prepare the conflict.
String value2 = "Different content";
String eTag2 = writeServer(blob, value2);
String value3 = "And now a conflicting string";
try
{
blob.setContentsUTF(value3);
fail("ConflictException expected");
}
catch (ConflictException expected)
{
blob.setETag(eTag2);
}
blob.setContentsUTF(value3);
BlobInfo blobInfo = readServer(blob);
assertThat(blobInfo.contents, is(value3));
assertThat(blobInfo.eTag, is(blob.getETag()));
}
@Test
public void testConflictResolution2WithCache() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN, cache);
IBlob blob = storage.getBlob(makeKey());
String value1 = "A short UTF-8 string value";
blob.setContentsUTF(value1);
// Prepare the conflict.
String value2 = "Different content";
String eTag2 = writeServer(blob, value2);
String value3 = "And now a conflicting string";
try
{
blob.setContentsUTF(value3);
fail("ConflictException expected");
}
catch (ConflictException expected)
{
blob.setETag(eTag2); // Delete cache.
try
{
readCache(blob.getKey(), null);
fail("FileNotFoundException expected");
}
catch (FileNotFoundException expected2)
{
// SUCCESS
}
try
{
readCache(blob.getKey(), ".properties");
fail("FileNotFoundException expected");
}
catch (FileNotFoundException expected2)
{
// SUCCESS
}
}
blob.setContentsUTF(value3);
assertThat(readCache(blob.getKey(), null), is(value3));
assertThat(readCache(blob.getKey(), ".properties"), containsString("etag=" + blob.getETag()));
}
@Test
public void testReauthenticate() throws Exception
{
IStorage storage = factory.create(APPLICATION_TOKEN);
IBlob blob = storage.getBlob(makeKey());
String value = "A short UTF-8 string value";
blob.setContentsUTF(value);
if (hasLocalServer())
{
assertThat(server.getSessions().size(), is(1));
server.getSessions().clear();
}
assertThat(blob.getContentsUTF(), is(value));
BlobInfo blobInfo = readServer(blob);
assertThat(blobInfo.contents, is(value));
assertThat(blobInfo.eTag, is(blob.getETag()));
if (hasLocalServer())
{
assertThat(server.getSessions().size(), is(1));
}
}
private String readCache(String key, String extension) throws IOException
{
try
{
return IOUtil.readUTF(cache.getFile(APPLICATION_TOKEN, key, extension));
}
catch (RuntimeException ex)
{
Throwable cause = ex.getCause();
if (cause instanceof IOException)
{
throw (IOException)cause;
}
throw ex;
}
}
private BlobInfo readServer(IBlob blob) throws IOException
{
BlobInfo result = new BlobInfo();
String applicationToken = blob.getStorage().getApplicationToken();
String key = blob.getKey();
if (hasLocalServer())
{
try
{
File blobFile = server.getUserFile(user, applicationToken, key, null);
File etagFile = server.getUserFile(user, applicationToken, key, ".etag");
if (!blobFile.isFile() || !etagFile.isFile())
{
return null;
}
result.contents = IOUtil.readUTF(blobFile);
result.eTag = IOUtil.readUTF(etagFile);
}
catch (RuntimeException ex)
{
Throwable cause = ex.getCause();
if (cause instanceof IOException)
{
throw (IOException)cause;
}
throw ex;
}
}
else
{
IStorage tmpStorage = factory.create(applicationToken);
IBlob tmpBlob = tmpStorage.getBlob(key);
result.contents = tmpBlob.getContentsUTF();
if (result.contents == null)
{
return null;
}
result.eTag = tmpBlob.getETag();
}
return result;
}
private String writeServer(IBlob blob, String value) throws IOException
{
String applicationToken = blob.getStorage().getApplicationToken();
String key = blob.getKey();
if (hasLocalServer())
{
String eTag = UUID.randomUUID().toString();
IOUtil.writeUTF(server.getUserFile(user, applicationToken, key, ".etag"), eTag);
IOUtil.writeUTF(server.getUserFile(user, applicationToken, key, null), value);
return eTag;
}
IStorage tmpStorage = factory.create(applicationToken);
IBlob tmpBlob = tmpStorage.getBlob(key);
tmpBlob.setETag(blob.getETag());
tmpBlob.setContentsUTF(value);
return tmpBlob.getETag();
}
private String makeKey()
{
if (hasLocalServer())
{
return KEY;
}
StringBuilder builder = new StringBuilder("T" + UUID.randomUUID().toString());
for (int i = 0; i < builder.length(); i++)
{
if (i == 25)
{
builder.setLength(25);
break;
}
char c = builder.charAt(i);
if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_'))
{
builder.replace(i, i + 1, "");
--i;
}
}
return builder.toString();
}
private boolean hasLocalServer()
{
return server != null;
}
/**
* @author Eike Stepper
*/
private static final class BlobInfo
{
public String eTag;
public String contents;
}
/**
* @author Eike Stepper
*/
private static final class TestCache extends FileStorageCache
{
public TestCache(File folder)
{
super(folder);
}
@Override
public File getFile(String applicationToken, String key, String extension)
{
return super.getFile(applicationToken, key, extension);
}
}
}