package ru.semiot.platform.apigateway.rest; import static ru.semiot.commons.restapi.AsyncResponseHelper.resume; import static ru.semiot.platform.apigateway.beans.impl.ContextProvider.API_DOCUMENTATION; import static ru.semiot.platform.apigateway.beans.impl.ContextProvider.ENTRYPOINT; import com.github.jsonldjava.core.JsonLdError; import com.github.jsonldjava.utils.JsonUtils; import org.aeonbits.owner.ConfigFactory; import org.apache.jena.query.QuerySolution; import org.apache.jena.query.ResultSet; 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.RDF; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.semiot.commons.namespaces.Hydra; import ru.semiot.commons.namespaces.Proto; import ru.semiot.commons.namespaces.SHACL; 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.impl.ContextProvider; import ru.semiot.platform.apigateway.beans.impl.SPARQLQueryService; import ru.semiot.platform.apigateway.utils.DataBase; 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.ArrayList; import java.util.List; 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.Produces; 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.UriInfo; @Path("/") @Stateless public class RootResource { private static final ServerConfig config = ConfigFactory.create(ServerConfig.class); private static final Logger logger = LoggerFactory.getLogger(RootResource.class); private static final String QUERY_SYSTEM_PROTOTYPES = "SELECT DISTINCT ?prototype {" + " ?device a proto:Individual, ssn:System ;" + " proto:hasPrototype ?prototype ." + " FILTER NOT EXISTS { [] ssn:hasSubSystem ?device }" + "}"; private static final String QUERY_COLLECTION_MEMBER = "SELECT ?uri {" + " <${COLLECTION_URI}> rdfs:range ?shape ." + " ?shape sh:property ?uri ." + " ?uri sh:predicate hydra:member ." + "}"; private static final String QUERY_INDIVIDUAL_PROPERTIES = "SELECT DISTINCT ?prototype ?property {" + " ?device a proto:Individual ;" + " proto:hasPrototype <${PROTOTYPE_URI}> ;" + " proto:hasPrototype ?prototype ;" + " ?property ?value ." + " FILTER(?property NOT IN (rdf:type, proto:hasPrototype))" + "}"; private static final String LINK_SYSTEMS = "systems"; private static final String VAR_PROTOTYPE = "prototype"; private static final String VAR_PROPERTY = "property"; private static final String VAR_URI = "uri"; private static final String VAR_COLLECTION_URI = "${COLLECTION_URI}"; private static final String VAR_PROTOTYPE_URI = "${PROTOTYPE_URI}"; public RootResource() { } @Inject private SPARQLQueryService query; @Context private UriInfo uriInfo; @Inject private ContextProvider contextProvider; @GET @Produces({MediaType.APPLICATION_LD_JSON, MediaType.APPLICATION_JSON}) public String entrypoint() throws JsonLdError, IOException, URISyntaxException { URI root = uriInfo.getRequestUri(); Model entrypoint = contextProvider.getRDFModel(ENTRYPOINT, root); Map<String, Object> frame = contextProvider.getFrame(ENTRYPOINT, root); return JsonUtils.toString(ModelJsonLdUtils.toJsonLdCompact(entrypoint, frame)); } @GET @Produces({MediaType.TEXT_HTML, MediaType.TEXT_PLAIN}) public Response index() { return Response.seeOther(URI.create("/index")).build(); } @GET @Path("/context") public String context() { URI root = uriInfo.getRequestUri(); return contextProvider.getContext(ContextProvider.COMMON_CONTEXT, root); } @GET @Path("/doc") @Produces({MediaType.APPLICATION_LD_JSON, MediaType.APPLICATION_JSON}) public void documentation(@Suspended final AsyncResponse response) throws JsonLdError, IOException { String rootURL = URIUtils.extractRootURL(uriInfo.getRequestUri()); Model apiDoc = contextProvider.getRDFModel(API_DOCUMENTATION, MapBuilder.newMap() .put(ContextProvider.VAR_ROOT_URL, rootURL) .put(ContextProvider.VAR_WAMP_URL, rootURL + config.wampPublicPath()) .build()); Map<String, Object> frame = contextProvider.getFrame(API_DOCUMENTATION, rootURL); Observable<List<Resource>> prototypes = query.select(QUERY_SYSTEM_PROTOTYPES) .map((ResultSet rs) -> defineResourceIndividual(apiDoc, rootURL, LINK_SYSTEMS, rs, SSN.System)); Observable<Model> supportedProperties = addSupportedProperties(apiDoc, rootURL, prototypes); supportedProperties.map((__) -> { try { return JsonUtils.toString(ModelJsonLdUtils.toJsonLdCompact(apiDoc, frame)); } catch (Throwable e) { throw Exceptions.propagate(e); } }).subscribe(resume(response)); } private List<Resource> defineResourceIndividual(Model model, String rootURL, String collectionName, ResultSet rs, Resource... classes) { List<Resource> resultPrototypes = new ArrayList<>(); final Resource apiDocResource = model.listResourcesWithProperty( RDF.type, Hydra.ApiDocumentation).next(); final Resource collection = ResourceFactory.createResource(rootURL + "/doc#" + collectionName); //Find the restriction on hydra:member of the given collection ResultSet results = query.select(model, QUERY_COLLECTION_MEMBER .replace(VAR_COLLECTION_URI, collection.getURI())); Resource restriction = null; if (results.hasNext()) { restriction = results.next().getResource(VAR_URI); } while (rs.hasNext()) { final Resource prototype = rs.next().getResource(VAR_PROTOTYPE); resultPrototypes.add(prototype); final Resource prototypeResource = ResourceUtils.createResourceFromClass( rootURL, prototype.getLocalName()); //Define in hydra:supportedClass model.add(apiDocResource, Hydra.supportedClass, prototypeResource); model.add(prototypeResource, RDF.type, Hydra.Class); model.add(prototypeResource, RDF.type, Proto.Individual); for (Resource clazz : classes) { model.add(prototypeResource, RDF.type, clazz); } model.add(prototypeResource, Proto.hasPrototype, prototype); //Define in the given collection if (restriction != null) { model.add(restriction, SHACL.clazz, prototypeResource); } } return resultPrototypes; } private Observable<Model> addSupportedProperties(Model model, String rootUrl, Observable<List<Resource>> observable) { return observable.map((prototypes) -> { List<Observable<ResultSet>> obs = new ArrayList<>(); prototypes.stream().forEach((prototype) -> obs.add(query.select( QUERY_INDIVIDUAL_PROPERTIES.replace(VAR_PROTOTYPE_URI, prototype.getURI())))); return Observable.merge(obs).toBlocking().toIterable(); }).map((Iterable<ResultSet> iter) -> { iter.forEach((ResultSet rs) -> { while (rs.hasNext()) { QuerySolution qs = rs.next(); Resource prototype = qs.getResource(VAR_PROTOTYPE); Resource prototypeResource = ResourceUtils.createResourceFromClass( rootUrl, prototype.getLocalName()); Resource property = ResourceFactory.createResource(); model.add(prototypeResource, Hydra.supportedProperty, property) .add(property, Hydra.property, ResourceFactory.createProperty(qs.getResource(VAR_PROPERTY).getURI())); } }); return model; }); } }