package org.ocpsoft.rewrite.transform.markup.impl; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletContext; import org.jruby.CompatVersion; import org.jruby.RubyInstanceConfig.CompileMode; import org.jruby.embed.LocalContextScope; import org.jruby.embed.LocalVariableBehavior; import org.jruby.embed.ScriptingContainer; import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite; import org.ocpsoft.rewrite.transform.StringTransformer; import org.ocpsoft.rewrite.transform.Transformer; /** * Base class for {@link Transformer} implementations that use JRuby scripts. * * @author Christian Kaltepoth */ public abstract class JRubyTransformer<T extends JRubyTransformer<T>> extends StringTransformer { static final String CONTAINER_STORE_KEY = JRubyTransformer.class.getName() + "_CONTAINER_INSTANCE"; private CompileMode compileMode = CompileMode.JIT; private CompatVersion compatVersion = CompatVersion.RUBY2_0; /** * Return the load paths to use for {@link ScriptingContainer#setLoadPaths(List)}. */ public abstract List<String> getLoadPaths(); /** * This method must perform the transformation using the supplied {@link ScriptingContainer}. The container is pre * initialized with a variable <code>input</code> which should be used by the script to perform the transformation. */ public abstract Object runScript(ScriptingContainer container); /** * Just returns the current object as the correct type for make the fluent builder work with subclasses. */ public abstract T self(); public JRubyTransformer() {} @Override public final String transform(HttpServletRewrite event, String input) { ScriptingContainer container = getContainer(event.getServletContext()); try { Object result = null; // 'input' will be the string to transform container.put("input", input); // execute the script returned by the implementation result = runScript(container); // the result must be a string return result != null ? result.toString() : null; } finally { if (container != null) container.clear(); } } private ScriptingContainer getContainer(ServletContext context) { @SuppressWarnings("unchecked") Map<Class<T>, ScriptingContainer> storage = (Map<Class<T>, ScriptingContainer>) context .getAttribute(CONTAINER_STORE_KEY); if (storage == null) { storage = new ConcurrentHashMap<Class<T>, ScriptingContainer>(); context.setAttribute(CONTAINER_STORE_KEY, storage); } ScriptingContainer cachedContainer = storage.get(getTransformerType()); if (cachedContainer == null) { cachedContainer = new ScriptingContainer(LocalContextScope.CONCURRENT, LocalVariableBehavior.TRANSIENT); cachedContainer.setRunRubyInProcess(false); storage.put(getTransformerType(), cachedContainer); // the user may have set a custom CompileMode if (compileMode != null) { cachedContainer.setCompileMode(compileMode); } // the user may have set a customn CompatVersion if (compatVersion != null) { cachedContainer.setCompatVersion(compatVersion); } // scripts typically need to set the load path for 3rd party gems List<String> loadPaths = getLoadPaths(); if (loadPaths != null && !loadPaths.isEmpty()) { cachedContainer.getLoadPaths().addAll(loadPaths); } // perform custom initialization of the container prepareContainer(cachedContainer); } return cachedContainer; } abstract protected Class<T> getTransformerType(); abstract protected void prepareContainer(ScriptingContainer container); /** * Allows to customize the {@link CompileMode} used by the JRuby runtime. */ public T compileMode(CompileMode compileMode) { this.compileMode = compileMode; return self(); } /** * Allows to customize the {@link CompatVersion} used by the JRuby runtime. */ public T compatVersion(CompatVersion compatVersion) { this.compatVersion = compatVersion; return self(); } }