Merge pull request #210 from lukaszlenart/jackson-xml
WW-4922: Jackson XML
diff --git a/core/src/main/java/com/opensymphony/xwork2/FileManager.java b/core/src/main/java/com/opensymphony/xwork2/FileManager.java
index c100923..8c70aa9 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 @@
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 @@
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 @@
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 dc5f0a7..a0fea58 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 @@
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 @@
}
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 8c1b71a..44a376a 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 {
- private JarFile jarFile;
+class StrutsJarURLConnection extends URLConnection {
+ private static final String FILE_URL_PREFIX = "file:";
- private StrutsJarURLConnection(URL url) throws MalformedURLException {
+ private JarURLConnection jarURLConnection;
+
+ private JarFile jarFile;
+ private String entryName;
+ private URL jarFileURL;
+
+ 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 @@
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<JarFile>() {
public JarFile run() throws IOException {
@@ -84,19 +144,34 @@
}
}
-
- static JarURLConnection openConnection(URL url) throws IOException {
- URLConnection conn = url.openConnection();
- if (conn instanceof JarURLConnection) {
- return (JarURLConnection) conn;
+ JarEntry getJarEntry() throws IOException {
+ if (jarURLConnection != null) {
+ return jarURLConnection.getJarEntry();
} else {
- try {
- conn.getInputStream().close();
- } catch (IOException ignored) {
- }
+ connect();
+ return jarFile.getJarEntry(entryName);
}
+ }
- StrutsJarURLConnection result = new StrutsJarURLConnection(url);
- return result;
+ @Override
+ public void setUseCaches(boolean usecaches) {
+ super.setUseCaches(usecaches);
+
+ if (jarURLConnection != null) {
+ jarURLConnection.setUseCaches(usecaches);
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ if (jarURLConnection != null) {
+ return jarURLConnection.getInputStream();
+ } else {
+ return jarFile.getInputStream(jarFile.getJarEntry(entryName));
+ }
+ }
+
+ 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 5fd4215..54a10b9 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 @@
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 @@
"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 @@
*/
private class ContainerProvidedURLConnection extends URLConnection {
- protected ContainerProvidedURLConnection(URL url) {
+ ContainerProvidedURLConnection(URL url) {
super(url);
}
diff --git a/plugins/jasperreports/pom.xml b/plugins/jasperreports/pom.xml
index e42ffa7..00b651d 100644
--- a/plugins/jasperreports/pom.xml
+++ b/plugins/jasperreports/pom.xml
@@ -49,6 +49,32 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>org.apache.struts</groupId>
+ <artifactId>struts2-junit-plugin</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>jsp-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java b/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java
index 2424690..c79e1b6 100644
--- a/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java
+++ b/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java
@@ -384,7 +384,10 @@
throw new ServletException(e.getMessage(), e);
} finally {
try {
- conn.close();
+ if (conn != null) {
+ // avoid NPE if connection was not used for the report
+ conn.close();
+ }
} catch (Exception e) {
LOG.warn("Could not close db connection properly", e);
}
diff --git a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
new file mode 100644
index 0000000..1bf55ac
--- /dev/null
+++ b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.struts2.views.jasperreports;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.mock.MockActionInvocation;
+import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import com.opensymphony.xwork2.util.ValueStack;
+import net.sf.jasperreports.engine.JasperCompileManager;
+import org.apache.struts2.StrutsStatics;
+import org.apache.struts2.StrutsTestCase;
+import org.easymock.IAnswer;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
+
+import java.net.URL;
+import java.sql.Connection;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+public class JasperReportsResultTest extends StrutsTestCase {
+ private MockActionInvocation invocation;
+ private ValueStack stack;
+
+ public void testConnClose() throws Exception {
+ JasperReportsResult result = new JasperReportsResult();
+ URL url = ClassLoaderUtil.getResource("org/apache/struts2/views/jasperreports/empty.jrxml", this.getClass());
+ JasperCompileManager.compileReportToFile(url.getFile(), url.getFile() + ".jasper");
+ result.setLocation("org/apache/struts2/views/jasperreports/empty.jrxml.jasper");
+ result.setFormat(JasperReportConstants.FORMAT_XML);
+
+ Connection connection = createMock(Connection.class);
+ final Boolean[] closed = {false};
+ connection.close();
+ expectLastCall().andAnswer(new IAnswer() {
+ @Override
+ public Object answer() throws Throwable {
+ closed[0] = true;
+ return null;
+ }
+ });
+ replay(connection);
+
+ stack.push(connection);
+ result.setConnection("top");
+
+ assertFalse(closed[0]);
+ result.execute(this.invocation);
+ verify(connection);
+ assertTrue(closed[0]);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setRequestURI("http://sumeruri");
+ ActionContext context = ActionContext.getContext();
+ context.put(StrutsStatics.HTTP_RESPONSE, response);
+ context.put(StrutsStatics.HTTP_REQUEST, request);
+ this.stack = context.getValueStack();
+ MockServletContext servletContext = new MockServletContext();
+ context.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
+ this.invocation = new MockActionInvocation();
+ this.invocation.setInvocationContext(context);
+ this.invocation.setStack(this.stack);
+ }
+}
diff --git a/plugins/jasperreports/src/test/resources/org/apache/struts2/views/jasperreports/empty.jrxml b/plugins/jasperreports/src/test/resources/org/apache/struts2/views/jasperreports/empty.jrxml
new file mode 100644
index 0000000..816d860
--- /dev/null
+++ b/plugins/jasperreports/src/test/resources/org/apache/struts2/views/jasperreports/empty.jrxml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="test" pageWidth="842" pageHeight="595" orientation="Landscape" columnWidth="802" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="e65f69ca-7c62-4b4b-abc6-2e1a8710f23e">
+</jasperReport>
\ No newline at end of file
diff --git a/plugins/junit/src/main/java/org/apache/struts2/StrutsJUnit4TestCase.java b/plugins/junit/src/main/java/org/apache/struts2/StrutsJUnit4TestCase.java
index b39349d..9a917e3 100644
--- a/plugins/junit/src/main/java/org/apache/struts2/StrutsJUnit4TestCase.java
+++ b/plugins/junit/src/main/java/org/apache/struts2/StrutsJUnit4TestCase.java
@@ -37,6 +37,7 @@
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
import org.springframework.mock.web.MockPageContext;
import org.springframework.mock.web.MockServletContext;
@@ -155,10 +156,7 @@
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, new HashMap<String, Object>(), true, false);
- ActionContext invocationContext = proxy.getInvocation().getInvocationContext();
- invocationContext.setParameters(HttpParameters.create(request.getParameterMap()).build());
- // set the action context to the one used by the proxy
- ActionContext.setContext(invocationContext);
+ initActionContext(proxy.getInvocation().getInvocationContext());
// this is normally done in onSetUp(), but we are using Struts internal
// objects (proxy and action invocation)
@@ -170,6 +168,20 @@
return proxy;
}
+ protected void initActionContext(ActionContext actionContext) {
+ actionContext.setParameters(HttpParameters.create(request.getParameterMap()).build());
+ initSession(actionContext);
+ // set the action context to the one used by the proxy
+ ActionContext.setContext(actionContext);
+ }
+
+ protected void initSession(ActionContext actionContext) {
+ if (actionContext.getSession() == null) {
+ actionContext.setSession(new HashMap<String, Object>());
+ request.setSession(new MockHttpSession(servletContext));
+ }
+ }
+
/**
* Finds an ActionMapping for a given request
*/
diff --git a/plugins/junit/src/test/java/org/apache/struts2/StrutsJUnit4TestCaseTest.java b/plugins/junit/src/test/java/org/apache/struts2/StrutsJUnit4TestCaseTest.java
index d3e0d15..8bc6915 100644
--- a/plugins/junit/src/test/java/org/apache/struts2/StrutsJUnit4TestCaseTest.java
+++ b/plugins/junit/src/test/java/org/apache/struts2/StrutsJUnit4TestCaseTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.struts2;
+import com.opensymphony.xwork2.ActionProxy;
import org.junit.Assert;
import org.junit.Test;
@@ -33,6 +34,13 @@
Assert.assertEquals("Test-2", output);
}
+ @Test
+ public void testSessionInitialized() throws Exception {
+ ActionProxy proxy = getActionProxy("/test/testAction-2.action");
+ Assert.assertNotNull("invocation session should being initialized",
+ proxy.getInvocation().getInvocationContext().getSession());
+ }
+
@Override
protected String getConfigPath() {
return "struts-test.xml";
diff --git a/pom.xml b/pom.xml
index fbd9375..4476fde 100644
--- a/pom.xml
+++ b/pom.xml
@@ -311,12 +311,12 @@
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
- <version>3.0.2</version>
+ <version>3.1.1</version>
<configuration>
<suppressionFiles>
<suppressionFile>src/etc/project-suppression.xml</suppressionFile>
</suppressionFiles>
- <failBuildOnCVSS>8</failBuildOnCVSS>
+ <failBuildOnCVSS>7</failBuildOnCVSS>
<skipProvidedScope>true</skipProvidedScope>
<skipRuntimeScope>true</skipRuntimeScope>
</configuration>
diff --git a/src/etc/project-suppression.xml b/src/etc/project-suppression.xml
index 2ef62e2..19454e3 100644
--- a/src/etc/project-suppression.xml
+++ b/src/etc/project-suppression.xml
@@ -22,16 +22,22 @@
<notes><![CDATA[
This suppresses false positives identified on Struts Annotations.
]]></notes>
- <gav regex="true">org\.apache\.struts:struts\-annotations\:1\.0\.6</gav>
+ <gav regex="true">org\.apache\.struts:struts\-annotations\:1\.0\.6.*$</gav>
<cpe>cpe:/a:apache:struts:1.0.6</cpe>
</suppress>
<suppress>
- <notes><![CDATA[
- This suppresses false positives identified on Struts 1.
- ]]></notes>
- <gav regex="true">org\.apache\.struts\:struts\-*:1\.3\.8</gav>
- <cpe>cpe:/a:apache:struts:1.3.8</cpe>
- <cpe>cpe:/a:apache:tiles:1.3.8</cpe>
- <cpe>cpe:/a:apache:struts:1.3.8</cpe>
+ <notes><![CDATA[file name: struts-core-1.3.8.jar]]></notes>
+ <gav regex="true">^org\.apache\.struts:struts\-core\:1\.3\.8.*$</gav>
+ <cpe>cpe:/a:apache:struts</cpe>
+ </suppress>
+ <suppress>
+ <notes><![CDATA[file name: struts-tiles-1.3.8.jar]]></notes>
+ <gav regex="true">^org\.apache\.struts:struts\-tiles\:1\.3\.8.*$</gav>
+ <cpe>cpe:/a:apache:struts</cpe>
+ </suppress>
+ <suppress>
+ <notes><![CDATA[file name: struts-taglib-1.3.8.jar]]></notes>
+ <gav regex="true">^org\.apache\.struts:struts\-taglib\:1\.3\.8.*$</gav>
+ <cpe>cpe:/a:apache:struts</cpe>
</suppress>
</suppressions>
\ No newline at end of file