From e009d74eee3239ffd51f226613a242fa8bd14b91 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 27 Apr 2010 13:20:23 +0000 Subject: [PATCH] SWS-351 - Arbitrary parameter injection for @Endpoints --- .../ws/server/endpoint/MethodEndpoint.java | 61 +++--- .../StaxPayloadMethodArgumentResolver.java | 173 ++++++++++++++++++ ...> AbstractMethodArgumentResolverTest.java} | 13 +- .../AbstractPayloadMethodProcessorTest.java | 54 +----- ...StaxPayloadMethodArgumentResolverTest.java | 155 ++++++++++++++++ 5 files changed, 370 insertions(+), 86 deletions(-) create mode 100644 core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/StaxPayloadMethodArgumentResolver.java rename core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/{AbstractPayloadMethodArgumentResolverTest.java => AbstractMethodArgumentResolverTest.java} (87%) create mode 100644 core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/StaxPayloadMethodArgumentResolverTest.java diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/MethodEndpoint.java b/core/src/main/java/org/springframework/ws/server/endpoint/MethodEndpoint.java index abf9c14e..447d17d2 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/MethodEndpoint.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/MethodEndpoint.java @@ -20,8 +20,9 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.springframework.beans.factory.BeanFactory; -import org.springframework.core.JdkVersion; +import org.springframework.core.MethodParameter; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * Represents a bean method that will be invoked as part of an incoming Web service message. @@ -61,7 +62,7 @@ public final class MethodEndpoint { * @param parameterTypes the method parameter types * @throws NoSuchMethodException when the method cannot be found */ - public MethodEndpoint(Object bean, String methodName, Class[] parameterTypes) throws NoSuchMethodException { + public MethodEndpoint(Object bean, String methodName, Class... parameterTypes) throws NoSuchMethodException { Assert.notNull(bean, "bean must not be null"); Assert.notNull(methodName, "method must not be null"); this.bean = bean; @@ -70,8 +71,8 @@ public final class MethodEndpoint { } /** - * Constructs a new method endpoint with the given bean name and method. The bean name will be lazily initized when - * {@link #invoke(Object[])} is called. + * Constructs a new method endpoint with the given bean name and method. The bean name will be lazily initialized when + * {@link #invoke(Object...)} is called. * * @param beanName the bean name * @param beanFactory the bean factory to use for bean initialization @@ -98,6 +99,21 @@ public final class MethodEndpoint { return this.method; } + /** Returns the method parameters for this method endpoint. */ + public MethodParameter[] getMethodParameters() { + int parameterCount = getMethod().getParameterTypes().length; + MethodParameter[] parameters = new MethodParameter[parameterCount]; + for (int i = 0; i < parameterCount; i++) { + parameters[i] = new MethodParameter(getMethod(), i); + } + return parameters; + } + + /** Returns the method return type, as {@code MethodParameter}. */ + public MethodParameter getReturnType() { + return new MethodParameter(method, -1); + } + /** * Invokes this method endpoint with the given arguments. * @@ -105,31 +121,34 @@ public final class MethodEndpoint { * @return the invocation result * @throws Exception when the method invocation results in an exception */ - public Object invoke(Object[] args) throws Exception { + public Object invoke(Object... args) throws Exception { Object endpoint = bean; if (endpoint instanceof String) { String endpointName = (String) endpoint; endpoint = beanFactory.getBean(endpointName); } + ReflectionUtils.makeAccessible(method); try { - return this.method.invoke(endpoint, args); + return method.invoke(endpoint, args); } catch (InvocationTargetException ex) { handleInvocationTargetException(ex); - throw new IllegalStateException("Unexpected exception thrown by method - " + - ex.getTargetException().getClass().getName() + ": " + ex.getTargetException().getMessage()); + throw new IllegalStateException( + "Unexpected exception thrown by method - " + ex.getTargetException().getClass().getName() + ": " + + ex.getTargetException().getMessage()); } } private void handleInvocationTargetException(InvocationTargetException ex) throws Exception { - if (ex.getTargetException() instanceof RuntimeException) { - throw (RuntimeException) ex.getTargetException(); + Throwable targetException = ex.getTargetException(); + if (targetException instanceof RuntimeException) { + throw (RuntimeException) targetException; } - if (ex.getTargetException() instanceof Error) { - throw (Error) ex.getTargetException(); + if (targetException instanceof Error) { + throw (Error) targetException; } - if (ex.getTargetException() instanceof Exception) { - throw (Exception) ex.getTargetException(); + if (targetException instanceof Exception) { + throw (Exception) targetException; } } @@ -150,19 +169,7 @@ public final class MethodEndpoint { } public String toString() { - if (JdkVersion.getMajorJavaVersion() <= JdkVersion.JAVA_14) { - return this.method.toString(); - } - else { - return GenericToStringProvider.toString(method); - } + return method.toGenericString(); } - /** Inner class to avoid a static JDK 1.5 dependency for generic string generation. */ - private static class GenericToStringProvider { - - public static String toString(Method method) { - return method.toGenericString(); - } - } } diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/StaxPayloadMethodArgumentResolver.java b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/StaxPayloadMethodArgumentResolver.java new file mode 100644 index 00000000..8b38d879 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/method/StaxPayloadMethodArgumentResolver.java @@ -0,0 +1,173 @@ +/* + * 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 java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamResult; + +import org.springframework.core.MethodParameter; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.endpoint.annotation.RequestPayload; +import org.springframework.xml.stream.XmlEventStreamReader; +import org.springframework.xml.transform.TransformerObjectSupport; +import org.springframework.xml.transform.TraxUtils; + +/** + * Implementation of {@link MethodArgumentResolver} that supports StAX {@link XMLStreamReader} and {@link + * XMLEventReader} arguments. + * + * @author Arjen Poutsma + * @since 2.0 + */ +@SuppressWarnings("Since15") +public class StaxPayloadMethodArgumentResolver extends TransformerObjectSupport implements MethodArgumentResolver { + + private XMLInputFactory inputFactory = createXmlInputFactory(); + + public boolean supportsParameter(MethodParameter parameter) { + if (parameter.getParameterAnnotation(RequestPayload.class) == null) { + return false; + } + else { + Class parameterType = parameter.getParameterType(); + return XMLStreamReader.class.equals(parameterType) || XMLEventReader.class.equals(parameterType); + } + } + + public Object resolveArgument(MessageContext messageContext, MethodParameter parameter) + throws TransformerException, XMLStreamException { + Source source = messageContext.getRequest().getPayloadSource(); + if (source == null) { + return null; + } + Class parameterType = parameter.getParameterType(); + if (XMLStreamReader.class.equals(parameterType)) { + return resolveStreamReader(source); + } + else if (XMLEventReader.class.equals(parameterType)) { + return resolveEventReader(source); + } + throw new UnsupportedOperationException(); + } + + private XMLStreamReader resolveStreamReader(Source requestSource) throws TransformerException, XMLStreamException { + XMLStreamReader streamReader = null; + if (TraxUtils.isStaxSource(requestSource)) { + streamReader = TraxUtils.getXMLStreamReader(requestSource); + if (streamReader == null) { + XMLEventReader eventReader = TraxUtils.getXMLEventReader(requestSource); + if (eventReader != null) { + try { + streamReader = new XmlEventStreamReader(eventReader); + } + catch (XMLStreamException ex) { + streamReader = null; + } + } + } + } + if (streamReader == null) { + try { + streamReader = inputFactory.createXMLStreamReader(requestSource); + } + catch (XMLStreamException ex) { + streamReader = null; + } + catch (UnsupportedOperationException ex) { + streamReader = null; + } + } + if (streamReader == null) { + // as a final resort, transform the source to a stream, and read from that + ByteArrayInputStream bis = convertToByteArrayInputStream(requestSource); + streamReader = inputFactory.createXMLStreamReader(bis); + } + return streamReader; + } + + private XMLEventReader resolveEventReader(Source requestSource) throws TransformerException, XMLStreamException { + XMLEventReader eventReader = null; + if (TraxUtils.isStaxSource(requestSource)) { + eventReader = TraxUtils.getXMLEventReader(requestSource); + if (eventReader == null) { + XMLStreamReader streamReader = TraxUtils.getXMLStreamReader(requestSource); + if (streamReader != null) { + try { + eventReader = inputFactory.createXMLEventReader(streamReader); + } + catch (XMLStreamException ex) { + eventReader = null; + } + } + + } + } + if (eventReader == null) { + try { + eventReader = inputFactory.createXMLEventReader(requestSource); + } + catch (XMLStreamException ex) { + eventReader = null; + } + catch (UnsupportedOperationException ex) { + eventReader = null; + } + } + if (eventReader == null) { + // as a final resort, transform the source to a stream, and read from that + ByteArrayInputStream bis = convertToByteArrayInputStream(requestSource); + eventReader = inputFactory.createXMLEventReader(bis); + } + return eventReader; + } + + /** + * Create a {@code XMLInputFactory} that this resolver will use to create {@link XMLStreamReader} and {@link + * XMLEventReader} objects. + *

+ * Can be overridden in subclasses, adding further initialization of the factory. The resulting factory is cached, + * so this method will only be called once. + * + * @return the created factory + */ + protected XMLInputFactory createXmlInputFactory() { + return XMLInputFactory.newInstance(); + } + + /** + * Converts the given source to a byte array input stream. + * + * @param source the source to convert + * @return the input stream + * @throws TransformerException in case of transformation errors + */ + protected ByteArrayInputStream convertToByteArrayInputStream(Source source) throws TransformerException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + transform(source, new StreamResult(bos)); + return new ByteArrayInputStream(bos.toByteArray()); + } + + + +} 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/AbstractMethodArgumentResolverTest.java similarity index 87% rename from core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractPayloadMethodArgumentResolverTest.java rename to core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractMethodArgumentResolverTest.java index 95f742be..dc5cca3c 100644 --- 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/AbstractMethodArgumentResolverTest.java @@ -32,13 +32,14 @@ import org.springframework.xml.transform.TransformerObjectSupport; import org.apache.axiom.om.OMAbstractFactory; import org.apache.axiom.soap.SOAPFactory; -public abstract class AbstractPayloadMethodArgumentResolverTest extends TransformerObjectSupport { +/** @author Arjen Poutsma */ +public class AbstractMethodArgumentResolverTest 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 static final String XML = "<" + LOCAL_NAME + " xmlns=\"" + NAMESPACE_URI + "\"/>"; protected MessageContext createSaajMessageContext() throws javax.xml.soap.SOAPException { javax.xml.soap.MessageFactory saajFactory = javax.xml.soap.MessageFactory.newInstance(); @@ -48,15 +49,14 @@ public abstract class AbstractPayloadMethodArgumentResolverTest extends Transfor } protected MessageContext createMockMessageContext() throws TransformerException { - MockWebServiceMessage request = - new MockWebServiceMessage(new StringSource(REQUEST)); + MockWebServiceMessage request = new MockWebServiceMessage(new StringSource(XML)); 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()); + transform(new StringSource(XML), request.getPayloadResult()); AxiomSoapMessageFactory soapMessageFactory = new AxiomSoapMessageFactory(); soapMessageFactory.afterPropertiesSet(); return new DefaultMessageContext(request, soapMessageFactory); @@ -65,11 +65,10 @@ public abstract class AbstractPayloadMethodArgumentResolverTest extends Transfor protected MessageContext createNonCachingAxiomMessageContext() throws Exception { SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory, false, false); - transform(new StringSource(REQUEST), request.getPayloadResult()); + transform(new StringSource(XML), 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 index a1dd56b5..6acfbbe1 100644 --- 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 @@ -16,37 +16,18 @@ 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 { +public abstract class AbstractPayloadMethodProcessorTest extends AbstractMethodArgumentResolverTest { - protected static final String NAMESPACE_URI = "http://springframework.org/ws"; - - protected static final String LOCAL_NAME = "request"; - - protected static final String XML = "<" + LOCAL_NAME + " xmlns=\"" + NAMESPACE_URI + "\"/>"; - - protected AbstractPayloadMethodProcessor processor; + private AbstractPayloadMethodProcessor processor; private MethodParameter[] supportedParameters; @@ -152,37 +133,6 @@ public abstract class AbstractPayloadMethodProcessorTest extends TransformerObje 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(XML)); - 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(XML), 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(XML), request.getPayloadResult()); - AxiomSoapMessageFactory soapMessageFactory = new AxiomSoapMessageFactory(); - soapMessageFactory.setPayloadCaching(false); - soapMessageFactory.afterPropertiesSet(); - return new DefaultMessageContext(request, soapMessageFactory); - } - public String unsupported(String s) { return s; } diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/StaxPayloadMethodArgumentResolverTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/StaxPayloadMethodArgumentResolverTest.java new file mode 100644 index 00000000..bb520166 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/StaxPayloadMethodArgumentResolverTest.java @@ -0,0 +1,155 @@ +/* + * 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.stream.XMLEventReader; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.springframework.core.MethodParameter; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.endpoint.annotation.RequestPayload; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** @author Arjen Poutsma */ +@SuppressWarnings("Since15") +public class StaxPayloadMethodArgumentResolverTest extends AbstractMethodArgumentResolverTest { + + private StaxPayloadMethodArgumentResolver resolver; + + private MethodParameter streamParameter; + + private MethodParameter eventParameter; + + @Before + public void setUp() throws Exception { + resolver = new StaxPayloadMethodArgumentResolver(); + streamParameter = new MethodParameter(getClass().getMethod("streamReader", XMLStreamReader.class), 0); + eventParameter = new MethodParameter(getClass().getMethod("eventReader", XMLEventReader.class), 0); + } + + @Test + public void resolveStreamReaderSaaj() throws Exception { + MessageContext messageContext = createSaajMessageContext(); + + Object result = resolver.resolveArgument(messageContext, streamParameter); + + testStreamReader(result); + } + + @Test + public void resolveStreamReaderAxiomCaching() throws Exception { + MessageContext messageContext = createCachingAxiomMessageContext(); + + Object result = resolver.resolveArgument(messageContext, streamParameter); + + testStreamReader(result); + } + + @Test + public void resolveStreamReaderAxiomNonCaching() throws Exception { + MessageContext messageContext = createNonCachingAxiomMessageContext(); + + Object result = resolver.resolveArgument(messageContext, streamParameter); + + testStreamReader(result); + } + + @Test + public void resolveStreamReaderStream() throws Exception { + MessageContext messageContext = createMockMessageContext(); + + Object result = resolver.resolveArgument(messageContext, streamParameter); + + testStreamReader(result); + } + + @Test + public void resolveEventReaderSaaj() throws Exception { + MessageContext messageContext = createSaajMessageContext(); + + Object result = resolver.resolveArgument(messageContext, eventParameter); + + testEventReader(result); + } + + @Test + public void resolveEventReaderAxiomCaching() throws Exception { + MessageContext messageContext = createCachingAxiomMessageContext(); + + Object result = resolver.resolveArgument(messageContext, eventParameter); + + testEventReader(result); + } + + @Test + public void resolveEventReaderAxiomNonCaching() throws Exception { + MessageContext messageContext = createNonCachingAxiomMessageContext(); + + Object result = resolver.resolveArgument(messageContext, eventParameter); + + testEventReader(result); + } + + @Test + public void resolveEventReaderStream() throws Exception { + MessageContext messageContext = createMockMessageContext(); + + Object result = resolver.resolveArgument(messageContext, eventParameter); + + testEventReader(result); + } + + + private void testStreamReader(Object result) throws XMLStreamException { + assertTrue("resolver does not return XMLStreamReader", result instanceof XMLStreamReader); + XMLStreamReader streamReader = (XMLStreamReader) result; + assertTrue("streamReader has no next element", streamReader.hasNext()); + assertEquals(XMLStreamConstants.START_ELEMENT, streamReader.nextTag()); + assertEquals("Invalid namespace", NAMESPACE_URI, streamReader.getNamespaceURI()); + assertEquals("Invalid local name", LOCAL_NAME, streamReader.getLocalName()); + } + + private void testEventReader(Object result) throws XMLStreamException { + assertTrue("resolver does not return XMLEventReader", result instanceof XMLEventReader); + XMLEventReader eventReader = (XMLEventReader) result; + assertTrue("eventReader has no next element", eventReader.hasNext()); + XMLEvent event = eventReader.nextTag(); + assertEquals(XMLStreamConstants.START_ELEMENT, event.getEventType()); + StartElement startElement = (StartElement) event; + assertEquals("Invalid namespace", NAMESPACE_URI, startElement.getName().getNamespaceURI()); + assertEquals("Invalid local name", LOCAL_NAME, startElement.getName().getLocalPart()); + } + + + public void invalid(XMLStreamReader streamReader) { + } + + public void streamReader(@RequestPayload XMLStreamReader streamReader) { + } + + public void eventReader(@RequestPayload XMLEventReader streamReader) { + } +}