package act.handler.builtin.controller; /*- * #%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.Destroyable; import act.ResponseImplBase; import act.app.ActionContext; import act.app.App; import act.app.AppInterceptorManager; import act.app.event.AppEventId; import act.controller.CacheSupportMetaInfo; import act.controller.ResponseCache; import act.controller.meta.*; import act.handler.RequestHandlerBase; import act.security.CORS; import act.security.CSRF; import act.util.AnnotatedClassFinder; import act.util.Global; import act.util.MissingAuthenticationHandler; import act.view.ActErrorResult; import act.view.RenderAny; import org.osgl.$; import org.osgl.cache.CacheService; import org.osgl.exception.UnexpectedException; import org.osgl.http.H; import org.osgl.logging.L; import org.osgl.logging.Logger; import org.osgl.mvc.result.*; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import java.util.Collection; import java.util.Collections; import java.util.ListIterator; import java.util.Set; import java.util.regex.Pattern; import static org.osgl.http.H.Method.GET; import static org.osgl.http.H.Method.POST; @ApplicationScoped public final class RequestHandlerProxy extends RequestHandlerBase { private static Logger logger = L.get(RequestHandlerProxy.class); private static final C.List<BeforeInterceptor> globalBeforeInterceptors = C.newList(); private static final C.List<AfterInterceptor> globalAfterInterceptors = C.newList(); private static final C.List<FinallyInterceptor> globalFinallyInterceptors = C.newList(); private static final C.List<ExceptionInterceptor> globalExceptionInterceptors = C.newList(); // for @Global on classes private static final C.Set<GroupInterceptorMetaInfo> globalFreeStyleInterceptors = C.newSet(); // for @Global on methods private static GroupInterceptorMetaInfo globalFreeStyleInterceptor = new GroupInterceptorMetaInfo(); public static final GroupInterceptorWithResult GLOBAL_BEFORE_INTERCEPTOR = new GroupInterceptorWithResult(globalBeforeInterceptors); public static final GroupAfterInterceptor GLOBAL_AFTER_INTERCEPTOR = new GroupAfterInterceptor(globalAfterInterceptors); public static final GroupFinallyInterceptor GLOBAL_FINALLY_INTERCEPTOR = new GroupFinallyInterceptor(globalFinallyInterceptors); public static final GroupExceptionInterceptor GLOBAL_EXCEPTION_INTERCEPTOR = new GroupExceptionInterceptor(globalExceptionInterceptors); private App app; private AppInterceptorManager appInterceptor; private CacheService cache; private String controllerClassName; private String actionMethodName; private String actionPath; private volatile ControllerAction actionHandler = null; private C.List<BeforeInterceptor> beforeInterceptors = C.newList(); private C.List<AfterInterceptor> afterInterceptors = C.newList(); private C.List<ExceptionInterceptor> exceptionInterceptors = C.newList(); private C.List<FinallyInterceptor> finallyInterceptors = C.newList(); private boolean sessionFree; private boolean express; private boolean supportCache; private CacheSupportMetaInfo cacheSupport; private MissingAuthenticationHandler missingAuthenticationHandler; private MissingAuthenticationHandler csrfFailureHandler; final GroupInterceptorWithResult BEFORE_INTERCEPTOR = new GroupInterceptorWithResult(beforeInterceptors); final GroupAfterInterceptor AFTER_INTERCEPTOR = new GroupAfterInterceptor(afterInterceptors); final GroupFinallyInterceptor FINALLY_INTERCEPTOR = new GroupFinallyInterceptor(finallyInterceptors); final GroupExceptionInterceptor EXCEPTION_INTERCEPTOR = new GroupExceptionInterceptor(exceptionInterceptors); @Inject public RequestHandlerProxy(String actionMethodName, App app) { int pos = actionMethodName.lastIndexOf('.'); final String ERR = "Invalid controller action: %s"; E.illegalArgumentIf(pos < 0, ERR, actionMethodName); controllerClassName = actionMethodName.substring(0, pos); E.illegalArgumentIf(S.isEmpty(controllerClassName), ERR, actionMethodName); this.actionMethodName = actionMethodName.substring(pos + 1); E.illegalArgumentIf(S.isEmpty(this.actionMethodName), ERR, actionMethodName); this.actionPath = actionMethodName; cache = app.config().cacheService("action_proxy"); this.app = app; this.appInterceptor = app.interceptorManager(); } @Override protected void releaseResources() { _releaseResourceCollections(afterInterceptors); _releaseResourceCollections(beforeInterceptors); _releaseResourceCollections(exceptionInterceptors); _releaseResourceCollections(finallyInterceptors); if (null != actionHandler) { actionHandler.destroy(); actionHandler = null; } } public static void releaseGlobalResources() { _releaseResourceCollections(globalAfterInterceptors); _releaseResourceCollections(globalBeforeInterceptors); _releaseResourceCollections(globalExceptionInterceptors); _releaseResourceCollections(globalFinallyInterceptors); _releaseResourceCollections(globalFreeStyleInterceptors); globalFreeStyleInterceptor.destroy(); // We must recreate this instance to prevent it from // been reused after destroyed globalFreeStyleInterceptor = new GroupInterceptorMetaInfo(); } private static void _releaseResourceCollections(Collection<? extends Destroyable> col) { Destroyable.Util.destroyAll(col, null); col.clear(); } public String controller() { return controllerClassName; } public String action() { return actionMethodName; } public NotFound notFoundOnMethod(String message) { return actionHandler.notFoundOnMethod(message); } @Override public void handle(ActionContext context) { ensureAgentsReady(); Result result = null; try { H.Method method = context.req().method(); boolean supportCache = this.supportCache && method == GET || (cacheSupport.supportPost && method == POST); String cacheKey = null; if (supportCache) { cacheKey = cacheSupport.cacheKey(context); ResponseCache cached = this.cache.get(cacheKey); if (null != cached) { cached.applyTo((ResponseImplBase) context.resp()); return; } context.enableCache(); } saveActionPath(context); context.startIntercepting(); result = handleBefore(context); if (null == result) { context.startHandling(); result = _handle(context); } context.startIntercepting(); Result afterResult = handleAfter(result, context); if (null != afterResult) { result = afterResult; } if (null == result) { result = context.nullValueResult(); } onResult(result, context); if (supportCache) { this.cache.put(cacheKey, context.resp(), cacheSupport.ttl); } } catch (Exception e) { logger.error(e, "Error handling request"); try { result = handleException(e, context); } catch (Exception e0) { logger.error(e0, "Error invoking exception handler"); } if (null == result) { result = ActErrorResult.of(e); } try { onResult(result, context); } catch (Exception e2) { logger.error(e2, "error rendering exception handle result"); onResult(ActErrorResult.of(e2), context); } } finally { try { handleFinally(context); } catch (Exception e) { logger.error(e, "Error invoking final handler"); } finally { context.destroy(); } } } @Override public boolean sessionFree() { ensureAgentsReady(); return sessionFree; } @Override public void prepareAuthentication(ActionContext context) { if (null != missingAuthenticationHandler) { context.forceMissingAuthenticationHandler(missingAuthenticationHandler); } if (null != csrfFailureHandler) { context.forceCsrfCheckingFailureHandler(csrfFailureHandler); } } @Override public boolean express(ActionContext context) { return express; } protected final void registerBeforeInterceptor(BeforeInterceptor interceptor) { insertInterceptor(beforeInterceptors, interceptor); } protected final void registerAfterInterceptor(AfterInterceptor interceptor) { insertInterceptor(afterInterceptors, interceptor); } protected final void registerExceptionInterceptor(ExceptionInterceptor interceptor) { insertInterceptor(exceptionInterceptors, interceptor); } protected final void registerFinallyInterceptor(FinallyInterceptor interceptor) { insertInterceptor(finallyInterceptors, interceptor); } private void onResult(Result result, ActionContext context) { context.dissolve(); boolean isRenderAny = false; try { if (result instanceof RenderAny) { RenderAny any = (RenderAny) result; isRenderAny = true; any.apply(context); } else { H.Request req = context.req(); H.Response resp = context.resp(); result.apply(req, resp); } } catch (RuntimeException e) { context.cacheTemplate(null); throw e; } finally { if (isRenderAny) { RenderAny.clearThreadLocals(); } } } private void ensureAgentsReady() { if (null == actionHandler) { synchronized (this) { if (null == actionHandler) { generateHandlers(); } } } } // could be used by View to resolve default path to template private void saveActionPath(ActionContext context) { context.actionPath(actionPath); } private boolean matches(Set<String> patterns) { if (patterns.contains(actionMethodName) || patterns.contains(actionPath)) { return true; } for (String s : patterns) { if (Pattern.compile(s).matcher(actionPath).matches()) { return true; } } return false; } private boolean applied(InterceptorMethodMetaInfo interceptor) { Set<String> blackList = interceptor.blackList(); if (!blackList.isEmpty()) { return !matches(blackList); } else { Set<String> whiteList = interceptor.whiteList(); if (!whiteList.isEmpty()) { return matches(whiteList); } return true; } } private ActionMethodMetaInfo findActionInfoFromParent(ControllerClassMetaInfo ctrlInfo, String methodName) { ActionMethodMetaInfo actionInfo; ControllerClassMetaInfo parent = ctrlInfo.parent(true); if (null == parent) { throw new UnexpectedException("Cannot find action method meta info: %s", actionPath); } while (true) { actionInfo = parent.action(methodName); if (null != actionInfo) { break; } parent = parent.parent(true); if (null == parent) { break; } } return new ActionMethodMetaInfo($.notNull(actionInfo), ctrlInfo); } private void generateHandlers() { ControllerClassMetaInfo ctrlInfo = app.classLoader().controllerClassMetaInfo(controllerClassName); ActionMethodMetaInfo actionInfo = ctrlInfo.action(actionMethodName); if (null == actionInfo) { actionInfo = findActionInfoFromParent(ctrlInfo, actionMethodName); } Act.Mode mode = Act.mode(); actionHandler = mode.createRequestHandler(actionInfo, app); sessionFree = actionHandler.sessionFree(); missingAuthenticationHandler = actionHandler.missingAuthenticationHandler(); csrfFailureHandler = actionHandler.csrfFailureHandler(); express = actionHandler.express(); cacheSupport = actionHandler.cacheSupport(); supportCache = cacheSupport.enabled; App app = this.app; if (supportCache) { cache = app.cache(); } GroupInterceptorMetaInfo interceptorMetaInfo = new GroupInterceptorMetaInfo(actionInfo.interceptors()); interceptorMetaInfo.mergeFrom(globalFreeStyleInterceptor); for (GroupInterceptorMetaInfo freeStyleInterceptor : globalFreeStyleInterceptors) { interceptorMetaInfo.mergeFrom(freeStyleInterceptor); } for (InterceptorMethodMetaInfo info : interceptorMetaInfo.beforeList()) { if (!applied(info)) { continue; } BeforeInterceptor interceptor = mode.createBeforeInterceptor(info, app); beforeInterceptors.add(interceptor); sessionFree = sessionFree && interceptor.sessionFree(); express = express && interceptor.express(); } for (InterceptorMethodMetaInfo info : interceptorMetaInfo.afterList()) { if (!applied(info)) { continue; } AfterInterceptor interceptor = mode.createAfterInterceptor(info, app); afterInterceptors.add(interceptor); sessionFree = sessionFree && interceptor.sessionFree(); express = express && interceptor.express(); } for (CatchMethodMetaInfo info : interceptorMetaInfo.catchList()) { if (!applied(info)) { continue; } ExceptionInterceptor interceptor = mode.createExceptionInterceptor(info, app); exceptionInterceptors.add(interceptor); sessionFree = sessionFree && interceptor.sessionFree(); express = express && interceptor.express(); } Collections.sort(exceptionInterceptors); for (InterceptorMethodMetaInfo info : interceptorMetaInfo.finallyList()) { if (!applied(info)) { continue; } FinallyInterceptor interceptor = mode.createFinallyInterceptor(info, app); finallyInterceptors.add(interceptor); sessionFree = sessionFree && interceptor.sessionFree(); express = express && interceptor.express(); } } public void accept(Handler.Visitor visitor) { ensureAgentsReady(); for (BeforeInterceptor i : globalBeforeInterceptors) { i.accept(visitor); } for (BeforeInterceptor i : beforeInterceptors) { i.accept(visitor); } actionHandler.accept(visitor); for (AfterInterceptor i : afterInterceptors) { i.accept(visitor); } for (AfterInterceptor i : globalAfterInterceptors) { i.accept(visitor); } for (FinallyInterceptor i : finallyInterceptors) { i.accept(visitor); } for (FinallyInterceptor i : globalFinallyInterceptors) { i.accept(visitor); } for (ExceptionInterceptor i : exceptionInterceptors) { i.accept(visitor); } for (ExceptionInterceptor i : globalExceptionInterceptors) { i.accept(visitor); } } @Override public CSRF.Spec csrfSpec() { ensureAgentsReady(); return actionHandler.csrfSpec(); } @Override public CORS.Spec corsSpec() { ensureAgentsReady(); return actionHandler.corsSpec(); } private Result handleBefore(ActionContext actionContext) throws Exception { Result r = GLOBAL_BEFORE_INTERCEPTOR.apply(actionContext); if (null == r) { r = appInterceptor.handleBefore(actionContext); } if (null == r) { r = BEFORE_INTERCEPTOR.apply(actionContext); } return r; } private Result _handle(ActionContext actionContext) throws Exception { try { return actionHandler.handle(actionContext); } catch (Result r) { return r; } } private Result handleAfter(Result result, ActionContext actionContext) throws Exception { result = AFTER_INTERCEPTOR.apply(result, actionContext); result = appInterceptor.handleAfter(result, actionContext); result = GLOBAL_AFTER_INTERCEPTOR.apply(result, actionContext); return result; } private void handleFinally(ActionContext actionContext) throws Exception { FINALLY_INTERCEPTOR.apply(actionContext); appInterceptor.handleFinally(actionContext); GLOBAL_FINALLY_INTERCEPTOR.apply(actionContext); } private Result handleException(Exception ex, ActionContext actionContext) throws Exception { Result r = EXCEPTION_INTERCEPTOR.apply(ex, actionContext); if (null == r) { r = appInterceptor.handleException(ex, actionContext); } if (null == r) { r = GLOBAL_EXCEPTION_INTERCEPTOR.apply(ex, actionContext); } return r; } @Override public String toString() { return actionPath; } public static void registerGlobalInterceptor(GroupInterceptorMetaInfo freeStyleInterceptor) { globalFreeStyleInterceptors.add(freeStyleInterceptor); } public static void registerGlobalInterceptor(InterceptorMethodMetaInfo interceptor, InterceptorType type) { globalFreeStyleInterceptor.add(interceptor, type); } public static void registerGlobalInterceptor(BeforeInterceptor interceptor) { insertInterceptor(globalBeforeInterceptors, interceptor); } public static void registerGlobalInterceptor(AfterInterceptor interceptor) { insertInterceptor(globalAfterInterceptors, interceptor); } public static void registerGlobalInterceptor(FinallyInterceptor interceptor) { insertInterceptor(globalFinallyInterceptors, interceptor); } public static void registerGlobalInterceptor(ExceptionInterceptor interceptor) { insertInterceptor(globalExceptionInterceptors, interceptor); Collections.sort(globalExceptionInterceptors); } @AnnotatedClassFinder(value = Global.class, callOn = AppEventId.PRE_START) @SuppressWarnings("unused") public static void registerGlobalInterceptors(Class<?> interceptorClass) { App app = Act.app(); if (BeforeInterceptor.class.isAssignableFrom(interceptorClass)) { BeforeInterceptor interceptor = (BeforeInterceptor) app.getInstance(interceptorClass); registerGlobalInterceptor(interceptor); } else if (AfterInterceptor.class.isAssignableFrom(interceptorClass)) { AfterInterceptor interceptor = (AfterInterceptor) app.getInstance(interceptorClass); registerGlobalInterceptor(interceptor); } else if (ExceptionInterceptor.class.isAssignableFrom(interceptorClass)) { ExceptionInterceptor interceptor = (ExceptionInterceptor) app.getInstance(interceptorClass); registerGlobalInterceptor(interceptor); } else if (FinallyInterceptor.class.isAssignableFrom(interceptorClass)) { FinallyInterceptor interceptor = (FinallyInterceptor) app.getInstance(interceptorClass); registerGlobalInterceptor(interceptor); } else { // check if this is a free style interceptor ControllerClassMetaInfo metaInfo = app.classLoader().controllerClassMetaInfo(interceptorClass.getName()); if (null != metaInfo) { registerGlobalInterceptor(metaInfo.interceptors()); } } } public static <T extends Handler> void insertInterceptor(C.List<T> list, T i) { int sz = list.size(); if (0 == sz) { list.add(i); } ListIterator<T> itr = list.listIterator(); while (itr.hasNext()) { T t = itr.next(); int n = i.compareTo(t); if (n < 0) { itr.add(i); return; } else if (n == 0) { if (i.equals(t)) { // already exists return; } else { itr.add(i); return; } } } list.add(i); } public static class GroupInterceptorWithResult { private C.List<? extends ActionHandler> interceptors; public GroupInterceptorWithResult(C.List<? extends ActionHandler> interceptors) { this.interceptors = interceptors; } public Result apply(ActionContext actionContext) throws Exception { try { if (interceptors.isEmpty()) return null; for (ActionHandler i : interceptors) { Result r = i.handle(actionContext); if (null != r) { return r; } } return null; } catch (Result r) { return r; } } } public static class GroupAfterInterceptor { private C.List<? extends AfterInterceptor> interceptors; public GroupAfterInterceptor(C.List<? extends AfterInterceptor> interceptors) { this.interceptors = interceptors; } public Result apply(Result result, ActionContext actionContext) throws Exception { for (AfterInterceptor i : interceptors) { result = i.handle(result, actionContext); } return result; } } public static class GroupFinallyInterceptor { private C.List<? extends FinallyInterceptor> interceptors; public GroupFinallyInterceptor(C.List<FinallyInterceptor> interceptors) { this.interceptors = interceptors; } public Void apply(ActionContext actionContext) throws Exception { if (interceptors.isEmpty()) return null; for (FinallyInterceptor i : interceptors) { i.handle(actionContext); } return null; } } public static class GroupExceptionInterceptor { private C.List<? extends ExceptionInterceptor> interceptors; public GroupExceptionInterceptor(C.List<? extends ExceptionInterceptor> interceptors) { this.interceptors = interceptors; } public Result apply(Exception e, ActionContext actionContext) throws Exception { try { if (interceptors.isEmpty()) return null; for (ExceptionInterceptor i : interceptors) { Result r = i.handle(e, actionContext); if (null != r) { return r; } } return null; } catch (Result r) { return r; } } } }