Revisit ScriptTemplateView resource loading

Resources are now retrieved using the application context in order to
support natively non-classpath locations like /WEB-INF/...

As a consequence of this refactoring, ScriptTemplateView#createClassLoader()
protected method as been removed, since it did not make sense anymore with
this new resource loading implementation.

Issue: SPR-14210
This commit is contained in:
Sebastien Deleuze
2016-04-26 14:40:39 +02:00
parent 9412f7a094
commit 15138ed96f
2 changed files with 52 additions and 59 deletions

View File

@@ -18,8 +18,6 @@ package org.springframework.web.servlet.view.script;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -40,7 +38,6 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationContextException;
import org.springframework.core.NamedThreadLocal; import org.springframework.core.NamedThreadLocal;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.scripting.support.StandardScriptEvalException; import org.springframework.scripting.support.StandardScriptEvalException;
@@ -96,7 +93,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
private Charset charset; private Charset charset;
private String resourceLoaderPath; private String[] resourceLoaderPaths;
private ResourceLoader resourceLoader; private ResourceLoader resourceLoader;
@@ -184,7 +181,16 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
* See {@link ScriptTemplateConfigurer#setResourceLoaderPath(String)} documentation. * See {@link ScriptTemplateConfigurer#setResourceLoaderPath(String)} documentation.
*/ */
public void setResourceLoaderPath(String resourceLoaderPath) { public void setResourceLoaderPath(String resourceLoaderPath) {
this.resourceLoaderPath = resourceLoaderPath; String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
this.resourceLoaderPaths = new String[paths.length + 1];
this.resourceLoaderPaths[0] = "";
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
if (!path.endsWith("/") && !path.endsWith(":")) {
path = path + "/";
}
this.resourceLoaderPaths[i + 1] = path;
}
} }
@@ -214,12 +220,12 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
if (this.charset == null) { if (this.charset == null) {
this.charset = (viewConfig.getCharset() != null ? viewConfig.getCharset() : DEFAULT_CHARSET); this.charset = (viewConfig.getCharset() != null ? viewConfig.getCharset() : DEFAULT_CHARSET);
} }
if (this.resourceLoaderPath == null) { if (this.resourceLoaderPaths == null) {
this.resourceLoaderPath = (viewConfig.getResourceLoaderPath() != null ? String resourceLoaderPath = viewConfig.getResourceLoaderPath();
viewConfig.getResourceLoaderPath() : DEFAULT_RESOURCE_LOADER_PATH); setResourceLoaderPath(resourceLoaderPath == null ? DEFAULT_RESOURCE_LOADER_PATH : resourceLoaderPath);
} }
if (this.resourceLoader == null) { if (this.resourceLoader == null) {
this.resourceLoader = new DefaultResourceLoader(createClassLoader()); this.resourceLoader = getApplicationContext();
} }
if (this.sharedEngine == null && viewConfig.isSharedEngine() != null) { if (this.sharedEngine == null && viewConfig.isSharedEngine() != null) {
this.sharedEngine = viewConfig.isSharedEngine(); this.sharedEngine = viewConfig.isSharedEngine();
@@ -282,10 +288,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
if (!ObjectUtils.isEmpty(this.scripts)) { if (!ObjectUtils.isEmpty(this.scripts)) {
try { try {
for (String script : this.scripts) { for (String script : this.scripts) {
Resource resource = this.resourceLoader.getResource(script); Resource resource = getResource(script);
if (!resource.exists()) {
throw new IllegalStateException("Script resource [" + script + "] not found");
}
engine.eval(new InputStreamReader(resource.getInputStream())); engine.eval(new InputStreamReader(resource.getInputStream()));
} }
} }
@@ -295,26 +298,14 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
} }
} }
protected ClassLoader createClassLoader() { protected Resource getResource(String location) {
String[] paths = StringUtils.commaDelimitedListToStringArray(this.resourceLoaderPath); for (String path : this.resourceLoaderPaths) {
List<URL> urls = new ArrayList<URL>(); Resource resource = this.resourceLoader.getResource(path + location);
try { if (resource.exists()) {
for (String path : paths) { return resource;
Resource[] resources = getApplicationContext().getResources(path);
if (resources.length > 0) {
for (Resource resource : resources) {
if (resource.exists()) {
urls.add(resource.getURL());
}
}
}
} }
} }
catch (IOException ex) { throw new IllegalStateException("Resource [" + location + "] not found");
throw new IllegalStateException("Cannot create class loader: " + ex.getMessage());
}
ClassLoader classLoader = getApplicationContext().getClassLoader();
return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader);
} }
protected ScriptTemplateConfig autodetectViewConfig() throws BeansException { protected ScriptTemplateConfig autodetectViewConfig() throws BeansException {
@@ -329,7 +320,6 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
} }
} }
@Override @Override
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
super.prepareResponse(request, response); super.prepareResponse(request, response);
@@ -365,7 +355,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
} }
protected String getTemplate(String path) throws IOException { protected String getTemplate(String path) throws IOException {
Resource resource = this.resourceLoader.getResource(path); Resource resource = getResource(path);
InputStreamReader reader = new InputStreamReader(resource.getInputStream(), this.charset); InputStreamReader reader = new InputStreamReader(resource.getInputStream(), this.charset);
return FileCopyUtils.copyToString(reader); return FileCopyUtils.copyToString(reader);
} }

View File

@@ -16,10 +16,8 @@
package org.springframework.web.servlet.view.script; package org.springframework.web.servlet.view.script;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -29,8 +27,6 @@ import java.util.concurrent.Future;
import javax.script.Invocable; import javax.script.Invocable;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import org.hamcrest.Matchers;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -62,8 +58,6 @@ public class ScriptTemplateViewTests {
private StaticWebApplicationContext wac; private StaticWebApplicationContext wac;
private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/script/";
@Before @Before
public void setup() { public void setup() {
@@ -71,7 +65,6 @@ public class ScriptTemplateViewTests {
this.wac = new StaticWebApplicationContext(); this.wac = new StaticWebApplicationContext();
this.wac.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer); this.wac.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer);
this.view = new ScriptTemplateView(); this.view = new ScriptTemplateView();
this.view.setUrl(RESOURCE_LOADER_PATH + "empty.txt");
} }
@Test @Test
@@ -211,26 +204,35 @@ public class ScriptTemplateViewTests {
fail(); fail();
} }
@Test @Test // SPR-14210
@SuppressWarnings("resource") public void resourceLoaderPath() throws Exception {
public void parentLoader() { MockServletContext servletContext = new MockServletContext();
this.view.setEngine(mock(InvocableScriptEngine.class)); this.wac.setServletContext(servletContext);
this.wac.refresh();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.wac);
MockHttpServletResponse response = new MockHttpServletResponse();
Map<String, Object> model = new HashMap<String, Object>();
InvocableScriptEngine engine = mock(InvocableScriptEngine.class);
when(engine.invokeFunction(any(), any(), any(), any())).thenReturn("foo");
this.view.setEngine(engine);
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH);
this.view.setApplicationContext(this.wac); this.view.setApplicationContext(this.wac);
ClassLoader classLoader = this.view.createClassLoader(); this.view.setUrl("org/springframework/web/servlet/view/script/empty.txt");
assertNotNull(classLoader); this.view.render(model, request, response);
URLClassLoader urlClassLoader = (URLClassLoader) classLoader; assertEquals("foo", response.getContentAsString());
assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(1));
assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(), response = new MockHttpServletResponse();
Matchers.endsWith("org/springframework/web/servlet/view/script/")); this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script/");
this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH + ",classpath:org/springframework/web/servlet/view/"); this.view.setUrl("empty.txt");
classLoader = this.view.createClassLoader(); this.view.render(model, request, response);
assertNotNull(classLoader); assertEquals("foo", response.getContentAsString());
urlClassLoader = (URLClassLoader) classLoader;
assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(2)); response = new MockHttpServletResponse();
assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(), Matchers.endsWith("org/springframework/web/servlet/view/script/")); this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script");
assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(), Matchers.endsWith("org/springframework/web/servlet/view/")); this.view.setUrl("empty.txt");
this.view.render(model, request, response);
assertEquals("foo", response.getContentAsString());
} }
@Test // SPR-13379 @Test // SPR-13379
@@ -244,7 +246,8 @@ public class ScriptTemplateViewTests {
Map<String, Object> model = new HashMap<String, Object>(); Map<String, Object> model = new HashMap<String, Object>();
this.view.setEngine(mock(InvocableScriptEngine.class)); this.view.setEngine(mock(InvocableScriptEngine.class));
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH); this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script/");
this.view.setUrl("empty.txt");
this.view.setApplicationContext(this.wac); this.view.setApplicationContext(this.wac);
this.view.render(model, request, response); this.view.render(model, request, response);