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

eu.fbk.knowledgestore.server.http.jaxrs.Files Maven / Gradle / Ivy

Go to download

The HTTP server module (ks-server-http) implements the Web API of the KnowledgeStore, which includes the two CRUD and SPARQL endpoints. The CRUD Endpoint supports the retrieval and manipulation of semi-structured data about resource, mention, entity and axiom records (encoded in RDF, possibly using JSONLD), and the upload / download of resource representation. The SPARQL Endpoint supports SPARQL SELECT, CONSTRUCT, DESCRIBE and ASK queries according to the W3C SPARQL protocol. The two endpoints are implemented on top of a component implementing the KnowledgeStore Java API (the Store interface), which can be either the the KnowledgeStore frontend (ks-frontend) or the Java Client. The implementation of the module is based on the Jetty Web sever (run in embedded mode) and the Jersey JAX-RS implementation. Reference documentation of the Web API is automatically generated using the Enunciate tool.

There is a newer version: 1.7.1
Show newest version
package eu.fbk.knowledgestore.server.http.jaxrs;

import java.io.InputStream;
import java.util.Date;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.codehaus.enunciate.jaxrs.ResponseCode;
import org.codehaus.enunciate.jaxrs.ResponseHeader;
import org.codehaus.enunciate.jaxrs.ResponseHeaders;
import org.codehaus.enunciate.jaxrs.StatusCodes;
import org.codehaus.enunciate.jaxrs.TypeHint;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.fbk.knowledgestore.Operation;
import eu.fbk.knowledgestore.Outcome;
import eu.fbk.knowledgestore.data.Data;
import eu.fbk.knowledgestore.data.Record;
import eu.fbk.knowledgestore.data.Representation;
import eu.fbk.knowledgestore.data.Stream;
import eu.fbk.knowledgestore.internal.jaxrs.Protocol;
import eu.fbk.knowledgestore.vocabulary.NFO;
import eu.fbk.knowledgestore.vocabulary.NIE;

/**
 * Manages a collection of files.
 * 

* This root REST resource allows the download, upload and removal of resource files in the * KnowledgeStore. *

*

* File download is performed via GET requests and supports caching and conditional requests based * on file modification date and ETag (MD5 hash of file content); file metadata is taken from the * ks:storedAs resource property and is returned via standard HTTP headers. *

*

* File upload can be performed via PUT requests whose body is the file content, or via POST * request with multipart/form-data body; the PUT approach should be preferred in client * libraries supporting the PUT operation, whereas the POST approach can be used when uploading * from an HTML form using a browser. File metadata can be supplied either via standard HTTP * headers or via custom X-KS-Content-Meta key-value headers. *

*

* File deletion can be performed either with DELETE requests or via POST requests lacking a * file form parameter. *

*/ @Path("/" + Protocol.PATH_REPRESENTATIONS) public class Files extends Resource { private static final Logger LOGGER = LoggerFactory.getLogger(Files.class); /** * Retrieves a file. Technically, this operation returns the representation of a file HTTP * resource whose URI is fully determined by the id query parameter that encodes * the URI of the KnowledgeStore resource the file refers to. The operation: *
    *
  • supports the use of HTTP preconditions in the form of If-Match, If-None-Match, * If-Modified-Since, If-Unmodified-Since headers;
  • *
  • allows the client to accept only representations in a certain MIME type, via Accept * header;
  • *
  • can enable / disable the use of server-side caches via header Cache-Control (specify * no-cache or no-store to disable caches).
  • *
* * @param id * the URI identifier of the KnowledgeStore resource (mandatory) * @param accept * the MIME type accepted by the client (optional); a 406 NOT ACCEPTABLE response * will be returned if the file representation has a non-compatible MIME type * @return the file content, on success, encoded using the specific file MIME type * @throws Exception * on error */ @GET @Produces("*/*") @TypeHint(InputStream.class) @StatusCodes({ @ResponseCode(code = 200, condition = "if the file is found and its representation " + "is returned"), @ResponseCode(code = 404, condition = "if the requested file does not exist (the " + "associated resource may exist or not)") }) @ResponseHeaders({ @ResponseHeader(name = "Content-Language", description = "the 2-letters ISO 639 " + "language code for file representation, if known"), @ResponseHeader(name = "Content-Disposition", description = "a content disposition " + "directive for browsers, including the suggested file name and date for " + "saving the file"), @ResponseHeader(name = "Content-MD5", description = "the MD5 hash of the file " + "representation") }) public Response get(@QueryParam(Protocol.PARAMETER_ID) final URI id, @HeaderParam(HttpHeaders.ACCEPT) @DefaultValue(MediaType.WILDCARD) final String accept) throws Exception { // Check query string parameters checkNotNull(id, Outcome.Status.ERROR_INVALID_INPUT, "Missing 'id' query parameter"); // Retrieve the file to return final Representation representation = getSession() // .download(id) // .timeout(getTimeout()) // .accept(accept.split(",")) // .caching(isCachingEnabled()) // .exec(); // Fail if file does not exist checkNotNull(representation, Outcome.Status.ERROR_OBJECT_NOT_FOUND, "Specified file does not exist"); closeOnCompletion(representation); // Retrieve file metadata and build the resulting Content-Disposition header final Record metadata = representation.getMetadata(); final Long fileSize = metadata.getUnique(NFO.FILE_SIZE, Long.class, null); final String fileName = metadata.getUnique(NFO.FILE_NAME, String.class, null); final String mimeType = metadata.getUnique(NIE.MIME_TYPE, String.class, null); final Date lastModified = extractLastModified(representation); final String tag = extractMD5(representation); final ContentDisposition disposition = ContentDisposition.type("attachment") .fileName(fileName) // .modificationDate(lastModified) // .size(fileSize != null ? fileSize : -1) // .build(); // Validate client preconditions, do negotiation and handle probe requests init(false, mimeType, lastModified, tag); // Stream the file to the client. Note that Content-Length is not set as it will not be // valid after GZIP compression is applied (Jersey should remove it, but it doesn't) return newResponseBuilder(Status.OK, representation, null).header( HttpHeaders.CONTENT_DISPOSITION, disposition).build(); } /** * Creates or updates a file, uploading its content as the entity of the HTTP request. * Technically, this operation stores the representation of a file HTTP resource whose * URI is fully determined by the id query parameter that encodes the URI of the * KnowledgeStore resource the file refers to. The operation: *
    *
  • can result either in the file being created or updated;
  • *
  • supports the use of HTTP preconditions in the form of If-Match, If-None-Match, * If-Modified-Since, If-Unmodified-Since headers;
  • *
  • can supply arbitrary metadata about the file using zero or more occurrences of the * X-KS-Content-Meta property value non-standard header, where properties and values * are encoded using the Turtle syntax.
  • *
* * @param id * the URI identifier of the KnowledgeStore resource (mandatory, must refer to an * existing resource for the request to be valid) * @param representation * the file to store * @return the operation outcome, encoded in one of the supported RDF MIME types * @throws Exception * on error */ @PUT @Consumes(MediaType.WILDCARD) @Produces(Protocol.MIME_TYPES_RDF) @TypeHint(Stream.class) @StatusCodes({ @ResponseCode(code = 200, condition = "if the file has been updated"), @ResponseCode(code = 201, condition = "if the file has been created") }) public Response put(@QueryParam(Protocol.PARAMETER_ID) final URI id, final Representation representation) throws Exception { // Schedule closing of input entity closeOnCompletion(representation); // Check query string parameters checkNotNull(id, Outcome.Status.ERROR_INVALID_INPUT, "Missing 'id' query parameter"); // Setup the UPLOAD operation, returning an error if parameters are wrong final Operation.Upload operation; try { operation = getSession().upload(id).timeout(getTimeout()) .representation(representation); } catch (final RuntimeException ex) { throw newException(Outcome.Status.ERROR_INVALID_INPUT, ex, null); } // Retrieve old file for the same resource Representation oldRepresentation = null; try { oldRepresentation = getSession().download(id).timeout(getTimeout()).exec(); closeOnCompletion(oldRepresentation); } catch (final Throwable ex) { LOGGER.error("Error retrieving current files associated to resource " + id, ex); } // Handle two cases for validating preconditions, doing negotiation and handling probes if (oldRepresentation == null) { // No old file: new file will be stored init(true, null); } else { // Old file exists: check preconditions based on its ETag and last modified final Date getLastModified = extractLastModified(oldRepresentation); final String getTag = extractMD5(oldRepresentation); oldRepresentation.close(); init(true, null, getLastModified, getTag); } // Perform the operation final Outcome outcome = operation.exec(); // Setup the response stream final int httpStatus = outcome.getStatus().getHTTPStatus(); final Stream entity = Stream.create(outcome); // Stream the Outcome result to the client return newResponseBuilder(httpStatus, entity, Protocol.STREAM_OF_OUTCOMES).build(); } /** * Creates, updates or deletes a file, using a multipart form data HTTP entity that is * compatible with the POST submission HTML forms. Technically, the operation targets the * Files HTTP resource (controller), supplying all the data necessary for * uploading the file in a single multipart message. The operation: *
    *
  • can result either in the file being created, updated or deleted (deletion occurs if no * file content is included in the multipart message);
  • *
  • can supply arbitrary metadata about the file using either property = value * form parameters encoded in the multipart message or by sending zero or more occurrences of * the X-KS Content-Meta non-standard property value header; in both cases, * properties and values are encoded using Turtle syntax.
  • *
* * @param formData * a multipart form data entity containing a body part for the id URI * parameter (must denote an existing resource for the request to be valid), a body * part for the file parameter and optional body parts for additional * metadata attributes about the uploaded file * @return the operation outcome, encoded in one of the supported RDF MIME types * @throws Exception * on error */ @POST @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(Protocol.MIME_TYPES_RDF) @TypeHint(Stream.class) @StatusCodes({ @ResponseCode(code = 200, condition = "if the file has been updated or deleted"), @ResponseCode(code = 201, condition = "if the file has been created") }) @ResponseHeaders({ @ResponseHeader(name = "Location", description = "the URI of " + "the created file") }) public Response post(final FormDataMultiPart formData) throws Exception { // Validate preconditions and handle probe requests here, before body is consumed // POST URI does not support GET, hence no tag and last modified init(true, null); // Process the form parameters encoded in the request body URI id = null; Representation representation = null; final Record record = Record.create(); for (final BodyPart bodyPart : formData.getBodyParts()) { // Handle three types of parameters final FormDataBodyPart part = (FormDataBodyPart) bodyPart; final String name = part.getName(); if ("id".equals(name)) { // 'id' parameter: the ID of the resource id = Data.getValueFactory().createURI(name); } else if ("file".equals(name)) { // 'file' parameter: the uploaded file, with some metadata representation = closeOnCompletion(part.getEntityAs(Representation.class)); final ContentDisposition disposition = checkNotNull(part.getContentDisposition(), Outcome.Status.ERROR_INVALID_INPUT, "Missing Content-Disposition header for body part " + part.getName()); final Record metadata = representation.getMetadata(); metadata.set(NFO.FILE_NAME, disposition.getFileName()); metadata.set(NFO.FILE_LAST_MODIFIED, disposition.getModificationDate()); } else { // other parameters: treat them as additional file metadata final URI property = (URI) Data.parseValue(name, Data.getNamespaceMap()); final Value value = Data.parseValue(part.getEntityAs(String.class), Data.getNamespaceMap()); record.add(property, value); } } // Check the ID parameters was supplied checkNotNull(id, Outcome.Status.ERROR_INVALID_INPUT, "Missing 'id' form parameter"); assert id != null; // If a file was uploaded, extend it with the additional metadata if (representation != null) { // Protocol.decodeMetadata(encodedMetadata, metadata); final Record metadata = representation.getMetadata(); for (final URI property : record.getProperties()) { metadata.set(property, record.get(property)); } } // Perform the operation final Outcome outcome = getSession().upload(id).timeout(getTimeout()) .representation(representation).exec(); // Setup the response stream final int httpStatus = outcome.getStatus().getHTTPStatus(); final Stream entity = Stream.create(outcome); // Stream the result to the client return newResponseBuilder(httpStatus, entity, Protocol.STREAM_OF_OUTCOMES).build(); } /** * Deletes a file. Technically, the operation targets a file HTTP resource whose URI is * fully determined by the id query parameter that encodes the URI of the * KnowledgeStore resource the file refers to. The operation supports the use of HTTP * preconditions in the form of If-Match, If-None-Match, If-Modified-Since, * If-Unmodified-Since headers. * * @param id * the URI identifier of the KnowledgeStore resource (mandatory) * @return the operation outcome, encoded in one of the supported RDF MIME types * @throws Exception * on error */ @DELETE @Produces(Protocol.MIME_TYPES_RDF) @TypeHint(Outcome.class) @StatusCodes({ @ResponseCode(code = 200, condition = "if the file has been deleted"), @ResponseCode(code = 404, condition = "if the file does not exist (the associated " + "resource may exist or not)") }) public Response delete(@QueryParam(Protocol.PARAMETER_ID) final URI id) throws Exception { // Check query string parameters checkNotNull(id, Outcome.Status.ERROR_INVALID_INPUT, "Missing 'id' query parameter"); // Retrieve the file to delete and fail if it does not exist final Representation oldRepresentation = getSession().download(id).timeout(getTimeout()) .exec(); closeOnCompletion(oldRepresentation); checkNotNull(oldRepresentation, Outcome.Status.ERROR_OBJECT_NOT_FOUND, "Specified file does not exist."); // Retrieve ETag and last modified for validation of preconditions final Date getLastModified = extractLastModified(oldRepresentation); final String getTag = extractMD5(oldRepresentation); oldRepresentation.close(); // Setup the UPLOAD operation, returning an error if parameters are wrong final Operation.Upload operation; try { operation = getSession().upload(id).timeout(getTimeout()).representation(null); } catch (final RuntimeException ex) { throw newException(Outcome.Status.ERROR_INVALID_INPUT, ex, null); } // Validate preconditions, do negotiation and handle probing init(true, null, getLastModified, getTag); // Perform the operation final Outcome outcome = operation.exec(); // Setup the resulting stream final int httpStatus = outcome.getStatus().getHTTPStatus(); final Stream entity = Stream.create(outcome); // Stream the Outcome result to the client return newResponseBuilder(httpStatus, entity, Protocol.STREAM_OF_OUTCOMES).build(); } private static Date extractLastModified(final Representation representation) { final Record metadata = representation.getMetadata(); return metadata.getUnique(NFO.FILE_LAST_MODIFIED, Date.class, null); } private static String extractMD5(final Representation representation) { final Record metadata = representation.getMetadata(); final Record hash = metadata.getUnique(NFO.HAS_HASH, Record.class, null); if (hash != null && "MD5".equals(hash.getUnique(NFO.HASH_ALGORITHM, String.class, null))) { return hash.getUnique(NFO.HASH_VALUE, String.class, null); } return null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy