/** * 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.index.service.util; import static com.entwinemedia.fn.data.json.Jsons.arr; import static com.entwinemedia.fn.data.json.Jsons.f; import static com.entwinemedia.fn.data.json.Jsons.obj; import static com.entwinemedia.fn.data.json.Jsons.v; import static java.lang.String.format; import org.opencastproject.matterhorn.search.SortCriterion; import org.opencastproject.matterhorn.search.impl.SortCriterionImpl; import org.opencastproject.util.DateTimeSupport; import org.opencastproject.util.data.Tuple; import com.entwinemedia.fn.Fx; import com.entwinemedia.fn.data.json.JValue; import com.entwinemedia.fn.data.json.SimpleSerializer; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.StreamingOutput; /** * Utils method for the Rest Endpoint implementation */ public final class RestUtils { private static final Logger logger = LoggerFactory.getLogger(RestUtils.class); private static final SimpleSerializer serializer = new SimpleSerializer(); private RestUtils() { } /** * Create an OK (200) response with the given JSON as body * * @param json * the JSON string to add to the response body. * @return an OK response */ public static Response okJson(JValue json) { return Response.ok(stream(serializer.fn.toJson(json)), MediaType.APPLICATION_JSON_TYPE).build(); } /** * Create an CONFLICT (409) response with the given JSON as body * * @param json * the JSON string to add to the response body. * @return an OK response */ public static Response conflictJson(JValue json) { return Response.status(Status.CONFLICT).entity(stream(serializer.fn.toJson(json))) .type(MediaType.APPLICATION_JSON_TYPE).build(); } /** * Create a NOT FOUND (404) response with the given messages and arguments * * @param msg * @param args * @return a NOT FOUND response */ public static Response notFound(String msg, Object... args) { return Response.status(Status.NOT_FOUND).entity(format(msg, args)).type(MediaType.TEXT_PLAIN_TYPE).build(); } /** * Create a INTERNAL SERVER ERROR (500) response with the given messages and arguments * * @param msg * @param args * @return an INTERNAL SERVER ERROR response */ public static Response serverError(String msg, Object... args) { return Response.status(Status.INTERNAL_SERVER_ERROR).entity(format(msg, args)).type(MediaType.TEXT_PLAIN_TYPE) .build(); } /** * Return the given list of value with the standard format for JSON list value with offset, limit and total * information. The JSON object in the response body has the following format: * * <pre> * { * results: [ * // array containing all the object from the given list * ], * count: 12, // The number of item returned (size of the given list) * offset: 2, // The result offset (given parameter) * limit: 12, // The maximal size of the list (given parameter) * total: 123 // The total number of items available in the system (given parameter) * } * </pre> * * @param jsonList * The list of value to return * @param offset * The result offset * @param limit * The maximal list size * @param total * The amount of available items in the system * @return a {@link Response} with an JSON object formatted like above as body. * @throws IllegalArgumentException * if the value list is null */ public static Response okJsonList(List<JValue> jsonList, int offset, int limit, long total) { if (jsonList == null) throw new IllegalArgumentException("The list of value must not be null."); JValue response = obj(f("results", arr(jsonList)), f("count", v(jsonList.size())), f("offset", v(offset)), f("limit", v(limit)), f("total", v(total))); return okJson(response); } /** * Create a streaming response entity. Pass it as an entity parameter to one of the response builder methods like * {@link org.opencastproject.util.RestUtil.R#ok(Object)}. */ public static StreamingOutput stream(final Fx<OutputStream> out) { return new StreamingOutput() { @Override public void write(OutputStream s) throws IOException, WebApplicationException { try (final BufferedOutputStream bs = new BufferedOutputStream(s)) { out.apply(bs); } } }; } /** * Parse a sort query parameter to a set of {@link SortCriterion}. The parameter has to be of the following form: * {@code <field name>:ASC|DESC} * * @param sort * the parameter string to parse * @return a set of sort criterion, never {@code null} */ public static Set<SortCriterion> parseSortQueryParameter(String sort) throws WebApplicationException { Set<SortCriterion> sortOrders = new HashSet<SortCriterion>(); StringTokenizer tokenizer = new StringTokenizer(sort, ","); while (tokenizer.hasMoreTokens()) { try { sortOrders.add(SortCriterionImpl.parse(tokenizer.nextToken())); } catch (IllegalArgumentException e) { throw new WebApplicationException(Status.BAD_REQUEST); } } return sortOrders; } /** * Parse the UTC format date range string to two Date objects to represent a range of dates. * <p> * Sample UTC date range format string:<br> * i.e. yyyy-MM-ddTHH:mm:ssZ/yyyy-MM-ddTHH:mm:ssZ e.g. 2014-09-27T16:25Z/2014-09-27T17:55Z * </p> * * @param fromToDateRange * The string that represents the UTC formed date range. * @return A Tuple with the two Dates * @throws IllegalArgumentException * Thrown if the input string is malformed */ public static Tuple<Date, Date> getFromAndToDateRange(String fromToDateRange) { String[] dates = fromToDateRange.split("/"); if (dates.length != 2) { logger.warn("The date range '{}' is malformed", fromToDateRange); throw new IllegalArgumentException("The date range string is malformed"); } Date fromDate = null; try { fromDate = new Date(DateTimeSupport.fromUTC(dates[0])); } catch (Exception e) { logger.warn("Unable to parse from date parameter '{}'", dates[0]); throw new IllegalArgumentException("Unable to parse from date parameter"); } Date toDate = null; try { toDate = new Date(DateTimeSupport.fromUTC(dates[1])); } catch (Exception e) { logger.warn("Unable to parse to date parameter '{}'", dates[1]); throw new IllegalArgumentException("Unable to parse to date parameter"); } return new Tuple<Date, Date>(fromDate, toDate); } /** * Parse the filter to a {@link Map} * * @param filter * the filters * @return the map of filter name and values */ public static Map<String, String> parseFilter(String filter) { Map<String, String> filters = new HashMap<>(); if (StringUtils.isNotBlank(filter)) { for (String f : filter.split(",")) { String[] filterTuple = f.split(":"); if (filterTuple.length < 2) { logger.debug("No value for filter '{}' in filters list: {}", filterTuple[0], filter); continue; } filters.put(filterTuple[0], f.substring(filterTuple[0].length() + 1)); } } return filters; } public static String getJsonString(JValue json) throws WebApplicationException, IOException { OutputStream output = new OutputStream() { private StringBuilder string = new StringBuilder(); @Override public void write(int b) throws IOException { this.string.append((char) b); } @Override public String toString() { return this.string.toString(); } }; stream(serializer.fn.toJson(json)).write(output); return output.toString(); } /** * Get a {@link String} value from a {@link JValue} ignoring errors. * * @param json * The {@link JValue} to convert to a {@link String} * @return The {@link String} representation of the {@link JValue} or an empty string if there was an error. */ public static String getJsonStringSilent(JValue json) { try { return getJsonString(json); } catch (Exception e) { return ""; } } }