Java tutorial
/* * Copyright 2015, 2016 Axel Faust * * Licensed under the Eclipse Public License (EPL), Version 1.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at * * https://www.eclipse.org/legal/epl-v10.html * * 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 de.axelfaust.alfresco.nashorn.repo.loaders; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyCheck; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.extensions.webscripts.MultiScriptLoader; import org.springframework.extensions.webscripts.ScriptContent; import org.springframework.extensions.webscripts.ScriptLoader; import org.springframework.extensions.webscripts.SearchPath; import de.axelfaust.alfresco.nashorn.repo.processor.ResettableScriptProcessorElement; /** * @author Axel Faust */ public class WebScriptURLStreamHandler extends URLStreamHandler implements InitializingBean, DisposableBean, ResettableScriptProcessorElement { private static final Logger LOGGER = LoggerFactory.getLogger(WebScriptURLStreamHandler.class); private static final List<String> SUFFIX_PRECEDENCE_LIST = Arrays.asList(null, ".nashornjs", ".js"); private static final String CLASSPATH_PATH_DESCRIPTION_PREFIX = "classpath*:"; protected Registry registry; protected NodeService nodeService; protected SearchPath searchPath; protected RetryingTransactionHelper retryingTransactionHelper; protected MultiScriptLoader scriptLoader; protected final Map<String, ScriptFile> scriptHandles = new HashMap<String, ScriptFile>(); protected final ThreadLocal<Map<URL, ScriptContent>> boundScriptContents = new ThreadLocal<Map<URL, ScriptContent>>() { /** * {@inheritedDoc} */ @Override protected Map<URL, ScriptContent> initialValue() { return new HashMap<URL, ScriptContent>(); } }; { try { final Class<?> cls = Class.forName("de.axelfaust.alfresco.nashorn.jdk8wa.webscript.Handler"); final Method setRealHandler = cls.getDeclaredMethod("setRealHandler", URLStreamHandler.class); setRealHandler.invoke(null, this); LOGGER.info("Registered {} as global webscript URL stream handler", this); } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { LOGGER.info( "JDK 8 workarounds library not available - {} not registered as global webscript URL stream handler", this); } } /** * {@inheritDoc} */ @Override public void afterPropertiesSet() { PropertyCheck.mandatory(this, "nodeService", this.nodeService); PropertyCheck.mandatory(this, "searchPath", this.searchPath); PropertyCheck.mandatory(this, "retryingTransactionHelper", this.retryingTransactionHelper); PropertyCheck.mandatory(this, "registry", this.registry); this.registry.register(this); } /** * {@inheritDoc} */ @Override public void destroy() { this.reset(); } /** * {@inheritDoc} */ @Override public void reset() { this.scriptHandles.values().forEach(x -> x.reset()); } /** * @param registry * the registry to set */ public void setRegistry(final Registry registry) { this.registry = registry; } /** * @param nodeService * the nodeService to set */ public void setNodeService(final NodeService nodeService) { this.nodeService = nodeService; } /** * @param searchPath * the searchPath to set */ public void setSearchPath(final SearchPath searchPath) { this.searchPath = searchPath; } /** * @param retryingTransactionHelper * the retryingTransactionHelper to set */ public void setRetryingTransactionHelper(final RetryingTransactionHelper retryingTransactionHelper) { this.retryingTransactionHelper = retryingTransactionHelper; } /** * Binds an URL to a script content to avoid re-resolving when {@link #openConnection(URL) opening the connection}. * * @param url * the URL of the script * @param scriptContent * the pre-resolved script content */ public void bindScriptContent(final URL url, final ScriptContent scriptContent) { ParameterCheck.mandatory("url", url); ParameterCheck.mandatory("scriptContent", scriptContent); this.boundScriptContents.get().put(url, scriptContent); } /** * Removes an URL to script content binding to avoid spillover into future executions on the same thread. * * @param url * the URL of the script */ public void unbindScriptContent(final URL url) { ParameterCheck.mandatory("url", url); this.boundScriptContents.get().remove(url); } /** * Resolves a module ID to a script content. * * @param moduleId * the module ID to resolve * @return the resolved script content or {@code null} if it could not be resolved */ public ScriptContent resolveScriptContent(final String moduleId) { ParameterCheck.mandatoryString("moduleId", moduleId); // lazy init since we might have only partially initialized searchPath during afterPropertiesSet if (this.scriptLoader == null) { final List<ScriptLoader> loaders = new ArrayList<ScriptLoader>(); this.searchPath.getStores().forEach(x -> loaders.add(x.getScriptLoader())); this.scriptLoader = new MultiScriptLoader(loaders.toArray(new ScriptLoader[0])); } ScriptContent scriptContent = null; for (final String suffix : SUFFIX_PRECEDENCE_LIST) { String script; if (suffix != null) { script = moduleId + suffix; } else { script = moduleId; } scriptContent = this.scriptLoader.getScript(script); if (scriptContent != null) { break; } } return scriptContent; } /** * {@inheritDoc} */ @Override protected URLConnection openConnection(final URL url) throws IOException { ScriptContent scriptContent = this.boundScriptContents.get().get(url); if (scriptContent == null) { final String moduleId = url.getPath(); scriptContent = this.resolveScriptContent(moduleId); if (scriptContent == null) { throw new IOException("Script " + url + " does not exist"); } } ScriptFile scriptFile; final String pathDescription = scriptContent.getPathDescription(); if (pathDescription.startsWith(CLASSPATH_PATH_DESCRIPTION_PREFIX)) { final String path = pathDescription.substring(CLASSPATH_PATH_DESCRIPTION_PREFIX.length()); final String lookup = "classpath:" + path; scriptFile = this.scriptHandles.get(lookup); if (scriptFile == null) { scriptFile = new ClasspathScriptFile(path); this.scriptHandles.put(lookup, scriptFile); } // no need to check scriptFile.exists() since the scriptLoader found it to be existing } else { final NodeRef scriptNode = this.tryGetNodeRef(scriptContent); if (scriptNode != null) { final String lookup = "node:" + scriptNode; scriptFile = this.scriptHandles.get(lookup); if (scriptFile == null) { scriptFile = new NodeScriptContentFile(scriptNode, scriptContent, this.nodeService, this.retryingTransactionHelper); this.scriptHandles.put(lookup, scriptFile); } } else { scriptFile = new ScriptContentFile(scriptContent); } } return new ScriptFileURLConnection(url, scriptFile); } protected NodeRef tryGetNodeRef(final ScriptContent scriptContent) { NodeRef result; try { // TODO Cache field for better performance final Field nodeRefField = scriptContent.getClass().getDeclaredField("nodeRef"); nodeRefField.setAccessible(true); result = (NodeRef) nodeRefField.get(scriptContent); } catch (final IllegalAccessException | NoSuchFieldException e) { result = null; } return result; } }