package act.view; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * 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. * #L% */ import act.Act; import act.app.*; import act.asm.AsmContext; import act.asm.AsmException; import act.exception.BindException; import act.util.ActError; import org.osgl.$; import org.osgl.exception.InvalidRangeException; import org.osgl.exception.UnsupportedException; import org.osgl.http.H; import org.osgl.mvc.annotation.ResponseStatus; import org.osgl.mvc.result.ErrorResult; import org.osgl.mvc.result.Result; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import javax.validation.ValidationException; import java.util.List; import java.util.Map; public class ActErrorResult extends ErrorResult implements ActError { protected SourceInfo sourceInfo; public ActErrorResult(H.Status status) { super(status); init(); populateSourceInfo(); } public ActErrorResult(H.Status status, String message, Object ... args) { super(status, message, args); init(); populateSourceInfo(); } public ActErrorResult(H.Status status, int errorCode) { super(status, errorCode); init(); populateSourceInfo(); } public ActErrorResult(H.Status status, int errorCode, String message, Object... args) { super(status, errorCode, message, args); init(); populateSourceInfo(); } public ActErrorResult(Throwable cause) { super(H.Status.INTERNAL_SERVER_ERROR, cause); init(); populateSourceInfo(cause); } private ActErrorResult(AsmException exception, boolean scanning) { super(H.Status.INTERNAL_SERVER_ERROR, exception.getCause(), errorMsg(exception, scanning)); init(); populateSourceInfo(exception.context()); } private static String errorMsg(AsmException exception, boolean scanning) { String userMsg = exception.getLocalizedMessage(); return (S.blank(userMsg)) ? S.concat("Error ", scanning ? "scanning" : "enhancing", " bytecode at ", exception.context().toString()) : userMsg; } public ActErrorResult(H.Status status, Throwable cause) { super(status, cause); init(); populateSourceInfo(cause); } public ActErrorResult(H.Status status, Throwable cause, String message, Object... args) { super(status, cause, message, args); init(); populateSourceInfo(cause); } public ActErrorResult(H.Status status, int errorCode, Throwable cause, String message, Object ... args) { super(status, errorCode, cause, message, args); init(); populateSourceInfo(cause); } public ActErrorResult(H.Status status, int errorCode, Throwable cause) { super(status, errorCode, cause); init(); populateSourceInfo(cause); } @Override public Throwable getCauseOrThis() { return rootCauseOf(this); } public SourceInfo sourceInfo() { return sourceInfo; } @Override public int statusCode() { Throwable cause = super.getCause(); int statusCode = null == cause ? -1 : userDefinedStatusCode(cause.getClass()); return -1 == statusCode ? super.statusCode() : statusCode; } public List<String> stackTrace() { List<String> l = C.newList(); Throwable t = getCauseOrThis(); while (null != t) { StackTraceElement[] a = t.getStackTrace(); for (StackTraceElement e : a) { l.add("at " + e.toString()); } t = t.getCause(); if (null != t) { l.add("Caused by " + t.toString()); } } return l; } @Override public boolean isErrorSpot(String traceLine, String nextTraceLine) { return false; } protected void init() {} protected void populateSourceInfo(Throwable t) { if (!Act.isDev()) { return; } if (t instanceof SourceInfo) { this.sourceInfo = (SourceInfo)t; } else { DevModeClassLoader cl = (DevModeClassLoader) App.instance().classLoader(); for (StackTraceElement stackTraceElement : t.getStackTrace()) { int line = stackTraceElement.getLineNumber(); if (line <= 0) { continue; } Source source = cl.source(stackTraceElement.getClassName()); if (null == source) { continue; } sourceInfo = new SourceInfoImpl(source, line); } } } protected void populateSourceInfo(AsmContext context) { if (!Act.isDev()) { return; } this.sourceInfo = Util.loadSourceInfo(context); } private void populateSourceInfo() { populateSourceInfo(new RuntimeException()); } private static Map<Class<? extends Throwable>, $.Function<Throwable, Result>> x = C.newMap(); static { $.Function<Throwable, Result> unsupported = new $.Transformer<Throwable, Result>() { @Override public Result transform(Throwable throwable) { return ActNotImplemented.create(throwable); } }; x.put(UnsupportedException.class, unsupported); x.put(UnsupportedOperationException.class, unsupported); x.put(IllegalStateException.class, new $.Transformer<Throwable, Result>() { @Override public Result transform(Throwable throwable) { return ActConflict.create(throwable); } }); $.Transformer<Throwable, Result> badRequest = new $.Transformer<Throwable, Result>() { @Override public Result transform(Throwable throwable) { return ActBadRequest.create(throwable); } }; x.put(IllegalArgumentException.class, badRequest); x.put(InvalidRangeException.class, badRequest); x.put(IndexOutOfBoundsException.class, badRequest); x.put(ValidationException.class, badRequest); x.put(BindException.class, badRequest); } private static Map<Class, Integer> userDefinedStatus = C.newMap(); private static int userDefinedStatusCode(Class<? extends Throwable> exCls) { Integer I = userDefinedStatus.get(exCls); if (null == I) { ResponseStatus rs = exCls.getAnnotation(ResponseStatus.class); if (null == rs) { I = -1; userDefinedStatus.put(exCls, -1); } else { I = rs.value(); } } return I; } public static Result of(Throwable t) { if (t instanceof Result) { return (Result) t; } else if (t instanceof org.rythmengine.exception.RythmException) { return Act.isDev() ? new RythmTemplateException((org.rythmengine.exception.RythmException) t) : ErrorResult.of(H.Status.INTERNAL_SERVER_ERROR); } else if (t instanceof AsmException) { return new ActErrorResult((AsmException) t, true); } else { $.Function<Throwable, Result> transformer = transformerOf(t); return null == transformer ? new ActErrorResult(t) : transformer.apply(t); } } public static ActErrorResult scanningError(AsmException exception) { return new ActErrorResult(exception, true); } public static ActErrorResult enhancingError(AsmException exception) { return new ActErrorResult(exception, false); } private static $.Function<Throwable, Result> transformerOf(Throwable t) { Class tc = t.getClass(); $.Function<Throwable, Result> transformer = x.get(tc); if (null != transformer) { return transformer; } for (Class c : x.keySet()) { if (c.isAssignableFrom(tc)) { return x.get(c); } } return null; } public static Result ofStatus(int statusCode) { return of(H.Status.of(statusCode)); } @Deprecated public static Result of(int statusCode) { E.illegalArgumentIf(statusCode < 400); switch (statusCode) { case 400: return ActBadRequest.create(); case 401: return ActUnauthorized.create(); case 403: return ActForbidden.create(); case 404: return ActNotFound.create(); case 405: return ActMethodNotAllowed.create(); case 409: return ActConflict.create(); case 501: return ActNotImplemented.create(); default: if (Act.isDev()) { return new ActErrorResult(new RuntimeException()); } else { return new ErrorResult(H.Status.of(statusCode)); } } } public static ErrorResult of(H.Status status) { E.illegalArgumentIf(!status.isClientError() && !status.isServerError()); if (Act.isDev()) { return new ActErrorResult(status); } else { return ErrorResult.of(status); } } public static ErrorResult of(H.Status status, String message, Object... args) { E.illegalArgumentIf(!status.isClientError() && !status.isServerError()); if (Act.isDev()) { return new ActErrorResult(status, message, args); } else { return ErrorResult.of(status, message, args); } } public static ErrorResult of(H.Status status, int errorCode) { E.illegalArgumentIf(!status.isClientError() && !status.isServerError()); if (Act.isDev()) { return new ActErrorResult(status, errorCode); } else { return ErrorResult.of(status, errorCode); } } public static ErrorResult of(H.Status status, int errorCode, String message, Object... args) { E.illegalArgumentIf(!status.isClientError() && !status.isServerError()); if (Act.isDev()) { return new ActErrorResult(status, errorCode, message, args); } else { return ErrorResult.of(status, errorCode, message, args); } } public static Throwable rootCauseOf(Throwable t) { if (null == t) { return null; } Throwable cause; for (;;) { cause = t.getCause(); if (null != cause) { t = cause; } else { break; } } return t; } }