/*
* 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.isis.viewer.wicket.ui.errors;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.isis.applib.NonRecoverableException;
import org.apache.isis.applib.services.error.ErrorReportingService;
import org.apache.isis.applib.services.error.Ticket;
import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
import org.apache.isis.viewer.wicket.model.models.ModelAbstract;
public class ExceptionModel extends ModelAbstract<List<StackTraceDetail>> {
private static final long serialVersionUID = 1L;
private static final String MAIN_MESSAGE_IF_NOT_RECOGNIZED = "Sorry, an unexpected error occurred.";
private List<StackTraceDetail> stackTraceDetailList;
private boolean recognized;
private boolean authorizationCause;
private final String mainMessage;
public static ExceptionModel create(String recognizedMessageIfAny, Exception ex) {
return new ExceptionModel(recognizedMessageIfAny, ex);
}
/**
* Three cases: authorization exception, else recognized, else or not recognized.
* @param recognizedMessageIfAny
* @param ex
*/
private ExceptionModel(String recognizedMessageIfAny, Exception ex) {
final ObjectMember.AuthorizationException authorizationException = causalChainOf(ex, ObjectMember.AuthorizationException.class);
if(authorizationException != null) {
this.authorizationCause = true;
this.mainMessage = authorizationException.getMessage();
} else {
this.authorizationCause = false;
if(recognizedMessageIfAny != null) {
this.recognized = true;
this.mainMessage = recognizedMessageIfAny;
} else {
this.recognized =false;
// see if we can find a NonRecoverableException in the stack trace
Iterable<NonRecoverableException> appEx = Iterables.filter(Throwables.getCausalChain(ex), NonRecoverableException.class);
Iterator<NonRecoverableException> iterator = appEx.iterator();
NonRecoverableException nonRecoverableException = iterator.hasNext() ? iterator.next() : null;
this.mainMessage = nonRecoverableException != null? nonRecoverableException.getMessage() : MAIN_MESSAGE_IF_NOT_RECOGNIZED;
}
}
stackTraceDetailList = asStackTrace(ex);
}
@Override
protected List<StackTraceDetail> load() {
return stackTraceDetailList;
}
private static <T extends Exception> T causalChainOf(Exception ex, Class<T> exType) {
final List<Throwable> causalChain = Throwables.getCausalChain(ex);
for (Throwable cause : causalChain) {
if(exType.isAssignableFrom(cause.getClass())) {
return (T)cause;
}
}
return null;
}
@Override
public void setObject(List<StackTraceDetail> stackTraceDetail) {
if(stackTraceDetail == null) {
return;
}
this.stackTraceDetailList = stackTraceDetail;
}
private Ticket ticket;
public Ticket getTicket() {
return ticket;
}
/**
* Optionally called if an {@link ErrorReportingService} has been configured and returns a <tt>non-null</tt> ticket
* to represent the fact that the error has been recorded.
*/
public void setTicket(final Ticket ticket) {
this.ticket = ticket;
}
public boolean isRecognized() {
return recognized;
}
public String getMainMessage() {
return mainMessage;
}
/**
* Whether this was an authorization exception (so UI can suppress information, eg stack trace).
*/
public boolean isAuthorizationException() {
return authorizationCause;
}
public List<StackTraceDetail> getStackTrace() {
return stackTraceDetailList;
}
private static List<StackTraceDetail> asStackTrace(Throwable ex) {
List<StackTraceDetail> stackTrace = Lists.newArrayList();
List<Throwable> causalChain = Throwables.getCausalChain(ex);
boolean firstTime = true;
for(Throwable cause: causalChain) {
if(!firstTime) {
stackTrace.add(StackTraceDetail.spacer());
stackTrace.add(StackTraceDetail.causedBy());
stackTrace.add(StackTraceDetail.spacer());
} else {
firstTime = false;
}
stackTrace.add(StackTraceDetail.exceptionClassName(cause));
stackTrace.add(StackTraceDetail.exceptionMessage(cause));
addStackTraceElements(cause, stackTrace);
}
return stackTrace;
}
private static void addStackTraceElements(Throwable ex, List<StackTraceDetail> stackTrace) {
for (StackTraceElement el : ex.getStackTrace()) {
stackTrace.add(StackTraceDetail.element(el));
}
}
}