/*******************************************************************************
* Copyright (c) 2015 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.server.tests.metastore;
import static org.junit.Assert.assertEquals;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.orion.internal.server.core.metastore.SimpleMetaStore;
import org.eclipse.orion.server.core.OrionConfiguration;
import org.eclipse.orion.server.core.metastore.IMetaStore;
import org.eclipse.orion.server.core.metastore.UserInfo;
import org.eclipse.orion.server.core.metastore.WorkspaceInfo;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tests to ensure that a workspace in a SimpleMetaStore can be successfully read and written concurrently from separate
* threads. See Bug 462608
*
* @author Anthony Hunter
*/
public class SimpleMetaStoreWorkspacePropertyConcurrencyTests {
protected static int THREAD_COUNT = 4;
protected static int PROPERTY_COUNT = 4;
protected JSONObject createProperty() throws JSONException {
JSONObject property = new JSONObject();
String propertyId = createRandomName();
Date date = new Date();
Format formatter = new SimpleDateFormat("EEEE MMMM d yyyy hh:mm:ss.SSS aaa");//$NON-NLS-1$
property.put("timestamp", date.getTime());
property.put("property", propertyId);
property.put("description", "Created property " + propertyId + " at " + formatter.format(date));
return property;
}
protected Thread createPropertyThread(int number) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Logger logger = LoggerFactory.getLogger("org.eclipse.orion.server.config"); //$NON-NLS-1$
IMetaStore metaStore = OrionConfiguration.getMetaStore();
String currentThreadName = Thread.currentThread().getName();
for (int i = 0; i < PROPERTY_COUNT; i++) {
// read the user
UserInfo userInfo = metaStore.readUser("anthony");
if (userInfo == null) {
logger.debug("Meta File Error, could not read user anthony to add a property.");
return;
}
// read the workspace
String workspaceId = userInfo.getWorkspaceIds().get(0);
WorkspaceInfo workspaceInfo = metaStore.readWorkspace(workspaceId);
if (workspaceInfo == null) {
logger.debug("Meta File Error, could not read workspace to add a property.");
return;
}
// set a new property
JSONObject propertyValue = createProperty();
String propertyKey = "property/" + currentThreadName + "/" + propertyValue.getString("property");
workspaceInfo.setProperty(propertyKey, propertyValue.toString());
metaStore.updateWorkspace(workspaceInfo);
// read the workspace again
workspaceInfo = metaStore.readWorkspace(workspaceId);
if (workspaceInfo == null) {
logger.debug("Meta File Error, could not read workspace to verify the property.");
} else if (workspaceInfo.getProperty(propertyKey) == null) {
logger.debug("Meta File Error, JSONObject is missing " + propertyKey + " that was just added.");
}
}
} catch (JSONException e) {
Logger logger = LoggerFactory.getLogger("org.eclipse.orion.server.config"); //$NON-NLS-1$
logger.debug("Meta File Error, cannot read JSON file from disk, reason: " + e.getLocalizedMessage()); //$NON-NLS-1$
} catch (CoreException e) {
Logger logger = LoggerFactory.getLogger("org.eclipse.orion.server.config"); //$NON-NLS-1$
logger.debug("Meta File Error, cannot read JSON file from disk, reason: " + e.getLocalizedMessage()); //$NON-NLS-1$
}
}
};
Thread thread = new Thread(runnable, "SimpleMetaStoreConcurrencyTestsThread-" + number);
thread.start();
return thread;
}
/**
* Create a random string of lower case letters between a length of eight and twelve characters to use as a unique
* name.
*
* @return a string of lower case letters.
*/
protected String createRandomName() {
String characters = "abcdefghijklmnopqrstuvxwxyz";
Random random = new Random();
int length = 8 + random.nextInt(4);
String name = new String();
for (int i = 0; i < length; i++) {
int next = random.nextInt(characters.length());
name = name + characters.charAt(next);
}
return name;
}
protected Thread deletePropertyThread(int number) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Logger logger = LoggerFactory.getLogger("org.eclipse.orion.server.config"); //$NON-NLS-1$
IMetaStore metaStore = OrionConfiguration.getMetaStore();
String currentThreadName = Thread.currentThread().getName();
List<String> propertyKeys = new ArrayList<String>();
// read the user
UserInfo userInfo = metaStore.readUser("anthony");
if (userInfo == null) {
logger.debug("Meta File Error, could not read user anthony to delete a property.");
return;
}
// read the workspace
String workspaceId = userInfo.getWorkspaceIds().get(0);
WorkspaceInfo workspaceInfo = metaStore.readWorkspace(workspaceId);
if (workspaceInfo == null) {
logger.debug("Meta File Error, could not read workspace to delete a property.");
return;
}
// get the list of properties to delete
for (String key : workspaceInfo.getProperties().keySet()) {
if (key.startsWith("property/" + currentThreadName)) {
propertyKeys.add(key);
}
}
// now delete the properties
for (String key : propertyKeys) {
// read the workspace
workspaceInfo = metaStore.readWorkspace(workspaceId);
if (workspaceInfo == null) {
logger.debug("Meta File Error, could not read workspace to delete a property.");
return;
}
// set a new property
workspaceInfo.setProperty(key, null);
metaStore.updateWorkspace(workspaceInfo);
// read the workspace again
workspaceInfo = metaStore.readWorkspace(workspaceId);
if (workspaceInfo == null) {
logger.debug("Meta File Error, could not read workspace to verify the property.");
} else if (workspaceInfo.getProperty(key) != null) {
logger.debug("Meta File Error, JSONObject contains " + key + " that was just deleted.");
}
}
} catch (CoreException e) {
Logger logger = LoggerFactory.getLogger("org.eclipse.orion.server.config"); //$NON-NLS-1$
logger.debug("Meta File Error, cannot read JSON file from disk, reason: " + e.getLocalizedMessage()); //$NON-NLS-1$
}
}
};
Thread thread = new Thread(runnable, "SimpleMetaStoreConcurrencyTestsThread-" + number);
thread.start();
return thread;
}
/**
* Tests creating properties in the metadata store in multiple concurrently running threads.
*
* @throws CoreException
*/
@Test
public void testSimpleMetaStoreCreateWorkspacePropertyConcurrency() throws CoreException {
// create the MetaStore
IMetaStore metaStore = OrionConfiguration.getMetaStore();
// create the user
UserInfo userInfo = new UserInfo();
userInfo.setUserName("anthony");
userInfo.setFullName("Anthony Hunter");
metaStore.createUser(userInfo);
// create the workspace
String workspaceName = SimpleMetaStore.DEFAULT_WORKSPACE_NAME;
WorkspaceInfo workspaceInfo = new WorkspaceInfo();
workspaceInfo.setFullName(workspaceName);
workspaceInfo.setUserId(userInfo.getUniqueId());
metaStore.createWorkspace(workspaceInfo);
// add properties to the user in multiple threads
Thread threads[] = new Thread[THREAD_COUNT];
for (int i = 0; i < threads.length; i++) {
threads[i] = createPropertyThread(i);
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
// just continue
}
}
// read the workspace and make sure the properties are there
userInfo = metaStore.readUser("anthony");
String workspaceId = userInfo.getWorkspaceIds().get(0);
workspaceInfo = metaStore.readWorkspace(workspaceId);
int count = 0;
Map<String, String> properties = workspaceInfo.getProperties();
for (String key : properties.keySet()) {
if (key.startsWith("property/")) {
count++;
}
}
// delete the user
metaStore.deleteUser(userInfo.getUniqueId());
assertEquals("Incomplete number of properties added for the user", THREAD_COUNT * PROPERTY_COUNT, count);
}
/**
* Tests deleting properties from the metadata store in multiple concurrently running threads.
*
* @throws CoreException
*/
@Test
public void testSimpleMetaStoreDeleteWorkspacePropertyConcurrency() throws CoreException {
// create the MetaStore
IMetaStore metaStore = OrionConfiguration.getMetaStore();
// create the user
UserInfo userInfo = new UserInfo();
userInfo.setUserName("anthony");
userInfo.setFullName("Anthony Hunter");
metaStore.createUser(userInfo);
// create the workspace
String workspaceName = SimpleMetaStore.DEFAULT_WORKSPACE_NAME;
WorkspaceInfo workspaceInfo = new WorkspaceInfo();
workspaceInfo.setFullName(workspaceName);
workspaceInfo.setUserId(userInfo.getUniqueId());
metaStore.createWorkspace(workspaceInfo);
// add properties to the user in multiple threads
Thread threads[] = new Thread[THREAD_COUNT];
for (int i = 0; i < threads.length; i++) {
threads[i] = createPropertyThread(i);
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
// just continue
}
}
// read the workspace and make sure the properties are there
userInfo = metaStore.readUser("anthony");
String workspaceId = userInfo.getWorkspaceIds().get(0);
workspaceInfo = metaStore.readWorkspace(workspaceId);
int count = 0;
Map<String, String> properties = workspaceInfo.getProperties();
for (String key : properties.keySet()) {
if (key.startsWith("property/")) {
count++;
}
}
assertEquals("Incomplete number of properties added for the user", THREAD_COUNT * PROPERTY_COUNT, count);
// delete properties in multiple threads
threads = new Thread[THREAD_COUNT];
for (int i = 0; i < threads.length; i++) {
threads[i] = deletePropertyThread(i);
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
// just continue
}
}
// read the workspace and make sure the properties are there
userInfo = metaStore.readUser("anthony");
workspaceId = userInfo.getWorkspaceIds().get(0);
workspaceInfo = metaStore.readWorkspace(workspaceId);
count = 0;
properties = workspaceInfo.getProperties();
for (String key : properties.keySet()) {
if (key.startsWith("property/")) {
count++;
}
}
// delete the user
metaStore.deleteUser(userInfo.getUniqueId());
assertEquals("Incomplete number of properties deleted for the user", 0, count);
}
}