/*******************************************************************************
* Copyright 2014 Miami-Dade County
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.sharegov.cirm.rest;
import static org.sharegov.cirm.OWL.businessObjectId;
import static org.sharegov.cirm.OWL.dataProperty;
import static org.sharegov.cirm.OWL.fullIri;
import static org.sharegov.cirm.OWL.individual;
import static org.sharegov.cirm.OWL.isBusinessObject;
import static org.sharegov.cirm.OWL.owlClass;
import static org.sharegov.cirm.utils.GenUtils.ko;
import static org.sharegov.cirm.utils.GenUtils.ok;
import java.io.File;
import java.net.URLDecoder;
import java.util.List;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import mjson.Json;
import org.hypergraphdb.HGHandle;
import org.semanticweb.owlapi.io.OWLFunctionalSyntaxOntologyFormat;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.semanticweb.owlapi.model.OWLNamedObject;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLOntologyStorageException;
import org.sharegov.cirm.BOntology;
import org.sharegov.cirm.CirmTransaction;
import org.sharegov.cirm.OWL;
import org.sharegov.cirm.OWLObjectToJson;
import org.sharegov.cirm.Refs;
import org.sharegov.cirm.StartUp;
import org.sharegov.cirm.event.EventDispatcher;
import org.sharegov.cirm.legacy.Permissions;
import org.sharegov.cirm.rdb.RelationalOWLPersister;
import org.sharegov.cirm.rdb.RelationalStoreExt;
import org.sharegov.cirm.utils.ThreadLocalStopwatch;
import org.sharegov.cirm.workflows.UserInputRequest;
import org.sharegov.cirm.workflows.Workflow;
import org.sharegov.cirm.workflows.WorkflowDone;
import org.sharegov.cirm.workflows.WorkflowExecutionContext;
import org.sharegov.cirm.workflows.WorkflowManager;
import org.sharegov.cirm.workflows.WorkflowRequestStep;
import org.sharegov.cirm.workflows.WorkflowStep;
@Path("op")
@Produces("application/json")
public class OperationService extends RestService
{
public static boolean DBG = true;
public static RelationalOWLPersister getPersister()
{
OWLNamedObject x = Refs.configSet.resolve().get("OperationsDatabaseConfig");
return RelationalOWLPersister.getInstance(x.getIRI());
}
private String handleException(Throwable e)
{
e.printStackTrace();
return "{\"error\": \"" + e.toString() + "\"}";
}
void saveOntology(OWLOntologyManager manager, OWLOntology ont) throws OWLOntologyStorageException
{
String filename = ont.getOntologyID().getOntologyIRI().toString();
filename = filename.substring("http://www.miamidade.gov/".length()).replace('/', '_') + ".owl";
File dir = new File(StartUp.getConfig().at("workingDir").asString() + "/src/ontology");
File f = new File(dir, filename);
manager.saveOntology(ont, new OWLFunctionalSyntaxOntologyFormat(), IRI.create(f));
}
public BOntology createBusinessObject(OWLClass type)
{
try
{
return BOntology.makeNewBusinessObject(type);
/*
String boid = OWLRefs.idFactory.resolve().newId("");
OWLOntologyManager manager = MetaService.get().getManager();
IRI ontologyIRI = IRI.create("http://www.miamidade.gov/bo/" +
type.getIRI().getFragment() + "/" + boid);
// The business object is created in its own ontology.
OWLOntology boOntology;
boOntology = manager.createOntology(ontologyIRI);
manager.applyChange(new AddImport(boOntology, manager.getOWLDataFactory().getOWLImportsDeclaration(
ontology().getOntologyID().getOntologyIRI())));
OWLNamedIndividual businessObject = businessObject(boOntology);
OWLAxiom axiom = manager.getOWLDataFactory().getOWLClassAssertionAxiom(type, businessObject);
manager.applyChange(new AddAxiom(boOntology, axiom));
getPersister().saveBusinessObjectOntology(boOntology);
return boOntology;
*/
}
catch (OWLOntologyCreationException e)
{
throw new RuntimeException(e);
}
}
@POST
@Path("/create/{classname}")
@Consumes("application/json")
public Json newBusinessObject(@PathParam("classname") String classname, Json object)
{
if (DBG) ThreadLocalStopwatch.getWatch().time("START newBusinessObject " + classname);
try
{
OWLClass theclass = owlClass(classname);
BOntology bontology = createBusinessObject(theclass);
bontology.setProperties(object);
getPersister().saveBusinessObjectOntology(bontology.getOntology());
return ok().set("bo", bontology.toJSON());
}
catch (Throwable t)
{
t.printStackTrace(System.err);
return ko(t);
}
finally
{
if (DBG) ThreadLocalStopwatch.getWatch().time("END newBusinessObject " + classname);
ThreadLocalStopwatch.dispose();
}
}
@GET
@Path("/new/{classname}")
@Produces("application/json")
public String newBusinessObject(@PathParam("classname") String classname)
{
try
{
OWLClass theclass = owlClass(classname);
BOntology bontology = createBusinessObject(theclass);
return bontology.toJSON().toString();
}
catch (Throwable ex)
{
ex.printStackTrace(System.err);
throw new RuntimeException(ex);
}
}
Json stepBack(WorkflowExecutionContext context)
{
Json result = null;
while (result == null && !context.getHistory().isEmpty())
{
context.backtrack();
// if (context.getCurrentStep() instanceof PromptUserTask)
// {
// Object request = ((WorkflowRequestStep)context.getCurrentStep().perform(context)).getRequest();
// if (request instanceof UserInputRequest)
// {
// result = ((UserInputRequest)request).getUispec();
// }
// else
// result = Json.object().set("error", "unknown workflow request type " + request);
// }
}
return result;
}
Json stepForward(WorkflowExecutionContext context)
{
Json result = null;
while (result == null)
{
WorkflowStep nextStep = context.getCurrentStep().perform(context);
if (nextStep instanceof WorkflowRequestStep)
{
Object request = ((WorkflowRequestStep)nextStep).getRequest();
if (request instanceof UserInputRequest)
{
result = ((UserInputRequest)request).getUispec();
}
else
result = Json.object().set("error", "unknown workflow request type " + request);
}
else if (nextStep instanceof WorkflowDone)
{
WorkflowDone done = (WorkflowDone)nextStep;
Json x = Json.object();
x.set("done", done.isSuccess());
if (done.getInfo() != null)
x.set("info", done.getInfo().toString());
else
{
Set<OWLLiteral> S = context.getBusinessObject().getDataPropertyValues(
dataProperty("hasExplanation"),
context.getBusinessObjectOntology().getOntology());
if (!S.isEmpty())
x.set("info", S.iterator().next().getLiteral());
}
result = x;
}
else
{
context.getHistory().push(context.getCurrentStep());
context.setCurrentStep(nextStep);
}
}
return result;
}
@GET
@Path("/workflow/{type}/{id}")
@Produces("application/json")
public String getWorkflow(@PathParam("type") String classname, @PathParam("id") String id)
{
try
{
// First, determine if the business object already has an associated workflow
// and if not, instantiate one, based on the business rules defined for its type.
WorkflowExecutionContext context = WorkflowManager.getInstance().getWorkflowContext(businessObjectId(classname, id));
if (context == null)
{
BOntology bontology = getBusinessObjectOntology(OWL.businessObjectId(classname, id));
context = WorkflowManager.getInstance().startWorkflow(bontology);
}
return stepForward(context).toString();
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
}
/**
* Submit a workflow action: either perform next step in workflow (from a user's
* perspective, or go back).
*
* @param classname
* @param id
* @param command
* @param formDataParam
* @return
*/
@POST
@Path("/workflow/{type}/{id}/{command}")
@Produces("application/json")
public String workflowStep(@PathParam("type") String classname,
@PathParam("id") String id,
@PathParam("command") String command,
@FormParam("data") String formDataParam)
{
if (DBG) ThreadLocalStopwatch.getWatch().time("START workflowStep " + id);
try
{
Json formData = Json.read(formDataParam);
WorkflowExecutionContext context = WorkflowManager.getInstance().getWorkflowContext(businessObjectId(classname, id));
if (context == null)
throw new Exception("No workflow started for object " + classname + "#" + id);
Json result = null;
if ("continue".equals(command))
{
WorkflowRequestStep step = (WorkflowRequestStep)context.getCurrentStep().perform(context);
step.setResponse(formData);
WorkflowStep next = step.perform(context);
context.moveTo(next);
result = stepForward(context);
}
else if ("back".equals(command))
{
result = stepBack(context);
}
getPersister().saveBusinessObjectOntology(context.getBusinessObjectOntology().getOntology());
return result.toString();
}
catch (Throwable e)
{
return handleException(e);
}
finally
{
if (DBG) ThreadLocalStopwatch.getWatch().time("END workflowStep " + id);
ThreadLocalStopwatch.dispose();
}
}
@POST
@Path("/workflow/create")
public Json createWorkflow(@FormParam("data") String boAsData)
{
Json data = Json.read(boAsData);
BOntology bontology = BOntology.makeRuntimeBOntology(data);
WorkflowExecutionContext context = WorkflowManager.getInstance().startWorkflow(bontology);
return ok().set("context", context.toJSON());
}
@GET
@Path("/workflow/create/{type}/{id}")
public Json createWorkflow(@PathParam("type") String classname, @PathParam("id") String id)
{
try
{
IRI classIri = fullIri(classname);
// First, determine if the business object already has an associated workflow
// and if not, instantiate one, based on the business rules defined for its type.
WorkflowExecutionContext context = WorkflowManager.getInstance().getWorkflowContext(businessObjectId(classname, id));
if (context == null)
{
BOntology bontology = getBusinessObjectOntology(OWL.businessObjectId(classname, id));
context = WorkflowManager.getInstance().startWorkflow(bontology);
return ok().set("context", context.toJSON());
}
else
return ko("workflow-exists").set("context", context.toJSON());
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
}
@GET
@Path("/workflow/state/{type}/{id}")
@Produces("application/json")
public Json workflowState(@PathParam("type") String classname,
@PathParam("id") String id)
{
try
{
// First, determine if the business object already has an associated workflow
// and if not, instantiate one, based on the business rules defined for its type.
WorkflowExecutionContext context = WorkflowManager.getInstance().getWorkflowContext(businessObjectId(classname, id));
if (context == null)
{
return ko("no-workflow");
}
// ? what else besides current task we want to send here? variables?
return ok().set("next",
Workflow.toJSON(context.getCurrentStep()));
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
}
@POST
@Path("/workflow/step/{type}/{id}")
@Produces("application/json")
public Json workflowStep(@PathParam("type") String classname,
@PathParam("id") String id,
@FormParam("data") String dataParam)
{
Json boAsJson = Json.read(dataParam);
WorkflowExecutionContext context = WorkflowManager.getInstance().getWorkflowContext(businessObjectId(classname, id));
if (context == null)
return ko("No workflow started for object " + classname + "#" + id);
WorkflowStep currentStep = context.getCurrentStep();
WorkflowStep nextStep = context.step();
return ok().set("next", Workflow.toJSON(nextStep))
.set("prev", Workflow.toJSON(currentStep))
.set("outputVariables", context.getStepOutput(currentStep));
}
private WorkflowExecutionContext getContextFromClientData(Json data)
{
if (!data.has("context"))
return null;
String classname = data.at("context").at("bo").at("type").asString();
WorkflowExecutionContext context = WorkflowManager.getInstance().makeRuntimeContext(OWL.fullIri(classname));
context.fromJSON(data.at("context"));
return context;
}
/**
* Evaluate the workflow until the next step loops back into itself or until a
* condition passed into the data parameter is met. The condition can be a
* 'stopCondition' on the value of an output variable name. Variable values
* are simply compared as strings here.
*
* @param classname
* @param id
* @return
*/
@POST
@Path("/workflow/forward")
@Produces("application/json")
public Json workflowForward(@FormParam("data") String dataParam)
{
Json data = Json.read(dataParam);
System.out.println(dataParam);
WorkflowExecutionContext context = getContextFromClientData(data);
if (context == null)
return ko("Missing workflow context.");
WorkflowStep lastStep = context.getHistory().isEmpty() ? null:context.getHistory().lastElement();
while (context.getCurrentStep() != null)
{
if (lastStep != null && data.has("stopCondition"))
{
String varname = data.at("stopCondition").at("variable").asString();
String value = data.at("stopCondition").at("value").asString();
Object varvalue = context.getStepOutput(lastStep).get(IRI.create(varname));
if (varvalue != null && value.equals(varvalue.toString()))
break;
}
WorkflowStep currentStep = context.getCurrentStep();
WorkflowStep nextStep = context.step();
if (nextStep == currentStep)
break;
else
lastStep = currentStep;
}
return ok().set("context", context.toJSON());
}
@POST
@Path("/workflow/back")
@Produces("application/json")
public Json workflowBack(@FormParam("count") int stepCount, @FormParam("data") String dataParam)
{
Json data = Json.read(dataParam);
WorkflowExecutionContext context = getContextFromClientData(data);
if (context == null)
return ko("Missing workflow context.");
while (stepCount-- > 0 && !context.getHistory().isEmpty())
{
context.backtrack();
}
return ok().set("context", context.toJSON());
}
@POST
@Path("/workflow/backto")
@Produces("application/json")
public Json workflowBackTo(@FormParam("data") String latestDataParam,
@FormParam("tostep") String toStepId)
{
HGHandle toStep = WorkflowManager.getInstance().getGraph().getHandleFactory().makeHandle(toStepId);
Json data = Json.read(latestDataParam);
WorkflowExecutionContext context = getContextFromClientData(data);
if (context == null)
return ko("Missing workflow context.");
while (context.getHistory().isEmpty())
{
if (context.getCurrentStep().getAtomHandle().equals(toStep))
break;
context.backtrack();
}
return ok().set("context", context.toJSON());
}
@POST
@Path("/workflow/back/{type}/{id}")
@Produces("application/json")
public Json workflowBack(@PathParam("type") String classname,
@PathParam("id") String id)
{
IRI classIri = fullIri(classname);
WorkflowExecutionContext context = WorkflowManager.getInstance().getWorkflowContext(businessObjectId(classname, id));
if (context == null)
return ko("No workflow started for object " + classname + "#" + id);
if (!context.getHistory().isEmpty())
return ko("Already at beginning.");
WorkflowStep top = context.getHistory().pop();
context.setCurrentStep(top);
return ok().set("next", Workflow.toJSON(context.getCurrentStep()));
}
public BOntology getBusinessObjectOntology(Long boId)
{
return new BOntology(getPersister().getBusinessObjectOntology(boId));
}
public BOntology getBusinessObjectOntology(IRI boIri)
{
return new BOntology(getPersister().getBusinessObjectOntology(boIri));
}
@GET
@Path("/get/{type}/{id}")
@Produces("application/json")
public Json getBusinessObject(@PathParam("type") String classname,
@PathParam("id") String id)
{
try
{
IRI boIri = businessObjectId(classname, id);
BOntology boOntology = getBusinessObjectOntology(boIri);
// MetaService.get().getOntologyLoader().getBusinessObjectOntology(classIri, id);
OWLObjectToJson mapper = new OWLObjectToJson();
mapper.setIncludeTypeInfo(true);
Json j = mapper.map(boOntology.getOntology(), boOntology.getBusinessObject(), null);
System.out.println(j.toString());
return j;
}
catch (Throwable e)
{
return Json.object("ko", true, "error", e.toString());
}
}
@GET
@Path("/list")
public Json list(@QueryParam("q") String queryAsString)
{
try
{
Json result = Json.array();
Json query = Json.read(queryAsString);
List<OWLEntity> L = getPersister().query(query);
//List<OWLObject> L = new ArrayList<OWLObject>();
for (OWLEntity x : L)
{
if (x instanceof OWLNamedIndividual &&
isBusinessObject(x.asOWLNamedIndividual().getIRI()))
{
BOntology on = this.getBusinessObjectOntology(((OWLNamedIndividual) x).getIRI());
result.add(OWL.toJSON(on.getOntology(), x.asOWLNamedIndividual()));
}
else
result.add(OWL.toJSON(x));
}
System.out.println(result);
return result;
}
catch (Throwable e)
{
e.printStackTrace(System.err);
return Json.object("error", e.toString());
}
}
@POST
@Path("/update/{type}/{id}")
public Json updateBusinessObject(@PathParam("type") String classname,
@PathParam("id") String id,
@FormParam("data") String formDataParam)
{
if (DBG) ThreadLocalStopwatch.getWatch().time("START updateBusinessObject " + id);
try {
Json data = Json.read(formDataParam);
WorkflowExecutionContext context = getContextFromClientData(data);
if (context == null)
context = WorkflowManager.getInstance().getWorkflowContext(businessObjectId(classname, id));
IRI boid = businessObjectId(classname, id);
BOntology o = getBusinessObjectOntology(boid);
o.setProperties(data);
if (context != null)
context.setBusinessObjectOntology(o);
getPersister().saveBusinessObjectOntology(o.getOntology());
if (context != null)
context.processEventRules();
return ok();
}
finally
{
if (DBG) ThreadLocalStopwatch.getWatch().time("END updateBusinessObject " + id);
ThreadLocalStopwatch.dispose();
}
}
@DELETE
@Path("/delete/{type}/{id}")
public Json removeBusinessObject(@PathParam("type") String classname,
@PathParam("id") String id)
{
IRI boid = businessObjectId(classname, id);
BOntology boOntology = getBusinessObjectOntology(boid);
getPersister().deleteBusinessObjectOntologyWithHistory(boOntology.getOntology());
return ok();
}
@GET
@Path("/actions/{type}/{id}")
public Json getActionsInContext(@PathParam("type") String classname,
@PathParam("id") String id,
@QueryParam("context") String context)
{
Json actions = Json.array();
actions.add(OWL.toJSON(individual("BO_Update")));
actions.add(OWL.toJSON(individual("BO_Delete")));
return actions;
}
@GET
@Path("/successpath/{type}/{id}")
public Json getShortestSuccessPath(@PathParam("type") String classname,
@PathParam("id") String id)
{
WorkflowExecutionContext context =
WorkflowManager.getInstance().getWorkflowContext(businessObjectId(classname, id));
// TODO...
return ok();
}
@SuppressWarnings("deprecation")
@GET
@Path("/eventrule")
public Json fireEventRule(@QueryParam("boiri") String boiri, @QueryParam("id") String ruleId)
{
try
{
System.out.println("Fire rule " + ruleId + " on " + URLDecoder.decode(boiri, "UTF-8"));
WorkflowExecutionContext context = WorkflowManager.getInstance().getWorkflowContext(IRI.create(boiri));
context.fireEventRule(ruleId);
}
catch (Throwable t)
{
return ko(t);
}
return ok();
}
@DELETE
@Path("/individual/{iri}")
public Json deleteIndividual(@PathParam("iri") String iri)
{
try
{
OWLOntology O = Refs.tempOntoManager.resolve().createOntology();
OWLNamedIndividual individual = individual(iri);
getPersister().readIndividualData(O, individual);
getPersister().deleteBusinessObjectOntologyWithHistory(O);
return ok();
}
catch (Throwable ex)
{
ex.printStackTrace(System.err);
return ko(ex);
}
finally
{
ThreadLocalStopwatch.dispose();
}
}
@GET
@Path("/individual/{iri}")
public Json getIndividual(@PathParam("iri") String iri)
{
try
{
OWLOntology O = Refs.tempOntoManager.resolve().createOntology();
OWLNamedIndividual individual = individual(iri);
getPersister().readIndividualData(O, individual);
return O.getAxioms().size() > 0 ?
ok().set("data", OWL.toJSON(O, individual)) :
ko("non-found");
}
catch (Throwable ex)
{
ex.printStackTrace(System.err);
return ko(ex);
}
finally
{
ThreadLocalStopwatch.dispose();
}
}
@POST
@Path("/individual/{iri}")
@Consumes("application/json")
public Json saveIndividual(@PathParam("iri") String iri, Json object)
{
if (DBG) ThreadLocalStopwatch.getWatch().time("START saveIndividual " + iri);
try
{
OWLOntology O = Refs.tempOntoManager.resolve().createOntology();
OWLNamedIndividual individual = individual(iri);
BOntology bo = new BOntology(O);
bo.setPropertiesFor(individual, object);
getPersister().saveBusinessObjectOntology(O);// .readIndividualData(O, individual);
OWLObjectToJson mapper = new OWLObjectToJson();
Json j = mapper.map(O, individual, null);
EventDispatcher.get().dispatch(individual,
owlClass(object.at("type").asString()),
object,
individual("BO_Update"));
return j;
}
catch (Throwable ex)
{
ex.printStackTrace(System.err);
return ko(ex);
}
finally
{
if (DBG) ThreadLocalStopwatch.getWatch().time("END saveIndividual " + iri);
ThreadLocalStopwatch.dispose();
}
}
@GET
@Path("/event/{since}")
public Json getEvent(@PathParam("since") long since)
{
try
{
return ok().set("events", Refs.clientPushQueue.resolve().getAfter(since));
}
catch (Throwable t)
{
return ko(t);
}
}
/**
* Get's the current time in milliseconds.
* Current implementation retrieves the time from the RelationalStore.
* @return Json containing time with millisecond accuracy.
*/
@GET
@Path("/time")
public Json getTime()
{
try
{
return ok().set("time", getPersister().getStore().getStoreTime().getTime());
}
catch (Throwable t)
{
return ko(t);
}
}
@POST
@Path("/db/sequence/reset")
public Json resetUserFriendlySequence()
{
try {
if (!isClientExempt()) {
return ko("Permission denied.");
}
RelationalStoreExt store = getPersister().getStoreExt();
store.txn(new CirmTransaction<Object>() {
@Override
public Object call() throws Exception {
store.resetUserFriendlySequenceNumber();
return null;
}
});
return ok().set("message", "Sequence was reset");
} catch (Throwable t) {
return ko(t);
}
}
// This is not a main program, just used for quick&dirty tests
public static void main(String [] argv)
{
OperationService op = new OperationService();
}
}