diff --git a/core-tiger/pom.xml b/core-tiger/pom.xml index d91ea4b6..ec0729c7 100644 --- a/core-tiger/pom.xml +++ b/core-tiger/pom.xml @@ -1,4 +1,5 @@ - + spring-ws org.springframework.ws @@ -39,6 +40,10 @@ org.springframework.ws spring-ws-core + + org.springframework.ws + spring-oxm-tiger + org.springframework @@ -73,4 +78,4 @@ 2.2 - \ No newline at end of file + diff --git a/core-tiger/src/main/java/org/springframework/ws/server/endpoint/adapter/GenericMarshallingMethodEndpointAdapter.java b/core-tiger/src/main/java/org/springframework/ws/server/endpoint/adapter/GenericMarshallingMethodEndpointAdapter.java new file mode 100644 index 00000000..53a97910 --- /dev/null +++ b/core-tiger/src/main/java/org/springframework/ws/server/endpoint/adapter/GenericMarshallingMethodEndpointAdapter.java @@ -0,0 +1,108 @@ +/* + * Copyright 2007 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; + +import java.lang.reflect.Method; + +import org.springframework.oxm.GenericMarshaller; +import org.springframework.oxm.GenericUnmarshaller; +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.Unmarshaller; +import org.springframework.oxm.jaxb.Jaxb2Marshaller; +import org.springframework.ws.server.endpoint.MethodEndpoint; + +/** + * Subclass of {@link MarshallingMethodEndpointAdapter} that supports {@link GenericMarshaller} and {@link + * GenericUnmarshaller}. More specifically, this adapter is aware of the {@link Method#getGenericParameterTypes()} and + * {@link Method#getGenericReturnType()}. + *

+ * Prefer to use this adapter rather than the plain {@link MarshallingMethodEndpointAdapter} in combination with Java 5 + * marshallers, such as the {@link Jaxb2Marshaller}. + * + * @author Arjen Poutsma + * @since 1.0.2 + */ +public class GenericMarshallingMethodEndpointAdapter extends MarshallingMethodEndpointAdapter { + + /** + * Creates a new GenericMarshallingMethodEndpointAdapter. The {@link Marshaller} and {@link + * Unmarshaller} must be injected using properties. + * + * @see #setMarshaller(org.springframework.oxm.Marshaller) + * @see #setUnmarshaller(org.springframework.oxm.Unmarshaller) + */ + public GenericMarshallingMethodEndpointAdapter() { + } + + /** + * Creates a new GenericMarshallingMethodEndpointAdapter with the given marshaller. If the given {@link + * Marshaller} also implements the {@link Unmarshaller} interface, it is used for both marshalling and + * unmarshalling. Otherwise, an exception is thrown. + *

+ * Note that all {@link Marshaller} implementations in Spring-WS also implement the {@link Unmarshaller} interface, + * so that you can safely use this constructor. + * + * @param marshaller object used as marshaller and unmarshaller + * @throws IllegalArgumentException when marshaller does not implement the {@link Unmarshaller} + * interface + */ + public GenericMarshallingMethodEndpointAdapter(Marshaller marshaller) { + super(marshaller); + } + + /** + * Creates a new GenericMarshallingMethodEndpointAdapter with the given marshaller and unmarshaller. + * + * @param marshaller the marshaller to use + * @param unmarshaller the unmarshaller to use + */ + public GenericMarshallingMethodEndpointAdapter(Marshaller marshaller, Unmarshaller unmarshaller) { + super(marshaller, unmarshaller); + } + + protected boolean supportsInternal(MethodEndpoint methodEndpoint) { + Method method = methodEndpoint.getMethod(); + return supportsReturnType(method) && supportsParameters(method); + } + + private boolean supportsReturnType(Method method) { + if (Void.TYPE.equals(method.getReturnType())) { + return true; + } + else { + if (getMarshaller() instanceof GenericMarshaller) { + return ((GenericMarshaller) getMarshaller()).supports(method.getGenericReturnType()); + } + else { + return getMarshaller().supports(method.getReturnType()); + } + } + } + + private boolean supportsParameters(Method method) { + if (method.getParameterTypes().length != 1) { + return false; + } + else if (getUnmarshaller() instanceof GenericUnmarshaller) { + GenericUnmarshaller genericUnmarshaller = (GenericUnmarshaller) getUnmarshaller(); + return genericUnmarshaller.supports(method.getGenericParameterTypes()[0]); + } + else { + return getUnmarshaller().supports(method.getParameterTypes()[0]); + } + } +} diff --git a/core-tiger/src/test/java/org/springframework/ws/server/endpoint/adapter/GenericMarshallingMethodEndpointAdapterTest.java b/core-tiger/src/test/java/org/springframework/ws/server/endpoint/adapter/GenericMarshallingMethodEndpointAdapterTest.java new file mode 100644 index 00000000..a5ed2eaa --- /dev/null +++ b/core-tiger/src/test/java/org/springframework/ws/server/endpoint/adapter/GenericMarshallingMethodEndpointAdapterTest.java @@ -0,0 +1,185 @@ +package org.springframework.ws.server.endpoint.adapter; + +import java.lang.reflect.Method; +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +import junit.framework.TestCase; +import static org.easymock.EasyMock.*; +import org.springframework.oxm.GenericMarshaller; +import org.springframework.oxm.GenericUnmarshaller; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.endpoint.MethodEndpoint; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public class GenericMarshallingMethodEndpointAdapterTest extends TestCase { + + private GenericMarshallingMethodEndpointAdapter adapter; + + private boolean noResponseInvoked; + + private GenericMarshaller marshallerMock; + + private GenericUnmarshaller unmarshallerMock; + + private boolean responseInvoked; + + protected void setUp() throws Exception { + adapter = new GenericMarshallingMethodEndpointAdapter(); + marshallerMock = createMock(GenericMarshaller.class); + adapter.setMarshaller(marshallerMock); + unmarshallerMock = createMock(GenericUnmarshaller.class); + adapter.setUnmarshaller(unmarshallerMock); + adapter.afterPropertiesSet(); + } + + public void testNoResponse() throws Exception { + WebServiceMessage messageMock = createMock(WebServiceMessage.class); + expect(messageMock.getPayloadSource()).andReturn(new StringSource("")); + WebServiceMessageFactory factoryMock = createMock(WebServiceMessageFactory.class); + MessageContext messageContext = new DefaultMessageContext(messageMock, factoryMock); + + Method noResponse = getClass().getMethod("noResponse", MyGenericType.class); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, noResponse); + expect(unmarshallerMock.unmarshal(isA(Source.class))).andReturn(new MyGenericType()); + + replay(marshallerMock, unmarshallerMock, messageMock, factoryMock); + + assertFalse("Method invoked", noResponseInvoked); + adapter.invoke(messageContext, methodEndpoint); + assertTrue("Method not invoked", noResponseInvoked); + + verify(marshallerMock, unmarshallerMock, messageMock, factoryMock); + } + + public void testNoRequestPayload() throws Exception { + WebServiceMessage messageMock = createMock(WebServiceMessage.class); + expect(messageMock.getPayloadSource()).andReturn(null); + WebServiceMessageFactory factoryMock = createMock(WebServiceMessageFactory.class); + MessageContext messageContext = new DefaultMessageContext(messageMock, factoryMock); + + Method noResponse = getClass().getMethod("noResponse", MyGenericType.class); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, noResponse); + + replay(marshallerMock, unmarshallerMock, messageMock, factoryMock); + + assertFalse("Method invoked", noResponseInvoked); + adapter.invoke(messageContext, methodEndpoint); + assertTrue("Method not invoked", noResponseInvoked); + + verify(marshallerMock, unmarshallerMock, messageMock, factoryMock); + } + + public void testResponse() throws Exception { + WebServiceMessage requestMock = createMock(WebServiceMessage.class); + expect(requestMock.getPayloadSource()).andReturn(new StringSource("")); + WebServiceMessage responseMock = createMock(WebServiceMessage.class); + expect(responseMock.getPayloadResult()).andReturn(new StringResult()); + WebServiceMessageFactory factoryMock = createMock(WebServiceMessageFactory.class); + expect(factoryMock.createWebServiceMessage()).andReturn(responseMock); + MessageContext messageContext = new DefaultMessageContext(requestMock, factoryMock); + + Method response = getClass().getMethod("response", MyGenericType.class); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, response); + expect(unmarshallerMock.unmarshal(isA(Source.class))).andReturn(new MyGenericType()); + marshallerMock.marshal(isA(MyGenericType.class), isA(Result.class)); + + replay(marshallerMock, unmarshallerMock, requestMock, responseMock, factoryMock); + + assertFalse("Method invoked", responseInvoked); + adapter.invoke(messageContext, methodEndpoint); + assertTrue("Method not invoked", responseInvoked); + + verify(marshallerMock, unmarshallerMock, requestMock, responseMock, factoryMock); + } + + public void testSupportedNoResponse() throws NoSuchMethodException { + Method noResponse = getClass().getMethod("noResponse", MyGenericType.class); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, noResponse); + expect(unmarshallerMock.supports(noResponse.getGenericParameterTypes()[0])).andReturn(true); + + replay(marshallerMock, unmarshallerMock); + + assertTrue("Method unsupported", adapter.supportsInternal(methodEndpoint)); + + verify(marshallerMock, unmarshallerMock); + } + + public void testSupportedResponse() throws NoSuchMethodException { + Method response = getClass().getMethod("response", MyGenericType.class); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, response); + + expect(unmarshallerMock.supports(response.getGenericParameterTypes()[0])).andReturn(true); + expect(marshallerMock.supports(response.getGenericReturnType())).andReturn(true); + + replay(marshallerMock, unmarshallerMock); + + assertTrue("Method unsupported", adapter.supportsInternal(methodEndpoint)); + + verify(marshallerMock, unmarshallerMock); + } + + public void testUnsupportedMethodMultipleParams() throws NoSuchMethodException { + Method unsupported = getClass().getMethod("unsupportedMultipleParams", String.class, String.class); + + replay(marshallerMock, unmarshallerMock); + + assertFalse("Method supported", adapter.supportsInternal(new MethodEndpoint(this, unsupported))); + + verify(marshallerMock, unmarshallerMock); + } + + public void testUnsupportedMethodWrongParam() throws NoSuchMethodException { + Method unsupported = getClass().getMethod("unsupportedWrongParam", String.class); + expect(unmarshallerMock.supports(unsupported.getGenericParameterTypes()[0])).andReturn(false); + expect(marshallerMock.supports(unsupported.getGenericReturnType())).andReturn(true); + + replay(marshallerMock, unmarshallerMock); + + assertFalse("Method supported", adapter.supportsInternal(new MethodEndpoint(this, unsupported))); + + verify(marshallerMock, unmarshallerMock); + } + + public void testUnsupportedMethodWrongReturnType() throws NoSuchMethodException { + Method unsupported = getClass().getMethod("unsupportedWrongParam", String.class); + expect(marshallerMock.supports(unsupported.getGenericReturnType())).andReturn(false); + + replay(marshallerMock, unmarshallerMock); + + assertFalse("Method supported", adapter.supportsInternal(new MethodEndpoint(this, unsupported))); + + verify(marshallerMock, unmarshallerMock); + } + + public void noResponse(MyGenericType type) { + noResponseInvoked = true; + + } + + public MyGenericType response(MyGenericType type) { + responseInvoked = true; + return type; + } + + public void unsupportedMultipleParams(String s1, String s2) { + } + + public String unsupportedWrongParam(String s) { + return s; + } + + private static class MyType { + + } + + private static class MyGenericType { + + } + + +} \ No newline at end of file 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 71adf01b..7f687afa 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 @@ -19,6 +19,7 @@ package org.springframework.ws.server.endpoint; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.springframework.core.JdkVersion; import org.springframework.util.Assert; /** @@ -120,6 +121,19 @@ public final class MethodEndpoint { } public String toString() { - return this.method.toString(); + if (JdkVersion.getMajorJavaVersion() <= JdkVersion.JAVA_14) { + return this.method.toString(); + } + else { + return GenericToStringProvider.toString(method); + } + } + + /** 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/MarshallingMethodEndpointAdapter.java b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapter.java index cd97e0f3..d24f2761 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapter.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapter.java @@ -56,14 +56,6 @@ public class MarshallingMethodEndpointAdapter extends AbstractMethodEndpointAdap private Unmarshaller unmarshaller; - public void setMarshaller(Marshaller marshaller) { - this.marshaller = marshaller; - } - - public void setUnmarshaller(Unmarshaller unmarshaller) { - this.unmarshaller = unmarshaller; - } - /** * Creates a new MarshallingMethodEndpointAdapter. The {@link Marshaller} and {@link Unmarshaller} must * be injected using properties. @@ -94,8 +86,8 @@ public class MarshallingMethodEndpointAdapter extends AbstractMethodEndpointAdap "MarshallingMethodEndpointAdapter(Marshaller, Unmarshaller) constructor."); } else { - this.marshaller = marshaller; - this.unmarshaller = (Unmarshaller) marshaller; + this.setMarshaller(marshaller); + this.setUnmarshaller((Unmarshaller) marshaller); } } @@ -108,13 +100,58 @@ public class MarshallingMethodEndpointAdapter extends AbstractMethodEndpointAdap public MarshallingMethodEndpointAdapter(Marshaller marshaller, Unmarshaller unmarshaller) { Assert.notNull(marshaller, "marshaller must not be null"); Assert.notNull(unmarshaller, "unmarshaller must not be null"); + this.setMarshaller(marshaller); + this.setUnmarshaller(unmarshaller); + } + + /** Returns the marshaller used for transforming objects into XML. */ + public Marshaller getMarshaller() { + return marshaller; + } + + /** Sets the marshaller used for transforming objects into XML. */ + public final void setMarshaller(Marshaller marshaller) { this.marshaller = marshaller; + } + + /** Returns the unmarshaller used for transforming XML into objects. */ + public Unmarshaller getUnmarshaller() { + return unmarshaller; + } + + /** Sets the unmarshaller used for transforming XML into objects. */ + public final void setUnmarshaller(Unmarshaller unmarshaller) { this.unmarshaller = unmarshaller; } public void afterPropertiesSet() throws Exception { - Assert.notNull(marshaller, "marshaller is required"); - Assert.notNull(unmarshaller, "unmarshaller is required"); + Assert.notNull(getMarshaller(), "marshaller is required"); + Assert.notNull(getUnmarshaller(), "unmarshaller is required"); + } + + protected void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception { + WebServiceMessage request = messageContext.getRequest(); + Object requestObject = unmarshalRequest(request); + Object responseObject = methodEndpoint.invoke(new Object[]{requestObject}); + if (responseObject != null) { + WebServiceMessage response = messageContext.getResponse(); + marshalResponse(responseObject, response); + } + } + + private Object unmarshalRequest(WebServiceMessage request) throws IOException { + Object requestObject = MarshallingUtils.unmarshal(getUnmarshaller(), request); + if (logger.isDebugEnabled()) { + logger.debug("Unmarshalled payload request to [" + requestObject + "]"); + } + return requestObject; + } + + private void marshalResponse(Object responseObject, WebServiceMessage response) throws IOException { + if (logger.isDebugEnabled()) { + logger.debug("Marshalling [" + responseObject + "] to response payload"); + } + MarshallingUtils.marshal(getMarshaller(), responseObject, response); } /** @@ -126,34 +163,19 @@ public class MarshallingMethodEndpointAdapter extends AbstractMethodEndpointAdap */ protected boolean supportsInternal(MethodEndpoint methodEndpoint) { Method method = methodEndpoint.getMethod(); - return (Void.TYPE.isAssignableFrom(method.getReturnType()) || marshaller.supports(method.getReturnType())) && - method.getParameterTypes().length == 1 && unmarshaller.supports(method.getParameterTypes()[0]); + return supportsReturnType(method) && supportsParameters(method); } - protected void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception { - WebServiceMessage request = messageContext.getRequest(); - Object requestObject = unmarshalRequest(request); - Object responseObject = methodEndpoint.invoke(new Object[]{requestObject}); - if (responseObject != null) { - WebServiceMessage response = messageContext.getResponse(); - marshalResponse(responseObject, response); + private boolean supportsReturnType(Method method) { + return (Void.TYPE.equals(method.getReturnType()) || getMarshaller().supports(method.getReturnType())); + } + + private boolean supportsParameters(Method method) { + if (method.getParameterTypes().length != 1) { + return false; } - - } - - private Object unmarshalRequest(WebServiceMessage request) throws IOException { - Object requestObject = MarshallingUtils.unmarshal(unmarshaller, request); - if (logger.isDebugEnabled()) { - logger.debug("Unmarshalled payload request to [" + requestObject + "]"); + else { + return getUnmarshaller().supports(method.getParameterTypes()[0]); } - return requestObject; } - - private void marshalResponse(Object responseObject, WebServiceMessage response) throws IOException { - if (logger.isDebugEnabled()) { - logger.debug("Marshalling [" + responseObject + "] to response payload"); - } - MarshallingUtils.marshal(marshaller, responseObject, response); - } - } diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapterTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapterTest.java index f130d5cd..0fb24082 100644 --- a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapterTest.java +++ b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapterTest.java @@ -160,7 +160,8 @@ public class MarshallingMethodEndpointAdapterTest extends TestCase { return s; } - public static class MyType { + private static class MyType { } + } \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/PayloadMethodEndpointAdapterTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/PayloadMethodEndpointAdapterTest.java index ea4aa2ef..5707de84 100644 --- a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/PayloadMethodEndpointAdapterTest.java +++ b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/PayloadMethodEndpointAdapterTest.java @@ -17,6 +17,8 @@ package org.springframework.ws.server.endpoint.adapter; import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; import junit.framework.TestCase; import org.springframework.ws.MockWebServiceMessage; @@ -42,12 +44,12 @@ public class PayloadMethodEndpointAdapterTest extends TestCase { } public void testSupportedNoResponse() throws NoSuchMethodException { - MethodEndpoint methodEndpoint = new MethodEndpoint(this, "noResponse", new Class[]{Source.class}); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, "noResponse", new Class[]{DOMSource.class}); assertTrue("Method unsupported", adapter.supportsInternal(methodEndpoint)); } public void testSupportedResponse() throws NoSuchMethodException { - MethodEndpoint methodEndpoint = new MethodEndpoint(this, "response", new Class[]{Source.class}); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, "response", new Class[]{StreamSource.class}); assertTrue("Method unsupported", adapter.supportsInternal(methodEndpoint)); } @@ -67,7 +69,7 @@ public class PayloadMethodEndpointAdapterTest extends TestCase { } public void testNoResponse() throws Exception { - MethodEndpoint methodEndpoint = new MethodEndpoint(this, "noResponse", new Class[]{Source.class}); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, "noResponse", new Class[]{DOMSource.class}); assertFalse("Method invoked", noResponseInvoked); adapter.invoke(messageContext, methodEndpoint); assertTrue("Method not invoked", noResponseInvoked); @@ -76,17 +78,17 @@ public class PayloadMethodEndpointAdapterTest extends TestCase { public void testResponse() throws Exception { WebServiceMessage request = new MockWebServiceMessage(""); messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory()); - MethodEndpoint methodEndpoint = new MethodEndpoint(this, "response", new Class[]{Source.class}); + MethodEndpoint methodEndpoint = new MethodEndpoint(this, "response", new Class[]{StreamSource.class}); assertFalse("Method invoked", responseInvoked); adapter.invoke(messageContext, methodEndpoint); assertTrue("Method not invoked", responseInvoked); } - public void noResponse(Source request) { + public void noResponse(DOMSource request) { noResponseInvoked = true; } - public Source response(Source request) { + public Source response(StreamSource request) { responseInvoked = true; return request; } diff --git a/oxm-tiger/src/main/java/org/springframework/oxm/GenericMarshaller.java b/oxm-tiger/src/main/java/org/springframework/oxm/GenericMarshaller.java new file mode 100644 index 00000000..1acac5e5 --- /dev/null +++ b/oxm-tiger/src/main/java/org/springframework/oxm/GenericMarshaller.java @@ -0,0 +1,40 @@ +/* + * Copyright 2007 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.oxm; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * Extension of the {@link Marshaller} interface that supports Java 5 generics. More specifically, this marshaller adds + * support for the new {@link Type} hierarchy, returned by methods such as {@link Method#getGenericParameterTypes()} and + * {@link Method#getGenericReturnType()}. + * + * @author Arjen Poutsma + * @since 1.0.2 + */ +public interface GenericMarshaller extends Marshaller { + + /** + * Indicates whether this marshaller can marshal instances of the supplied type. + * + * @param type the type that this marshaller is being asked if it can marshal + * @return true if this marshaller can indeed marshal instances of the supplied type; + * false otherwise + */ + boolean supports(Type type); +} diff --git a/oxm-tiger/src/main/java/org/springframework/oxm/GenericUnmarshaller.java b/oxm-tiger/src/main/java/org/springframework/oxm/GenericUnmarshaller.java new file mode 100644 index 00000000..f9bdc677 --- /dev/null +++ b/oxm-tiger/src/main/java/org/springframework/oxm/GenericUnmarshaller.java @@ -0,0 +1,40 @@ +/* + * Copyright 2007 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.oxm; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * Extension of the {@link Unmarshaller} interface that supports Java 5 generics. More specifically, this unmarshaller + * adds support for the new {@link Type} hierarchy, returned by methods such as {@link + * Method#getGenericParameterTypes()} and {@link Method#getGenericReturnType()}. + * + * @author Arjen Poutsma + * @since 1.0.2 + */ +public interface GenericUnmarshaller extends Unmarshaller { + + /** + * Indicates whether this unmarshaller can unmarshal instances of the supplied type. + * + * @param type the type that this unmarshaller is being asked if it can marshal + * @return true if this unmarshaller can indeed unmarshal to the supplied type; false + * otherwise + */ + boolean supports(Type type); +} diff --git a/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index f449ad26..254365aa 100644 --- a/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -21,11 +21,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.net.URLEncoder; -import java.util.HashMap; +import java.util.Arrays; import java.util.Map; import java.util.UUID; import javax.activation.DataHandler; @@ -34,9 +36,10 @@ import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; -import javax.xml.bind.JAXBIntrospector; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.attachment.AttachmentMarshaller; import javax.xml.bind.attachment.AttachmentUnmarshaller; @@ -45,6 +48,8 @@ import javax.xml.transform.Source; import javax.xml.validation.Schema; import org.springframework.core.io.Resource; +import org.springframework.oxm.GenericMarshaller; +import org.springframework.oxm.GenericUnmarshaller; import org.springframework.oxm.XmlMappingException; import org.springframework.oxm.mime.MimeContainer; import org.springframework.oxm.mime.MimeMarshaller; @@ -77,7 +82,8 @@ import org.springframework.xml.validation.SchemaLoaderUtils; * @see #setAdapters(javax.xml.bind.annotation.adapters.XmlAdapter[]) * @since 1.0.0 */ -public class Jaxb2Marshaller extends AbstractJaxbMarshaller implements MimeMarshaller, MimeUnmarshaller { +public class Jaxb2Marshaller extends AbstractJaxbMarshaller + implements MimeMarshaller, MimeUnmarshaller, GenericMarshaller, GenericUnmarshaller { private Resource[] schemaResources; @@ -97,8 +103,6 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller implements MimeMarsh private boolean mtomEnabled = false; - private Map supportedClasses = new HashMap(); - /** * Sets the XmlAdapters to be registered with the JAXB Marshaller and * Unmarshaller @@ -163,28 +167,56 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller implements MimeMarsh this.unmarshallerListener = unmarshallerListener; } - public boolean supports(Class clazz) { - if (JAXBElement.class.isAssignableFrom(clazz)) { - return true; + public boolean supports(Type type) { + if (type instanceof Class) { + return supportsInternal((Class) type, true); } - else if (!supportedClasses.containsKey(clazz)) { - boolean supported = false; - Object instance = null; - try { - instance = clazz.newInstance(); + else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + if (JAXBElement.class.equals(parameterizedType.getRawType())) { + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + for (int i = 0; i < typeArguments.length; i++) { + if (typeArguments[i] instanceof Class) { + if (!supportsInternal((Class) typeArguments[i], false)) { + return false; + } + } + else if (!supports(typeArguments[i])) { + return false; + } + } + return true; } - catch (InstantiationException e) { - // not supported - } - catch (IllegalAccessException e) { - // not supported - } - JAXBIntrospector introspector = getJaxbContext().createJAXBIntrospector(); - supported = introspector.isElement(instance); - supportedClasses.put(clazz, supported); } - return supportedClasses.get(clazz); + return false; } + + public boolean supports(Class clazz) { + return supportsInternal(clazz, true); + } + + private boolean supportsInternal(Class clazz, boolean checkForXmlRootElement) { + if (checkForXmlRootElement && clazz.getAnnotation(XmlRootElement.class) == null) { + return false; + } + if (clazz.getAnnotation(XmlType.class) == null) { + return false; + } + if (StringUtils.hasLength(getContextPath())) { + String className = ClassUtils.getQualifiedName(clazz); + int lastDotIndex = className.lastIndexOf('.'); + if (lastDotIndex == -1) { + return false; + } + String packageName = className.substring(0, lastDotIndex); + return getContextPath().startsWith(packageName); + } + else if (!ObjectUtils.isEmpty(classesToBeBound)) { + return Arrays.asList(classesToBeBound).contains(clazz); + } + return false; + } + /* * JAXBContext */ diff --git a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java index 49d3e384..136b554e 100644 --- a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java +++ b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java @@ -18,10 +18,12 @@ package org.springframework.oxm.jaxb; import java.io.ByteArrayOutputStream; import java.io.StringWriter; +import java.lang.reflect.Method; import java.util.Collections; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.stream.XMLEventWriter; @@ -39,6 +41,7 @@ import org.springframework.core.io.Resource; import org.springframework.oxm.XmlMappingException; import org.springframework.oxm.jaxb2.FlightType; import org.springframework.oxm.jaxb2.Flights; +import org.springframework.oxm.jaxb2.ObjectFactory; import org.springframework.oxm.mime.MimeContainer; import org.springframework.util.FileCopyUtils; import org.springframework.xml.transform.StaxResult; @@ -192,8 +195,16 @@ public class Jaxb2MarshallerTest extends XMLTestCase { } public void testSupports() throws Exception { - assertTrue("Jaxb2Marshaller does not support Flights", marshaller.supports(Flights.class)); - assertTrue("Jaxb2Marshaller does not support JAXBElement", marshaller.supports(JAXBElement.class)); + Method createFlights = ObjectFactory.class.getDeclaredMethod("createFlights"); + assertTrue("Jaxb2Marshaller does not support Flights", + marshaller.supports(createFlights.getGenericReturnType())); + Method createFlight = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class); + assertTrue("Jaxb2Marshaller does not support JAXBElement", + marshaller.supports(createFlight.getGenericReturnType())); + assertFalse("Jaxb2Marshaller supports non-parameterized JAXBElement", marshaller.supports(JAXBElement.class)); + JAXBElement testElement = + new JAXBElement(new QName("something"), Jaxb2MarshallerTest.class, null, this); + assertFalse("Jaxb2Marshaller supports wrong JAXBElement", marshaller.supports(testElement.getClass())); } public void testMarshalAttachments() throws Exception { diff --git a/samples/airline/src/main/webapp/WEB-INF/ws-servlet.xml b/samples/airline/src/main/webapp/WEB-INF/ws-servlet.xml index 3ca13ad3..6e812f97 100644 --- a/samples/airline/src/main/webapp/WEB-INF/ws-servlet.xml +++ b/samples/airline/src/main/webapp/WEB-INF/ws-servlet.xml @@ -1,8 +1,6 @@ - @@ -103,7 +101,7 @@ MarshallingMethodEndpointAdapter. --> - + This adapter allows for methods that need and returns marshalled objects. The MarshallingEndpoint uses JAXB 2 objects.