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.ActionContext; import act.app.App; import act.app.AppByteCodeScannerBase; import act.asm.AnnotationVisitor; import act.asm.AsmException; import act.asm.MethodVisitor; import act.asm.Type; import act.inject.genie.GenieInjector; import act.inject.param.ParamValueLoader; import act.inject.param.ProvidedValueLoader; import act.mail.MailerContext; import act.plugin.Plugin; import act.util.AsmTypes; import act.util.ByteCodeVisitor; import com.esotericsoftware.reflectasm.MethodAccess; import org.osgl.$; import org.osgl.inject.BeanSpec; import org.osgl.util.E; import org.osgl.util.S; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Plugin developer could extend this interface to inject * implicit variables to view template */ public abstract class ImplicitVariableProvider implements Plugin { /** * Returns a list of implicit variables the plugin needs to inject * into template render arguments for action view */ public abstract List<ActionViewVarDef> implicitActionViewVariables(); /** * Returns a list of implicit variables the plugin needs to inject * into template render arguments for mailer view */ public abstract List<MailerViewVarDef> implicitMailerViewVariables(); @Override public void register() { Act.viewManager().register(this); } public static class TemplateVariableScanner extends AppByteCodeScannerBase { private static class Meta { String className; String methodName; String varName; boolean isStatic; @Override public int hashCode() { return $.hc(className, methodName); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Meta) { Meta that = $.cast(obj); return $.eq(that.className, this.className) && $.eq(that.methodName, this.methodName); } return false; } @Override public String toString() { return S.concat("Template variable provider: ", className, "::", methodName); } } private Set<Meta> providers = new HashSet<>(); public TemplateVariableScanner(final App app) { app.jobManager().beforeAppStart(new Runnable() { @Override public void run() { register(app); } }); } @Override protected boolean shouldScan(String className) { return true; } @Override public ByteCodeVisitor byteCodeVisitor() { return new ByteCodeVisitor() { private String className; @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { if (isPublic(access)) { className = Type.getObjectType(name).getClassName(); } super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod(int access, final String methodName, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions); if (null == className) { return mv; } final boolean isStatic = isStatic(access); return new MethodVisitor(ASM5, mv) { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { AnnotationVisitor av = super.visitAnnotation(desc, visible); Type annoType = Type.getType(desc); if (AsmTypes.TEMPLATE_VARIABLE.asmType().equals(annoType)) { final Meta meta = new Meta(); meta.className = className; meta.methodName = methodName; meta.isStatic = isStatic; if (providers.contains(meta)) { throw AsmException.of("@ProvidesImplicitTemplateVariable annotated method cannot be overloaded: %s", meta.toString()); } providers.add(meta); return new AnnotationVisitor(ASM5, av) { @Override public void visit(String name, Object value) { super.visit(name, value); if ("value".equals(name)) { meta.varName = value.toString(); } } }; } return av; } }; } }; } @Override public void scanFinished(String className) { } private void register(App app) { for (Meta meta : providers) { register(meta, app); } } private void register(Meta meta, App app) { Class<?> cls = $.classForName(meta.className, app.classLoader()); Method method = findMethod(cls, meta.methodName); E.unexpectedIf(null == method, "Unable to find method %s", meta); register(meta, cls, method, app); } private Method findMethod(Class<?> cls, String methodName) { Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { if (!method.getName().equals(methodName)) { continue; } if (method.isAnnotationPresent(ProvidesImplicitTemplateVariable.class)) { return method; } } return null; } private void register(Meta meta, Class<?> cls, Method method, App app) { final ReflectedActionViewVarDef def = new ReflectedActionViewVarDef(meta, cls, method, app); if (def.supportMailer) { MailerViewVarDef mailerVarDef = new MailerViewVarDef(meta.varName, def.returnType()) { @Override public Object eval(MailerContext context) { return def.getValue(context.app()); } }; Act.viewManager().registerAppDefinedVar(mailerVarDef); } if (def.supportAction) { ActionViewVarDef actionVarDef = new ActionViewVarDef(meta.varName, def.returnType()) { @Override public Object eval(ActionContext context) { return def.getValue(context.app()); } }; Act.viewManager().registerAppDefinedVar(actionVarDef); } } private static class ReflectedActionViewVarDef { private Class<?> cls; private Method method; private MethodAccess methodAccess; private int methodIndex; private ParamValueLoader[] loaders; private boolean supportAction; private boolean supportMailer; private int paramLen; protected ReflectedActionViewVarDef(Meta meta, Class<?> cls, Method method, App app) { this.cls = cls; this.method = method; if (!meta.isStatic) { methodAccess = MethodAccess.get(cls); methodIndex = methodAccess.getIndex(method.getName(), method.getParameterTypes()); } initLoaders(app); } private void initLoaders(App app) { java.lang.reflect.Type[] types = method.getGenericParameterTypes(); int len = types.length; ParamValueLoader[] loaders = new ParamValueLoader[len]; if (0 < len) { GenieInjector injector = app.injector(); Annotation[][] annos = method.getParameterAnnotations(); for (int i = 0; i < len; ++i) { java.lang.reflect.Type type = types[i]; Annotation[] aa = annos[i]; BeanSpec spec = BeanSpec.of(type, aa, injector); E.unexpectedIf(!injector.injectable(spec), ""); loaders[i] = ProvidedValueLoader.get(spec, injector); if (spec.isInstanceOf(ActionContext.class) || requireAction(type)) { supportAction = true; } else if (spec.isInstanceOf(MailerContext.class)) { supportMailer = true; } } } if (!supportAction && !supportMailer) { supportAction = true; supportMailer = true; } this.loaders = loaders; this.paramLen = len; } private boolean requireAction(java.lang.reflect.Type type) { String name = type.toString(); if (name.contains("org.osgl.http")) { return true; } return false; } Class<?> returnType() { return method.getReturnType(); } Object getValue(App app) { Object[] params = params(); if (null != methodAccess) { return methodAccess.invoke(app.getInstance(cls), methodIndex, params); } else { return $.invokeStatic(method, params); } } private Object[] params() { Object[] params = new Object[paramLen]; for (int i = 0; i < paramLen; ++i) { ParamValueLoader loader = loaders[i]; params[i] = loader.load(null, null, false); } return params; } } } }