package com.thinkaurelius.faunus.tinkerpop.rexster; import com.tinkerpop.rexster.RexsterResourceContext; import com.tinkerpop.rexster.extension.AbstractRexsterExtension; import com.tinkerpop.rexster.extension.ExtensionDefinition; import com.tinkerpop.rexster.extension.ExtensionDescriptor; import com.tinkerpop.rexster.extension.ExtensionMethod; import com.tinkerpop.rexster.extension.ExtensionNaming; import com.tinkerpop.rexster.extension.ExtensionPoint; import com.tinkerpop.rexster.extension.ExtensionResponse; import com.tinkerpop.rexster.extension.HttpMethod; import com.tinkerpop.rexster.extension.RexsterContext; import org.apache.log4j.Logger; import org.codehaus.jettison.json.JSONObject; import javax.ws.rs.core.Response; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Allows for remote execution and monitoring of a Faunus job via Rexster. * * @author Stephen Mallette (http://stephen.genoprime.com) */ @ExtensionNaming(namespace = FaunusRexsterExecutorExtension.EXTENSION_NAMESPACE, name = FaunusRexsterExecutorExtension.EXTENSION_NAME) public class FaunusRexsterExecutorExtension extends AbstractRexsterExtension { private static final Logger logger = Logger.getLogger(FaunusRexsterExecutorExtension.class); public static final String EXTENSION_NAMESPACE = "faunus"; public static final String EXTENSION_NAME = "executor"; public static final String STATUS_ERROR = "error"; public static final String STATUS_PROCESSING = "processing"; public static final String STATUS_COMPLETE = "complete"; public static final String FIELD_JOB = "job"; public static final String FIELD_STATUS = "status"; public static final String FIELD_MESSAGE = "message"; private static final ConcurrentMap<String,FaunusEvaluationJob> jobs = new ConcurrentHashMap<String, FaunusEvaluationJob>(); @ExtensionDefinition(extensionPoint = ExtensionPoint.GRAPH, method = HttpMethod.POST) @ExtensionDescriptor(description = "Execute a Faunus job") public ExtensionResponse evaluatePost(@RexsterContext final RexsterResourceContext context) { final String script = context.getRequestObject().optString("script"); if (script == null || script.isEmpty()) { ExtensionMethod extMethod = context.getExtensionMethod(); return ExtensionResponse.error("the script parameter cannot be empty", null, Response.Status.BAD_REQUEST.getStatusCode(), null, generateErrorJson(extMethod.getExtensionApiAsJson())); } final JSONObject config = context.getRequestObject().optJSONObject("config"); final UUID jobId = UUID.randomUUID(); final FaunusEvaluationJob job = new FaunusEvaluationJob(script, jobId, context.getRexsterApplicationGraph(), convertToMap(config)); logger.info(String.format("Faunus script [%s] assigned job id: %s", script, jobId)); jobs.put(jobId.toString(), job); new Thread(new Runnable() { @Override public void run() { job.performJob(); } }).start(); return ExtensionResponse.ok(new HashMap<String,Object>() {{ put(FIELD_JOB, jobId.toString()); }}); } @ExtensionDefinition(extensionPoint = ExtensionPoint.GRAPH, method = HttpMethod.GET) @ExtensionDescriptor(description = "Get Faunus job status") public ExtensionResponse evaluateGet(@RexsterContext final RexsterResourceContext context) { final String job = context.getRequestObject().optString("job"); if (job == null || job.isEmpty()) { ExtensionMethod extMethod = context.getExtensionMethod(); return ExtensionResponse.error("the job parameter cannot be empty", null, Response.Status.BAD_REQUEST.getStatusCode(), null, generateErrorJson(extMethod.getExtensionApiAsJson())); } if (!jobs.containsKey(job)) { return new ExtensionResponse(Response.status(Response.Status.NOT_FOUND).build()); } synchronized (this) { final FaunusEvaluationJob fej = jobs.get(job); final String status = fej.isComplete() ? (fej.isError() ? STATUS_ERROR : STATUS_COMPLETE) : STATUS_PROCESSING; if (fej.isComplete()) { jobs.remove(job); } return ExtensionResponse.ok(new HashMap<String,Object>(){{ put(FIELD_JOB, job); put(FIELD_STATUS, status); put(FIELD_MESSAGE, fej.getErrorMessage()); }}); } } private static Map<String,String> convertToMap(final JSONObject config) { final Map<String,String> m = new HashMap<String, String>(); if (config != null) { final Iterator itty = config.keys(); while (itty.hasNext()) { final String currentKey = itty.next().toString(); m.put(currentKey, config.optString(currentKey)); } } return m; } }