first import of gridworks project broker with two implementations, a local one that uses BerkeleyDB and a cloud one that uses Google App Engine

NOTE: code has not yet been tested but it's functionally complete and compiles

git-svn-id: http://google-refine.googlecode.com/svn/trunk@1009 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
Stefano Mazzocchi 2010-06-20 08:46:09 +00:00
parent 0663b898e8
commit 619f914b80
31 changed files with 1581 additions and 0 deletions

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/butterfly-trunk-r25.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/butterfly-trunk-r25-sources.jar"/>
<classpathentry kind="lib" path="/gridworks-server/lib/servlet-api-2.5.jar" sourcepath="/gridworks-server/lib-src/servlet-api-2.5-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/json-20100208.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/json-20100208-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/httpclient-4.0.1.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/httpclient-4.0.1-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/httpcore-4.0.1.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/httpcore-4.0.1-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/slf4j-api-1.5.6.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/slf4j-api-1.5.6-sources.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/gridworks-broker"/>
<classpathentry kind="output" path="module/MOD-INF/classes"/>
</classpath>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_DISABLED_BUILDER" value="com.google.appengine.eclipse.core.projectValidator"/>
<mapAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
</launchConfiguration>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_DISABLED_BUILDER" value="com.google.gdt.eclipse.core.webAppProjectValidator"/>
<mapAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
</launchConfiguration>

43
broker/appengine/.project Normal file
View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>gridworks-appengine-broker</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/com.google.gdt.eclipse.core.webAppProjectValidator.launch</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>com.google.appengine.eclipse.core.enhancerbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/com.google.appengine.eclipse.core.projectValidator.launch</value>
</dictionary>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>com.google.appengine.eclipse.core.gaeNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,3 @@
#Wed May 26 15:13:15 PDT 2010
eclipse.preferences.version=1
validationExclusions=src/com/metaweb/gridworks/appengine/*ClientConnection*.java

View File

@ -0,0 +1,5 @@
#Wed May 26 15:11:38 PDT 2010
eclipse.preferences.version=1
jarsExcludedFromWebInfLib=
warSrcDir=
warSrcDirIsOutput=true

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>$APPID</application>
<version>$VERSION</version>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
</appengine-web-app>

View File

@ -0,0 +1,29 @@
#
# Butterfly Configuration
#
# NOTE: properties passed to the JVM using '-Dkey=value' from the command line
# override the settings in this file.
# indicates the URL path where butterfly is available in the proxy URL space
# as there is no way of knowing otherwise as this information is not
# transferred thru the HTTP protocol or otherwise (different story if
# the appserver is connected thru a different protocol such as AJP)
butterfly.url = /
# ---------- Miscellaneous ----------
#butterfly.locale.language = en
#butterfly.locale.country = US
#butterfly.timeZone = GMT+09:00
# ---------- Module ------
butterfly.modules.path = modules
butterfly.modules.wirings = WEB-INF/modules.properties
# ---------- Clustering ----
#butterfly.routing.cookie.maxage = -1

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
<persistence-manager-factory name="transactional">
<property name="javax.jdo.PersistenceManagerFactoryClass"
value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
<property name="javax.jdo.option.ConnectionURL" value="appengine"/>
<property name="javax.jdo.option.NontransactionalRead" value="true"/>
<property name="javax.jdo.option.NontransactionalWrite" value="false"/>
<property name="javax.jdo.option.RetainValues" value="true"/>
<property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
</persistence-manager-factory>
</jdoconfig>

Binary file not shown.

View File

@ -0,0 +1,13 @@
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers
.level = WARN

View File

@ -0,0 +1,5 @@
#
# Butterfly Modules Configuration
#
appengine-broker = /

View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<servlet>
<servlet-name>Butterfly</servlet-name>
<servlet-class>edu.mit.simile.butterfly.Butterfly</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Butterfly</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -0,0 +1,5 @@
name = appengine-broker
extends = broker
description = Google App Engine implementation of Gridworks Broker Module
module-impl = com.metaweb.gridworks.broker.AppEngineGridworksBroker
templating = false

View File

@ -0,0 +1,243 @@
package com.metaweb.gridworks.appengine;
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.allowTruncate;
import java.io.ByteArrayOutputStream;
import java.net.InetAddress;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLSession;
import org.apache.http.Header;
import org.apache.http.HttpConnectionMetrics;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
class AppEngineClientConnection implements ManagedClientConnection {
// Managed is the composition of ConnectionReleaseTrigger,
// HttpClientConnection, HttpConnection, HttpInetConnection
private HttpRoute _route;
private Object _state;
private boolean _reuseable;
public AppEngineClientConnection(HttpRoute route, Object state) {
_route = route;
_state = state;
}
// ManagedClientConnection methods
public HttpRoute getRoute() {
return _route;
}
public Object getState() {
return _state;
}
public SSLSession getSSLSession() {
return null;
}
public boolean isSecure() {
// XXX maybe parse the url to see if it's https?
return false;
}
public boolean isMarkedReusable() {
return _reuseable;
}
public void markReusable() {
_reuseable = true;
}
public void layerProtocol(HttpContext context, HttpParams params) {
return;
}
public void open(HttpRoute route, HttpContext context, HttpParams params) {
return;
}
public void setIdleDuration(long duration, TimeUnit unit) {
return;
}
public void setState(Object state) {
_state = state;
}
public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) {
return;
}
public void tunnelTarget(boolean secure, HttpParams params) {
return;
}
public void unmarkReusable() {
_reuseable = false;
}
// ConnectionReleaseTrigger methods
public void releaseConnection() {
return;
}
public void abortConnection() {
return;
}
// HttpClientConnection methods
private HTTPRequest _appengine_hrequest;
private HTTPResponse _appengine_hresponse;
public void flush() {
return;
}
public boolean isResponseAvailable(int timeout) {
// XXX possibly use Async fetcher
return true;
}
public void receiveResponseEntity(org.apache.http.HttpResponse apache_response) {
byte[] data = _appengine_hresponse.getContent();
if (data != null) {
apache_response.setEntity(new ByteArrayEntity(data));
}
}
public HttpResponse receiveResponseHeader() {
URLFetchService ufs = URLFetchServiceFactory.getURLFetchService();
try {
_appengine_hresponse = ufs.fetch(_appengine_hrequest);
} catch (java.io.IOException e) {
throw new RuntimeException(e);
}
org.apache.http.HttpResponse apache_response =
new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 0),
_appengine_hresponse.getResponseCode(),
null);
for (HTTPHeader h : _appengine_hresponse.getHeaders()) {
apache_response.addHeader(h.getName(), h.getValue());
}
return apache_response;
}
public void sendRequestEntity(org.apache.http.HttpEntityEnclosingRequest request) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
org.apache.http.HttpEntity ent = request.getEntity();
if (ent != null) {
try {
ent.writeTo(os);
} catch (java.io.IOException e) {
throw new RuntimeException(e);
}
}
_appengine_hrequest.setPayload(os.toByteArray());
}
public void sendRequestHeader(org.apache.http.HttpRequest apache_request) {
URL request_url;
HttpHost host = _route.getTargetHost();
String protocol = host.getSchemeName();
String addr = host.getHostName();
int port = host.getPort();
String path = apache_request.getRequestLine().getUri();
try {
request_url = new URL(protocol, addr, port, path);
} catch (java.net.MalformedURLException e) {
throw new RuntimeException(e);
}
HTTPMethod method = HTTPMethod.valueOf(apache_request.getRequestLine().getMethod());
_appengine_hrequest = new HTTPRequest(request_url, method, allowTruncate()
.doNotFollowRedirects());
Header[] apache_headers = apache_request.getAllHeaders();
for (int i = 0; i < apache_headers.length; i++) {
Header h = apache_headers[i];
_appengine_hrequest
.setHeader(new HTTPHeader(h.getName(), h.getValue()));
}
}
// HttpConnection methods
public void close() {
return;
}
public HttpConnectionMetrics getMetrics() {
return null;
}
public int getSocketTimeout() {
return -1;
}
public boolean isOpen() {
return true;
}
public boolean isStale() {
return false;
}
public void setSocketTimeout(int timeout) {
return;
}
public void shutdown() {
return;
}
// HttpInetConnection methods
public InetAddress getLocalAddress() {
return null;
}
public int getLocalPort() {
return -1;
}
public InetAddress getRemoteAddress() {
return null;
}
public int getRemotePort() {
return -1;
}
}

View File

@ -0,0 +1,76 @@
package com.metaweb.gridworks.appengine;
import java.net.InetAddress;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ClientConnectionRequest;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.params.HttpParams;
public class AppEngineClientConnectionManager implements ClientConnectionManager {
private SchemeRegistry schemes;
class NoopSocketFactory implements SocketFactory {
public Socket connectSocket(Socket sock, String host, int port, InetAddress addr, int lport, HttpParams params) {
return null;
}
public Socket createSocket() {
return null;
}
public boolean isSecure(Socket sock) {
return false;
}
}
public AppEngineClientConnectionManager() {
SocketFactory noop_sf = new NoopSocketFactory();
schemes = new SchemeRegistry();
schemes.register(new Scheme("http", noop_sf, 80));
schemes.register(new Scheme("https", noop_sf, 443));
}
public void closeExpiredConnections() {
return;
}
public void closeIdleConnections(long idletime, TimeUnit tunit) {
return;
}
public ManagedClientConnection getConnection(HttpRoute route, Object state) {
return new AppEngineClientConnection(route, state);
}
public SchemeRegistry getSchemeRegistry() {
return schemes;
}
public void releaseConnection(ManagedClientConnection conn, long valid, TimeUnit tuint) {
return;
}
public ClientConnectionRequest requestConnection(final HttpRoute route, final Object state) {
return new ClientConnectionRequest() {
public void abortRequest() {
return;
}
public ManagedClientConnection getConnection(long idletime, TimeUnit tunit) {
return AppEngineClientConnectionManager.this.getConnection(route, state);
}
};
}
public void shutdown() {
return;
}
}

View File

@ -0,0 +1,322 @@
package com.metaweb.gridworks.broker;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Transaction;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.appengine.api.datastore.Text;
import com.metaweb.gridworks.appengine.AppEngineClientConnectionManager;
public class AppEngineGridworksBroker extends GridworksBroker {
protected static final Logger logger = LoggerFactory.getLogger("gridworks.broker.appengine");
PersistenceManagerFactory pmfInstance;
@Override
public void init(ServletConfig config) throws Exception {
super.init(config);
pmfInstance = JDOHelper.getPersistenceManagerFactory("transactional");
}
@Override
public void destroy() throws Exception {
}
// ---------------------------------------------------------------------------------
protected HttpClient getHttpClient() {
ClientConnectionManager cm = new AppEngineClientConnectionManager();
return new DefaultHttpClient(cm, null);
}
// ---------------------------------------------------------------------------------
protected void getLock(HttpServletResponse response, String pid) throws Exception {
PersistenceManager pm = pmfInstance.getPersistenceManager();
try {
respond(response, lockToJSON(getLock(pm,pid)));
} finally {
pm.close();
}
}
protected void obtainLock(HttpServletResponse response, String pid, String uid) throws Exception {
PersistenceManager pm = pmfInstance.getPersistenceManager();
try {
Lock lock = getLock(pm, pid);
if (lock == null) {
Transaction tx = pm.currentTransaction();
try {
tx.begin();
lock = new Lock(Long.toHexString(tx.hashCode()), pid, uid);
pm.makePersistent(lock);
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
}
respond(response, lockToJSON(lock));
} finally {
pm.close();
}
}
protected void releaseLock(HttpServletResponse response, String pid, String uid, String lid) throws Exception {
PersistenceManager pm = pmfInstance.getPersistenceManager();
try {
Lock lock = getLock(pm, pid);
if (lock != null) {
if (!lock.id.equals(lid)) {
throw new RuntimeException("Lock id doesn't match, can't release the lock");
}
if (!lock.uid.equals(uid)) {
throw new RuntimeException("User id doesn't match the lock owner, can't release the lock");
}
Transaction tx = pm.currentTransaction();
try {
tx.begin();
pm.deletePersistent(lock);
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
}
respond(response, OK);
} finally {
pm.close();
}
}
// ----------------------------------------------------------------------------------------------------
protected void startProject(HttpServletResponse response, String pid, String uid, String lid, String data) throws Exception {
PersistenceManager pm = pmfInstance.getPersistenceManager();
try {
checkLock(pm, pid, uid, lid);
Project project = getProject(pm, pid);
if (project != null) {
throw new RuntimeException("Project '" + pid + "' already exists");
}
Transaction tx = pm.currentTransaction();
try {
tx.begin();
project = new Project(pid, data);
pm.makePersistent(project);
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
respond(response, OK);
} finally {
pm.close();
}
}
protected void addTransformations(HttpServletResponse response, String pid, String uid, String lid, List<String> transformations) throws Exception {
PersistenceManager pm = pmfInstance.getPersistenceManager();
try {
checkLock(pm, pid, uid, lid);
Project project = getProject(pm, pid);
if (project == null) {
throw new RuntimeException("Project '" + pid + "' not found");
}
Transaction tx = pm.currentTransaction();
try {
for (String s : transformations) {
project.transformations.add(new Text(s));
}
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
respond(response, OK);
} finally {
pm.close();
}
}
// ---------------------------------------------------------------------------------
protected void getProject(HttpServletResponse response, String pid) throws Exception {
PersistenceManager pm = pmfInstance.getPersistenceManager();
try {
Project project = getProject(pm, pid);
Writer w = response.getWriter();
JSONWriter writer = new JSONWriter(w);
writer.object();
writer.key("data"); writer.value(project.data.toString());
writer.key("transformations");
writer.array();
for (Text s : project.transformations) {
writer.value(s.toString());
}
writer.endArray();
writer.endObject();
w.flush();
w.close();
} finally {
pm.close();
}
}
protected void getHistory(HttpServletResponse response, String pid, int tindex) throws Exception {
PersistenceManager pm = pmfInstance.getPersistenceManager();
try {
Project project = getProject(pm, pid);
Writer w = response.getWriter();
JSONWriter writer = new JSONWriter(w);
writer.object();
writer.key("transformations");
writer.array();
int size = project.transformations.size();
for (int i = tindex; i < size; i++) {
writer.value(project.transformations.get(i).toString());
}
writer.endArray();
writer.endObject();
w.flush();
w.close();
} finally {
pm.close();
}
}
// ---------------------------------------------------------------------------------
Project getProject(PersistenceManager pm, String pid) {
Project project = pm.getObjectById(Project.class, pid);
if (project == null) {
throw new RuntimeException("Project '" + pid + "' is not managed by this broker");
}
return project;
}
@PersistenceCapable
static class Project {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
String pid;
@Persistent
List<Text> transformations = new ArrayList<Text>();
@Persistent
Text data;
Project(String pid, String data) {
this.pid = pid;
this.data = new Text(data);
}
}
// ---------------------------------------------------------------------------------
Lock getLock(PersistenceManager pm, String pid) {
return pm.getObjectById(Lock.class, pid);
}
void checkLock(PersistenceManager pm, String pid, String uid, String lid) {
Lock lock = getLock(pm, pid);
if (lock == null) {
throw new RuntimeException("No lock was found with the given Lock id '" + lid + "', you have to have a valid lock on a project in order to start it");
}
if (!lock.pid.equals(pid)) {
throw new RuntimeException("Lock '" + lid + "' is for another project: " + pid);
}
if (!lock.uid.equals(uid)) {
throw new RuntimeException("Lock '" + lid + "' is owned by another user: " + uid);
}
}
JSONObject lockToJSON(Lock lock) throws JSONException {
JSONObject o = new JSONObject();
if (lock != null) {
o.put("lock_id", lock.id);
o.put("project_id", lock.pid);
o.put("user_id", lock.uid);
}
return o;
}
@PersistenceCapable
static class Lock {
@Persistent
String id;
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
String pid;
@Persistent
String uid;
Lock(String id, String pid, String uid) {
this.id = id;
this.pid = pid;
this.uid = uid;
}
}
}

12
broker/core/.classpath Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/butterfly-trunk-r25.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/butterfly-trunk-r25-sources.jar"/>
<classpathentry kind="lib" path="/gridworks-server/lib/servlet-api-2.5.jar" sourcepath="/gridworks-server/lib-src/servlet-api-2.5-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/json-20100208.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/json-20100208-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/httpclient-4.0.1.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/httpclient-4.0.1-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/httpcore-4.0.1.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/httpcore-4.0.1-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/slf4j-api-1.5.6.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/slf4j-api-1.5.6-sources.jar"/>
<classpathentry kind="output" path="module/MOD-INF/classes"/>
</classpath>

17
broker/core/.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>gridworks-broker</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,4 @@
name = broker
description = Gridworks Broker
module-impl = com.metaweb.gridworks.broker.GridworksBroker
templating = false

View File

@ -0,0 +1,265 @@
package com.metaweb.gridworks.broker;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreProtocolPNames;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.mit.simile.butterfly.ButterflyModuleImpl;
/**
* This class contains all the code shared by various implementations of a Gridworks Broker.
*
* A broker is a server used by multiple Gridworks installations to enable collaborative
* development over the same project.
*
* Broker implementations differ in how they store their state but all of them are required
* to extend this abstract class and implement the services that are called via HTTP.
*
*/
public abstract class GridworksBroker extends ButterflyModuleImpl {
protected static final Logger logger = LoggerFactory.getLogger("gridworks.broker");
static final protected String USER_INFO_URL = "http://www.freebase.com/api/service/user_info";
static final protected String DELEGATED_OAUTH_HEADER = "X-Freebase-Credentials";
static final protected String OAUTH_HEADER = "Authorization";
static protected String OK;
static {
try {
JSONObject o = new JSONObject();
o.put("status","ok");
OK = o.toString();
} catch (JSONException e) {
// not going to happen;
}
}
protected HttpClient httpclient;
@Override
public void init(ServletConfig config) throws Exception {
super.init(config);
httpclient = getHttpClient();
}
@Override
public void destroy() throws Exception {
httpclient.getConnectionManager().shutdown();
}
@Override
public boolean process(String path, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("> process {}", path);
} else {
logger.info("process {}", path);
}
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "application/json");
try {
String uid = getUserId(request);
logger.debug("uid: {}", path);
String pid = getParameter(request, "pid");
logger.debug("pid: {}", path);
// NOTE: conditionals should be ordered by call frequency estimate to (slightly) improve performance
// we could be using a hashtable and some classes that implement the commands, but the complexity overhead
// doesn't seem to justify the marginal benefit.
if ("get_lock".equals(path)) {
getLock(response, pid);
} else if ("obtain_lock".equals(path)) {
obtainLock(response, pid, uid);
} else if ("release_lock".equals(path)) {
releaseLock(response, pid, uid, getParameter(request, "lock"));
} else if ("history".equals(path)) {
getHistory(response, pid, getInteger(request, "tindex"));
} else if ("transform".equals(path)) {
addTransformations(response, pid, uid, getParameter(request, "lock"), getList(request, "transformations"));
} else if ("start".equals(path)) {
startProject(response, pid, uid, getParameter(request, "lock"), getParameter(request, "data"));
} else if ("get".equals(path)) {
getProject(response, pid);
}
} catch (RuntimeException e) {
logger.error("runtime error", e);
respondError(response, e.getMessage());
} catch (Exception e) {
logger.error("internal error", e);
respondException(response, e);
}
if (logger.isDebugEnabled()) logger.debug("< process {}", path);
return super.process(path, request, response);
}
// ----------------------------------------------------------------------------------------
protected abstract HttpClient getHttpClient();
protected abstract void getLock(HttpServletResponse response, String pid) throws Exception;
protected abstract void obtainLock(HttpServletResponse response, String pid, String uid) throws Exception;
protected abstract void releaseLock(HttpServletResponse response, String pid, String uid, String lock) throws Exception;
protected abstract void startProject(HttpServletResponse response, String pid, String uid, String lock, String data) throws Exception;
protected abstract void addTransformations(HttpServletResponse response, String pid, String uid, String lock, List<String> transformations) throws Exception;
protected abstract void getProject(HttpServletResponse response, String pid) throws Exception;
protected abstract void getHistory(HttpServletResponse response, String pid, int tindex) throws Exception;
// ----------------------------------------------------------------------------------------
@SuppressWarnings("unchecked")
protected String getUserId(HttpServletRequest request) throws Exception {
String oauth = request.getHeader(DELEGATED_OAUTH_HEADER);
if (oauth == null) {
throw new RuntimeException("The request needs to contain the '" + DELEGATED_OAUTH_HEADER + "' header set to obtain user identity via Freebase.");
}
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
Map<String,String> params = (Map<String,String>) request.getParameterMap();
for (Entry<String,String> e : params.entrySet()) {
formparams.add(new BasicNameValuePair((String) e.getKey(), (String) e.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");
HttpPost httpRequest = new HttpPost(USER_INFO_URL);
httpRequest.setHeader(OAUTH_HEADER, oauth);
httpRequest.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "Gridworks Broker");
httpRequest.setEntity(entity);
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String responseBody = httpclient.execute(httpRequest, responseHandler);
JSONObject o = new JSONObject(responseBody);
return o.getString("username");
}
// ----------------------------------------------------------------------------------------
static protected String getParameter(HttpServletRequest request, String name) throws ServletException {
String param = request.getParameter(name);
if (param == null) {
throw new ServletException("request must come with a '" + name + "' parameter");
}
return param;
}
static protected List<String> getList(HttpServletRequest request, String name) throws ServletException, JSONException {
String param = getParameter(request, name);
JSONArray a = new JSONArray(param);
List<String> result = new ArrayList<String>(a.length());
for (int i = 0; i < a.length(); i++) {
result.add(a.getString(i));
}
return result;
}
static protected int getInteger(HttpServletRequest request, String name) throws ServletException, JSONException {
return Integer.parseInt(getParameter(request, name));
}
static protected void respondError(HttpServletResponse response, String error) throws IOException, ServletException {
if (response == null) {
throw new ServletException("Response object can't be null");
}
try {
JSONObject o = new JSONObject();
o.put("code", "error");
o.put("message", error);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
respond(response, o.toString());
} catch (JSONException e) {
e.printStackTrace(response.getWriter());
}
}
static protected void respondException(HttpServletResponse response, Exception e) throws IOException, ServletException {
if (response == null) {
throw new ServletException("Response object can't be null");
}
try {
JSONObject o = new JSONObject();
o.put("code", "error");
o.put("message", e.getMessage());
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.flush();
sw.flush();
o.put("stack", sw.toString());
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
respond(response, o.toString());
} catch (JSONException e1) {
e.printStackTrace(response.getWriter());
}
}
static protected void respond(HttpServletResponse response, JSONObject content) throws IOException, ServletException {
if (content == null) {
throw new ServletException("Content object can't be null");
}
JSONObject o = new JSONObject();
respond(response, o.toString());
}
static protected void respond(HttpServletResponse response, String content) throws IOException, ServletException {
if (response == null) {
throw new ServletException("Response object can't be null");
}
Writer w = response.getWriter();
if (w != null) {
w.write(content);
w.flush();
w.close();
} else {
throw new ServletException("response returned a null writer");
}
}
}

14
broker/local/.classpath Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/butterfly-trunk-r25.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/butterfly-trunk-r25-sources.jar"/>
<classpathentry kind="lib" path="/gridworks-server/lib/servlet-api-2.5.jar" sourcepath="/gridworks-server/lib-src/servlet-api-2.5-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/json-20100208.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/json-20100208-sources.jar"/>
<classpathentry kind="lib" path="module/MOD-INF/lib/bdb-je-4.0.103.jar" sourcepath="module/MOD-INF/lib-src/bdb-je-4.0.103-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/httpclient-4.0.1.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/httpclient-4.0.1-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/httpcore-4.0.1.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/httpcore-4.0.1-sources.jar"/>
<classpathentry kind="lib" path="/gridworks/webapp/WEB-INF/lib/slf4j-api-1.5.6.jar" sourcepath="/gridworks/webapp/WEB-INF/lib-src/slf4j-api-1.5.6-sources.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/gridworks-broker"/>
<classpathentry kind="output" path="module/MOD-INF/classes"/>
</classpath>

17
broker/local/.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>gridworks-local-broker</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,29 @@
#
# Butterfly Configuration
#
# NOTE: properties passed to the JVM using '-Dkey=value' from the command line
# override the settings in this file.
# indicates the URL path where butterfly is available in the proxy URL space
# as there is no way of knowing otherwise as this information is not
# transferred thru the HTTP protocol or otherwise (different story if
# the appserver is connected thru a different protocol such as AJP)
butterfly.url = /
# ---------- Miscellaneous ----------
#butterfly.locale.language = en
#butterfly.locale.country = US
#butterfly.timeZone = GMT+09:00
# ---------- Module ------
butterfly.modules.path = modules
butterfly.modules.wirings = WEB-INF/modules.properties
# ---------- Clustering ----
#butterfly.routing.cookie.maxage = -1

View File

@ -0,0 +1,5 @@
#
# Butterfly Modules Configuration
#
local-broker = /

View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<servlet>
<servlet-name>Butterfly</servlet-name>
<servlet-class>edu.mit.simile.butterfly.Butterfly</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Butterfly</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -0,0 +1,67 @@
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/*
* Copyright (c) 2002-2010 Oracle. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Redistributions in any form must be accompanied by information on
* how to obtain complete source code for the Oracle Berkeley DB
* Java Edition software and any accompanying software that uses the
* Oracle Berkeley DB Java Edition software. The source code must
* either be included in the distribution or be available for no
* more than the cost of distribution plus a nominal fee, and must be
* freely redistributable under reasonable conditions. For an
* executable file, complete source code means the source code for all
* modules it contains. It does not include source code for modules or
* files that typically accompany the major components of the operating
* system on which the executable file runs.
*
* THIS SOFTWARE IS PROVIDED BY ORACLE ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
* NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL ORACLE BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/***
* ASM: a very small and fast Java bytecode manipulation framework
* Copyright (c) 2000-2005 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

Binary file not shown.

View File

@ -0,0 +1,5 @@
name = local-broker
extends = broker
description = Local implementation of Gridworks Broker Module
module-impl = com.metaweb.gridworks.broker.LocalDBGridworksBroker
templating = false

View File

@ -0,0 +1,309 @@
package com.metaweb.gridworks.broker;
import java.io.File;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.StoreConfig;
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.PrimaryKey;
public class LocalGridworksBroker extends GridworksBroker {
protected static final Logger logger = LoggerFactory.getLogger("gridworks.broker.local");
Environment env;
EntityStore projectStore;
EntityStore lockStore;
PrimaryIndex<String,Project> projectById;
PrimaryIndex<String,Lock> lockByProject;
protected HttpClient httpclient;
@Override
public void init(ServletConfig config) throws Exception {
super.init(config);
File dataPath = new File("data"); // FIXME: data should be configurable;
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
envConfig.setTransactional(true);
env = new Environment(dataPath, envConfig);
StoreConfig storeConfig = new StoreConfig();
storeConfig.setAllowCreate(true);
storeConfig.setTransactional(true);
projectStore = new EntityStore(env, "ProjectsStore", storeConfig);
lockStore = new EntityStore(env, "LockStore", storeConfig);
projectById = projectStore.getPrimaryIndex(String.class, Project.class);
lockByProject = lockStore.getPrimaryIndex(String.class, Lock.class);
}
@Override
public void destroy() throws Exception {
super.destroy();
if (projectStore != null) {
projectStore.close();
}
if (lockStore != null) {
lockStore.close();
}
if (env != null) {
env.cleanLog();
env.close();
}
}
// ---------------------------------------------------------------------------------
protected HttpClient getHttpClient() {
return new DefaultHttpClient();
}
// ---------------------------------------------------------------------------------
protected void getLock(HttpServletResponse response, String pid) throws Exception {
respond(response, lockToJSON(getLock(pid)));
}
protected void obtainLock(HttpServletResponse response, String pid, String uid) throws Exception {
Transaction txn = env.beginTransaction(null, null);
Lock lock = getLock(pid);
if (lock == null) {
try {
lock = new Lock(Long.toHexString(txn.getId()), pid, uid);
lockByProject.put(txn, lock);
txn.commit();
} finally {
if (txn != null) {
txn.abort();
txn = null;
}
}
}
respond(response, lockToJSON(lock));
}
protected void releaseLock(HttpServletResponse response, String pid, String uid, String lid) throws Exception {
Transaction txn = env.beginTransaction(null, null);
Lock lock = getLock(pid);
if (lock != null) {
try {
if (!lock.id.equals(lid)) {
throw new RuntimeException("Lock id doesn't match, can't release the lock");
}
if (!lock.uid.equals(uid)) {
throw new RuntimeException("User id doesn't match the lock owner, can't release the lock");
}
lockByProject.delete(pid);
txn.commit();
} finally {
if (txn != null) {
txn.abort();
txn = null;
}
}
}
respond(response, OK);
}
// ----------------------------------------------------------------------------------------------------
protected void startProject(HttpServletResponse response, String pid, String uid, String lid, String data) throws Exception {
Transaction txn = env.beginTransaction(null, null);
checkLock(pid, uid, lid);
if (projectById.contains(pid)) {
throw new RuntimeException("Project '" + pid + "' already exists");
}
try {
projectById.put(txn, new Project(pid, data));
txn.commit();
} finally {
if (txn != null) {
txn.abort();
txn = null;
}
}
respond(response, OK);
}
protected void addTransformations(HttpServletResponse response, String pid, String uid, String lid, List<String> transformations) throws Exception {
Transaction txn = env.beginTransaction(null, null);
checkLock(pid, uid, lid);
Project project = getProject(pid);
if (project == null) {
throw new RuntimeException("Project '" + pid + "' not found");
}
try {
project.transformations.addAll(transformations);
txn.commit();
} finally {
if (txn != null) {
txn.abort();
txn = null;
}
}
respond(response, OK);
}
// ---------------------------------------------------------------------------------
protected void getProject(HttpServletResponse response, String pid) throws Exception {
Project project = getProject(pid);
Writer w = response.getWriter();
JSONWriter writer = new JSONWriter(w);
writer.object();
writer.key("data"); writer.value(project.data);
writer.key("transformations");
writer.array();
for (String s : project.transformations) {
writer.value(s);
}
writer.endArray();
writer.endObject();
w.flush();
w.close();
}
protected void getHistory(HttpServletResponse response, String pid, int tindex) throws Exception {
Project project = getProject(pid);
Writer w = response.getWriter();
JSONWriter writer = new JSONWriter(w);
writer.object();
writer.key("transformations");
writer.array();
int size = project.transformations.size();
for (int i = tindex; i < size; i++) {
writer.value(project.transformations.get(i));
}
writer.endArray();
writer.endObject();
w.flush();
w.close();
}
// ---------------------------------------------------------------------------------
Project getProject(String pid) {
Project project = projectById.get(pid);
if (project == null) {
throw new RuntimeException("Project '" + pid + "' is not managed by this broker");
}
return project;
}
@Entity
class Project {
@PrimaryKey
String pid;
List<String> transformations = new ArrayList<String>();
String data;
Project(String pid, String data) {
this.pid = pid;
this.data = data;
}
@SuppressWarnings("unused")
private Project() {}
}
// ---------------------------------------------------------------------------------
Lock getLock(String pid) {
return lockByProject.get(pid);
}
void checkLock(String pid, String uid, String lid) {
Lock lock = getLock(pid);
if (lock == null) {
throw new RuntimeException("No lock was found with the given Lock id '" + lid + "', you have to have a valid lock on a project in order to start it");
}
if (!lock.pid.equals(pid)) {
throw new RuntimeException("Lock '" + lid + "' is for another project: " + pid);
}
if (!lock.uid.equals(uid)) {
throw new RuntimeException("Lock '" + lid + "' is owned by another user: " + uid);
}
}
JSONObject lockToJSON(Lock lock) throws JSONException {
JSONObject o = new JSONObject();
if (lock != null) {
o.put("lock_id", lock.id);
o.put("project_id", lock.pid);
o.put("user_id", lock.uid);
}
return o;
}
@Entity
class Lock {
@PrimaryKey
String pid;
String id;
String uid;
Lock(String id, String pid, String uid) {
this.id = id;
this.pid = pid;
this.uid = uid;
}
@SuppressWarnings("unused")
private Lock() {}
}
}