Author: radu Date: Tue Jul 14 12:01:33 2015 New Revision: 1690904 URL: http://svn.apache.org/r1690904 Log: SLING-913 - Add a cache for pre-compiled scripts
* implemented a size-bound event-based ScriptCache that can store compilation results for ScriptEngines that implement Compilable * provided a Felix web console plugin for the ScriptCache implementation so that cache entries can be manually removed from the cache, if needed * updated JavaDoc to comply with Java 1.8 Added: sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/CachedScript.java sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptCache.java sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptNameAware.java sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/ScriptNameAwareReader.java sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachedScriptImpl.java sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachingMap.java sling/trunk/bundles/scripting/core/src/main/resources/ sling/trunk/bundles/scripting/core/src/main/resources/scriptcache/ sling/trunk/bundles/scripting/core/src/main/resources/scriptcache/ui/ sling/trunk/bundles/scripting/core/src/main/resources/scriptcache/ui/scriptcache.js Modified: sling/trunk/bundles/scripting/api/pom.xml sling/trunk/bundles/scripting/api/src/main/java/javax/script/ScriptEngineManager.java sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/BindingsValuesProvider.java sling/trunk/bundles/scripting/core/pom.xml sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/DefaultSlingScript.java sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/LogWriter.java sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextIT.java Modified: sling/trunk/bundles/scripting/api/pom.xml URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/api/pom.xml?rev=1690904&r1=1690903&r2=1690904&view=diff ============================================================================== --- sling/trunk/bundles/scripting/api/pom.xml (original) +++ sling/trunk/bundles/scripting/api/pom.xml Tue Jul 14 12:01:33 2015 @@ -54,7 +54,7 @@ javax.script </Import-Package> <Export-Package> - org.apache.sling.scripting.api;version=2.2.0 + org.apache.sling.scripting.api;version=2.3.0 </Export-Package> </instructions> </configuration> Modified: sling/trunk/bundles/scripting/api/src/main/java/javax/script/ScriptEngineManager.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/api/src/main/java/javax/script/ScriptEngineManager.java?rev=1690904&r1=1690903&r2=1690904&view=diff ============================================================================== --- sling/trunk/bundles/scripting/api/src/main/java/javax/script/ScriptEngineManager.java (original) +++ sling/trunk/bundles/scripting/api/src/main/java/javax/script/ScriptEngineManager.java Tue Jul 14 12:01:33 2015 @@ -73,6 +73,8 @@ public class ScriptEngineManager { /** * @deprecated use {@link #getBindings()} instaed. This method is introduced * by the Livetribe JSR-223 implementation and is wrong. + * + * @return the bindings */ @Deprecated public Bindings getGlobalScope() { Modified: sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/BindingsValuesProvider.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/BindingsValuesProvider.java?rev=1690904&r1=1690903&r2=1690904&view=diff ============================================================================== --- sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/BindingsValuesProvider.java (original) +++ sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/BindingsValuesProvider.java Tue Jul 14 12:01:33 2015 @@ -28,7 +28,7 @@ public interface BindingsValuesProvider /** The name of the multi-value service property that defines the context(s) to which * a BindingsValuesProvider applies. This service property is optional, if not set - * the default value is {@link @DEFAULT_CONTEXT} + * the default value is {@link #DEFAULT_CONTEXT} */ String CONTEXT = "context"; Added: sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/CachedScript.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/CachedScript.java?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/CachedScript.java (added) +++ sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/CachedScript.java Tue Jul 14 12:01:33 2015 @@ -0,0 +1,44 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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. + ******************************************************************************/ + +package org.apache.sling.scripting.api; + +import javax.script.CompiledScript; + +/** + * The {@code CachedScript} provides an abstraction on top of {@link CompiledScript} such that compiled scripts can be cached for further + * executions. + */ +public interface CachedScript { + + /** + * Returns the path of the script which was compiled and cached. + * + * @return the script's path + */ + String getScriptPath(); + + /** + * Returns the compiled script which can be used for further executions / evaluations. + * + * @return the compiled script + */ + CompiledScript getCompiledScript(); + +} Added: sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptCache.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptCache.java?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptCache.java (added) +++ sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptCache.java Tue Jul 14 12:01:33 2015 @@ -0,0 +1,55 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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. + ******************************************************************************/ + +package org.apache.sling.scripting.api; + +/** + * The {@code ScriptCache} service interface defines a cache for compiled scripts. Implementations of this interface should be thread-safe. + */ +public interface ScriptCache { + + /** + * Retrieves the {@link CachedScript} corresponding to the script accessible from the {@code scriptPath}. + * + * @param scriptPath the path from where the script can be accessed + * @return the {@link CachedScript} if one exists, {@code null} otherwise + */ + CachedScript getScript(String scriptPath); + + /** + * Stores a {@link CachedScript} in the cache. If a previous version of it exist in the cache it is overridden. + * + * @param script the {@link CachedScript} that should be stored in the cache + */ + void putScript(CachedScript script); + + /** + * Empties the cache. + */ + void clear(); + + /** + * Removes the script identified by {@code scriptPath} from the cache. + * + * @param scriptPath the path from where the script can be accessed + * @return {@code true} if a script was cached from that path and was removed, {@code false} otherwise + */ + boolean removeScript(String scriptPath); + +} Added: sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptNameAware.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptNameAware.java?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptNameAware.java (added) +++ sling/trunk/bundles/scripting/api/src/main/java/org/apache/sling/scripting/api/ScriptNameAware.java Tue Jul 14 12:01:33 2015 @@ -0,0 +1,34 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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. + ******************************************************************************/ + +package org.apache.sling.scripting.api; + +/** + * Marker interface for various {@link java.io.Reader}s from which a script's name can be obtained. + */ +public interface ScriptNameAware { + + /** + * Returns the name of the script provided by a marked {@link java.io.Reader} implementation. + * + * @return the script's name + */ + String getScriptName(); + +} Modified: sling/trunk/bundles/scripting/core/pom.xml URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/pom.xml?rev=1690904&r1=1690903&r2=1690904&view=diff ============================================================================== --- sling/trunk/bundles/scripting/core/pom.xml (original) +++ sling/trunk/bundles/scripting/core/pom.xml Tue Jul 14 12:01:33 2015 @@ -61,7 +61,7 @@ <configuration> <instructions> <Export-Package> - org.apache.sling.scripting.core;version=2.0.8, + org.apache.sling.scripting.core;version=2.1.0, org.apache.sling.scripting.core.servlet;version=1.0.0 </Export-Package> <Private-Package> @@ -136,7 +136,13 @@ <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.scripting.api</artifactId> - <version>2.1.6</version> + <version>2.1.7-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.threads</artifactId> + <version>3.1.0</version> <scope>provided</scope> </dependency> @@ -156,6 +162,24 @@ <groupId>org.apache.felix</groupId> <artifactId>org.apache.felix.scr.annotations</artifactId> </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.webconsole</artifactId> + <version>3.1.8</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.4</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>2.4</version> + <scope>provided</scope> + </dependency> <dependency> <groupId>org.slf4j</groupId> Added: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/ScriptNameAwareReader.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/ScriptNameAwareReader.java?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/ScriptNameAwareReader.java (added) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/ScriptNameAwareReader.java Tue Jul 14 12:01:33 2015 @@ -0,0 +1,53 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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. + ******************************************************************************/ + +package org.apache.sling.scripting.core; + + +import java.io.FilterReader; +import java.io.Reader; + +import org.apache.sling.scripting.api.ScriptNameAware; + +/** + * The {@code ScriptNameAwareReader} is a {@link FilterReader} marked with the {@link ScriptNameAware} interface. This reader allows + * retrieving the contained script's name. + */ +public final class ScriptNameAwareReader extends FilterReader implements ScriptNameAware { + + private String scriptName; + + /** + * Creates a {@code ScriptNameAwareReader} based on another {@link Reader}. + * + * @param in the base {@link Reader} + * @param scriptName the script's name + */ + public ScriptNameAwareReader(Reader in, String scriptName) { + super(in); + this.scriptName = scriptName; + } + + @Override + public String getScriptName() { + return scriptName; + } + + +} Modified: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/DefaultSlingScript.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/DefaultSlingScript.java?rev=1690904&r1=1690903&r2=1690904&view=diff ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/DefaultSlingScript.java (original) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/DefaultSlingScript.java Tue Jul 14 12:01:33 2015 @@ -28,6 +28,7 @@ import static org.apache.sling.api.scrip import static org.apache.sling.api.scripting.SlingBindings.SLING; import java.io.BufferedReader; +import java.io.FilterReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -46,6 +47,8 @@ import java.util.Map; import java.util.Set; import javax.script.Bindings; +import javax.script.Compilable; +import javax.script.CompiledScript; import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; @@ -72,6 +75,11 @@ import org.apache.sling.api.scripting.Sl import org.apache.sling.api.scripting.SlingScriptConstants; import org.apache.sling.api.scripting.SlingScriptHelper; import org.apache.sling.scripting.api.BindingsValuesProvider; +import org.apache.sling.scripting.api.CachedScript; +import org.apache.sling.scripting.api.ScriptCache; +import org.apache.sling.scripting.api.ScriptNameAware; +import org.apache.sling.scripting.core.ScriptNameAwareReader; +import org.apache.sling.scripting.core.impl.helper.CachedScriptImpl; import org.apache.sling.scripting.core.impl.helper.ProtectedBindings; import org.osgi.framework.BundleContext; import org.slf4j.Logger; @@ -90,6 +98,7 @@ class DefaultSlingScript implements Slin new HashSet<String>(Arrays.asList(REQUEST, RESPONSE, READER, SLING, RESOURCE, OUT, LOG)); /** The resource pointing to the script. */ + private final Resource scriptResource; /** The name of the script (the resource path) */ @@ -116,6 +125,9 @@ class DefaultSlingScript implements Slin /** The cache for services. */ private final ServiceCache cache; + /* The cache for compiled scripts. */ + private final ScriptCache scriptCache; + /** * Constructor * @param bundleContext The bundle context @@ -128,12 +140,14 @@ class DefaultSlingScript implements Slin final Resource scriptResource, final ScriptEngine scriptEngine, final Collection<BindingsValuesProvider> bindingsValuesProviders, - final ServiceCache cache) { + final ServiceCache cache, + final ScriptCache scriptCache) { this.scriptResource = scriptResource; this.scriptEngine = scriptEngine; this.bundleContext = bundleContext; this.bindingsValuesProviders = bindingsValuesProviders; this.cache = cache; + this.scriptCache = scriptCache; this.scriptName = this.scriptResource.getPath(); // Now know how to get the input stream, we still have to decide // on the encoding of the stream's data. Primarily we assume it is @@ -358,7 +372,22 @@ class DefaultSlingScript implements Slin } // evaluate the script - final Object result = scriptEngine.eval(reader, ctx); + final Object result; + if (method == null && this.scriptEngine instanceof Compilable) { + CachedScript cachedScript = scriptCache.getScript(scriptName); + if (cachedScript == null) { + ScriptNameAwareReader snReader = new ScriptNameAwareReader(reader, scriptName); + CompiledScript compiledScript = ((Compilable) scriptEngine).compile(snReader); + cachedScript = new CachedScriptImpl(scriptName, compiledScript); + scriptCache.putScript(cachedScript); + LOGGER.debug("Adding {} to the script cache.", scriptName); + } else { + LOGGER.debug("Script {} was already cached.", scriptName); + } + result = cachedScript.getCompiledScript().eval(ctx); + } else { + result = scriptEngine.eval(reader, ctx); + } // call method - if supplied and script engine supports direct invocation if ( method != null && (this.scriptEngine instanceof Invocable)) { Modified: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/LogWriter.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/LogWriter.java?rev=1690904&r1=1690903&r2=1690904&view=diff ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/LogWriter.java (original) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/LogWriter.java Tue Jul 14 12:01:33 2015 @@ -41,6 +41,8 @@ public class LogWriter extends Writer { /** * Creates a writer based on the given logger. + * + * @param logger the logger */ public LogWriter(Logger logger) { this.logger = logger; Added: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java (added) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java Tue Jul 14 12:01:33 2015 @@ -0,0 +1,141 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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. + ******************************************************************************/ + +package org.apache.sling.scripting.core.impl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.felix.webconsole.AbstractWebConsolePlugin; +import org.apache.felix.webconsole.WebConsoleConstants; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.scripting.api.ScriptCache; +import org.osgi.framework.Constants; + +@Component +@Service +@Properties({ + @Property(name = Constants.SERVICE_DESCRIPTION, value = "Script Cache"), + @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"), + @Property(name = WebConsoleConstants.PLUGIN_LABEL, value = ScriptCacheConsolePlugin.CONSOLE_LABEL), + @Property(name = WebConsoleConstants.PLUGIN_TITLE, value = ScriptCacheConsolePlugin.CONSOLE_TITLE), + @Property(name = "felix.webconsole.category", value = "Sling") +}) +public class ScriptCacheConsolePlugin extends AbstractWebConsolePlugin { + + public static final String CONSOLE_LABEL = "scriptcache"; + public static final String CONSOLE_TITLE = "Script Cache Status"; + public static final String RESOURCES = CONSOLE_LABEL + "/ui"; + + private static final String SCRIPTCACHE_JS = "scriptcache.js"; + private static final String CTYPE_JAVASCRIPT = "application/javascript"; + private static final String POST_SCRIPT = "script"; + + @Reference + private ScriptCache scriptCache = null; + + public ScriptCacheConsolePlugin() { + super(); + } + + @Override + public String getTitle() { + return CONSOLE_TITLE; + } + + @Override + public String getLabel() { + return CONSOLE_LABEL; + } + + @Override + protected void renderContent(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) + throws ServletException, IOException { + if (scriptCache instanceof ScriptCacheImpl) { + ScriptCacheImpl scriptCacheImpl = (ScriptCacheImpl) scriptCache; + List<String> scripts = new ArrayList<String>(scriptCacheImpl.getCachedScripts()); + StringBuilder sb = new StringBuilder(); + sb.append("<script type='text/javascript' src='").append(RESOURCES).append("/").append(SCRIPTCACHE_JS).append("'></script>"); + sb.append("<div id='cached-scripts' class='ui-widget statline'>"); + if (scripts.size() > 0) { + Collections.sort(scripts); + sb.append("<p class='ui-widget-header'>Cached Scripts</p>"); + sb.append("<table class='nicetable ui-widget-content'>"); + int i = 0; + for (String script : scripts) { + sb.append("<tr class='").append(i % 2 == 0 ? "even" : "odd").append(" ui-state-default'><td>").append(++i).append + ("<td><code>").append(script).append("</code></td><td><button type='button' " + + "data-script='").append(script).append("'>Remove</button></td></tr>"); + } + sb.append("<tr><td colspan='3'><button type='button' id='clearcache'>Clear Cache</button></td></tr>"); + sb.append("</table>"); + + } else { + sb.append("<p class='ui-state-highlight'>The Script Cache doesn't contain any scripts.</p>"); + } + sb.append("</div>"); + httpServletResponse.getWriter().write(sb.toString()); + } + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if (request.getRequestURI().endsWith(RESOURCES + "/" + SCRIPTCACHE_JS)) { + response.setContentType(CTYPE_JAVASCRIPT); + IOUtils.copy(getClass().getResourceAsStream("/" + RESOURCES + "/" + SCRIPTCACHE_JS), response.getOutputStream()); + } else { + super.doGet(request, response); + } + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String script = req.getParameter(POST_SCRIPT); + if (StringUtils.isNotEmpty(script)) { + if ("all".equals(script)) { + scriptCache.clear(); + renderContent(req, resp); + } else { + boolean success = scriptCache.removeScript(script); + if (success) { + renderContent(req, resp); + } + } + resp.setStatus(HttpServletResponse.SC_OK); + } else { + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + } +} + + Added: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java (added) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java Tue Jul 14 12:01:33 2015 @@ -0,0 +1,302 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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. + ******************************************************************************/ + +package org.apache.sling.scripting.core.impl; + +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.PropertyUnbounded; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.ReferencePolicy; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingConstants; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.commons.osgi.PropertiesUtil; +import org.apache.sling.commons.threads.ThreadPool; +import org.apache.sling.commons.threads.ThreadPoolManager; +import org.apache.sling.scripting.api.CachedScript; +import org.apache.sling.scripting.api.ScriptCache; +import org.apache.sling.scripting.core.impl.helper.CachingMap; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + metatype = true, + label = "Apache Sling Script Cache", + description = "The Script Cache is useful for running previously compiled scripts." +) +@Properties({ + @Property( + name = ScriptCacheImpl.PROP_CACHE_SIZE, + intValue = ScriptCacheImpl.DEFAULT_CACHE_SIZE, + label = "Cache Size", + description = "The Cache Size defines the maximum number of compiled script references that will be stored in the cache's" + + " internal map." + ), + @Property( + name = ScriptCacheImpl.PROP_ADDITIONAL_EXTENSIONS, + value = "", + label = "Additional Extensions", + description = "Scripts from the search paths with these extensions will also be monitored so that changes to them will " + + "clean the cache if the cache contains them.", + unbounded = PropertyUnbounded.ARRAY + ) +}) +@Service(ScriptCache.class) +@Reference( + name = "scriptEngineFactory", + cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, + referenceInterface = ScriptEngineFactory.class, + policy = ReferencePolicy.DYNAMIC +) +@SuppressWarnings("unused") +/** + * The {@code ScriptCache} stores information about {@link CompiledScript} instances evaluated by various {@link ScriptEngine}s that + * implement the {@link Compilable} interface. + */ +public class ScriptCacheImpl implements EventHandler, ScriptCache { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScriptCacheImpl.class); + + public static final int DEFAULT_CACHE_SIZE = 65536; + public static final String PROP_CACHE_SIZE = "org.apache.sling.scripting.cache.size"; + public static final String PROP_ADDITIONAL_EXTENSIONS = "org.apache.sling.scripting.cache.additional_extensions"; + + private BundleContext bundleContext; + private Map<String, SoftReference<CachedScript>> internalMap; + private ServiceRegistration eventHandlerServiceRegistration = null; + private Set<String> extensions = new HashSet<String>(); + private String[] additionalExtensions = new String[]{}; + + // use a static policy so that we can reconfigure the watched script files if the search paths are changed + @Reference(policy = ReferencePolicy.STATIC) + private ResourceResolverFactory rrf = null; + + @Reference + private ThreadPoolManager threadPoolManager = null; + + private ThreadPool threadPool; + private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + private final Lock readLock = rwl.readLock(); + private final Lock writeLock = rwl.writeLock(); + boolean active = false; + + public ScriptCacheImpl() { + internalMap = new CachingMap<CachedScript>(DEFAULT_CACHE_SIZE); + } + + @Override + public CachedScript getScript(String scriptPath) { + readLock.lock(); + SoftReference<CachedScript> reference = null; + try { + reference = internalMap.get(scriptPath); + } finally { + readLock.unlock(); + } + return reference != null ? reference.get() : null; + } + + @Override + public void putScript(CachedScript script) { + SoftReference<CachedScript> reference = new SoftReference<CachedScript>(script); + writeLock.lock(); + try { + internalMap.put(script.getScriptPath(), reference); + } finally { + writeLock.unlock(); + } + } + + @Override + public void clear() { + writeLock.lock(); + try { + internalMap.clear(); + } finally { + writeLock.unlock(); + } + } + + @Override + public boolean removeScript(String scriptPath) { + writeLock.lock(); + try { + SoftReference<CachedScript> reference = internalMap.remove(scriptPath); + if (reference != null) { + return true; + } + return false; + } finally { + writeLock.unlock(); + } + } + + @Override + public void handleEvent(final Event event) { + /** + * since the events trigger a synchronised map operation (remove in this case) we should handle events asynchronously so that we + * don't block event processing + */ + final String topic = event.getTopic(); + if (SlingConstants.TOPIC_RESOURCE_CHANGED.equals(topic) || SlingConstants.TOPIC_RESOURCE_REMOVED.equals(topic)) { + Runnable eventTask = new Runnable() { + @Override + public void run() { + String path = (String) event.getProperty(SlingConstants.PROPERTY_PATH); + writeLock.lock(); + try { + internalMap.remove(path); + LOGGER.debug("Detected script change for {} - removed entry from the cache.", path); + } finally { + writeLock.unlock(); + } + } + }; + threadPool.execute(eventTask); + } + } + + protected Set<String> getCachedScripts() { + readLock.lock(); + try { + return internalMap.keySet(); + } finally { + readLock.unlock(); + } + } + + @Activate + @SuppressWarnings("unused") + protected void activate(ComponentContext componentContext) { + threadPool = threadPoolManager.get("Script Cache Thread Pool"); + bundleContext = componentContext.getBundleContext(); + Dictionary properties = componentContext.getProperties(); + additionalExtensions = PropertiesUtil.toStringArray(properties.get(PROP_ADDITIONAL_EXTENSIONS)); + int newMaxCacheSize = PropertiesUtil.toInteger(properties.get(PROP_CACHE_SIZE), DEFAULT_CACHE_SIZE); + if (newMaxCacheSize != DEFAULT_CACHE_SIZE) { + // change the map only if there's a configuration change regarding the cache's max size + CachingMap<CachedScript> newMap = new CachingMap<CachedScript>(newMaxCacheSize); + newMap.putAll(internalMap); + internalMap = newMap; + } + configureCache(); + active = true; + } + + @SuppressWarnings("unchecked") + private void configureCache() { + writeLock.lock(); + ResourceResolver adminResolver = null; + try { + if (eventHandlerServiceRegistration != null) { + eventHandlerServiceRegistration.unregister(); + } + internalMap.clear(); + extensions.addAll(Arrays.asList(additionalExtensions)); + if (extensions.size() > 0) { + adminResolver = rrf.getAdministrativeResourceResolver(null); + StringBuilder eventHandlerFilter = new StringBuilder("(|"); + for (String searchPath : adminResolver.getSearchPath()) { + for (String extension : extensions) { + eventHandlerFilter.append("(path=").append(searchPath).append("**/*.").append(extension).append(")"); + } + } + eventHandlerFilter.append(")"); + Dictionary eventHandlerProperties = new Hashtable(); + eventHandlerProperties.put(EventConstants.EVENT_FILTER, eventHandlerFilter.toString()); + eventHandlerProperties.put(EventConstants.EVENT_TOPIC, + new String[]{SlingConstants.TOPIC_RESOURCE_CHANGED, SlingConstants.TOPIC_RESOURCE_REMOVED}); + eventHandlerServiceRegistration = bundleContext.registerService(EventHandler.class.getName(), this, eventHandlerProperties); + } + } catch (LoginException e) { + LOGGER.error("Unable to set automated cache invalidation for the ScriptCache.", e); + } finally { + if (adminResolver != null) { + adminResolver.close(); + } + writeLock.unlock(); + } + } + + @Deactivate + @SuppressWarnings("unused") + protected void deactivate(ComponentContext componentContext) { + internalMap.clear(); + if (eventHandlerServiceRegistration != null) { + eventHandlerServiceRegistration.unregister(); + eventHandlerServiceRegistration = null; + } + if (threadPool != null) { + threadPoolManager.release(threadPool); + threadPool = null; + } + active = false; + } + + protected void bindScriptEngineFactory(ScriptEngineFactory scriptEngineFactory, Map<String, Object> properties) { + ScriptEngine engine = scriptEngineFactory.getScriptEngine(); + if (engine instanceof Compilable) { + /** + * we only care about creating an EventHandler that monitors scripts generated by script engines which implement Compilable + */ + for (String extension : scriptEngineFactory.getExtensions()) { + extensions.add(extension); + } + if (active) { + configureCache(); + } + } + } + + protected void unbindScriptEngineFactory(ScriptEngineFactory scriptEngineFactory, Map<String, Object> properties) { + for (String extension : scriptEngineFactory.getExtensions()) { + extensions.remove(extension); + } + if (active) { + configureCache(); + } + } +} Modified: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java?rev=1690904&r1=1690903&r2=1690904&view=diff ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java (original) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java Tue Jul 14 12:01:33 2015 @@ -31,6 +31,7 @@ import org.apache.sling.api.resource.Res import org.apache.sling.commons.mime.MimeTypeProvider; import org.apache.sling.scripting.api.BindingsValuesProvider; import org.apache.sling.scripting.api.BindingsValuesProvidersByContext; +import org.apache.sling.scripting.api.ScriptCache; import org.apache.sling.scripting.core.impl.helper.SlingScriptEngineManager; import org.osgi.framework.BundleContext; import org.osgi.service.component.ComponentContext; @@ -73,6 +74,9 @@ public class SlingScriptAdapterFactory i @Reference private BindingsValuesProvidersByContext bindingsValuesProviderTracker; + @Reference + private ScriptCache scriptCache; + // ---------- AdapterFactory ----------------------------------------------- @SuppressWarnings("unchecked") @@ -88,7 +92,7 @@ public class SlingScriptAdapterFactory i bindingsValuesProviderTracker.getBindingsValuesProviders(engine.getFactory(), BINDINGS_CONTEXT); // unchecked cast return (AdapterType) new DefaultSlingScript(this.bundleContext, - resource, engine, bindingsValuesProviders, this.serviceCache); + resource, engine, bindingsValuesProviders, this.serviceCache, scriptCache); } return null; Added: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachedScriptImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachedScriptImpl.java?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachedScriptImpl.java (added) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachedScriptImpl.java Tue Jul 14 12:01:33 2015 @@ -0,0 +1,43 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + ******************************************************************************/ + +package org.apache.sling.scripting.core.impl.helper; + +import javax.script.CompiledScript; + +import org.apache.sling.scripting.api.CachedScript; + +public class CachedScriptImpl implements CachedScript { + + private String scriptPath; + private CompiledScript compiledScript; + + public CachedScriptImpl(String scriptPath, CompiledScript compiledScript) { + this.scriptPath = scriptPath; + this.compiledScript = compiledScript; + } + + public String getScriptPath() { + return scriptPath; + } + + public CompiledScript getCompiledScript() { + return compiledScript; + } +} Added: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachingMap.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachingMap.java?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachingMap.java (added) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/helper/CachingMap.java Tue Jul 14 12:01:33 2015 @@ -0,0 +1,54 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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. + ******************************************************************************/ + +package org.apache.sling.scripting.core.impl.helper; + +import java.lang.ref.SoftReference; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * <p> + * The {@code CachingMap} class offers an implementation of a {@link LinkedHashMap} that can be used for caches. The maps' values are + * {@link SoftReference}s, such that garbage collection can be performed on the cache, if needed. + * </p> + * <p> + * Read / write operations are <i>NOT</i> synchronised. + * </p> + * + * @param <T> the type to which {@link SoftReference}s will be kept + */ +public class CachingMap<T> extends LinkedHashMap<String, SoftReference<T>> { + + private int capacity; + + /** + * Creates a caching map with a maximum capacity equal to the {@code capacity} parameter. + * + * @param capacity the maximum capacity; if {@code capacity < 1} then this map will always remove the latest added element + */ + public CachingMap(int capacity) { + this.capacity = capacity; + } + + @Override + protected boolean removeEldestEntry(Map.Entry<String, SoftReference<T>> eldest) { + return size() > capacity; + } +} Added: sling/trunk/bundles/scripting/core/src/main/resources/scriptcache/ui/scriptcache.js URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/resources/scriptcache/ui/scriptcache.js?rev=1690904&view=auto ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/resources/scriptcache/ui/scriptcache.js (added) +++ sling/trunk/bundles/scripting/core/src/main/resources/scriptcache/ui/scriptcache.js Tue Jul 14 12:01:33 2015 @@ -0,0 +1,51 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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. + ******************************************************************************/ +(function () { + $(document).ready(function() { + $('div#cached-scripts button[id!="clearcache"]').on('click', function (e) { + e.preventDefault(); + var script = $(this).data('script'); + clearCache(script); + }); + + $('#clearcache').on('click', function (e) { + e.preventDefault(); + clearCache('all'); + }); + }); + + function clearCache(script) { + $.ajax( + { + type: 'POST', + data: { + script: script + } + } + ).success( + function (data) { + $('#cached-scripts').replaceWith(data); + } + ).fail( + function () { + alert("Unable to clear " + (script === 'all' ? 'cache' : 'script ' + script)); + } + ); + } +})(); \ No newline at end of file Modified: sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextIT.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextIT.java?rev=1690904&r1=1690903&r2=1690904&view=diff ============================================================================== --- sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextIT.java (original) +++ sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextIT.java Tue Jul 14 12:01:33 2015 @@ -57,15 +57,15 @@ import org.osgi.framework.ServiceRegistr @RunWith(PaxExam.class) public class BindingsValuesProvidersByContextIT { - + @Inject private BindingsValuesProvidersByContext bvpProvider; - + @Inject private BundleContext bundleContext; - + private final List<ServiceRegistration> regs = new ArrayList<ServiceRegistration>(); - + @org.ops4j.pax.exam.Configuration public Option[] config() { final String localRepo = System.getProperty("maven.repo.local", ""); @@ -75,7 +75,7 @@ public class BindingsValuesProvidersByCo if(!bundleFile.canRead()) { throw new IllegalArgumentException( "Cannot read from bundle file " + bundleFile.getAbsolutePath()); } - + return options( when( localRepo.length() > 0 ).useOptions( systemProperty("org.ops4j.pax.url.mvn.localRepository").value(localRepo) @@ -84,30 +84,34 @@ public class BindingsValuesProvidersByCo bundle(bundleFile.toURI().toString()), mavenBundle("org.apache.felix", "org.apache.felix.scr", "1.6.2"), mavenBundle("org.apache.felix", "org.apache.felix.eventadmin", "1.3.2"), - - mavenBundle("org.apache.sling", "org.apache.sling.scripting.api", "2.1.6"), + mavenBundle("org.apache.felix", "org.apache.felix.webconsole", "3.1.8"), + + mavenBundle("org.apache.sling", "org.apache.sling.scripting.api", "2.1.7-SNAPSHOT"), + mavenBundle("org.apache.sling", "org.apache.sling.commons.threads", "3.1.0"), mavenBundle("org.apache.sling", "org.apache.sling.api", "2.4.2"), mavenBundle("org.apache.sling", "org.apache.sling.commons.mime", "2.1.4"), mavenBundle("org.apache.sling", "org.apache.sling.commons.osgi", "2.2.0"), - - mavenBundle("org.mortbay.jetty", "servlet-api-2.5", "6.1.14") + + mavenBundle("org.mortbay.jetty", "servlet-api-2.5", "6.1.14"), + mavenBundle("commons-io", "commons-io", "2.4"), + mavenBundle("commons-lang", "commons-lang", "2.4") ), junitBundles() ); } - + @Before public void setup() { regs.clear(); } - + @After public void cleanup() { for(ServiceRegistration reg : regs) { reg.unregister(); } } - + private Dictionary<String, Object> getProperties(String context, String engineName) { final Dictionary<String, Object> props = new Hashtable<String, Object>(); @@ -126,21 +130,21 @@ public class BindingsValuesProvidersByCo public String toString() { return id; } - + public void addBindings(Bindings b) { } }; - + regs.add(bundleContext.registerService(BindingsValuesProvider.class.getName(), bvp, getProperties(context, engineName))); } - + private void addBVPWithServiceRanking(final String id, String context, String engineName, int serviceRanking) { final BindingsValuesProvider bvp = new BindingsValuesProvider() { @Override public String toString() { return id; } - + public void addBindings(Bindings b) { } }; @@ -148,7 +152,7 @@ public class BindingsValuesProvidersByCo properties.put(Constants.SERVICE_RANKING, serviceRanking); regs.add(bundleContext.registerService(BindingsValuesProvider.class.getName(), bvp, properties)); } - + private void addMap(final String id, String context, String engineName) { final Map<String, Object> result = new HashMap<String, Object>() { private static final long serialVersionUID = 1L; @@ -158,65 +162,65 @@ public class BindingsValuesProvidersByCo return "M_" + id; } }; - + regs.add(bundleContext.registerService(Map.class.getName(), result, getProperties(context, engineName))); } - + private ScriptEngineFactory factory(final String engineName) { return new ScriptEngineFactory() { - + public ScriptEngine getScriptEngine() { return null; } - + public String getProgram(String... arg0) { return null; } - + public Object getParameter(String arg0) { return null; } - + public String getOutputStatement(String arg0) { return null; } - + public List<String> getNames() { final List<String> names = new ArrayList<String>(); names.add(engineName); return names; } - + public List<String> getMimeTypes() { return null; } - + public String getMethodCallSyntax(String arg0, String arg1, String... arg2) { return null; } - + public String getLanguageVersion() { return null; } - + public String getLanguageName() { return null; } - + public List<String> getExtensions() { return null; } - + public String getEngineVersion() { return null; } - + public String getEngineName() { return engineName; } }; } - + private String asString(Collection<?> data, boolean sortList) { final List<String> maybeSorted = new ArrayList<String>(); for(Object o : data) { @@ -225,7 +229,7 @@ public class BindingsValuesProvidersByCo if(sortList) { Collections.sort(maybeSorted); } - + final StringBuilder sb = new StringBuilder(); for(String str : maybeSorted) { if(sb.length() > 0) { @@ -235,11 +239,11 @@ public class BindingsValuesProvidersByCo } return sb.toString(); } - + private String asString(Collection<?> data) { return asString(data, true); } - + @Test public void testAny() { addBVP("one", null, "js"); @@ -247,15 +251,15 @@ public class BindingsValuesProvidersByCo addBVP("three", null, "*"); addBVP("four", null, "ANY"); addBVP("five", null, "basic"); - + assertEquals("four,one,three,two", asString(bvpProvider.getBindingsValuesProviders(factory("js"), null))); assertEquals("five,four,three,two", asString(bvpProvider.getBindingsValuesProviders(factory("basic"), null))); assertEquals("four,three,two", asString(bvpProvider.getBindingsValuesProviders(factory("other"), null))); - + final String unsorted = asString(bvpProvider.getBindingsValuesProviders(factory("js"), null), false); assertTrue("Expecting js language-specific BVP at the end", unsorted.endsWith("one")); } - + @Test public void testContextsAndLanguages() { addBVP("foo", null, "js"); @@ -272,11 +276,11 @@ public class BindingsValuesProvidersByCo assertEquals("o1,o2,o3", asString(bvpProvider.getBindingsValuesProviders(factory("js"), "other"))); assertEquals("o4,python", asString(bvpProvider.getBindingsValuesProviders(factory("python"), "python"))); assertEquals("", asString(bvpProvider.getBindingsValuesProviders(factory("js"), "unusedContext"))); - + final String unsorted = asString(bvpProvider.getBindingsValuesProviders(factory("python"), "python"), false); assertTrue("Expecting python language-specific BVP at the end", unsorted.endsWith("python")); } - + @Test public void testMapsAndBvps() { addBVP("foo", null, "js"); @@ -293,11 +297,11 @@ public class BindingsValuesProvidersByCo assertEquals("M_o1,M_o3,o2", asString(bvpProvider.getBindingsValuesProviders(factory("js"), "other"))); assertEquals("", asString(bvpProvider.getBindingsValuesProviders(factory("js"), "unusedContext"))); assertEquals("M_python,o4", asString(bvpProvider.getBindingsValuesProviders(factory("python"), "python"))); - + final String unsorted = asString(bvpProvider.getBindingsValuesProviders(factory("python"), "python"), false); assertTrue("Expecting python language-specific BVP at the end", unsorted.endsWith("M_python")); } - + @Test public void testBVPsWithServiceRankingA() { addBVPWithServiceRanking("last", null, "js", Integer.MAX_VALUE); @@ -305,7 +309,7 @@ public class BindingsValuesProvidersByCo addBVPWithServiceRanking("first", null, "js", Integer.MIN_VALUE); assertEquals("first,second,last", asString(bvpProvider.getBindingsValuesProviders(factory("js"), null), false)); } - + @Test public void testBVPsWithServiceRankingB() { addBVPWithServiceRanking("first", null, "js", Integer.MIN_VALUE); @@ -313,7 +317,7 @@ public class BindingsValuesProvidersByCo addBVPWithServiceRanking("last", null, "js", Integer.MAX_VALUE); assertEquals("first,second,last", asString(bvpProvider.getBindingsValuesProviders(factory("js"), null), false)); } - + @Test public void testBVPsWithServiceRankingC() { addBVPWithServiceRanking("second", "request", "js", 0);