This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-2815-xstream in repository https://gitbox.apache.org/repos/asf/struts.git
commit bb7161029669b6d2055ac42ff17de315ef1272f0 Author: Lukasz Lenart <lukaszlen...@apache.org> AuthorDate: Mon Oct 17 08:54:03 2022 +0200 WW-2815 Refactors XStreamHandler to allow to provide a custom configuration --- .../struts2/rest/handler/XStreamHandler.java | 33 +++-- .../XStreamAllowedClassNames.java} | 4 +- .../XStreamAllowedClasses.java} | 4 +- .../{ => xstream}/XStreamPermissionProvider.java | 2 +- .../XStreamProvider.java} | 12 +- .../struts2/rest/handler/XStreamHandlerTest.java | 160 +++++++++++++++++++++ 6 files changed, 195 insertions(+), 20 deletions(-) diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java index 22e597561..d3534e32b 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java @@ -21,6 +21,7 @@ package org.apache.struts2.rest.handler; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ModelDriven; import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; import com.thoughtworks.xstream.security.ArrayTypePermission; import com.thoughtworks.xstream.security.ExplicitTypePermission; import com.thoughtworks.xstream.security.NoTypePermission; @@ -29,12 +30,15 @@ import com.thoughtworks.xstream.security.PrimitiveTypePermission; import com.thoughtworks.xstream.security.TypePermission; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.rest.handler.xstream.XStreamAllowedClassNames; +import org.apache.struts2.rest.handler.xstream.XStreamAllowedClasses; +import org.apache.struts2.rest.handler.xstream.XStreamPermissionProvider; +import org.apache.struts2.rest.handler.xstream.XStreamProvider; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.Collection; -import java.util.Date; import java.util.Map; import java.util.Set; @@ -68,7 +72,15 @@ public class XStreamHandler extends AbstractContentTypeHandler { } protected XStream createXStream(ActionInvocation invocation) { - XStream stream = new XStream(); + XStream stream; + if (invocation.getAction() instanceof XStreamProvider) { + LOG.debug("Using provider {} to create instance of XStream", invocation.getAction().getClass().getSimpleName()); + stream = ((XStreamProvider) invocation.getAction()).createXStream(); + } else { + LOG.debug("Creating default XStream instance using Stax driver: {}", StaxDriver.class.getSimpleName()); + stream = new XStream(new StaxDriver()); + } + LOG.debug("Clears existing permissions"); stream.addPermission(NoTypePermission.NONE); @@ -82,13 +94,13 @@ public class XStreamHandler extends AbstractContentTypeHandler { private void addPerActionPermission(ActionInvocation invocation, XStream stream) { Object action = invocation.getAction(); - if (action instanceof AllowedClasses) { - Set<Class<?>> allowedClasses = ((AllowedClasses) action).allowedClasses(); - stream.addPermission(new ExplicitTypePermission(allowedClasses.toArray(new Class[allowedClasses.size()]))); + if (action instanceof XStreamAllowedClasses) { + Set<Class<?>> allowedClasses = ((XStreamAllowedClasses) action).allowedClasses(); + stream.addPermission(new ExplicitTypePermission(allowedClasses.toArray(new Class[0]))); } - if (action instanceof AllowedClassNames) { - Set<String> allowedClassNames = ((AllowedClassNames) action).allowedClassNames(); - stream.addPermission(new ExplicitTypePermission(allowedClassNames.toArray(new String[allowedClassNames.size()]))); + if (action instanceof XStreamAllowedClassNames) { + Set<String> allowedClassNames = ((XStreamAllowedClassNames) action).allowedClassNames(); + stream.addPermission(new ExplicitTypePermission(allowedClassNames.toArray(new String[0]))); } if (action instanceof XStreamPermissionProvider) { Collection<TypePermission> permissions = ((XStreamPermissionProvider) action).getTypePermissions(); @@ -101,13 +113,12 @@ public class XStreamHandler extends AbstractContentTypeHandler { protected void addDefaultPermissions(ActionInvocation invocation, XStream stream) { stream.addPermission(new ExplicitTypePermission(new Class[]{invocation.getAction().getClass()})); if (invocation.getAction() instanceof ModelDriven) { - stream.addPermission(new ExplicitTypePermission(new Class[]{((ModelDriven) invocation.getAction()).getModel().getClass()})); + stream.addPermission(new ExplicitTypePermission(new Class[]{((ModelDriven<?>) invocation.getAction()).getModel().getClass()})); } stream.addPermission(NullPermission.NULL); stream.addPermission(PrimitiveTypePermission.PRIMITIVES); stream.addPermission(ArrayTypePermission.ARRAYS); stream.addPermission(CollectionTypePermission.COLLECTIONS); - stream.addPermission(new ExplicitTypePermission(new Class[]{Date.class})); } public String getContentType() { @@ -125,7 +136,7 @@ public class XStreamHandler extends AbstractContentTypeHandler { @Override public boolean allows(Class type) { return type != null && type.isInterface() && - (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)); + (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)); } } diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClassNames.java similarity index 90% copy from plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java copy to plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClassNames.java index c7c23ba58..340ac9c9b 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClassNames.java @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.rest.handler; +package org.apache.struts2.rest.handler.xstream; import java.util.Set; -public interface AllowedClassNames { +public interface XStreamAllowedClassNames { Set<String> allowedClassNames(); } diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClasses.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClasses.java similarity index 90% rename from plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClasses.java rename to plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClasses.java index 149a32c90..504c35f89 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClasses.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClasses.java @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.rest.handler; +package org.apache.struts2.rest.handler.xstream; import java.util.Set; -public interface AllowedClasses { +public interface XStreamAllowedClasses { Set<Class<?>> allowedClasses(); } diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamPermissionProvider.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamPermissionProvider.java similarity index 95% rename from plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamPermissionProvider.java rename to plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamPermissionProvider.java index b58bf91db..f163912cf 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamPermissionProvider.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamPermissionProvider.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.rest.handler; +package org.apache.struts2.rest.handler.xstream; import com.thoughtworks.xstream.security.TypePermission; diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamProvider.java similarity index 73% rename from plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java rename to plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamProvider.java index c7c23ba58..517029d74 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamProvider.java @@ -16,10 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.rest.handler; +package org.apache.struts2.rest.handler.xstream; -import java.util.Set; +import com.thoughtworks.xstream.XStream; -public interface AllowedClassNames { - Set<String> allowedClassNames(); +/** + * An interface to be implemented by an action to create/provide an instance + * of XStream - it allows customisation per action + */ +public interface XStreamProvider { + XStream createXStream(); } diff --git a/plugins/rest/src/test/java/org/apache/struts2/rest/handler/XStreamHandlerTest.java b/plugins/rest/src/test/java/org/apache/struts2/rest/handler/XStreamHandlerTest.java new file mode 100644 index 000000000..3510634ed --- /dev/null +++ b/plugins/rest/src/test/java/org/apache/struts2/rest/handler/XStreamHandlerTest.java @@ -0,0 +1,160 @@ +/* + * 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.rest.handler; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; +import com.thoughtworks.xstream.security.ExplicitTypePermission; +import com.thoughtworks.xstream.security.TypePermission; +import org.apache.struts2.rest.handler.xstream.XStreamAllowedClassNames; +import org.apache.struts2.rest.handler.xstream.XStreamAllowedClasses; +import org.apache.struts2.rest.handler.xstream.XStreamPermissionProvider; +import org.apache.struts2.rest.handler.xstream.XStreamProvider; + +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class XStreamHandlerTest extends XWorkTestCase { + + private XStreamHandler handler; + private MockActionInvocation ai; + + public void setUp() throws Exception { + super.setUp(); + handler = new XStreamHandler(); + ai = new MockActionInvocation(); + ActionSupport action = new ActionSupport(); + ActionContext context = ActionContext.of(new HashMap<>()).withLocale(Locale.US); + ai.setInvocationContext(context); + ai.setAction(action); + } + + public void testObjectToXml() throws Exception { + // given + SimpleBean obj = new SimpleBean(); + obj.setName("Jan"); + obj.setAge(12L); + obj.setParents(Arrays.asList("Adam", "Ewa")); + + // when + Writer stream = new StringWriter(); + handler.fromObject(ai, obj, null, stream); + + // then + stream.flush(); + assertThat(stream.toString()) + .contains("<org.apache.struts2.rest.handler.SimpleBean>") + .contains("<name>Jan</name>") + .contains("<age>12</age>") + .contains("<parents class=\"java.util.Arrays$ArrayList\">") + .contains("<string>Adam</string>") + .contains("<string>Ewa</string>") + .contains("</org.apache.struts2.rest.handler.SimpleBean>"); + } + + public void testXmlToObject() { + // given + String xml = "<?xml version='1.0' encoding='UTF-8'?><org.apache.struts2.rest.handler.SimpleBean><name>Jan</name><age>12</age><parents class=\"java.util.ArrayList\"><string>Adam</string><string>Ewa</string></parents></org.apache.struts2.rest.handler.SimpleBean>"; + + SimpleBean obj = new SimpleBean(); + ai.setAction(new SimpleAction()); + + // when + Reader in = new StringReader(xml); + handler.toObject(ai, in, obj); + + // then + assertNotNull(obj); + assertEquals("Jan", obj.getName()); + assertEquals(12L, obj.getAge().longValue()); + assertNotNull(obj.getParents()); + assertThat(obj.getParents()) + .hasSize(2) + .containsExactly("Adam", "Ewa"); + } + + public void testXmlToObjectWithAliases() { + // given + String xml = "<?xml version='1.0' encoding='UTF-8'?><data><name>Jan</name><age>12</age><parents><string>Adam</string><string>Ewa</string></parents></data>"; + + SimpleBean obj = new SimpleBean(); + ai.setAction(new SimpleAliasAction()); + + // when + Reader in = new StringReader(xml); + handler.toObject(ai, in, obj); + + // then + assertNotNull(obj); + assertEquals("Jan", obj.getName()); + assertEquals(12L, obj.getAge().longValue()); + assertNotNull(obj.getParents()); + assertThat(obj.getParents()) + .hasSize(2) + .containsExactly("Adam", "Ewa"); + } + + private static class SimpleAction implements XStreamAllowedClasses, XStreamAllowedClassNames, XStreamPermissionProvider { + @Override + public Set<Class<?>> allowedClasses() { + Set<Class<?>> classes = new HashSet<>(); + classes.add(SimpleBean.class); + return classes; + } + + @Override + public Set<String> allowedClassNames() { + HashSet<String> strings = new HashSet<>(); + strings.add(ArrayList.class.getName()); + return strings; + } + + @Override + public Collection<TypePermission> getTypePermissions() { + ArrayList<TypePermission> permissions = new ArrayList<>(); + permissions.add(new ExplicitTypePermission(new Class[]{String.class})); + return permissions; + } + } + + private static class SimpleAliasAction extends SimpleAction implements XStreamProvider { + @Override + public XStream createXStream() { + XStream stream = new XStream(new StaxDriver()); + stream.alias("parents", ArrayList.class); + stream.alias("data", SimpleBean.class); + return stream; + } + } +}