Java tutorial
/** * 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 2015 Chiori-chan. All Right Reserved. * * @author Chiori Greene * @email chiorigreene@gmail.com */ package com.chiorichan.factory; import groovy.lang.Binding; import groovy.lang.GroovyRuntimeException; import groovy.lang.GroovyShell; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.io.Charsets; import org.apache.commons.io.FileUtils; import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.customizers.ImportCustomizer; import org.codehaus.groovy.control.customizers.SecureASTCustomizer; import com.chiorichan.ContentTypes; import com.chiorichan.Loader; import com.chiorichan.factory.interpreters.GSPInterpreter; import com.chiorichan.factory.interpreters.GroovyInterpreter; import com.chiorichan.factory.interpreters.HTMLInterpreter; import com.chiorichan.factory.interpreters.Interpreter; import com.chiorichan.factory.parsers.IncludesParser; import com.chiorichan.factory.parsers.LinksParser; import com.chiorichan.factory.postprocessors.ImagePostProcessor; import com.chiorichan.factory.postprocessors.JSMinPostProcessor; import com.chiorichan.factory.postprocessors.PostProcessor; import com.chiorichan.factory.preprocessors.CoffeePreProcessor; import com.chiorichan.factory.preprocessors.LessPreProcessor; import com.chiorichan.factory.preprocessors.PreProcessor; import com.chiorichan.http.WebInterpreter; import com.chiorichan.lang.EvalFactoryException; import com.chiorichan.lang.IgnorableEvalException; import com.chiorichan.site.Site; import com.google.common.collect.Lists; import com.google.common.collect.Sets; public class EvalFactory { public class GroovyShellTracker { private GroovyShell shell = null; private boolean inUse = false; public GroovyShellTracker(GroovyShell shell) { this.shell = shell; } public GroovyShell getShell() { return shell; } public void setInUse(boolean inUse) { this.inUse = inUse; } public boolean isInUse() { return inUse; } @Override public String toString() { return "GroovyShellTracker(shell=" + shell + ",inUse=" + inUse + ")"; } } protected Charset encoding = Charsets .toCharset(Loader.getConfig().getString("server.defaultEncoding", "UTF-8")); protected static List<PreProcessor> preProcessors = Lists.newCopyOnWriteArrayList(); protected static List<Interpreter> interpreters = Lists.newCopyOnWriteArrayList(); protected static List<PostProcessor> postProcessors = Lists.newCopyOnWriteArrayList(); protected ShellFactory shellFactory = new ShellFactory(); protected Set<GroovyShellTracker> groovyShells = Sets.newLinkedHashSet(); protected ByteArrayOutputStream bs = new ByteArrayOutputStream(); protected Binding binding; static { // TODO Allow to override and/or extending of Pre-Processors, Interpreters and Post-Processors. /** * Register Pre-Processors */ if (Loader.getConfig().getBoolean("advanced.processors.coffeeProcessorEnabled", true)) register(new CoffeePreProcessor()); if (Loader.getConfig().getBoolean("advanced.processors.lessProcessorEnabled", true)) register(new LessPreProcessor()); // register( new SassPreProcessor() ); /** * Register Interpreters */ if (Loader.getConfig().getBoolean("advanced.interpreters.gspEnabled", true)) register(new GSPInterpreter()); if (Loader.getConfig().getBoolean("advanced.interpreters.groovyEnabled", true)) register(new GroovyInterpreter()); register(new HTMLInterpreter()); /** * Register Post-Processors */ if (Loader.getConfig().getBoolean("advanced.processors.minifierJSProcessorEnabled", true)) register(new JSMinPostProcessor()); if (Loader.getConfig().getBoolean("advanced.processors.imageProcessorEnabled", true)) register(new ImagePostProcessor()); } protected EvalFactory(Binding binding) { this.binding = binding; setOutputStream(bs); } public static EvalFactory create(Binding binding) { return new EvalFactory(binding); } public static EvalFactory create(BindingProvider provider) { return provider.getEvalFactory(); } public void setVariable(String key, Object val) { binding.setVariable(key, val); } protected GroovyShellTracker getUnusedShellTracker() { for (GroovyShellTracker tracker : groovyShells) if (!tracker.isInUse()) return tracker; GroovyShell shell = getNewShell(); GroovyShellTracker tracker = new GroovyShellTracker(shell); groovyShells.add(tracker); return tracker; } protected GroovyShell getUnusedShell() { for (GroovyShellTracker tracker : groovyShells) if (!tracker.isInUse()) return tracker.getShell(); GroovyShell shell = getNewShell(); groovyShells.add(new GroovyShellTracker(shell)); return shell; } /** * Attempts to create a new GroovyShell instance using our own CompilerConfigurations * * @return * new instance of GroovyShell */ protected GroovyShell getNewShell() { CompilerConfiguration configuration = new CompilerConfiguration(); ImportCustomizer imports = new ImportCustomizer(); SecureASTCustomizer secure = new SecureASTCustomizer(); /* * Groovy Imports :P */ imports.addStarImports("com.chiorichan.lang", "com.chiorichan.util", "java.sql"); imports.addImports("com.chiorichan.Loader", "com.chiorichan.lang.HttpError"); /* * Finalize Imports and implement Sandbox */ configuration.addCompilationCustomizers(imports, secure); /* * Set Groovy Base Script Class */ configuration.setScriptBaseClass(ScriptingBaseGroovy.class.getName()); /* * Set default encoding */ configuration.setSourceEncoding(encoding.name()); return new GroovyShell(Loader.class.getClassLoader(), binding, configuration); } protected GroovyShellTracker getTracker(GroovyShell shell) { for (GroovyShellTracker t : groovyShells) if (t.getShell() == shell) return t; return null; } public List<ScriptTraceElement> getScriptTrace() { return shellFactory.examineStackTrace(Thread.currentThread().getStackTrace()); } /** * Attempts to find the current line number for the current groovy script. * * @return The current line number. Returns -1 if no there was a problem getting the current line number. */ public int getLineNumber() { List<ScriptTraceElement> scriptTrace = getScriptTrace(); if (scriptTrace.size() < 1) return -1; return scriptTrace.get(scriptTrace.size() - 1).lineNum; } public String getFileName() { List<ScriptTraceElement> scriptTrace = getScriptTrace(); if (scriptTrace.size() < 1) return "<unknown>"; String fileName = scriptTrace.get(scriptTrace.size() - 1).metaData.fileName; if (fileName == null || fileName.isEmpty()) return "<unknown>"; return fileName; } protected void lock(GroovyShell shell) { GroovyShellTracker tracker = getTracker(shell); if (tracker == null) { tracker = new GroovyShellTracker(shell); groovyShells.add(tracker); } tracker.setInUse(true); } protected void unlock(GroovyShell shell) { GroovyShellTracker tracker = getTracker(shell); if (tracker != null) tracker.setInUse(false); } public void setOutputStream(ByteArrayOutputStream bs) { try { binding.setProperty("out", new PrintStream(bs, true, encoding.name())); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } public void setEncoding(Charset encoding) { this.encoding = encoding; setOutputStream(bs); } /** * * @param orig * , The original class you would like to override. * @param replace * , An instance of the class you are overriding with. Must extend the original class. */ public static boolean overrideProcessor(Class<? extends PreProcessor> orig, PreProcessor replace) { if (!orig.isAssignableFrom(replace.getClass())) return false; for (PreProcessor p : preProcessors) if (p.getClass().equals(orig)) preProcessors.remove(p); register(replace); return true; } /** * * @param orig * , The original class you would like to override. * @param replace * , An instance of the class you are overriding with. Must extend the original class. */ public static boolean overrideInterpreter(Class<? extends Interpreter> orig, Interpreter replace) { if (!orig.isAssignableFrom(replace.getClass())) return false; for (Interpreter p : interpreters) if (p.getClass().equals(orig)) interpreters.remove(p); register(replace); return true; } /** * * @param orig * The original class you would like to override. * @param replace * An instance of the class you are overriding with. Must extend the original class. */ public static boolean overrideProcessor(Class<? extends PostProcessor> orig, PostProcessor replace) { if (!orig.isAssignableFrom(replace.getClass())) return false; for (PostProcessor p : postProcessors) if (p.getClass().equals(orig)) postProcessors.remove(p); register(replace); return true; } public static void register(PreProcessor preProcessor) { preProcessors.add(preProcessor); } public static void register(Interpreter interpreter) { interpreters.add(interpreter); } public static void register(PostProcessor postProcessor) { postProcessors.add(postProcessor); } public EvalFactoryResult eval(File fi, Site site) throws EvalFactoryException { EvalMetaData codeMeta = new EvalMetaData(); codeMeta.shell = FileInterpreter.determineShellFromName(fi.getName()); codeMeta.fileName = fi.getAbsolutePath(); return eval(fi, codeMeta, site); } public EvalFactoryResult eval(File fi, EvalMetaData meta, Site site) throws EvalFactoryException { try { return eval(FileUtils.readFileToString(fi, encoding), meta, site); } catch (IOException e) { throw new EvalFactoryException(e, shellFactory); } } public EvalFactoryResult eval(FileInterpreter fi, Site site) throws EvalFactoryException { return eval(fi, null, site); } public EvalFactoryResult eval(FileInterpreter fi, EvalMetaData meta, Site site) throws EvalFactoryException { if (meta == null) meta = new EvalMetaData(); if (fi instanceof WebInterpreter) meta.params = ((WebInterpreter) fi).getRewriteParams(); else meta.params = fi.getParams(); meta.contentType = fi.getContentType(); meta.shell = fi.getParams().get("shell"); meta.fileName = (fi.getFile() != null) ? fi.getFile().getAbsolutePath() : fi.getParams().get("file"); return eval(fi.consumeString(), meta, site); } public EvalFactoryResult eval(String code, Site site) throws EvalFactoryException { EvalMetaData codeMeta = new EvalMetaData(); codeMeta.shell = "html"; return eval(code, codeMeta, site); } public EvalFactoryResult eval(String code, EvalMetaData meta, Site site) throws EvalFactoryException { EvalFactoryResult result = new EvalFactoryResult(meta, site); if (code == null || code.isEmpty()) return result.setReason("Code Block was null or empty!"); if (meta.contentType == null) if (meta.fileName == null) meta.contentType = meta.shell; else meta.contentType = ContentTypes.getContentType(meta.fileName); meta.source = code; meta.site = site; try { if (site != null) code = runParsers(code, site); } catch (Exception e) { result.addException(new IgnorableEvalException("Exception caught while running parsers", e)); } for (PreProcessor p : preProcessors) { Set<String> handledTypes = new HashSet<String>(Arrays.asList(p.getHandledTypes())); for (String t : handledTypes) if (t.equalsIgnoreCase(meta.shell) || meta.contentType.toLowerCase().contains(t.toLowerCase()) || t.equalsIgnoreCase("all")) { try { String evaled = p.process(meta, code); if (evaled != null) { code = evaled; break; } } catch (Exception e) { result.addException( new IgnorableEvalException("Exception caught while running PreProcessor `" + p.getClass().getSimpleName() + "`", e)); } } } GroovyShellTracker tracker = getUnusedShellTracker(); GroovyShell shell = tracker.getShell(); shell.setVariable("__FILE__", meta.fileName); ByteBuf output = Unpooled.buffer(); boolean success = false; Loader.getLogger().fine("Locking GroovyShell '" + shell.toString() + "' for execution of '" + meta.fileName + "', length '" + code.length() + "'"); tracker.setInUse(true); byte[] saved = bs.toByteArray(); bs.reset(); for (Interpreter s : interpreters) { Set<String> handledTypes = new HashSet<String>(Arrays.asList(s.getHandledTypes())); for (String she : handledTypes) { if (she.equalsIgnoreCase(meta.shell) || she.equalsIgnoreCase("all")) { try { result.obj = s.eval(meta, code, shellFactory.setShell(shell), bs); } catch (EvalFactoryException e) { throw e; } catch (CompilationFailedException e) // This is usually a parsing exception { throw new EvalFactoryException(e, shellFactory, meta); } catch (SecurityException e) { throw new EvalFactoryException(e, shellFactory, meta); } catch (GroovyRuntimeException e) { throw new EvalFactoryException(e, shellFactory); } catch (Exception e) { throw new EvalFactoryException(e, shellFactory); } success = true; break; } } } try { output.writeBytes((success) ? bs.toByteArray() : code.getBytes(encoding)); bs.reset(); bs.write(saved); } catch (IOException e) { e.printStackTrace(); } Loader.getLogger().fine("Unlocking GroovyShell '" + shell.toString() + "' for execution of '" + meta.fileName + "', length '" + code.length() + "'"); tracker.setInUse(false); for (PostProcessor p : postProcessors) { Set<String> handledTypes = new HashSet<String>(Arrays.asList(p.getHandledTypes())); for (String t : handledTypes) if (t.equalsIgnoreCase(meta.shell) || meta.contentType.toLowerCase().contains(t.toLowerCase()) || t.equalsIgnoreCase("all")) { try { ByteBuf finished = p.process(meta, output); if (finished != null) { output = finished; break; } } catch (Exception e) { result.addException( new IgnorableEvalException("Exception caught while running PostProcessor `" + p.getClass().getSimpleName() + "`", e)); } } } return result.setResult(output, true); } private String runParsers(String source, Site site) throws Exception { source = new IncludesParser().runParser(source, site, this); source = new LinksParser().runParser(source, site); return source; } /** * Called when each request is finished * This method is mostly used to clear cache from the request */ public void onFinished() { shellFactory.onFinished(); } public ShellFactory getShellFactory() { return shellFactory; } }