blob: 978755bfaf79e4548d0dd216536c36178eb32347 [file] [log] [blame]
/*
* 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);
}
}