org.opencastproject.util.RestUtil Maven / Gradle / Ivy
/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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 org.opencastproject.util;
import static org.opencastproject.util.Jsons.obj;
import static org.opencastproject.util.Jsons.p;
import static org.opencastproject.util.data.Monadics.mlist;
import static org.opencastproject.util.data.Option.option;
import static org.opencastproject.util.data.Tuple.tuple;
import static org.opencastproject.util.data.functions.Strings.split;
import static org.opencastproject.util.data.functions.Strings.trimToNil;
import org.opencastproject.job.api.JaxbJob;
import org.opencastproject.job.api.Job;
import org.opencastproject.rest.ErrorCodeException;
import org.opencastproject.rest.RestConstants;
import org.opencastproject.systems.OpencastConstants;
import org.opencastproject.util.Jsons.Obj;
import org.opencastproject.util.data.Function;
import org.opencastproject.util.data.Monadics;
import org.opencastproject.util.data.Option;
import org.opencastproject.util.data.Tuple;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.ComponentContext;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.regex.Pattern;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/** Utility functions for REST endpoints. */
public final class RestUtil {
private RestUtil() {
}
/**
* Return the endpoint's server URL and the service path by extracting the relevant parameters from the
* ComponentContext.
*
* @param cc
* ComponentContext to get configuration from
* @return (serverUrl, servicePath)
* @throws Error
* if the service path is not configured for this component
*/
public static Tuple getEndpointUrl(ComponentContext cc) {
return getEndpointUrl(cc, OpencastConstants.SERVER_URL_PROPERTY, RestConstants.SERVICE_PATH_PROPERTY);
}
/**
* Return the endpoint's server URL and the service path by extracting the relevant parameters from the
* ComponentContext.
*
* @param cc
* ComponentContext to get configuration from
* @param serverUrlKey
* Configuration key for the server URL
* @param servicePathKey
* Configuration key for the service path
* @return (serverUrl, servicePath)
* @throws Error
* if the service path is not configured for this component
*/
public static Tuple getEndpointUrl(ComponentContext cc, String serverUrlKey, String servicePathKey) {
final String serverUrl = option(cc.getBundleContext().getProperty(serverUrlKey)).getOrElse(
UrlSupport.DEFAULT_BASE_URL);
final String servicePath = option((String) cc.getProperties().get(servicePathKey)).getOrElse(
Option. error(RestConstants.SERVICE_PATH_PROPERTY + " property not configured"));
return tuple(serverUrl, servicePath);
}
/** Create a file response. */
public static Response.ResponseBuilder fileResponse(File f, String contentType, Option fileName) {
final Response.ResponseBuilder b = Response.ok(f).header("Content-Type", contentType)
.header("Content-Length", f.length());
for (String fn : fileName)
b.header("Content-Disposition", "attachment; filename=" + fn);
return b;
}
/** Create a file response. */
public static Response.ResponseBuilder fileResponse(File f, Option contentType, Option fileName) {
final Response.ResponseBuilder b = Response.ok(f).header("Content-Length", f.length());
for (String t : contentType)
b.header("Content-Type", t);
for (String fn : fileName)
b.header("Content-Disposition", "attachment; filename=" + fn);
return b;
}
/**
* create a partial file response
*
* @param f
* the requested file
* @param contentType
* the contentType to send
* @param fileName
* the filename to send
* @param rangeHeader
* the range header
* @return the Responsebuilder
* @throws IOException
* if something goes wrong
*/
public static Response.ResponseBuilder partialFileResponse(File f, String contentType, Option fileName,
String rangeHeader) throws IOException {
String rangeValue = rangeHeader.trim().substring("bytes=".length());
long fileLength = f.length();
long start;
long end;
if (rangeValue.startsWith("-")) {
end = fileLength - 1;
start = fileLength - 1 - Long.parseLong(rangeValue.substring("-".length()));
} else {
String[] range = rangeValue.split("-");
start = Long.parseLong(range[0]);
end = range.length > 1 ? Long.parseLong(range[1]) : fileLength - 1;
}
if (end > fileLength - 1) {
end = fileLength - 1;
}
// send partial response status code
Response.ResponseBuilder response = Response.status(206);
if (start <= end) {
long contentLength = end - start + 1;
response.header("Accept-Ranges", "bytes");
response.header("Connection", "Close");
response.header("Content-Length", contentLength + "");
response.header("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
response.header("Content-Type", contentType);
response.entity(new ChunkedFileInputStream(f, start, end));
}
return response;
}
/**
* Create a stream response.
*
* @deprecated use
* {@link org.opencastproject.util.RestUtil.R#ok(java.io.InputStream, String, org.opencastproject.util.data.Option, org.opencastproject.util.data.Option)}
* instead
*/
@Deprecated
public static Response.ResponseBuilder streamResponse(InputStream in, String contentType, Option streamLength,
Option fileName) {
final Response.ResponseBuilder b = Response.ok(in).header("Content-Type", contentType);
for (Long l : streamLength)
b.header("Content-Length", l);
for (String fn : fileName)
b.header("Content-Disposition", "attachment; filename=" + fn);
return b;
}
/**
* Return JSON if format
== json, XML else.
*
* @deprecated use {@link #getResponseType(String)}
*/
@Deprecated
public static MediaType getResponseFormat(String format) {
return "json".equalsIgnoreCase(format) ? MediaType.APPLICATION_JSON_TYPE : MediaType.APPLICATION_XML_TYPE;
}
/** Return JSON if type
== json, XML else. */
public static MediaType getResponseType(String type) {
return "json".equalsIgnoreCase(type) ? MediaType.APPLICATION_JSON_TYPE : MediaType.APPLICATION_XML_TYPE;
}
private static final Function CSV_SPLIT = split(Pattern.compile(","));
/**
* Split a comma separated request param into a list of trimmed strings discarding any blank parts.
*
* x=comma,separated,,%20value -> ["comma", "separated", "value"]
*/
public static Monadics.ListMonadic splitCommaSeparatedParam(Option param) {
for (String p : param)
return mlist(CSV_SPLIT.apply(p)).bind(trimToNil);
return mlist();
}
public static String generateErrorResponse(ErrorCodeException e) {
Obj json = obj(p("error", obj(p("code", e.getErrorCode()), p("message", StringUtils.trimToEmpty(e.getMessage())))));
return json.toJson();
}
/** Response builder functions. */
public static final class R {
private R() {
}
public static Response ok() {
return Response.ok().build();
}
public static Response ok(Object entity) {
return Response.ok().entity(entity).build();
}
public static Response ok(boolean entity) {
return Response.ok().entity(Boolean.toString(entity)).build();
}
public static Response ok(Jsons.Obj json) {
return Response.ok().entity(json.toJson()).type(MediaType.APPLICATION_JSON_TYPE).build();
}
public static Response ok(Job job) {
return Response.ok().entity(new JaxbJob(job)).build();
}
public static Response ok(MediaType type, Object entity) {
return Response.ok(entity, type).build();
}
/**
* Create a response with status OK from a stream.
*
* @param in
* the input stream to read from
* @param contentType
* the content type to set the Content-Type response header to
* @param streamLength
* an optional value for the Content-Length response header
* @param fileName
* an optional file name for the Content-Disposition response header
*/
public static Response ok(InputStream in, String contentType, Option streamLength, Option fileName) {
return ok(in, option(contentType), streamLength, fileName);
}
/**
* Create a response with status OK from a stream.
*
* @param in
* the input stream to read from
* @param contentType
* the content type to set the Content-Type response header to
* @param streamLength
* an optional value for the Content-Length response header
* @param fileName
* an optional file name for the Content-Disposition response header
*/
public static Response ok(InputStream in, Option contentType, Option streamLength,
Option fileName) {
final Response.ResponseBuilder b = Response.ok(in);
for (String t : contentType)
b.header("Content-Type", t);
for (Long l : streamLength)
b.header("Content-Length", l);
for (String fn : fileName)
b.header("Content-Disposition", "attachment; filename=" + fn);
return b.build();
}
public static Response created(URI location) {
return Response.created(location).build();
}
public static Response notFound() {
return Response.status(Response.Status.NOT_FOUND).build();
}
public static Response notFound(Object entity) {
return Response.status(Response.Status.NOT_FOUND).entity(entity).build();
}
public static Response notFound(Object entity, MediaType type) {
return Response.status(Response.Status.NOT_FOUND).entity(entity).type(type).build();
}
public static Response locked() {
return Response.status(423).build();
}
public static Response serverError() {
return Response.serverError().build();
}
public static Response conflict() {
return Response.status(Response.Status.CONFLICT).build();
}
public static Response noContent() {
return Response.noContent().build();
}
public static Response badRequest() {
return Response.status(Response.Status.BAD_REQUEST).build();
}
public static Response badRequest(String msg) {
return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
}
public static Response forbidden() {
return Response.status(Response.Status.FORBIDDEN).build();
}
public static Response forbidden(String msg) {
return Response.status(Response.Status.FORBIDDEN).entity(msg).build();
}
public static Response conflict(String msg) {
return Response.status(Response.Status.CONFLICT).entity(msg).build();
}
/**
* create a partial file response
*
* @param f
* the requested file
* @param contentType
* the contentType to send
* @param fileName
* the filename to send
* @param rangeHeader
* the range header
* @return the Responsebuilder
* @throws IOException
* if something goes wrong
*/
/**
* Creates a precondition failed status response
*
* @return a precondition failed status response
*/
public static Response preconditionFailed() {
return Response.status(Response.Status.PRECONDITION_FAILED).build();
}
/**
* Creates a precondition failed status response with a message
*
* @param message
* The message body
* @return a precondition failed status response with a message
*/
public static Response preconditionFailed(String message) {
return Response.status(Response.Status.PRECONDITION_FAILED).entity(message).build();
}
}
}