001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019package org.apache.isis.viewer.restfulobjects.server;
020
021import java.util.List;
022
023import javax.ws.rs.Path;
024import javax.ws.rs.core.MediaType;
025import javax.ws.rs.core.Response;
026import javax.ws.rs.core.Response.ResponseBuilder;
027import javax.ws.rs.ext.ExceptionMapper;
028import javax.ws.rs.ext.Provider;
029
030import org.apache.commons.lang.exception.ExceptionUtils;
031import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
032import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
033import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
034import org.apache.isis.viewer.restfulobjects.applib.util.JsonMapper;
035
036import com.google.common.collect.Lists;
037
038//@Path("/") // FIXME: workaround for TomEE ... but breaks the RestEasy TCK tests so commented out:-(
039@Provider
040public class RestfulObjectsApplicationExceptionMapper implements ExceptionMapper<RestfulObjectsApplicationException> {
041
042    @Override
043    public Response toResponse(final RestfulObjectsApplicationException ex) {
044        final ResponseBuilder builder = Response.status(ex.getHttpStatusCode().getJaxrsStatusType());
045
046        // body and content-type
047        final JsonRepresentation bodyRepr = ex.getBody();
048        final Throwable cause = ex.getCause();
049        if (bodyRepr != null) {
050            final String body = bodyRepr.toString();
051            builder.entity(body);
052            builder.type(MediaType.APPLICATION_JSON); // generic; the spec doesn't define what the media type should be
053        } else if(cause == null) {
054            builder.type(MediaType.APPLICATION_JSON); // generic; the spec doesn't define what the media type should be
055        } else { 
056            String body;
057            try {
058                body = JsonMapper.instance().write(ExceptionPojo.create(cause));
059            } catch (final Exception e) {
060                // fallback
061                body = "{ \"exception\": \"" + ExceptionUtils.getFullStackTrace(cause) + "\" }";
062            }
063            builder.entity(body);
064            builder.type(RestfulMediaType.APPLICATION_JSON_ERROR);
065        }
066
067        final String message = ex.getMessage();
068        if (message != null) {
069            builder.header(RestfulResponse.Header.WARNING.getName(), RestfulResponse.Header.WARNING.render(message));
070        }
071        return builder.build();
072    }
073
074    private static class ExceptionPojo {
075
076        public static ExceptionPojo create(final Throwable ex) {
077            return new ExceptionPojo(ex);
078        }
079
080        private static String format(final StackTraceElement stackTraceElement) {
081            return stackTraceElement.toString();
082        }
083
084        private final int httpStatusCode;
085        private final String message;
086        private final List<String> stackTrace = Lists.newArrayList();
087        private ExceptionPojo causedBy;
088
089        public ExceptionPojo(final Throwable ex) {
090            httpStatusCode = getHttpStatusCodeIfAny(ex);
091            this.message = ex.getMessage();
092            final StackTraceElement[] stackTraceElements = ex.getStackTrace();
093            for (final StackTraceElement stackTraceElement : stackTraceElements) {
094                this.stackTrace.add(format(stackTraceElement));
095            }
096            final Throwable cause = ex.getCause();
097            if (cause != null && cause != ex) {
098                this.causedBy = new ExceptionPojo(cause);
099            }
100        }
101
102        private int getHttpStatusCodeIfAny(final Throwable ex) {
103            if (!(ex instanceof HasHttpStatusCode)) {
104                return 0;
105            }
106            final HasHttpStatusCode hasHttpStatusCode = (HasHttpStatusCode) ex;
107            return hasHttpStatusCode.getHttpStatusCode().getStatusCode();
108        }
109
110        @SuppressWarnings("unused")
111        public int getHttpStatusCode() {
112            return httpStatusCode;
113        }
114
115        @SuppressWarnings("unused")
116        public String getMessage() {
117            return message;
118        }
119
120        @SuppressWarnings("unused")
121        public List<String> getStackTrace() {
122            return stackTrace;
123        }
124
125        @SuppressWarnings("unused")
126        public ExceptionPojo getCausedBy() {
127            return causedBy;
128        }
129
130    }
131
132
133}