From e79c19d277651f818b1ea90a3d27cec418928e75 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Sat, 23 Jul 2016 18:23:44 +0200 Subject: [PATCH] DATACMNS-885 - Added XML support for incoming projections based on XMLBeam. Added an XML flavor of the support added in previous commits based on XML Beam [0]. [0] http://www.xmlbeam.org. --- pom.xml | 8 ++ .../data/web/XmlBeamHttpMessageConverter.java | 112 ++++++++++++++++++ .../config/SpringDataWebConfiguration.java | 5 + .../XmlBeamHttpMessageConverterUnitTests.java | 85 +++++++++++++ template.mf | 1 + 5 files changed, 211 insertions(+) create mode 100644 src/main/java/org/springframework/data/web/XmlBeamHttpMessageConverter.java create mode 100644 src/test/java/org/springframework/data/web/XmlBeamHttpMessageConverterUnitTests.java diff --git a/pom.xml b/pom.xml index 86b80f697..19255544a 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ DATACMNS 2.11.7 + 1.4.8 @@ -213,6 +214,13 @@ true + + org.xmlbeam + xmlprojector + ${xmlbeam} + true + + diff --git a/src/main/java/org/springframework/data/web/XmlBeamHttpMessageConverter.java b/src/main/java/org/springframework/data/web/XmlBeamHttpMessageConverter.java new file mode 100644 index 000000000..8fc17bcc7 --- /dev/null +++ b/src/main/java/org/springframework/data/web/XmlBeamHttpMessageConverter.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2016 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.data.web; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.util.ConcurrentReferenceHashMap; +import org.xmlbeam.ProjectionFactory; +import org.xmlbeam.XBProjector; + +/** + * A read-only {@link HttpMessageConverter} to create XMLBeam-based projection instances for interfaces. + * + * @author Oliver Gierke + * @see http://www.xmlbeam.org + * @soundtrack Dr. Kobayashi Maru & The Mothership Connection - Anthem (EPisode One) + */ +public class XmlBeamHttpMessageConverter implements HttpMessageConverter { + + private final ProjectionFactory projectionFactory; + private final Map, Boolean> supportedTypesCache = new ConcurrentReferenceHashMap, Boolean>(); + + /** + * Creates a new {@link XmlBeamHttpMessageConverter}. + */ + public XmlBeamHttpMessageConverter() { + this.projectionFactory = new XBProjector(); + } + + /* + * (non-Javadoc) + * @see org.springframework.http.converter.HttpMessageConverter#canRead(java.lang.Class, org.springframework.http.MediaType) + */ + @Override + public boolean canRead(Class type, MediaType mediaType) { + + Class rawType = ResolvableType.forType(type).getRawClass(); + + Boolean result = supportedTypesCache.get(rawType); + + if (result != null) { + return result; + } + + result = mediaType.isCompatibleWith(MediaType.APPLICATION_XML) && rawType.isInterface() + && AnnotationUtils.findAnnotation(rawType, ProjectedPayload.class) != null; + + supportedTypesCache.put(rawType, result); + + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.http.converter.HttpMessageConverter#canWrite(java.lang.Class, org.springframework.http.MediaType) + */ + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.http.converter.HttpMessageConverter#getSupportedMediaTypes() + */ + @Override + public List getSupportedMediaTypes() { + return Collections.singletonList(MediaType.APPLICATION_XML); + } + + /* + * (non-Javadoc) + * @see org.springframework.http.converter.HttpMessageConverter#read(java.lang.Class, org.springframework.http.HttpInputMessage) + */ + @Override + public Object read(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + return projectionFactory.io().stream(inputMessage.getBody()).read(clazz); + } + + /* + * (non-Javadoc) + * @see org.springframework.http.converter.HttpMessageConverter#write(java.lang.Object, org.springframework.http.MediaType, org.springframework.http.HttpOutputMessage) + */ + @Override + public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException {} +} diff --git a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java index 91cc870f0..9b6436292 100644 --- a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java +++ b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.data.web.ProjectingJackson2HttpMessageConverter; import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver; import org.springframework.data.web.SortHandlerMethodArgumentResolver; +import org.springframework.data.web.XmlBeamHttpMessageConverter; import org.springframework.format.FormatterRegistry; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.converter.HttpMessageConverter; @@ -125,5 +126,9 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter { converters.add(0, converter); } + + if (ClassUtils.isPresent("org.xmlbeam.XBProjector", context.getClassLoader())) { + converters.add(0, new XmlBeamHttpMessageConverter()); + } } } diff --git a/src/test/java/org/springframework/data/web/XmlBeamHttpMessageConverterUnitTests.java b/src/test/java/org/springframework/data/web/XmlBeamHttpMessageConverterUnitTests.java new file mode 100644 index 000000000..957bfee56 --- /dev/null +++ b/src/test/java/org/springframework/data/web/XmlBeamHttpMessageConverterUnitTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 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.data.web; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.http.HttpInputMessage; +import org.xmlbeam.annotation.XBRead; + +/** + * Unit tests for {@link XmlBeamHttpMessageConverter}. + * + * @author Oliver Gierke + * @soundtrack Dr. Kobayashi Maru & The Mothership Connection - Anthem (EPisode One) + */ +@RunWith(MockitoJUnitRunner.class) +public class XmlBeamHttpMessageConverterUnitTests { + + XmlBeamHttpMessageConverter converter = new XmlBeamHttpMessageConverter(); + + @Mock HttpInputMessage message; + + /** + * @see DATACMNS-885 + */ + @Test + public void findsTopLevelElements() throws Exception { + + preparePayload("DaveMatthews"); + + Customer customer = (Customer) converter.read(Customer.class, message); + + assertThat(customer.getFirstname(), is("Dave")); + assertThat(customer.getLastname(), is("Matthews")); + } + + /** + * @see DATACMNS-885 + */ + @Test + public void findsNestedElements() throws Exception { + + preparePayload("DaveMatthews"); + + Customer customer = (Customer) converter.read(Customer.class, message); + + assertThat(customer.getFirstname(), is("Dave")); + assertThat(customer.getLastname(), is("Matthews")); + } + + private void preparePayload(String payload) throws IOException { + when(message.getBody()).thenReturn(new ByteArrayInputStream(payload.getBytes())); + } + + public interface Customer { + + @XBRead("//firstname") + String getFirstname(); + + @XBRead("//lastname") + String getLastname(); + } +} diff --git a/template.mf b/template.mf index 498857286..e22637488 100644 --- a/template.mf +++ b/template.mf @@ -40,5 +40,6 @@ Import-Template: org.slf4j.*;version="${slf4j:[=.=.=,+1.0.0)}", org.threeten.bp.*;version="${threetenbp:[=.=.=,+1.0.0)}";resolution:=optional, org.w3c.dom.*;version="0", + org.xmlbeam.*;version="${xmlbeam:[=.=.=,+1.0.0)}";resolution:=optional, scala.*;version="${scala:[=.=.=,+1.0.0)}";resolution:=optional DynamicImport-Package: *