All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atomgraph.server.model.impl.ResourceBase Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show newest version
/**
 *  Copyright 2012 Martynas Jusevičius 
 *
 *  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 com.atomgraph.server.model.impl;

import com.atomgraph.core.MediaTypes;
import org.apache.jena.ontology.*;
import org.apache.jena.query.*;
import org.apache.jena.rdf.model.*;
import org.apache.jena.reasoner.Reasoner;
import org.apache.jena.reasoner.rulesys.GenericRuleReasoner;
import org.apache.jena.sparql.util.Loader;
import org.apache.jena.update.UpdateRequest;
import com.sun.jersey.api.core.ResourceContext;
import java.net.URI;
import java.util.List;
import java.util.Locale;
import javax.ws.rs.Path;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.ResponseBuilder;
import com.atomgraph.core.exception.NotFoundException;
import com.atomgraph.core.model.Service;
import com.atomgraph.core.util.Link;
import com.atomgraph.processor.vocabulary.LDT;
import com.atomgraph.core.model.impl.QueriedResourceBase;
import com.atomgraph.processor.exception.OntologyException;
import com.atomgraph.processor.util.RulePrinter;
import com.atomgraph.processor.util.TemplateCall;
import java.util.Collections;
import java.util.Iterator;
import org.apache.jena.vocabulary.RDF;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spinrdf.arq.ARQ2SPIN;
import org.spinrdf.vocabulary.SPIN;

/**
 * Base class of generic read-write Linked Data resources.
 * Configured declaratively using sitemap ontology to provide Linked Data layer over a SPARQL endpoint.
 * Supports pagination on containers (implemented using SPARQL query solution modifiers).
 * 
 * @author Martynas Jusevičius {@literal }
 * @see OntResource
 * @see 15 Solution Sequences and Modifiers
 */
@Path("/")
public class ResourceBase extends QueriedResourceBase implements com.atomgraph.server.model.Resource, com.atomgraph.server.model.QueriedResource
{
    private static final Logger log = LoggerFactory.getLogger(ResourceBase.class);
        
    private final com.atomgraph.processor.model.Application application;
    private final Ontology ontology;
    private final TemplateCall templateCall;
    private final OntResource ontResource;
    private final ResourceContext resourceContext;
    private final HttpHeaders httpHeaders;  
    private final QuerySolutionMap querySolutionMap;
    private final Query query;
    private final UpdateRequest update;

    /**
     * Public JAX-RS instance. Suitable for subclassing.
     * If the request URI does not match any URI template in the sitemap ontology, 404 Not Found is returned.
     * 
     * If the matching template extends ldt:Document, this resource becomes a page resource and
     * HATEOS metadata is added (relations to the container and previous/next page resources).
     * 
     * @param application LDT application
     * @param mediaTypes mediaTypes
     * @param uriInfo URI information of the current request
     * @param request current request
     * @param service SPARQL service
     * @param ontology LDT ontology
     * @param templateCall templateCall
     * @param httpHeaders HTTP headers of the current request
     * @param resourceContext resource context
     */
    public ResourceBase(@Context UriInfo uriInfo, @Context Request request, @Context MediaTypes mediaTypes,
            @Context Service service, @Context com.atomgraph.processor.model.Application application, @Context Ontology ontology, @Context TemplateCall templateCall,
            @Context HttpHeaders httpHeaders, @Context ResourceContext resourceContext)
    {
        this(uriInfo, request, mediaTypes, uriInfo.getAbsolutePath(),
                service, application, ontology, templateCall,
                httpHeaders, resourceContext);
    }

    protected ResourceBase(final UriInfo uriInfo, final Request request, final MediaTypes mediaTypes, final URI uri,
            final Service service, final com.atomgraph.processor.model.Application application, final Ontology ontology, final TemplateCall templateCall,
            final HttpHeaders httpHeaders, final ResourceContext resourceContext)
    {
        super(uriInfo, request, mediaTypes, uri, service);

        if (templateCall == null)
        {
            if (log.isDebugEnabled()) log.debug("Resource {} has not matched any template, returning 404 Not Found", getURI());
            throw new NotFoundException("Resource has not matched any template");
        }
        if (application == null) throw new IllegalArgumentException("Application cannot be null");
        if (ontology == null) throw new IllegalArgumentException("Ontology cannot be null");
        if (httpHeaders == null) throw new IllegalArgumentException("HttpHeaders cannot be null");
        if (resourceContext == null) throw new IllegalArgumentException("ResourceContext cannot be null");

        // we are not making permanent changes to base ontology because OntologyProvider always makes a copy
        this.application = application;
        this.ontology = ontology;
        this.ontResource = ontology.getOntModel().createOntResource(getURI().toString());
        this.templateCall = templateCall;
        this.httpHeaders = httpHeaders;
        this.resourceContext = resourceContext;
        this.querySolutionMap = templateCall.getQuerySolutionMap();
        this.querySolutionMap.add(SPIN.THIS_VAR_NAME, ontResource); // ?this
        
        if (templateCall.getTemplate().getQuery() == null)
        {
            if (log.isErrorEnabled()) log.error("ldt:query value for template '{}' is missing", templateCall.getTemplate());
            throw new OntologyException("ldt:query value for template '" + templateCall.getTemplate() + "' is missing");
        }
        Resource queryType = templateCall.getTemplate().getQuery().getPropertyResourceValue(RDF.type);
        if (queryType == null)
        {
            if (log.isErrorEnabled()) log.error("ldt:query value for template '{}' does not have a type", templateCall.getTemplate());
            throw new OntologyException("ldt:query value of template '" + templateCall.getTemplate() + "' does not have a type");
        }
        if (queryType.canAs(org.spinrdf.model.Template.class)) // ldt:query value is a spin:Template call, not sp:Query
        {
            org.spinrdf.model.Template queryTemplate = queryType.as(org.spinrdf.model.Template.class);
            this.query = new ParameterizedSparqlString(ARQ2SPIN.getTextOnly(queryTemplate.getBody()), querySolutionMap, uriInfo.getBaseUri().toString()).asQuery();
        }
        else
            this.query = new ParameterizedSparqlString(ARQ2SPIN.getTextOnly(templateCall.getTemplate().getQuery()), querySolutionMap, uriInfo.getBaseUri().toString()).asQuery();
        
        if (templateCall.getTemplate().getUpdate() != null)
        {
            Resource updateType = templateCall.getTemplate().getUpdate().getPropertyResourceValue(RDF.type);
            if (updateType == null)
            {
                if (log.isErrorEnabled()) log.error("ldt:update value for template '{}' does not have a type", templateCall.getTemplate());
                throw new OntologyException("ldt:update value for template '" + templateCall.getTemplate() + "' does not have a type");
            }
            if (updateType.canAs(org.spinrdf.model.Template.class)) // ldt:update value is a spin:Template call, not sp:Update
            {
                org.spinrdf.model.Template updateTemplate = updateType.as(org.spinrdf.model.Template.class);
                this.update = new ParameterizedSparqlString(ARQ2SPIN.getTextOnly(updateTemplate.getBody()), querySolutionMap, uriInfo.getBaseUri().toString()).asUpdate();
            }
            else
                this.update = new ParameterizedSparqlString(ARQ2SPIN.getTextOnly(templateCall.getTemplate().getUpdate()), querySolutionMap, uriInfo.getBaseUri().toString()).asUpdate();
        }
        else
            this.update = null;

        if (log.isDebugEnabled()) log.debug("Constructing ResourceBase with matched Template: {}", templateCall.getTemplate());
    }
    
    /**
     * Returns sub-resource instance.
     * By default matches any path.
     * 
     * @return resource object
     */
    @Path("{path: .+}")
    @Override
    public Object getSubResource()
    {
        if (getTemplateCall().getTemplate().getLoadClass() != null)
        {
            Resource javaClass = getTemplateCall().getTemplate().getLoadClass();
            if (!javaClass.isURIResource())
            {
                if (log.isErrorEnabled()) log.error("ldt:loadClass value of template '{}' is not a URI resource", getTemplateCall().getTemplate());
                throw new OntologyException("ldt:loadClass value of template '" + getTemplateCall().getTemplate() + "' is not a URI resource");
            }

            Class clazz = Loader.loadClass(javaClass.getURI());
            if (clazz == null)
            {
                if (log.isErrorEnabled()) log.error("Java class with URI '{}' could not be loaded", javaClass.getURI());
                throw new OntologyException("Java class with URI '" + javaClass.getURI() + "' not found");
            }

            if (log.isDebugEnabled()) log.debug("Loading Java class with URI: {}", javaClass.getURI());
            return getResourceContext().getResource(clazz);
        }

        return this;
    }

    /**
     * Handles POST method. Appends the submitted RDF representation to the application's dataset.
     * 
     * @param dataset the RDF payload
     * @return response 200 OK
     */
    @Override
    public Response post(Dataset dataset)
    {
        if (dataset == null) throw new IllegalArgumentException("Dataset cannot be null");

        getService().getDatasetAccessor().add(dataset.getDefaultModel());
        
        Iterator it = dataset.listNames();
        while (it.hasNext())
        {
            String graphName = it.next();
            getService().getDatasetAccessor().add(graphName, dataset.getNamedModel(graphName));
        }
        
        return Response.ok().build();
    }

    /**
     * Handles PUT method. Deletes the resource description (if any) and
     * appends the submitted RDF representation to the application's dataset.
     * 
     * @param dataset RDF payload
     * @return response 201 Created if resource did not exist, 200 OK if it did
     */
    @Override
    public Response put(Dataset dataset)
    {
        try
        {
            delete();
            
            return post(dataset);
        }
        catch (NotFoundException ex)
        {
            post(dataset);
            
            return Response.created(getURI()).build();
        }
    }

    /**
     * Handles DELETE method, deletes the RDF representation of this resource from the application's dataset, and
     * returns response.
     * 
     * @return response 204 No Content
     */
    @Override
    public Response delete()
    {
        get(); // will throw NotFoundException if resource does not exist (query result is empty)
        
        if (getUpdate() == null) return Response.status(501).build(); // 501 Not Implemented
            
        if (log.isDebugEnabled()) log.debug("DELETE UpdateRequest: {}", getUpdate());
        getService().getEndpointAccessor().update(getUpdate(), Collections.emptyList(), Collections.emptyList());

        return Response.noContent().build(); // subsequent GET might still return 200 OK, depending on query solution map
    }

    
    /**
     * Returns variable bindings for description query.
     * 
     * @return map with variable bindings
     */
    public QuerySolutionMap getQuerySolutionMap()
    {
        return querySolutionMap;
    }
        
    /**
     * Creates response builder from response dataset.
     * Adds matched sitemap class as affordance metadata in 
Link
header. * * @param dataset response RDF dataset * @return response builder */ @Override public ResponseBuilder getResponseBuilder(Dataset dataset) { ResponseBuilder rb = super.getResponseBuilder(dataset); rb.cacheControl(getCacheControl()); rb.header("Link", new Link(URI.create(getTemplateCall().getTemplate().getURI()), LDT.template.getURI(), null)); rb.header("Link", new Link(URI.create(getApplication().getOntology().getURI()), LDT.ontology.getURI(), null)); rb.header("Link", new Link(getUriInfo().getBaseUri(), LDT.base.getURI(), null)); Reasoner reasoner = getTemplateCall().getTemplate().getOntModel().getSpecification().getReasoner(); if (reasoner instanceof GenericRuleReasoner) { GenericRuleReasoner grr = (GenericRuleReasoner)reasoner; rb.header("Rules", RulePrinter.print(grr.getRules())); // grr.getRules().toString() - prevented by JENA-1030 bug } return rb; } /** * Creates response builder from response model. * Adds matched sitemap class as affordance metadata in
Link
header. * * @param model response RDF model * @return response builder */ @Override public ResponseBuilder getResponseBuilder(Model model) { ResponseBuilder rb = super.getResponseBuilder(model); rb.cacheControl(getCacheControl()); rb.header("Link", new Link(URI.create(getTemplateCall().getTemplate().getURI()), LDT.template.getURI(), null)); rb.header("Link", new Link(URI.create(getApplication().getOntology().getURI()), LDT.ontology.getURI(), null)); rb.header("Link", new Link(getUriInfo().getBaseUri(), LDT.base.getURI(), null)); Reasoner reasoner = getTemplateCall().getTemplate().getOntModel().getSpecification().getReasoner(); if (reasoner instanceof GenericRuleReasoner) { GenericRuleReasoner grr = (GenericRuleReasoner)reasoner; rb.header("Rules", RulePrinter.print(grr.getRules())); // grr.getRules().toString() - prevented by JENA-1030 bug } return rb; } /** * Content languages supported by the matching LDT template. * @see Content-Language * * @return list of locales */ @Override public List getLanguages() { return getTemplateCall().getTemplate().getLanguages(); } /** * Returns this resource as ontology resource. * * @return ontology resource */ public OntResource getOntResource() { return ontResource; } /** * Returns LDT template that the URI of this resource matches. * If the request URI did not match any template, 404 Not Found was returned. * * @return ontology class */ @Override public TemplateCall getTemplateCall() { return templateCall; } /** * Returns the cache control of this resource, if specified. * The control value can be specified as ldt:cacheControl on templates in the sitemap ontology. * * @return cache control object or null, if not specified */ public CacheControl getCacheControl() { return getTemplateCall().getTemplate().getCacheControl(); } /** * Returns query used to retrieve RDF description of this resource. * Query solution bindings are applied by default. * * @return query object with applied solution bindings * @see #getQuerySolutionMap() */ @Override public Query getQuery() { return query; } /** * Returns update used to remove RDF description of this resource. * Query solution bindings are applied by default. * * @return update object with applied solution bindings * @see #getQuerySolutionMap() */ @Override public UpdateRequest getUpdate() { return update; } /** * Returns HTTP headers of the current request. * * @return header object */ public HttpHeaders getHttpHeaders() { return httpHeaders; } public ResourceContext getResourceContext() { return resourceContext; } @Override public com.atomgraph.processor.model.Application getApplication() { return application; } @Override public Ontology getOntology() { return ontology; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy