com.chiorichan.factory.groovy.GroovyRegistry.java Source code

Java tutorial

Introduction

Here is the source code for com.chiorichan.factory.groovy.GroovyRegistry.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2016 Chiori Greene a.k.a. Chiori-chan <me@chiorichan.com>
 * All Right Reserved.
 */
package com.chiorichan.factory.groovy;

import groovy.lang.Binding;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovyShell;
import groovy.lang.MissingMethodException;
import groovy.lang.Script;
import groovy.transform.TimedInterrupt;
import io.netty.handler.codec.http.HttpResponseStatus;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.ErrorCollector;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
import org.codehaus.groovy.control.messages.ExceptionMessage;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.syntax.SyntaxException;

import com.chiorichan.AppConfig;
import com.chiorichan.Loader;
import com.chiorichan.account.Account;
import com.chiorichan.account.AccountManager;
import com.chiorichan.account.AccountType;
import com.chiorichan.account.auth.AccountAuthenticator;
import com.chiorichan.event.EventBus;
import com.chiorichan.factory.ScriptingContext;
import com.chiorichan.factory.ScriptingEngine;
import com.chiorichan.factory.ScriptingFactory;
import com.chiorichan.factory.ScriptingRegistry;
import com.chiorichan.lang.ExceptionCallback;
import com.chiorichan.lang.ExceptionContext;
import com.chiorichan.lang.ExceptionReport;
import com.chiorichan.lang.ReportingLevel;
import com.chiorichan.lang.SandboxSecurityException;
import com.chiorichan.lang.ScriptingException;
import com.chiorichan.permission.PermissionManager;
import com.chiorichan.permission.References;
import com.chiorichan.plugin.PluginManager;
import com.chiorichan.session.SessionManager;
import com.chiorichan.site.Site;
import com.chiorichan.site.SiteManager;
import com.chiorichan.tasks.TaskManager;
import com.chiorichan.tasks.Ticks;
import com.chiorichan.tasks.Timings;
import com.chiorichan.util.Looper;
import com.google.common.collect.Maps;

/**
 * Handles the registry of the Groovy related scripting language
 */
public class GroovyRegistry implements ScriptingRegistry {
    /*
     * Groovy Imports :P
     */
    private static final GroovyImportCustomizer imports = new GroovyImportCustomizer();

    private static final Class<?>[] classImports = new Class<?>[] { References.class, NestedScript.class,
            Loader.class, AccountManager.class, AccountType.class, Account.class, AccountAuthenticator.class,
            EventBus.class, PermissionManager.class, PluginManager.class, TaskManager.class, Ticks.class,
            Timings.class, SessionManager.class, SiteManager.class, Site.class, ScriptingContext.class };
    private static final String[] starImports = new String[] { "com.chiorichan.lang", "com.chiorichan.factory.api",
            "com.chiorichan.util", "org.apache.commons.lang3.text", "org.ocpsoft.prettytime", "java.util",
            "java.net", "com.google.common.base" };
    private static final Class<?>[] staticImports = new Class<?>[] { Looper.class, ReportingLevel.class,
            HttpResponseStatus.class };
    private static final GroovySandbox secure = new GroovySandbox();

    /*
     * Groovy Sandbox Customization
     */
    private static final ASTTransformationCustomizer timedInterrupt = new ASTTransformationCustomizer(
            TimedInterrupt.class);

    static {
        imports.addImports(classImports);
        imports.addStarImports(starImports);
        imports.addStaticStars(staticImports);

        // Transforms scripts to limit their execution to 30 seconds.
        long timeout = AppConfig.get().getLong("advanced.security.defaultScriptTimeout", 30L);
        if (timeout > 0) {
            Map<String, Object> timedInterruptParams = Maps.newHashMap();
            timedInterruptParams.put("value", timeout);
            timedInterrupt.setAnnotationParameters(timedInterruptParams);
        }
    }

    private static Map<String, String> scriptCacheMd5 = new HashMap<>();

    public static Script getCachedScript(ScriptingContext context, Binding binding) {
        try {
            // File classFile = FileFunc.buildFile( context.cache(), context.scriptPackage().replace( '.', File.separatorChar ), context.scriptSimpleName() + ".class" );
            if (scriptCacheMd5.containsKey(context.scriptClassName())) {
                if (scriptCacheMd5.get(context.site().getId() + "//" + context.scriptClassName())
                        .equals(context.md5())) {
                    Class<?> scriptClass = Class.forName(context.scriptClassName());
                    Constructor<?> con = scriptClass.getConstructor(Binding.class);
                    Script script = (Script) con.newInstance(binding);
                    return script;
                }
            } else
                scriptCacheMd5.put(context.site().getId() + "//" + context.scriptClassName(), context.md5());
        } catch (Throwable t) {

        }
        return null;
    }

    public GroovyRegistry() {
        // if ( Loader.getConfig().getBoolean( "advanced.scripting.gspEnabled", true ) )
        // EvalFactory.register( new EmbeddedGroovyScriptProcessor() );
        // if ( Loader.getConfig().getBoolean( "advanced.scripting.groovyEnabled", true ) )
        // EvalFactory.register( new GroovyScriptProcessor() );

        ScriptingFactory.register(this);

        ExceptionReport.registerException(new ExceptionCallback() {
            @Override
            public ReportingLevel callback(Throwable cause, ExceptionReport report, ExceptionContext context) {
                MultipleCompilationErrorsException exp = (MultipleCompilationErrorsException) cause;
                ErrorCollector e = exp.getErrorCollector();
                boolean abort = false;

                for (Object err : e.getErrors())
                    if (err instanceof Throwable) {
                        if (report.handleException((Throwable) err, context))
                            abort = true;
                    } else if (err instanceof ExceptionMessage) {
                        if (report.handleException(((ExceptionMessage) err).getCause(), context))
                            abort = true;
                    } else if (err instanceof SyntaxErrorMessage) {
                        report.handleException(((SyntaxErrorMessage) err).getCause(), context);
                        abort = true;
                    } else if (err instanceof Message) {
                        StringWriter writer = new StringWriter();
                        ((Message) err).write(new PrintWriter(writer, true));
                        report.handleException(new ScriptingException(ReportingLevel.E_NOTICE, writer.toString()),
                                context);
                    }
                return abort ? ReportingLevel.E_ERROR : ReportingLevel.E_IGNORABLE;
            }
        }, MultipleCompilationErrorsException.class);

        ExceptionReport.registerException(new ExceptionCallback() {
            @Override
            public ReportingLevel callback(Throwable cause, ExceptionReport report, ExceptionContext context) {
                report.addException(ReportingLevel.E_ERROR, cause);
                return ReportingLevel.E_ERROR;
            }
        }, TimeoutException.class, MissingMethodException.class, CompilationFailedException.class,
                SandboxSecurityException.class, GroovyRuntimeException.class);

        ExceptionReport.registerException(new ExceptionCallback() {
            @Override
            public ReportingLevel callback(Throwable cause, ExceptionReport report, ExceptionContext context) {
                report.addException(ReportingLevel.E_PARSE, cause);
                return ReportingLevel.E_PARSE;
            }
        }, SyntaxException.class);

        /**
         * {@link TimeoutException} is thrown when a script does not exit within an alloted amount of time.<br>
         * {@link MissingMethodException} is thrown when a groovy script tries to call a non-existent method<br>
         * {@link SyntaxException} is for when the user makes a syntax coding error<br>
         * {@link CompilationFailedException} is for when compilation fails from source errors<br>
         * {@link SandboxSecurityException} thrown when script attempts to access a blacklisted API<br>
         * {@link GroovyRuntimeException} thrown for basically all remaining Groovy exceptions not caught above
         */
    }

    @SuppressWarnings("deprecation")
    public GroovyShell getNewShell(ScriptingContext context, Binding binding) {
        CompilerConfiguration configuration = new CompilerConfiguration();

        /*
         * Finalize Imports and implement Sandbox
         */
        configuration.addCompilationCustomizers(imports, timedInterrupt, secure);

        /*
         * Set Groovy Base Script Class
         * TODO Implement new groovy API base
         */
        configuration.setScriptBaseClass(ScriptingBaseGroovy.class.getName());

        /*
         * Set default encoding
         */
        configuration.setSourceEncoding(context.factory().charset().name());

        configuration.setTargetDirectory(context.cache());

        return new GroovyShell(Loader.class.getClassLoader(), binding, configuration);
    }

    @Override
    public ScriptingEngine[] makeEngines(ScriptingContext context) {
        return new ScriptingEngine[] { new GroovyEngine(this), new EmbeddedGroovyEngine(this) };
    }

    public Script makeScript(GroovyShell shell, ScriptingContext context) throws ScriptingException {
        return makeScript(shell, context.readString(), context);
    }

    public Script makeScript(GroovyShell shell, String source, ScriptingContext context) throws ScriptingException {
        // TODO Determine if a package node is prohibited and replace with an alternative, e.g., public, private, etc.

        if (source.contains("package "))
            throw new ScriptingException(ReportingLevel.E_ERROR,
                    "Package path is predefined by Groovy Engine, remove `package` from source.");

        if (context.scriptPackage() != null) {
            source = "package " + context.scriptPackage() + "; " + source;
            context.baseSource(source);
        }

        return shell.parse(source, context.scriptName());
    }
}