| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.brooklyn.rest.util; |
| |
| import java.util.Set; |
| |
| 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.ext.ExceptionMapper; |
| import javax.ws.rs.ext.Provider; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.yaml.snakeyaml.error.YAMLException; |
| import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; |
| import org.apache.brooklyn.rest.domain.ApiError; |
| import org.apache.brooklyn.rest.domain.ApiError.Builder; |
| import org.apache.brooklyn.util.collections.MutableSet; |
| import org.apache.brooklyn.util.core.flags.ClassCoercionException; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.exceptions.UserFacingException; |
| import org.apache.brooklyn.util.text.Strings; |
| |
| @Provider |
| public class DefaultExceptionMapper implements ExceptionMapper<Throwable> { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionMapper.class); |
| |
| static Set<Class<?>> warnedUnknownExceptions = MutableSet.of(); |
| |
| /** |
| * Maps a throwable to a response. |
| * <p/> |
| * Returns {@link WebApplicationException#getResponse} if the exception is an instance of |
| * {@link WebApplicationException}. Otherwise maps known exceptions to responses. If no |
| * mapping is found a {@link Status#INTERNAL_SERVER_ERROR} is assumed. |
| */ |
| @Override |
| public Response toResponse(Throwable throwable1) { |
| |
| LOG.debug("REST request running as {} threw: {}", Entitlements.getEntitlementContext(), |
| Exceptions.collapse(throwable1)); |
| if (LOG.isTraceEnabled()) { |
| LOG.trace("Full details of "+Entitlements.getEntitlementContext()+" "+throwable1, throwable1); |
| } |
| |
| Throwable throwable2 = Exceptions.getFirstInteresting(throwable1); |
| // Some methods will throw this, which gets converted automatically |
| if (throwable2 instanceof WebApplicationException) { |
| WebApplicationException wae = (WebApplicationException) throwable2; |
| return wae.getResponse(); |
| } |
| |
| // The nicest way for methods to provide errors, wrap as this, and the stack trace will be suppressed |
| if (throwable2 instanceof UserFacingException) { |
| return ApiError.of(throwable2.getMessage()).asBadRequestResponseJson(); |
| } |
| |
| // For everything else, a trace is supplied |
| |
| // Assume ClassCoercionExceptions are caused by TypeCoercions from input parameters gone wrong |
| // And IllegalArgumentException for malformed input parameters. |
| if (throwable2 instanceof ClassCoercionException || throwable2 instanceof IllegalArgumentException) { |
| return ApiError.of(throwable2).asBadRequestResponseJson(); |
| } |
| |
| // YAML exception |
| if (throwable2 instanceof YAMLException) { |
| return ApiError.builder().message(throwable2.getMessage()).prefixMessage("Invalid YAML").build().asBadRequestResponseJson(); |
| } |
| |
| if (!Exceptions.isPrefixBoring(throwable2)) { |
| if ( warnedUnknownExceptions.add( throwable2.getClass() )) { |
| LOG.warn("REST call generated exception type "+throwable2.getClass()+" unrecognized in "+getClass()+" (subsequent occurrences will be logged debug only): " + throwable2, throwable2); |
| } |
| } |
| |
| Throwable throwable3 = Exceptions.collapse(throwable2); |
| Builder rb = ApiError.builderFromThrowable(throwable3); |
| if (Strings.isBlank(rb.getMessage())) |
| rb.message("Internal error. Contact server administrator to consult logs for more details."); |
| if (Exceptions.isPrefixImportant(throwable3)) |
| rb.message(Exceptions.getPrefixText(throwable3)+": "+rb.getMessage()); |
| return rb.build().asResponse(Status.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON_TYPE); |
| } |
| } |