package ru.semiot.platform.apigateway.rest; import static ru.semiot.commons.restapi.AsyncResponseHelper.resume; import com.github.jsonldjava.core.JsonLdError; import com.github.jsonldjava.utils.JsonUtils; import org.aeonbits.owner.ConfigFactory; import org.apache.jena.datatypes.xsd.XSDDatatype; import org.apache.jena.query.QuerySolution; import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.DCTerms; import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.semiot.commons.namespaces.Hydra; import ru.semiot.commons.namespaces.HydraFilter; import ru.semiot.commons.namespaces.Proto; import ru.semiot.commons.namespaces.SSN; 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.SPARQLQueryService; import ru.semiot.platform.apigateway.utils.MapBuilder; import ru.semiot.platform.apigateway.utils.URIUtils; import rx.Observable; import rx.exceptions.Exceptions; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Map; import javax.ejb.Stateless; import javax.inject.Inject; import javax.ws.rs.GET; 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") @Stateless public class SystemResource extends AbstractSystemResource { private static final ServerConfig config = ConfigFactory.create(ServerConfig.class); private static final Logger logger = LoggerFactory.getLogger(SystemResource.class); private static final String QUERY_GET_ALL_SYSTEMS = "SELECT ?uri ?id ?label ?prototype {" + " ?uri a ssn:System, proto:Individual ;" + " dcterms:identifier ?id ;" + " proto:hasPrototype ?prototype ." + " OPTIONAL { ?uri rdfs:label ?label }" + " FILTER NOT EXISTS { [] ssn:hasSubSystem ?uri }" + "} " + "ORDER BY ?id " + "LIMIT ${LIMIT} " + "OFFSET ${OFFSET} "; private static final String COUNT_TOTAL_SYSTEMS = "SELECT (COUNT(*) AS ?count) {" + "?system a ssn:System, proto:Individual ." + "FILTER NOT EXISTS { [] ssn:hasSubSystem ?system }" + "}"; private static final String QUERY_DESCRIBE_SYSTEM = "CONSTRUCT {" + " ?system ?p ?o ." + " ?o ?o_p ?o_o ." + "} WHERE {" + " ?system ?p ?o ;" + " dcterms:identifier \"${SYSTEM_ID}\" ." + " OPTIONAL {" + " ?o ?o_p ?o_o ." + " FILTER(?p NOT IN (rdf:type, proto:hasPrototype))" + " }" + "}"; private static final String VAR_URI = "uri"; private static final String VAR_ID = "id"; private static final String VAR_LABEL = "label"; private static final String VAR_PROTOTYPE = "prototype"; private static final String VAR_LIMIT = "${LIMIT}"; private static final String VAR_OFFSET = "${OFFSET}"; private static final String VAR_PAGE_SIZE = "${PAGE_SIZE}"; private static final int FIRST_PAGE = 1; public SystemResource() { super(); } @Inject private SPARQLQueryService sparqlQuery; @Inject private TSDBQueryService tsdbQuery; @Inject private ContextProvider contextProvider; @Context private UriInfo uriInfo; @GET @Produces({MediaType.APPLICATION_LD_JSON, MediaType.APPLICATION_JSON}) public void listSystems(@Suspended final AsyncResponse response, @QueryParam("page") Integer page, @QueryParam("size") Integer size) throws JsonLdError, IOException { URI root = uriInfo.getRequestUri(); if (page == null) { URI redirectUri = UriBuilder.fromUri(root) .queryParam("page", FIRST_PAGE).queryParam("size", config.systemsPageSize()) .build(); response.resume(Response.seeOther(redirectUri).build()); } else { final int pageSize = size != null && size != 0 ? size : config.systemsPageSize(); final Model model = contextProvider.getRDFModel(ContextProvider.SYSTEM_COLLECTION, MapBuilder.newMap() .put(ContextProvider.VAR_ROOT_URL, URIUtils.extractRootURL(root)) .put(VAR_PAGE_SIZE, pageSize) .put(ContextProvider.VAR_QUERY_PARAMS, "?page=" + page + "&size=" + pageSize) .build()); final Map<String, Object> frame = contextProvider.getFrame( ContextProvider.SYSTEM_COLLECTION, root); int offset = page > 1 ? (page - 1) * pageSize : 0; Resource collection = model.listResourcesWithProperty( RDF.type, Hydra.PartialCollectionView).next(); Resource parentCollection = model.listObjectsOfProperty(collection, HydraFilter.viewOf) .next().asResource(); Observable<Integer> totalItems = sparqlQuery.select(COUNT_TOTAL_SYSTEMS) .map((ResultSet rs) -> { if (rs.hasNext()) { int total = rs.next().getLiteral("count").getInt(); model.add(parentCollection, Hydra.totalItems, ResourceFactory .createTypedLiteral(String.valueOf(total), XSDDatatype.XSDinteger)); if ((page + 1) * pageSize <= total) { model.add(collection, Hydra.next, ResourceFactory.createResource(UriBuilder.fromUri(root) .replaceQueryParam("page", page + 1) .replaceQueryParam("size", pageSize).build() .toASCIIString())); } model.add(collection, Hydra.last, ResourceFactory.createResource(UriBuilder.fromUri(root) .replaceQueryParam("page", computeLastPage(total, pageSize)) .replaceQueryParam("size", pageSize) .build().toASCIIString())); return total; } else { logger.error("Can't count number of existing systems!"); return null; } }); Observable<ResultSet> systems = sparqlQuery.select(QUERY_GET_ALL_SYSTEMS .replace(VAR_LIMIT, String.valueOf(pageSize)) .replace(VAR_OFFSET, String.valueOf(offset))); Observable.zip(systems, totalItems, (ResultSet rs, Integer total) -> { while (rs.hasNext()) { QuerySolution qs = rs.next(); Resource system = qs.getResource(VAR_URI); Literal systemId = qs.getLiteral(VAR_ID); Literal systemLabel = qs.getLiteral(VAR_LABEL); Resource prototype = qs.getResource(VAR_PROTOTYPE); model.add(collection, Hydra.member, system); model.add(system, DCTerms.identifier, systemId); model.add(system, RDF.type, ResourceUtils.createResourceFromClass(root, prototype.getLocalName())); if (systemLabel != null) { model.add(system, RDFS.label, systemLabel); } } try { return JsonUtils.toPrettyString(ModelJsonLdUtils.toJsonLdCompact(model, frame)); } catch (IOException | JsonLdError e) { throw Exceptions.propagate(e); } }).subscribe(resume(response)); } } @GET @Path("{id}") @Produces({MediaType.APPLICATION_LD_JSON, MediaType.APPLICATION_JSON}) public void getSystem(@Suspended final AsyncResponse response, @PathParam("id") String id) throws URISyntaxException, IOException { URI root = uriInfo.getRequestUri(); String rootUrl = URIUtils.extractRootURL(root); Model model = contextProvider.getRDFModel(ContextProvider.SYSTEM_SINGLE, MapBuilder.newMap() .put(ContextProvider.VAR_ROOT_URL, rootUrl) .put(ContextProvider.VAR_SYSTEM_ID, id).build()); sparqlQuery.describe(QUERY_DESCRIBE_SYSTEM.replace("${SYSTEM_ID}", id)).map((Model result) -> { model.add(result); try { Literal systemId = ResourceFactory.createTypedLiteral(id, XSDDatatype.XSDstring); boolean found = model.contains(null, DCTerms.identifier, systemId); if (found) { Resource system = model.listResourcesWithProperty(DCTerms.identifier, systemId).next(); Resource prototype = model.listObjectsOfProperty(system, Proto.hasPrototype) .next().asResource(); Resource prototypeResource = ResourceUtils.createResourceFromClass( root, prototype.getLocalName()); model.add(system, RDF.type, prototypeResource); if (model.contains(null, RDF.type, SSN.SensingDevice)) { model.add(system, ResourceFactory.createProperty(rootUrl + "/doc#observations"), ResourceFactory.createResource(rootUrl + "/systems/" + systemId + "/observations")); } Map<String, Object> frame = contextProvider.getFrame(ContextProvider.SYSTEM_SINGLE, MapBuilder.newMap() .put(ContextProvider.VAR_ROOT_URL, rootUrl) .put(ContextProvider.VAR_SYSTEM_TYPE, prototypeResource.getURI()) .build()); return JsonUtils.toPrettyString(ModelJsonLdUtils.toJsonLdCompact(model, frame)); } else { throw new WebApplicationException(Response.Status.NOT_FOUND); } } catch (Exception ex) { throw Exceptions.propagate(ex); } }).subscribe(resume(response)); } private int computeLastPage(int total, int pageSize) { if (total > 0) { Double value = Double.valueOf(total / pageSize); return Double.compare(value.intValue(), value) == 0 ? value.intValue() + 1 : value.intValue(); } else { return FIRST_PAGE; } } }