/* * Copyright 2015-2017 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * 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 org.hawkular.alerts.rest; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import org.hawkular.alerts.api.exception.NotFoundException; import org.hawkular.alerts.api.model.paging.Page; import org.hawkular.alerts.api.model.paging.PageContext; import org.hawkular.alerts.rest.json.Link; import org.jboss.logging.Logger; import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; /** * Helper class used to build REST responses and deal with errors. * * @author Lucas Ponce */ public class ResponseUtil { public static Response internalError(Exception e) { if (e.getMessage() == null) { return internalError(e.toString()); } else { return internalError(e.getMessage()); } } public static Response internalError(String message) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(new ApiError(message)).type(APPLICATION_JSON_TYPE).build(); } public static Response notFound(String message) { return Response.status(Response.Status.NOT_FOUND) .entity(new ApiError(message)).type(APPLICATION_JSON_TYPE).build(); } public static Response ok(Object entity) { return Response.status(Response.Status.OK).entity(entity).type(APPLICATION_JSON_TYPE).build(); } public static <T> Response paginatedOk(Page<T> page, UriInfo uri) { //extract the data out of the page List<T> data = new ArrayList<>(page); Response.ResponseBuilder response = Response.status(Response.Status.OK).entity(data); createPagingHeader(response, uri, page); return response.build(); } public static Response ok() { return Response.status(Response.Status.OK).type(APPLICATION_JSON_TYPE).build(); } public static Response badRequest(String message) { return Response.status(Response.Status.BAD_REQUEST) .entity(new ApiError(message)).type(APPLICATION_JSON_TYPE).build(); } public static Response onException(Exception e, Logger log) { if (e instanceof NotFoundException) { return notFound(e.getMessage()); } if (null != e.getCause() && e.getCause() instanceof NotFoundException) { return notFound(e.getCause().getMessage()); } if (e instanceof IllegalArgumentException) { return badRequest(e.getMessage()); } if (null != e.getCause() && e.getCause() instanceof IllegalArgumentException) { return badRequest(e.getCause().getMessage()); } if (null != log) { log.debug(e.getMessage(), e); } return internalError(e); } public static void populateQueryParamsMap(Class<?> clazz, Map<String, Set<String>> queryParamValidationMap) { Arrays.asList(clazz.getDeclaredMethods()).stream() .filter(m -> m.isAnnotationPresent(QueryParamValidation.class)) .forEach(m -> { String name = m.getAnnotation(QueryParamValidation.class).name(); Set<String> queryParams = Arrays.asList(m.getParameters()).stream() .filter(p -> p.isAnnotationPresent(QueryParam.class)) .map(p -> p.getAnnotation(QueryParam.class).value()) .collect(Collectors.toSet()); queryParamValidationMap.put(name, queryParams); }); } public static void checkForUnknownQueryParams(final UriInfo uri, final Set<String> expected) { Set<String> received = uri.getQueryParameters().keySet(); if (received.contains("ignoreUnknownQueryParams")) { return; } Set<String> unknown = received.stream() .filter(p -> !expected.contains(p) && !RequestUtil.PARAMS_PAGING.contains(p)) .collect(Collectors.toSet()); if (!unknown.isEmpty()) { String message = "Unknown Query Parameter(s): " + unknown.toString(); throw new IllegalArgumentException(message); } return; } /** * Create the paging headers for collections and attach them to the passed builder. Those are represented as * <i>Link:</i> http headers that carry the URL for the pages and the respective relation. * <p>In addition a <i>X-Total-Count</i> header is created that contains the whole collection size.</p> * * @param builder The ResponseBuilder that receives the headers * @param uriInfo The uriInfo of the incoming request to build the urls * @param resultList The collection with its paging information */ public static void createPagingHeader(final Response.ResponseBuilder builder, final UriInfo uriInfo, final Page<?> resultList) { UriBuilder uriBuilder; PageContext pc = resultList.getPageContext(); int page = pc.getPageNumber(); List<Link> links = new ArrayList<>(); if (pc.isLimited() && resultList.getTotalSize() > (pc.getPageNumber() + 1) * pc.getPageSize()) { int nextPage = page + 1; uriBuilder = uriInfo.getRequestUriBuilder(); // adds ?q, ?per_page, ?page, etc. if needed uriBuilder.replaceQueryParam("page", nextPage); links.add(new Link("next", uriBuilder.build().toString())); } if (page > 0) { int prevPage = page - 1; uriBuilder = uriInfo.getRequestUriBuilder(); // adds ?q, ?per_page, ?page, etc. if needed uriBuilder.replaceQueryParam("page", prevPage); links.add(new Link("prev", uriBuilder.build().toString())); } // A link to the last page if (pc.isLimited()) { long lastPage = resultList.getTotalSize() / pc.getPageSize(); if (resultList.getTotalSize() % pc.getPageSize() == 0) { lastPage -= 1; } uriBuilder = uriInfo.getRequestUriBuilder(); // adds ?q, ?per_page, ?page, etc. if needed uriBuilder.replaceQueryParam("page", lastPage); links.add(new Link("last", uriBuilder.build().toString())); } // A link to the current page uriBuilder = uriInfo.getRequestUriBuilder(); // adds ?q, ?per_page, ?page, etc. if needed StringBuilder linkHeader = new StringBuilder(new Link("current", uriBuilder.build().toString()) .rfc5988String()); //followed by the rest of the link defined above links.forEach((l) -> linkHeader.append(", ").append(l.rfc5988String())); //add that all as a single Link header to the response builder.header("Link", linkHeader.toString()); // Create a total size header builder.header("X-Total-Count", resultList.getTotalSize()); } @ApiModel(description = "Payload for a simple REST deleted number response.") public static class ApiDeleted { @ApiModelProperty(value = "Deleted items.") @JsonInclude private final Integer deleted; public ApiDeleted(Integer deleted) { this.deleted = deleted; } public Integer getDeleted() { return deleted; } } @ApiModel(description = "Payload for a REST error response.") public static class ApiError { @ApiModelProperty(value = "Description of the error message.") @JsonInclude private final String errorMsg; public ApiError(String errorMsg) { this.errorMsg = errorMsg != null && !errorMsg.trim().isEmpty() ? errorMsg : "No details"; } public String getErrorMsg() { return errorMsg; } } }