diff --git a/core/src/main/java/com/opensymphony/xwork2/FileManager.java b/core/src/main/java/com/opensymphony/xwork2/FileManager.java index c100923f1b..8c70aa90c8 100644 --- a/core/src/main/java/com/opensymphony/xwork2/FileManager.java +++ b/core/src/main/java/com/opensymphony/xwork2/FileManager.java @@ -36,7 +36,7 @@ public interface FileManager { void setReloadingConfigs(boolean reloadingConfigs); /** - * Checks if given file changed and must be reloaded if {@link #setReloadingConfigs(boolean)} is true + * Checks if given file changed and must be reloaded * * @param fileName to check * @return true if file changed @@ -44,7 +44,7 @@ public interface FileManager { boolean fileNeedsReloading(String fileName); /** - * Checks if file represented by provided URL should be reloaded + * Checks if file represented by provided URL changed and must be reloaded * * @param fileUrl url to a file * @return true if file exists and should be reloaded, if url is null return false @@ -61,7 +61,7 @@ public interface FileManager { InputStream loadFile(URL fileUrl); /** - * Adds file to list of monitored files if {@link #setReloadingConfigs(boolean)} is true + * Adds file to list of monitored files * * @param fileUrl {@link URL} to file to be monitored */ diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java index dc5f0a7578..a0fea58c40 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java @@ -37,7 +37,7 @@ public class JarEntryRevision extends Revision { private long lastModified; public static Revision build(URL fileUrl, FileManager fileManager) { - JarURLConnection conn = null; + StrutsJarURLConnection conn = null; try { conn = StrutsJarURLConnection.openConnection(fileUrl); conn.setUseCaches(false); @@ -70,7 +70,7 @@ private JarEntryRevision(URL jarFileURL, long lastModified) { } public boolean needsReloading() { - JarURLConnection conn = null; + StrutsJarURLConnection conn = null; long lastLastModified = lastModified; try { conn = StrutsJarURLConnection.openConnection(jarFileURL); diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/StrutsJarURLConnection.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/StrutsJarURLConnection.java index 8c1b71a795..44a376aafa 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/fs/StrutsJarURLConnection.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/StrutsJarURLConnection.java @@ -20,34 +20,89 @@ import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.jar.JarEntry; import java.util.jar.JarFile; /** - * WW-4901 Decouples from underlying implementation of {@link URL#openConnection()} + * WW-4901 If was needed, decouples from underlying implementation of {@link URL#openConnection()} * e.g. from IBM WebSphere com.ibm.ws.classloader.Handler$ClassLoaderURLConnection + * WW-4920 Also decouples from and fixes {@link JarURLConnection#parseSpecs(URL)} if was needed + * e.g. from Oracle WebLogic which may report jar urls like "zip:C:/web-app-lib-path/some-jar.jar" + * but {@link JarURLConnection#parseSpecs(URL)} breaks on such urls + * While {@link JarURLConnection#parseSpecs(URL)} is private, then we had to extend {@link URLConnection} instead * @since 2.5.15 */ -class StrutsJarURLConnection extends JarURLConnection { +class StrutsJarURLConnection extends URLConnection { + private static final String FILE_URL_PREFIX = "file:"; + + private JarURLConnection jarURLConnection; + private JarFile jarFile; + private String entryName; + private URL jarFileURL; - private StrutsJarURLConnection(URL url) throws MalformedURLException { + private StrutsJarURLConnection(URL url) throws IOException { super(url); + + URLConnection conn = this.url.openConnection(); + if (conn instanceof JarURLConnection) {//decoupling is not needed? + jarURLConnection = (JarURLConnection) conn; + } else { + try { + conn.getInputStream().close(); + } catch (IOException ignored) { + } + parseSpecs(url); + } } - @Override - public JarFile getJarFile() throws IOException { - connect(); - return jarFile; + /** + * A fixed copy of {@link JarURLConnection#parseSpecs(URL)} + */ + private void parseSpecs(URL url) throws MalformedURLException, UnsupportedEncodingException { + String spec = url.getFile(); + + int separator = spec.indexOf("!/"); + /* + * REMIND: we don't handle nested JAR URLs + */ + if (separator == -1) { + throw new MalformedURLException("no !/ found in url spec:" + spec); + } + + // start of fixing JarURLConnection#parseSpecs(URL) via handling MalformedURLException + String jarFileSpec = spec.substring(0, separator++); + try { + jarFileURL = new URL(jarFileSpec); + } catch (MalformedURLException e) { + // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar". + // This usually indicates that the jar file resides in the file system. + if (!jarFileSpec.startsWith("/")) { + jarFileSpec = "/" + jarFileSpec; + } + jarFileURL = new URL(FILE_URL_PREFIX + jarFileSpec); + } + // end of fix + + entryName = null; + + /* if ! is the last letter of the innerURL, entryName is null */ + if (++separator != spec.length()) { + entryName = spec.substring(separator, spec.length()); + entryName = URLDecoder.decode (entryName, "UTF-8"); + } } @Override @@ -56,7 +111,12 @@ public void connect() throws IOException { return; } - try (final InputStream in = getJarFileURL().openConnection().getInputStream()) { + if (jarURLConnection != null) { + connected = true; + return; + } + + try (final InputStream in = jarFileURL.openConnection().getInputStream()) { jarFile = AccessController.doPrivileged( new PrivilegedExceptionAction() { public JarFile run() throws IOException { @@ -84,19 +144,34 @@ public JarFile run() throws IOException { } } + JarEntry getJarEntry() throws IOException { + if (jarURLConnection != null) { + return jarURLConnection.getJarEntry(); + } else { + connect(); + return jarFile.getJarEntry(entryName); + } + } + + @Override + public void setUseCaches(boolean usecaches) { + super.setUseCaches(usecaches); + + if (jarURLConnection != null) { + jarURLConnection.setUseCaches(usecaches); + } + } - static JarURLConnection openConnection(URL url) throws IOException { - URLConnection conn = url.openConnection(); - if (conn instanceof JarURLConnection) { - return (JarURLConnection) conn; + @Override + public InputStream getInputStream() throws IOException { + if (jarURLConnection != null) { + return jarURLConnection.getInputStream(); } else { - try { - conn.getInputStream().close(); - } catch (IOException ignored) { - } + return jarFile.getInputStream(jarFile.getJarEntry(entryName)); } + } - StrutsJarURLConnection result = new StrutsJarURLConnection(url); - return result; + static StrutsJarURLConnection openConnection(URL url) throws IOException { + return new StrutsJarURLConnection(url); } } diff --git a/core/src/test/java/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.java b/core/src/test/java/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.java index 5fd4215d10..54a10b90a9 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.java @@ -23,6 +23,7 @@ import com.opensymphony.xwork2.XWorkTestCase; import org.apache.commons.io.IOUtils; +import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -67,6 +68,7 @@ public void testNeedsReloading() throws Exception { createJarFile(now); URL url = new URL("jar:file:target/JarEntryRevisionTest_testNeedsReloading.jar!/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.class"); Revision entry = JarEntryRevision.build(url, fileManager); + assert entry != null; assertFalse(entry.needsReloading()); createJarFile(now + 60000); @@ -81,6 +83,30 @@ public void testNeedsReloadingWithContainerProvidedURLConnection() throws Except "jar:file:target/JarEntryRevisionTest_testNeedsReloading.jar!/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.class", new ContainerProvidedURLStreamHandler()); Revision entry = JarEntryRevision.build(url, fileManager); + assert entry != null; + assertFalse(entry.needsReloading()); + + createJarFile(now + 60000); + assertTrue(entry.needsReloading()); + } + + public void testNeedsReloadingWithContainerProvidedURLConnectionEmptyProtocol() throws Exception { + long now = System.currentTimeMillis(); + + createJarFile(now); + File targetDir = new File("target"); + String targetUrlStr = targetDir.toURI().toURL().toString(); + if (targetUrlStr.startsWith("file:")) { + targetUrlStr = targetUrlStr.substring(5);//emptying protocol; we expect framework will fix it + } + if (targetUrlStr.startsWith("/")) { + targetUrlStr = targetUrlStr.substring(1);//we expect framework will fix it also + } + URL url = new URL(null, + "zip:" + targetUrlStr + "JarEntryRevisionTest_testNeedsReloading.jar!/com/opensymphony/xwork2/util/fs/JarEntryRevisionTest.class", + new ContainerProvidedURLStreamHandler()); + Revision entry = JarEntryRevision.build(url, fileManager); + assert entry != null; assertFalse(entry.needsReloading()); createJarFile(now + 60000); @@ -107,7 +133,7 @@ protected URLConnection openConnection(URL u) throws IOException { */ private class ContainerProvidedURLConnection extends URLConnection { - protected ContainerProvidedURLConnection(URL url) { + ContainerProvidedURLConnection(URL url) { super(url); }