Package yalp.templates

Source Code of yalp.templates.GroovyTemplate$ExecutableTemplate$ActionBridge

package yalp.templates;

import com.jamonapi.Monitor;
import com.jamonapi.MonitorFactory;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.GroovyShell;
import groovy.lang.MissingPropertyException;
import groovy.lang.Script;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

import org.apache.commons.lang.StringEscapeUtils;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilationUnit.GroovyClassOperation;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.tools.GroovyClass;
import yalp.Logger;
import yalp.Yalp;
import yalp.Yalp.Mode;
import yalp.classloading.BytecodeCache;
import yalp.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer;
import yalp.data.binding.Unbinder;
import yalp.exceptions.ActionNotFoundException;
import yalp.exceptions.NoRouteFoundException;
import yalp.exceptions.YalpException;
import yalp.exceptions.TagInternalException;
import yalp.exceptions.TemplateCompilationException;
import yalp.exceptions.TemplateExecutionException;
import yalp.exceptions.TemplateExecutionException.DoBodyException;
import yalp.exceptions.TemplateNotFoundException;
import yalp.exceptions.UnexpectedException;
import yalp.i18n.Lang;
import yalp.i18n.Messages;
import yalp.libs.Codec;
import yalp.mvc.Http;
import yalp.utils.Java;
import yalp.mvc.ActionInvoker;
import yalp.mvc.Http.Request;
import yalp.mvc.Router;
import yalp.templates.types.SafeCSVFormatter;
import yalp.templates.types.SafeHTMLFormatter;
import yalp.templates.types.SafeXMLFormatter;
import yalp.utils.HTML;

/**
* A template
*/
public class GroovyTemplate extends BaseTemplate {

    static final Map<String, SafeFormatter> safeFormatters = new HashMap<String, SafeFormatter>();

    static {
        safeFormatters.put("csv", new SafeCSVFormatter());
        safeFormatters.put("html", new SafeHTMLFormatter());
        safeFormatters.put("xml", new SafeXMLFormatter());
    }

    public static <T> void registerFormatter(String format, SafeFormatter formatter) {
        safeFormatters.put(format, formatter);
    }

    static {
        new GroovyShell().evaluate("java.lang.String.metaClass.if = { condition -> if(condition) delegate; else '' }");
    }

    public GroovyTemplate(String name, String source) {
        super(name, source);
    }

    public GroovyTemplate(String source) {
        super(source);
    }

    public static class TClassLoader extends GroovyClassLoader {

        public TClassLoader() {
            super(Yalp.classloader);
        }

        public Class defineTemplate(String name, byte[] byteCode) {
            return defineClass(name, byteCode, 0, byteCode.length, Yalp.classloader.protectionDomain);
        }
    }

    @SuppressWarnings("unchecked")
    void directLoad(byte[] code) throws Exception {
        TClassLoader tClassLoader = new TClassLoader();
        String[] lines = new String(code, "utf-8").split("\n");
        this.linesMatrix = (HashMap<Integer, Integer>) Java.deserialize(Codec.decodeBASE64(lines[1]));
        this.doBodyLines = (HashSet<Integer>) Java.deserialize(Codec.decodeBASE64(lines[3]));
        for (int i = 4; i < lines.length; i = i + 2) {
            String className = lines[i];
            byte[] byteCode = Codec.decodeBASE64(lines[i + 1]);
            Class c = tClassLoader.defineTemplate(className, byteCode);
            if (compiledTemplate == null) {
                compiledTemplate = c;
            }
        }
    }

    public void compile() {
        if (compiledTemplate == null) {
            try {
                long start = System.currentTimeMillis();

                TClassLoader tClassLoader = new TClassLoader();

                // Let's compile the groovy source
                final List<GroovyClass> groovyClassesForThisTemplate = new ArrayList<GroovyClass>();
                // ~~~ Please !
                CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
                compilerConfiguration.setSourceEncoding("utf-8"); // ouf
                CompilationUnit compilationUnit = new CompilationUnit(compilerConfiguration);
                compilationUnit.addSource(new SourceUnit(name, compiledSource, compilerConfiguration, tClassLoader, compilationUnit.getErrorCollector()));
                Field phasesF = compilationUnit.getClass().getDeclaredField("phaseOperations");
                phasesF.setAccessible(true);
                LinkedList[] phases = (LinkedList[]) phasesF.get(compilationUnit);
                LinkedList<GroovyClassOperation> output = new LinkedList<GroovyClassOperation>();
                phases[Phases.OUTPUT] = output;
                output.add(new GroovyClassOperation() {
                    public void call(GroovyClass gclass) {
                        groovyClassesForThisTemplate.add(gclass);
                    }
                });
                compilationUnit.compile();
                // ouf

                // Define script classes
                StringBuilder sb = new StringBuilder();
                sb.append("LINESMATRIX" + "\n");
                sb.append(Codec.encodeBASE64(Java.serialize(linesMatrix)).replaceAll("\\s", ""));
                sb.append("\n");
                sb.append("DOBODYLINES" + "\n");
                sb.append(Codec.encodeBASE64(Java.serialize(doBodyLines)).replaceAll("\\s", ""));
                sb.append("\n");
                for (GroovyClass gclass : groovyClassesForThisTemplate) {
                    tClassLoader.defineTemplate(gclass.getName(), gclass.getBytes());
                    sb.append(gclass.getName());
                    sb.append("\n");
                    sb.append(Codec.encodeBASE64(gclass.getBytes()).replaceAll("\\s", ""));
                    sb.append("\n");
                }
                // Cache
                BytecodeCache.cacheBytecode(sb.toString().getBytes("utf-8"), name, source);
                compiledTemplate = tClassLoader.loadClass(groovyClassesForThisTemplate.get(0).getName());
                if (System.getProperty("precompile") != null) {
                    try {
                        // emit bytecode to standard class layout as well
                        File f = Yalp.getFile("precompiled/templates/" + name.replaceAll("\\{(.*)\\}", "from_$1").replace(":", "_").replace("..", "parent"));
                        f.getParentFile().mkdirs();
                        FileOutputStream fos = new FileOutputStream(f);
                        fos.write(sb.toString().getBytes("utf-8"));
                        fos.close();
                    } catch (Exception e) {
                        Logger.warn(e, "Unexpected");
                    }
                }

                if (Logger.isTraceEnabled()) {
                    Logger.trace("%sms to compile template %s to %d classes", System.currentTimeMillis() - start, name, groovyClassesForThisTemplate.size());
                }

            } catch (MultipleCompilationErrorsException e) {
                if (e.getErrorCollector().getLastError() != null) {
                    SyntaxErrorMessage errorMessage = (SyntaxErrorMessage) e.getErrorCollector().getLastError();
                    SyntaxException syntaxException = errorMessage.getCause();
                    Integer line = this.linesMatrix.get(syntaxException.getLine());
                    if (line == null) {
                        line = 0;
                    }
                    String message = syntaxException.getMessage();
                    if (message.indexOf("@") > 0) {
                        message = message.substring(0, message.lastIndexOf("@"));
                    }
                    throw new TemplateCompilationException(this, line, message);
                }
                throw new UnexpectedException(e);
            } catch (Exception e) {
                throw new UnexpectedException(e);
            }
        }
        compiledTemplateName = compiledTemplate.getName();
    }

    @Override
    public String render(Map<String, Object> args) {
        try {
            return super.render(args);
        } finally {
            currentTemplate.remove();
        }
    }

    @Override
    protected String internalRender(Map<String, Object> args) {
        compile();
        Binding binding = new Binding(args);
        binding.setVariable("yalp", new Yalp());
        binding.setVariable("messages", new Messages());
        binding.setVariable("lang", Lang.get());
        // If current response-object is present, add _response_encoding'
        Http.Response currentResponse = Http.Response.current();
        if (currentResponse != null) {
            binding.setVariable("_response_encoding", currentResponse.encoding);
        }
        StringWriter writer = null;
        Boolean applyLayouts = false;

        // must check if this is the first template being rendered..
        // If this template is called from inside another template,
        // then args("out") have already been initialized

        if (!args.containsKey("out")) {
            // This is the first template being rendered.
            // We have to set up the PrintWriter that this (and all sub-templates) are going
            // to write the output to..
            applyLayouts = true;
            layout.set(null);
            writer = new StringWriter();
            binding.setProperty("out", new PrintWriter(writer));
            currentTemplate.set(this);
        }
        if (!args.containsKey("_body") && !args.containsKey("_isLayout") && !args.containsKey("_isInclude")) {
            layoutData.set(new HashMap<Object, Object>());
            TagContext.init();
        }
        ExecutableTemplate t = (ExecutableTemplate) InvokerHelper.createScript(compiledTemplate, binding);
        t.init(this);
        Monitor monitor = null;
        try {
            monitor = MonitorFactory.start(name);
            long start = System.currentTimeMillis();
            t.run();
            monitor.stop();
            monitor = null;
            if (Logger.isTraceEnabled()) {
                Logger.trace("%sms to render template %s", System.currentTimeMillis() - start, name);
            }
        } catch (NoRouteFoundException e) {
            if (e.isSourceAvailable()) {
                throw e;
            }
            throwException(e);
        } catch (YalpException e) {
            throw (YalpException) cleanStackTrace(e);
        } catch (DoBodyException e) {
            if (Yalp.mode == Mode.DEV) {
                compiledTemplate = null;
                BytecodeCache.deleteBytecode(name);
            }
            Exception ex = (Exception) e.getCause();
            throwException(ex);
        } catch (Throwable e) {
            if (Yalp.mode == Mode.DEV) {
                compiledTemplate = null;
                BytecodeCache.deleteBytecode(name);
            }
            throwException(e);
        } finally {
            if (monitor != null) {
                monitor.stop();
            }
        }
        if (applyLayouts && layout.get() != null) {
            Map<String, Object> layoutArgs = new HashMap<String, Object>(args);
            layoutArgs.remove("out");
            layoutArgs.put("_isLayout", true);
            String layoutR = layout.get().internalRender(layoutArgs);

            // Must replace '____%LAYOUT%____' inside the string layoutR with the content from writer..
            final String whatToFind = "____%LAYOUT%____";
            final int pos = layoutR.indexOf(whatToFind);
            if (pos >= 0) {
                // prepending and appending directly to writer/buffer to prevent us
                // from having to duplicate the string.
                // this makes us use half of the memory!
                writer.getBuffer().insert(0, layoutR.substring(0, pos));
                writer.append(layoutR.substring(pos + whatToFind.length()));
                return writer.toString().trim();
            }
            return layoutR;
        }
        if (writer != null) {
            return writer.toString();
        }
        return null;
    }

    Throwable cleanStackTrace(Throwable e) {
        List<StackTraceElement> cleanTrace = new ArrayList<StackTraceElement>();
        for (StackTraceElement se : e.getStackTrace()) {
            //Here we are parsing the classname to find the file on disk the template was generated from.
            //See GroovyTemplateCompiler.head() for more info.
            if (se.getClassName().startsWith("Template_")) {
                String tn = se.getClassName().substring(9);
                if (tn.indexOf("$") > -1) {
                    tn = tn.substring(0, tn.indexOf("$"));
                }
                BaseTemplate template = TemplateLoader.templates.get(tn);
                if (template != null) {
                    Integer line = template.linesMatrix.get(se.getLineNumber());
                    if (line != null) {
                        String ext = "";
                        if (tn.indexOf(".") > -1) {
                            ext = tn.substring(tn.indexOf(".") + 1);
                            tn = tn.substring(0, tn.indexOf("."));
                        }
                        StackTraceElement nse = new StackTraceElement(TemplateLoader.templates.get(tn).name, ext, "line", line);
                        cleanTrace.add(nse);
                    }
                }
            }
            if (!se.getClassName().startsWith("org.codehaus.groovy.") && !se.getClassName().startsWith("groovy.") && !se.getClassName().startsWith("sun.reflect.") && !se.getClassName().startsWith("java.lang.reflect.") && !se.getClassName().startsWith("Template_")) {
                cleanTrace.add(se);
            }
        }
        e.setStackTrace(cleanTrace.toArray(new StackTraceElement[cleanTrace.size()]));
        return e;
    }

    /**
     * Groovy template
     */
    public static abstract class ExecutableTemplate extends Script {

        // Leave this field public to allow custom creation of TemplateExecutionException from different pkg
        public GroovyTemplate template;
        private String extension;

        public void init(GroovyTemplate t) {
            template = t;
            int index = template.name.lastIndexOf(".");
            if (index > 0) {
                extension = template.name.substring(index + 1);
            }
        }

        @Override
        public Object getProperty(String property) {
            try {
                if (property.equals("actionBridge")) {
                    return new ActionBridge(this);
                }
                return super.getProperty(property);
            } catch (MissingPropertyException mpe) {
                return null;
            }
        }

        public void invokeTag(Integer fromLine, String tag, Map<String, Object> attrs, Closure body) {
            String templateName = tag.replace(".", "/");
            String callerExtension = (extension != null) ? extension : "tag";

            BaseTemplate tagTemplate = null;
            try {
                tagTemplate = (BaseTemplate) TemplateLoader.load("tags/" + templateName + "." + callerExtension);
            } catch (TemplateNotFoundException e) {
                try {
                    tagTemplate = (BaseTemplate) TemplateLoader.load("tags/" + templateName + ".tag");
                } catch (TemplateNotFoundException ex) {
                    if (callerExtension.equals("tag")) {
                        throw new TemplateNotFoundException("tags/" + templateName + ".tag", template, fromLine);
                    }
                    throw new TemplateNotFoundException("tags/" + templateName + "." + callerExtension + " or tags/" + templateName + ".tag", template, fromLine);
                }
            }
            TagContext.enterTag(tag);
            Map<String, Object> args = new HashMap<String, Object>();
            args.put("session", getBinding().getVariables().get("session"));
            args.put("flash", getBinding().getVariables().get("flash"));
            args.put("request", getBinding().getVariables().get("request"));
            args.put("params", getBinding().getVariables().get("params"));
            args.put("yalp", getBinding().getVariables().get("yalp"));
            args.put("lang", getBinding().getVariables().get("lang"));
            args.put("messages", getBinding().getVariables().get("messages"));
            args.put("out", getBinding().getVariable("out"));
            args.put("_attrs", attrs);
            // all other vars are template-specific
            args.put("_caller", getBinding().getVariables());
            if (attrs != null) {
                for (Map.Entry<String, Object> entry : attrs.entrySet()) {
                    args.put("_" + entry.getKey(), entry.getValue());
                }
            }
            args.put("_body", body);
            try {
                tagTemplate.internalRender(args);
            } catch (TagInternalException e) {
                throw new TemplateExecutionException(template, fromLine, e.getMessage(), template.cleanStackTrace(e));
            } catch (TemplateNotFoundException e) {
                throw new TemplateNotFoundException(e.getPath(), template, fromLine);
            }
            TagContext.exitTag();
        }

        public Class _(String className) throws Exception {
            try {
                return Yalp.classloader.loadClass(className);
            } catch (ClassNotFoundException e) {
                return null;
            }
        }

        /**
         * This method is faster to call from groovy than __safe() since we only evaluate val.toString()
         * if we need to
         */
        public String __safeFaster(Object val) {
            if (val instanceof RawData) {
                return ((RawData) val).data;
            } else if (extension != null) {
                SafeFormatter formatter = safeFormatters.get(extension);
                if (formatter != null) {
                    return formatter.format(template, val);
                }
            }
            return (val != null) ? val.toString() : "";
        }

        public String __getMessage(Object[] val) {
            if (val == null) {
                throw new NullPointerException("You are trying to resolve a message with an expression " +
                        "that is resolved to null - " +
                        "have you forgotten quotes around the message-key?");
            }
            if (val.length == 1) {
                return Messages.get(val[0]);
            } else {
                // extract args from val
                Object[] args = new Object[val.length - 1];
                for (int i = 1; i < val.length; i++) {
                    args[i - 1] = val[i];
                }
                return Messages.get(val[0], args);
            }
        }

        public String __reverseWithCheck_absolute_true(String action) {
            return __reverseWithCheck(action, true);
        }

        public String __reverseWithCheck_absolute_false(String action) {
            return __reverseWithCheck(action, false);
        }

        private String __reverseWithCheck(String action, boolean absolute) {
            return Router.reverseWithCheck(action, Yalp.getVirtualFile(action), absolute);
        }

        public String __safe(Object val, String stringValue) {
            if (val instanceof RawData) {
                return ((RawData) val).data;
            }
            if (!template.name.endsWith(".html") || TagContext.hasParentTag("verbatim")) {
                return stringValue;
            }
            return HTML.htmlEscape(stringValue);
        }

        public Object get(String key) {
            return GroovyTemplate.layoutData.get().get(key);
        }

        static class ActionBridge extends GroovyObjectSupport {

            ExecutableTemplate template = null;
            String controller = null;
            boolean absolute = false;

            public ActionBridge(ExecutableTemplate template, String controllerPart, boolean absolute) {
                this.template = template;
                this.controller = controllerPart;
                this.absolute = absolute;
            }

            public ActionBridge(ExecutableTemplate template) {
                this.template = template;
            }

            @Override
            public Object getProperty(String property) {
                return new ActionBridge(template, controller == null ? property : controller + "." + property, absolute);
            }

            public Object _abs() {
                this.absolute = true;
                return this;
            }

            @Override
            @SuppressWarnings("unchecked")
            public Object invokeMethod(String name, Object param) {
                try {
                    if (controller == null) {
                        controller = Request.current().controller;
                    }
                    String action = controller + "." + name;
                    if (action.endsWith(".call")) {
                        action = action.substring(0, action.length() - 5);
                    }
                    try {
                        Map<String, Object> r = new HashMap<String, Object>();
                        Method actionMethod = (Method) ActionInvoker.getActionMethod(action)[1];
                        String[] names = (String[]) actionMethod.getDeclaringClass().getDeclaredField("$" + actionMethod.getName() + LocalVariablesNamesTracer.computeMethodHash(actionMethod.getParameterTypes())).get(null);
                        if (param instanceof Object[]) {
                            if (((Object[]) param).length == 1 && ((Object[]) param)[0] instanceof Map) {
                                r = (Map<String, Object>) ((Object[]) param)[0];
                            } else {
                                // too many parameters versus action, possibly a developer error. we must warn him.
                                if (names.length < ((Object[]) param).length) {
                                    throw new NoRouteFoundException(action, null);
                                }
                                for (int i = 0; i < ((Object[]) param).length; i++) {
                                    if (((Object[]) param)[i] instanceof Router.ActionDefinition && ((Object[]) param)[i] != null) {
                                        Unbinder.unBind(r, ((Object[]) param)[i].toString(), i < names.length ? names[i] : "", actionMethod.getAnnotations());
                                    } else if (isSimpleParam(actionMethod.getParameterTypes()[i])) {
                                        if (((Object[]) param)[i] != null) {
                                            Unbinder.unBind(r, ((Object[]) param)[i].toString(), i < names.length ? names[i] : "", actionMethod.getAnnotations());
                                        }
                                    } else {
                                        Unbinder.unBind(r, ((Object[]) param)[i], i < names.length ? names[i] : "", actionMethod.getAnnotations());
                                    }
                                }
                            }
                        }
                        Router.ActionDefinition def = Router.reverse(action, r);
                        if (absolute) {
                            def.absolute();
                        }
                        if (template.template.name.endsWith(".xml")) {
                            def.url = def.url.replace("&", "&amp;");
                        }
                        return def;
                    } catch (ActionNotFoundException e) {
                        throw new NoRouteFoundException(action, null);
                    }
                } catch (Exception e) {
                    if (e instanceof YalpException) {
                        throw (YalpException) e;
                    }
                    throw new UnexpectedException(e);
                }
            }
        }
    }

    static boolean isSimpleParam(Class type) {
        return Number.class.isAssignableFrom(type) || type.equals(String.class) || type.isPrimitive();
    }
}
TOP

Related Classes of yalp.templates.GroovyTemplate$ExecutableTemplate$ActionBridge

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.