From e23d509e8c19bdbe5d461fce9bc7bf89ad4970f8 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 27 Apr 2010 12:30:43 +0000 Subject: [PATCH] SWS-351 - Arbitrary parameter injection for @Endpoints --- .../AbstractPayloadMethodProcessor.java | 5 +- .../method/DomMethodArgumentProcessor.java | 89 +++++++++ .../method/MethodArgumentResolver.java | 4 +- ...ractPayloadMethodArgumentResolverTest.java | 75 +++++++ .../AbstractPayloadMethodProcessorTest.java | 187 ++++++++++++++++++ .../DomMethodArgumentProcessorTest.java | 73 +++++++ 6 files changed, 430 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/DomMethodArgumentProcessor.java create mode 100644 core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodArgumentResolverTest.java create mode 100644 core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodProcessorTest.java create mode 100644 core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/DomMethodArgumentProcessorTest.java diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodProcessor.java b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodProcessor.java index ae276c24..2feaeb7f 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodProcessor.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodProcessor.java @@ -19,6 +19,7 @@ package org.springframework.ws.server.endpoint.adapter.method; import javax.xml.transform.Source; import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; import org.springframework.ws.WebServiceMessage; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.endpoint.annotation.RequestPayload; @@ -43,7 +44,8 @@ public abstract class AbstractPayloadMethodProcessor extends TransformerObjectSu * This implementation gets checks if the given parameter is annotated with {@link RequestPayload}, and invokes * {@link #supportsRequestPayloadParameter(MethodParameter)} afterwards. */ - public final boolean supportsMethodParameter(MethodParameter parameter) { + public final boolean supportsParameter(MethodParameter parameter) { + Assert.isTrue(parameter.getParameterIndex() >= 0, "Parameter index larger smaller than 0"); if (parameter.getParameterAnnotation(RequestPayload.class) == null) { return false; } @@ -92,6 +94,7 @@ public abstract class AbstractPayloadMethodProcessor extends TransformerObjectSu * and invokes {@link #supportsResponsePayloadReturnType(MethodParameter)} afterwards. */ public final boolean supportsReturnType(MethodParameter returnType) { + Assert.isTrue(returnType.getParameterIndex() == -1, "Parameter index is not -1"); if (returnType.getMethodAnnotation(ResponsePayload.class) == null) { return false; } diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/DomMethodArgumentProcessor.java b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/DomMethodArgumentProcessor.java new file mode 100644 index 00000000..423a1ff9 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/DomMethodArgumentProcessor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed 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.springframework.ws.server.endpoint.adapter.method; + +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.core.MethodParameter; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Implementation of {@link MethodArgumentResolver} and {@link MethodReturnValueHandler} that supports W3C DOM + * {@linkplain Element elements}. + * + * @author Arjen Poutsma + * @since 2.0 + */ +public class DomMethodArgumentProcessor extends AbstractPayloadMethodProcessor { + + // MethodArgumentResolver + + @Override + protected boolean supportsRequestPayloadParameter(MethodParameter parameter) { + return supports(parameter); + } + + @Override + protected Object resolveRequestPayloadArgument(Source requestPayload, MethodParameter parameter) throws Exception { + if (requestPayload instanceof DOMSource) { + return resolveArgumentDomSource(parameter, (DOMSource) requestPayload); + } + else { + DOMResult domResult = new DOMResult(); + transform(requestPayload, domResult); + DOMSource domSource = new DOMSource(domResult.getNode()); + return resolveArgumentDomSource(parameter, domSource); + } + } + + private Node resolveArgumentDomSource(MethodParameter parameter, DOMSource requestSource) { + Class parameterType = parameter.getParameterType(); + Node requestNode = requestSource.getNode(); + if (parameterType.isAssignableFrom(requestNode.getClass())) { + return requestNode; + } + else if (Element.class.equals(parameterType) && requestNode instanceof Document) { + Document document = (Document) requestNode; + return document.getDocumentElement(); + } + // should not happen + throw new UnsupportedOperationException(); + } + + // MethodReturnValueHandler + + @Override + protected boolean supportsResponsePayloadReturnType(MethodParameter returnType) { + return supports(returnType); + } + + @Override + protected Source createResponsePayload(MethodParameter returnType, Object returnValue) { + Node returnedNode = (Node) returnValue; + return new DOMSource(returnedNode); + } + + private boolean supports(MethodParameter parameter) { + return Element.class.equals(parameter.getParameterType()); + } + +} diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/MethodArgumentResolver.java b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/MethodArgumentResolver.java index 6f42549d..54dd0a57 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/MethodArgumentResolver.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/MethodArgumentResolver.java @@ -35,14 +35,14 @@ public interface MethodArgumentResolver { * @param parameter the method parameter to check * @return {@code true} if this resolver supports the supplied parameter; {@code false} otherwise */ - boolean supportsMethodParameter(MethodParameter parameter); + boolean supportsParameter(MethodParameter parameter); /** * Resolves the given parameter into a method argument. * * @param messageContext the current message context * @param parameter the parameter to resolve to an argument. This parameter must have previously been passed to - * the {@link #supportsMethodParameter(MethodParameter)} method of this interface, which must + * the {@link #supportsParameter(MethodParameter)} method of this interface, which must * have returned {@code true}. * @return the resolved argument. May be {@code null}. * @throws Exception in case of errors diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodArgumentResolverTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodArgumentResolverTest.java new file mode 100644 index 00000000..95f742be --- /dev/null +++ b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodArgumentResolverTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed 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.springframework.ws.server.endpoint.adapter.method; + +import javax.xml.transform.TransformerException; + +import org.springframework.ws.MockWebServiceMessage; +import org.springframework.ws.MockWebServiceMessageFactory; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.xml.transform.StringSource; +import org.springframework.xml.transform.TransformerObjectSupport; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; + +public abstract class AbstractPayloadMethodArgumentResolverTest extends TransformerObjectSupport { + + protected static final String NAMESPACE_URI = "http://springframework.org/ws"; + + protected static final String LOCAL_NAME = "request"; + + private static final String REQUEST = "<" + LOCAL_NAME + " xmlns=\"" + NAMESPACE_URI + "\"/>"; + + protected MessageContext createSaajMessageContext() throws javax.xml.soap.SOAPException { + javax.xml.soap.MessageFactory saajFactory = javax.xml.soap.MessageFactory.newInstance(); + javax.xml.soap.SOAPMessage saajMessage = saajFactory.createMessage(); + saajMessage.getSOAPBody().addChildElement(LOCAL_NAME, "", NAMESPACE_URI); + return new DefaultMessageContext(new SaajSoapMessage(saajMessage), new SaajSoapMessageFactory(saajFactory)); + } + + protected MessageContext createMockMessageContext() throws TransformerException { + MockWebServiceMessage request = + new MockWebServiceMessage(new StringSource(REQUEST)); + return new DefaultMessageContext(request, new MockWebServiceMessageFactory()); + } + + protected MessageContext createCachingAxiomMessageContext() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory, true, false); + transform(new StringSource(REQUEST), request.getPayloadResult()); + AxiomSoapMessageFactory soapMessageFactory = new AxiomSoapMessageFactory(); + soapMessageFactory.afterPropertiesSet(); + return new DefaultMessageContext(request, soapMessageFactory); + } + + protected MessageContext createNonCachingAxiomMessageContext() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory, false, false); + transform(new StringSource(REQUEST), request.getPayloadResult()); + AxiomSoapMessageFactory soapMessageFactory = new AxiomSoapMessageFactory(); + soapMessageFactory.setPayloadCaching(false); + soapMessageFactory.afterPropertiesSet(); + return new DefaultMessageContext(request, soapMessageFactory); + } + +} diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodProcessorTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodProcessorTest.java new file mode 100644 index 00000000..38bb58ae --- /dev/null +++ b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodProcessorTest.java @@ -0,0 +1,187 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed 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.springframework.ws.server.endpoint.adapter.method; + +import javax.xml.transform.TransformerException; + +import org.springframework.core.MethodParameter; +import org.springframework.ws.MockWebServiceMessage; +import org.springframework.ws.MockWebServiceMessageFactory; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.xml.transform.StringSource; +import org.springframework.xml.transform.TransformerObjectSupport; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractPayloadMethodProcessorTest extends TransformerObjectSupport { + + protected static final String NAMESPACE_URI = "http://springframework.org/ws"; + + protected static final String LOCAL_NAME = "request"; + + private static final String REQUEST = "<" + LOCAL_NAME + " xmlns=\"" + NAMESPACE_URI + "\"/>"; + + protected AbstractPayloadMethodProcessor processor; + + private MethodParameter[] supportedParameters; + + private MethodParameter[] supportedReturnTypes; + + @Before + public final void setUp() throws NoSuchMethodException { + processor = createProcessor(); + supportedParameters = createSupportedParameters(); + supportedReturnTypes = createSupportedReturnTypes(); + } + + protected abstract AbstractPayloadMethodProcessor createProcessor(); + + protected abstract MethodParameter[] createSupportedParameters() throws NoSuchMethodException; + + protected abstract MethodParameter[] createSupportedReturnTypes() throws NoSuchMethodException; + + @Test + public void supportsParameter() throws NoSuchMethodException { + for (MethodParameter supportedParameter : supportedParameters) { + assertTrue("processor does not support " + supportedParameter.getParameterType() + " parameter", + processor.supportsParameter(supportedParameter)); + } + MethodParameter unsupportedParameter = + new MethodParameter(getClass().getMethod("unsupported", String.class), 0); + assertFalse("processor supports invalid parameter", processor.supportsParameter(unsupportedParameter)); + } + + @Test + public void supportsReturnType() throws NoSuchMethodException { + for (MethodParameter supportedReturnType : supportedReturnTypes) { + assertTrue("processor does not support " + supportedReturnType.getParameterType() + " return type", + processor.supportsReturnType(supportedReturnType)); + } + MethodParameter unsupportedReturnType = + new MethodParameter(getClass().getMethod("unsupported", String.class), -1); + assertFalse("processor supports invalid return type", processor.supportsReturnType(unsupportedReturnType)); + } + + @Test + public void saajArgument() throws Exception { + testResolveArgument(createSaajMessageContext()); + } + + @Test + public void mockArgument() throws Exception { + testResolveArgument(createMockMessageContext()); + } + + @Test + public void axiomCachingArgument() throws Exception { + testResolveArgument(createCachingAxiomMessageContext()); + } + + @Test + public void axiomNonCachingArgument() throws Exception { + testResolveArgument(createNonCachingAxiomMessageContext()); + } + + private void testResolveArgument(MessageContext messageContext) throws Exception { + for (MethodParameter supportedParameter : supportedParameters) { + Object argument = processor.resolveArgument(messageContext, supportedParameter); + + testArgument(argument, supportedParameter); + } + } + + + protected abstract void testArgument(Object argument, MethodParameter parameter); + + @Test + public void saajReturnValue() throws Exception { + testHandleReturnValue(createSaajMessageContext()); + } + + @Test + public void mockReturnValue() throws Exception { + testHandleReturnValue(createMockMessageContext()); + } + + @Test + public void axiomCachingReturnValue() throws Exception { + testHandleReturnValue(createCachingAxiomMessageContext()); + } + + @Test + public void axiomNonCachingReturnValue() throws Exception { + testHandleReturnValue(createNonCachingAxiomMessageContext()); + } + + + private void testHandleReturnValue(MessageContext messageContext) throws Exception { + for (MethodParameter supportedReturnType : supportedReturnTypes) { + Object returnValue = getReturnValue(supportedReturnType); + processor.handleReturnValue(messageContext, supportedReturnType, returnValue); + assertTrue("No response created", messageContext.hasResponse()); + } + } + + protected abstract Object getReturnValue(MethodParameter returnType) throws Exception; + + protected MessageContext createSaajMessageContext() throws javax.xml.soap.SOAPException { + javax.xml.soap.MessageFactory saajFactory = javax.xml.soap.MessageFactory.newInstance(); + javax.xml.soap.SOAPMessage saajMessage = saajFactory.createMessage(); + saajMessage.getSOAPBody().addChildElement(LOCAL_NAME, "", NAMESPACE_URI); + return new DefaultMessageContext(new SaajSoapMessage(saajMessage), new SaajSoapMessageFactory(saajFactory)); + } + + protected MessageContext createMockMessageContext() throws TransformerException { + MockWebServiceMessage request = new MockWebServiceMessage(new StringSource(REQUEST)); + return new DefaultMessageContext(request, new MockWebServiceMessageFactory()); + } + + protected MessageContext createCachingAxiomMessageContext() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory, true, false); + transform(new StringSource(REQUEST), request.getPayloadResult()); + AxiomSoapMessageFactory soapMessageFactory = new AxiomSoapMessageFactory(); + soapMessageFactory.afterPropertiesSet(); + return new DefaultMessageContext(request, soapMessageFactory); + } + + protected MessageContext createNonCachingAxiomMessageContext() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory, false, false); + transform(new StringSource(REQUEST), request.getPayloadResult()); + AxiomSoapMessageFactory soapMessageFactory = new AxiomSoapMessageFactory(); + soapMessageFactory.setPayloadCaching(false); + soapMessageFactory.afterPropertiesSet(); + return new DefaultMessageContext(request, soapMessageFactory); + } + + public String unsupported(String s) { + return s; + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/DomMethodArgumentProcessorTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/DomMethodArgumentProcessorTest.java new file mode 100644 index 00000000..a2743e35 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/DomMethodArgumentProcessorTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed 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.springframework.ws.server.endpoint.adapter.method; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.springframework.core.MethodParameter; +import org.springframework.ws.server.endpoint.annotation.RequestPayload; +import org.springframework.ws.server.endpoint.annotation.ResponsePayload; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class DomMethodArgumentProcessorTest extends AbstractPayloadMethodProcessorTest { + + @Override + protected AbstractPayloadMethodProcessor createProcessor() { + return new DomMethodArgumentProcessor(); + } + + @Override + protected MethodParameter[] createSupportedParameters() throws NoSuchMethodException { + return new MethodParameter[]{ + new MethodParameter(getClass().getMethod("element", Element.class), 0)}; + } + + @Override + protected MethodParameter[] createSupportedReturnTypes() throws NoSuchMethodException { + return new MethodParameter[]{new MethodParameter(getClass().getMethod("element", Element.class), -1)}; + } + + @Override + protected void testArgument(Object argument, MethodParameter parameter) { + assertTrue("argument not a node", argument instanceof Node); + Node node = (Node) argument; + assertEquals("Invalid namespace", NAMESPACE_URI, node.getNamespaceURI()); + assertEquals("Invalid local name", LOCAL_NAME, node.getLocalName()); + } + + @Override + protected Element getReturnValue(MethodParameter returnType) throws ParserConfigurationException { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.newDocument(); + return document.createElementNS(NAMESPACE_URI, LOCAL_NAME); + } + + @ResponsePayload + public Element element(@RequestPayload Element element) { + return element; + } + +}