package ru.semiot.platform.apigateway.rest;
import static ru.semiot.commons.restapi.AsyncResponseHelper.resume;
import static ru.semiot.commons.restapi.AsyncResponseHelper.resumeOnError;
import com.github.jsonldjava.core.JsonLdError;
import com.github.jsonldjava.utils.JsonUtils;
import org.aeonbits.owner.ConfigFactory;
import org.apache.jena.ext.com.google.common.base.Strings;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ResIterator;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.vocabulary.RDF;
import ru.semiot.commons.namespaces.DUL;
import ru.semiot.commons.namespaces.Hydra;
import ru.semiot.commons.namespaces.SEMIOT;
import ru.semiot.commons.rdf.ModelJsonLdUtils;
import ru.semiot.commons.restapi.MediaType;
import ru.semiot.platform.apigateway.ServerConfig;
import ru.semiot.platform.apigateway.beans.TSDBQueryService;
import ru.semiot.platform.apigateway.beans.impl.ContextProvider;
import ru.semiot.platform.apigateway.beans.impl.DeviceProxyService;
import ru.semiot.platform.apigateway.beans.impl.SPARQLQueryService;
import ru.semiot.platform.apigateway.utils.MapBuilder;
import ru.semiot.platform.apigateway.utils.RDFUtils;
import ru.semiot.platform.apigateway.utils.URIUtils;
import rx.Observable;
import rx.exceptions.Exceptions;
import java.io.IOException;
import java.net.URI;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
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 javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
@Path("/systems/{system_id}/processes/{process_id}")
@Stateless
public class ProcessResource {
private static final ServerConfig config = ConfigFactory.create(ServerConfig.class);
private static final String QUERY_DESCRIBE_PROCESS = "CONSTRUCT { <${PROCESS_URI}> ?x ?y } "
+ " WHERE {<${PROCESS_URI}> ?x ?y} ";
private static final String QUERY_DESCRIBE_COMMANDS
= "CONSTRUCT { "
+ " ?command ?x ?y ."
+ " ?y ?x1 ?y1 ."
+ " ?y2 ?x3 ?y3 ."
+ " }"
+ " WHERE {"
+ " <${PROCESS_URI}> proto:hasPrototype ?prototype . "
+ " ?prototype semiot:supportedCommand ?command . "
+ " ?command ?x ?y ."
+ " OPTIONAL { "
+ " ?y ?x1 ?y1 . "
+ " ?y ?x2 ?y2 ."
+ " ?y2 ?x3 ?y3 ."
+ " FILTER(?x2 NOT IN (semiot:forParameter)) "
+ " }"
+ " }";
private static final String VAR_PROCESS_URI = "${PROCESS_URI}";
@Inject
ContextProvider contextProvider;
@Inject
DeviceProxyService dps;
@Inject
SPARQLQueryService metadata;
@Inject
TSDBQueryService tsdb;
@Context
UriInfo uriInfo;
@GET
@Produces({MediaType.APPLICATION_LD_JSON, MediaType.APPLICATION_JSON})
public void getProcess(@Suspended AsyncResponse response, @PathParam("system_id") String systemId)
throws IOException {
URI requestUri = uriInfo.getRequestUri();
String rootUrl = URIUtils.extractRootURL(requestUri);
Resource system = ResourceFactory.createResource(
uriInfo.getRequestUriBuilder().replacePath("/systems/" + systemId).build().toASCIIString());
Model model = contextProvider.getRDFModel(ContextProvider.PROCESS_SINGLE,
MapBuilder.newMap()
.put(ContextProvider.VAR_ROOT_URL, rootUrl)
.put(ContextProvider.VAR_PROCESS_URI, requestUri.toASCIIString())
.build());
Object frame = contextProvider.getFrame(ContextProvider.PROCESS_SINGLE, rootUrl);
metadata.describe(QUERY_DESCRIBE_PROCESS.replace(VAR_PROCESS_URI, requestUri.toASCIIString()))
.map((Model rs) -> {
model.add(rs);
try {
if (!RDFUtils.listResourcesWithProperty(model, RDF.type, SEMIOT.Process).isEmpty()) {
return ResourceFactory.createResource(requestUri.toASCIIString());
} else {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
} catch (Throwable ex) {
throw Exceptions.propagate(ex);
}
})
.map((Resource process) ->
addSupportedCommands(model, system, process).toBlocking().first())
.map((__) -> {
try {
return JsonUtils.toPrettyString(ModelJsonLdUtils.toJsonLdCompact(model, frame));
} catch (Throwable ex) {
throw Exceptions.propagate(ex);
}
}).subscribe(resume(response));
}
@POST
@Consumes({MediaType.APPLICATION_LD_JSON, MediaType.TEXT_TURTLE})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_LD_JSON})
public void executeCommand(@Suspended AsyncResponse response,
@PathParam("system_id") String systemId, Model command) {
try {
URI root = uriInfo.getRequestUri();
Map<String, Object> frame = contextProvider.getFrame(ContextProvider.COMMANDRESULT, root);
if (Strings.isNullOrEmpty(systemId)) {
response.resume(Response.status(Response.Status.NOT_FOUND).build());
}
if (command == null || command.isEmpty()) {
response.resume(Response.status(Response.Status.BAD_REQUEST).build());
} else {
dps.executeCommand(systemId, command).map((commandResult) -> {
try {
return Response.ok(ModelJsonLdUtils.toJsonLdCompact(commandResult, frame)).build();
} catch (JsonLdError | IOException ex) {
throw Exceptions.propagate(ex);
}
}).subscribe(resume(response));
}
} catch (Throwable ex) {
resumeOnError(response, ex);
}
}
@GET
@Path("/commandResults")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_LD_JSON})
public void getCommandResults(@Suspended AsyncResponse response,
@PathParam("system_id") String systemId, @PathParam("process_id") String processId,
@QueryParam("start") ZonedDateTime start, @QueryParam("end") ZonedDateTime end) {
URI root = uriInfo.getRequestUri();
String rootURL = URIUtils.extractRootURL(root);
Map params = MapBuilder.newMap()
.put(ContextProvider.VAR_ROOT_URL, rootURL)
.put(ContextProvider.VAR_WAMP_URL, UriBuilder
.fromUri(rootURL + config.wampPublicPath())
.scheme(config.wampProtocolScheme())
.build())
.put(ContextProvider.VAR_SYSTEM_ID, systemId)
.put(ContextProvider.VAR_PROCESS_ID, processId)
.build();
if (Strings.isNullOrEmpty(systemId)) {
response.resume(Response.status(Response.Status.NOT_FOUND).build());
}
if (start == null) {
tsdb.queryDateTimeOfLatestCommandResult(systemId, processId).subscribe((dateTime) -> {
if (dateTime != null) {
response.resume(Response.seeOther(UriBuilder.fromUri(root)
.queryParam("start", dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
.build())
.build());
} else {
try {
Map<String, Object> frame = contextProvider.getFrame(
ContextProvider.PROCESS_COMMANDRESULTS_COLLECTION, rootURL);
params.put(ContextProvider.VAR_QUERY_PARAMS, "?noparams");
Model model = contextProvider.getRDFModel(
ContextProvider.PROCESS_COMMANDRESULTS_COLLECTION, params);
Resource view = RDFUtils.subjectWithProperty(
model, RDF.type, Hydra.PartialCollectionView);
model.removeAll(view, null, null);
response.resume(JsonUtils.toPrettyString(
ModelJsonLdUtils.toJsonLdCompact(model, frame)));
} catch (Throwable ex) {
resumeOnError(response, ex);
}
}
}, resumeOnError(response));
} else {
String queryParams = "?start=" + start;
if (end != null) {
queryParams += "&end=" + end;
}
params.put(ContextProvider.VAR_QUERY_PARAMS, queryParams);
Model model = contextProvider.getRDFModel(
ContextProvider.PROCESS_COMMANDRESULTS_COLLECTION, params);
tsdb.queryCommandResultsByRange(systemId, processId, start, end).subscribe((result) -> {
try {
Map<String, Object> frame = contextProvider.getFrame(
ContextProvider.PROCESS_COMMANDRESULTS_PARTIAL_COLLECTION, rootURL);
model.add(result);
Resource collection = model.listSubjectsWithProperty(
RDF.type, Hydra.PartialCollectionView).next();
ResIterator iter = model.listSubjectsWithProperty(RDF.type, SEMIOT.CommandResult);
while (iter.hasNext()) {
Resource obs = iter.next();
model.add(collection, Hydra.member, obs);
}
response.resume(JsonUtils.toPrettyString(ModelJsonLdUtils.toJsonLdCompact(model, frame)));
} catch (Throwable e) {
resumeOnError(response, e);
}
}, resumeOnError(response));
}
}
private Observable<Model> addSupportedCommands(Model model, Resource system, Resource process) {
return metadata.describe(QUERY_DESCRIBE_COMMANDS.replace(VAR_PROCESS_URI, process.getURI()))
.map((Model rs) -> {
Resource operation = ResourceFactory.createResource();
rs.add(process, Hydra.supportedOperation, operation)
.add(operation, RDF.type, Hydra.Operation)
.add(operation, Hydra.method, "POST")
.add(operation, Hydra.returns, SEMIOT.CommandResult);
ResIterator commandIter = rs.listSubjectsWithProperty(RDF.type, SEMIOT.Command);
while (commandIter.hasNext()) {
Resource command = commandIter.next();
rs.add(operation, Hydra.expects, command)
.add(command, SEMIOT.forProcess, process)
.add(command, DUL.associatedWith, system);
}
model.add(rs);
return model;
});
}
}