DATACMNS-164 - Added support for XML/JSON marshaling of Page, Pageable and Sort.

Added XmlTypeAdapter implementations that convert the PageImpl, PageRequest and Sort value objects into a JAXB marshalable object.
This commit is contained in:
Oliver Gierke
2012-05-02 13:48:50 +02:00
parent cf71070de6
commit c64f01414f
15 changed files with 899 additions and 1 deletions

View File

@@ -130,6 +130,20 @@
<version>${webbeans.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>${org.springframework.hateoas.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.3U1</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2010 the original author or authors.
* Copyright 2008-2012 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.

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012 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.domain.jaxb;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.domain.jaxb.SpringDataJaxb.OrderDto;
/**
* XmlAdapter to convert {@link Order} instances into {@link OrderDto}s and vice versa.
*
* @author Oliver Gierke
*/
public class OrderAdapter extends XmlAdapter<OrderDto, Order> {
public static final OrderAdapter INSTANCE = new OrderAdapter();
/*
* (non-Javadoc)
* @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object)
*/
@Override
public OrderDto marshal(Order order) throws Exception {
if (order == null) {
return null;
}
OrderDto dto = new OrderDto();
dto.direction = order.getDirection();
dto.property = order.getProperty();
return dto;
}
/*
* (non-Javadoc)
* @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object)
*/
@Override
public Order unmarshal(OrderDto source) throws Exception {
return source == null ? null : new Order(source.direction, source.property);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2012 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.domain.jaxb;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.jaxb.SpringDataJaxb.PageDto;
import org.springframework.hateoas.Link;
/**
* {@link XmlAdapter} to convert {@link Page} instances into {@link PageDto} instances and vice versa.
*
* @author Oliver Gierke
*/
public class PageAdapter extends XmlAdapter<PageDto, Page<Object>> {
/*
* (non-Javadoc)
* @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object)
*/
@Override
public PageDto marshal(Page<Object> source) throws Exception {
if (source == null) {
return null;
}
PageDto dto = new PageDto();
dto.content = source.getContent();
dto.add(getLinks(source));
return dto;
}
/*
* (non-Javadoc)
* @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object)
*/
@Override
public Page<Object> unmarshal(PageDto v) throws Exception {
return null;
}
/**
* Return additional links that shall be added to the {@link PageDto}.
*
* @param source the source {@link Page}.
* @return
*/
protected List<Link> getLinks(Page<?> source) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2012 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.domain.jaxb;
import java.util.Collections;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.jaxb.SpringDataJaxb.OrderDto;
import org.springframework.data.domain.jaxb.SpringDataJaxb.PageRequestDto;
import org.springframework.data.domain.jaxb.SpringDataJaxb.SortDto;
/**
* {@link XmlAdapter} to convert {@link Pageable} instances int a {@link PageRequestDto} and vice versa.
*
* @author Oliver Gierke
*/
class PageableAdapter extends XmlAdapter<PageRequestDto, Pageable> {
/*
* (non-Javadoc)
* @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object)
*/
@Override
public PageRequestDto marshal(Pageable request) throws Exception {
SortDto sortDto = SortAdapter.INSTANCE.marshal(request.getSort());
PageRequestDto dto = new PageRequestDto();
dto.orders = sortDto == null ? Collections.<OrderDto> emptyList() : sortDto.orders;
dto.page = request.getPageNumber();
dto.size = request.getPageSize();
return dto;
}
/*
* (non-Javadoc)
* @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object)
*/
@Override
public Pageable unmarshal(PageRequestDto v) throws Exception {
if (v.orders.isEmpty()) {
return new PageRequest(v.page, v.size);
}
SortDto sortDto = new SortDto();
sortDto.orders = v.orders;
Sort sort = SortAdapter.INSTANCE.unmarshal(sortDto);
return new PageRequest(v.page, v.size, sort);
}
}

View File

@@ -0,0 +1,42 @@
package org.springframework.data.domain.jaxb;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.jaxb.SpringDataJaxb.SortDto;
/**
* {@link XmlAdapter} to convert {@link Sort} instances into {@link SortDto} instances and vice versa.
*
* @author Oliver Gierke
*/
public class SortAdapter extends XmlAdapter<SortDto, Sort> {
public static final SortAdapter INSTANCE = new SortAdapter();
/*
* (non-Javadoc)
* @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object)
*/
@Override
public SortDto marshal(Sort source) throws Exception {
if (source == null) {
return null;
}
SortDto dto = new SortDto();
dto.orders = SpringDataJaxb.marshal(source, OrderAdapter.INSTANCE);
return dto;
}
/*
* (non-Javadoc)
* @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object)
*/
@Override
public Sort unmarshal(SortDto source) throws Exception {
return source == null ? null : new Sort(SpringDataJaxb.unmarshal(source.orders, OrderAdapter.INSTANCE));
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright 2012 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.domain.jaxb;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.util.Assert;
/**
* Helper class containing utility methods to implement JAXB {@link XmlAdapter}s as well as the DTO types to be
* marshalled by JAXB.
*
* @author Oliver Gierke
*/
public class SpringDataJaxb {
public static final String NAMESPACE = "http://www.springframework.org/schema/data/jaxb";
/**
* The DTO for {@link Pageable}s/{@link PageRequest}s.
*
* @author Oliver Gierke
*/
@XmlRootElement(name = "page-request", namespace = NAMESPACE)
@XmlAccessorType(XmlAccessType.FIELD)
public static class PageRequestDto {
@XmlAttribute
int page, size;
@XmlElement(name = "order", namespace = NAMESPACE)
List<OrderDto> orders = new ArrayList<OrderDto>();
}
/**
* The DTO for {@link Sort}.
*
* @author Oliver Gierke
*/
@XmlRootElement(name = "sort", namespace = NAMESPACE)
@XmlAccessorType(XmlAccessType.FIELD)
public static class SortDto {
@XmlElement(name = "order", namespace = SpringDataJaxb.NAMESPACE)
List<OrderDto> orders = new ArrayList<OrderDto>();
}
/**
* The DTO for {@link Order}.
*
* @author Oliver Gierke
*/
@XmlRootElement(name = "order", namespace = NAMESPACE)
@XmlAccessorType(XmlAccessType.FIELD)
public static class OrderDto {
@XmlAttribute
String property;
@XmlAttribute
Direction direction;
}
/**
* The DTO for {@link Page}.
*
* @author Oliver Gierke
*/
@XmlRootElement(name = "page", namespace = NAMESPACE)
@XmlAccessorType(XmlAccessType.FIELD)
public static class PageDto extends ResourceSupport {
@XmlAnyElement
@XmlElementWrapper(name = "content")
List<Object> content;
}
/**
* Unmarshals each element of the given {@link Collection} using the given {@link XmlAdapter}.
*
* @param source
* @param adapter must not be {@literal null}.
* @return
* @throws Exception
*/
public static <T, S> List<T> unmarshal(Collection<S> source, XmlAdapter<S, T> adapter) throws Exception {
Assert.notNull(adapter);
if (source == null || source.isEmpty()) {
return Collections.emptyList();
}
List<T> result = new ArrayList<T>(source.size());
for (S element : source) {
result.add(adapter.unmarshal(element));
}
return result;
}
/**
* Marshals each of the elements of the given {@link Iterable} using the given {@link XmlAdapter}.
*
* @param source
* @param adapter must not be {@literal null}.
* @return
* @throws Exception
*/
public static <T, S> List<S> marshal(Iterable<T> source, XmlAdapter<S, T> adapter) throws Exception {
Assert.notNull(adapter);
if (source == null) {
return Collections.emptyList();
}
List<S> result = new ArrayList<S>();
for (T element : source) {
result.add(adapter.marshal(element));
}
return result;
}
}

View File

@@ -0,0 +1,21 @@
/**
* Central domain abstractions especially to be used in combination with the {@link org.springframework.data.repository.Repository} abstraction.
*
* @see org.springframework.data.repository.Repository
*/
@XmlSchema(xmlns = { @XmlNs(prefix = "sd", namespaceURI = org.springframework.data.domain.jaxb.SpringDataJaxb.NAMESPACE) })
@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(value = org.springframework.data.domain.jaxb.PageableAdapter.class, type = Pageable.class),
@XmlJavaTypeAdapter(value = org.springframework.data.domain.jaxb.SortAdapter.class, type = Sort.class),
@XmlJavaTypeAdapter(value = org.springframework.data.domain.jaxb.PageAdapter.class, type = Page.class) })
package org.springframework.data.domain.jaxb;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

View File

@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema targetNamespace="http://www.w3.org/2005/Atom" elementFormDefault="qualified"
attributeFormDefault="unqualified"
xmlns:atom="http://www.w3.org/2005/Atom" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:annotation>
<xs:documentation>
This version of the Atom schema is based on version 1.0 of the format specifications,
found here http://www.atomenabled.org/developers/syndication/atom-format-spec.php.
</xs:documentation>
</xs:annotation>
<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/03/xml.xsd" />
<xs:annotation>
<xs:documentation>
An Atom document may have two root elements, feed and entry, as defined in section 2.
</xs:documentation>
</xs:annotation>
<xs:element name="feed" type="atom:feedType"/>
<xs:element name="entry" type="atom:entryType"/>
<xs:complexType name="textType" mixed="true">
<xs:annotation>
<xs:documentation>
The Atom text construct is defined in section 3.1 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:any namespace="http://www.w3.org/1999/xhtml" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="type" >
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="text"/>
<xs:enumeration value="html"/>
<xs:enumeration value="xhtml"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:complexType>
<xs:complexType name="personType">
<xs:annotation>
<xs:documentation>
The Atom person construct is defined in section 3.2 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element name="name" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="uri" type="atom:uriType" minOccurs="0" maxOccurs="1" />
<xs:element name="email" type="atom:emailType" minOccurs="0" maxOccurs="1" />
<xs:any namespace="##other"/>
</xs:choice>
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:complexType>
<xs:simpleType name="emailType">
<xs:annotation>
<xs:documentation>
Schema definition for an email address.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:normalizedString">
<xs:pattern value="\w+@(\w+\.)+\w+" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="feedType">
<xs:annotation>
<xs:documentation>
The Atom feed construct is defined in section 4.1.1 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:choice minOccurs="3" maxOccurs="unbounded">
<xs:element name="author" type="atom:personType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="category" type="atom:categoryType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="contributor" type="atom:personType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="generator" type="atom:generatorType" minOccurs="0" maxOccurs="1" />
<xs:element name="icon" type="atom:iconType" minOccurs="0" maxOccurs="1" />
<xs:element name="id" type="atom:idType" minOccurs="1" maxOccurs="1" />
<xs:element name="link" type="atom:linkType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="logo" type="atom:logoType" minOccurs="0" maxOccurs="1" />
<xs:element name="rights" type="atom:textType" minOccurs="0" maxOccurs="1" />
<xs:element name="subtitle" type="atom:textType" minOccurs="0" maxOccurs="1" />
<xs:element name="title" type="atom:textType" minOccurs="1" maxOccurs="1" />
<xs:element name="updated" type="atom:dateTimeType" minOccurs="1" maxOccurs="1" />
<xs:element name="entry" type="atom:entryType" minOccurs="0" maxOccurs="unbounded" />
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
</xs:choice>
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:complexType>
<xs:complexType name="entryType">
<xs:annotation>
<xs:documentation>
The Atom entry construct is defined in section 4.1.2 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="author" type="atom:personType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="category" type="atom:categoryType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="content" type="atom:contentType" minOccurs="0" maxOccurs="1" />
<xs:element name="contributor" type="atom:personType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="id" type="atom:idType" minOccurs="1" maxOccurs="1" />
<xs:element name="link" type="atom:linkType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="published" type="atom:dateTimeType" minOccurs="0" maxOccurs="1" />
<xs:element name="rights" type="atom:textType" minOccurs="0" maxOccurs="1" />
<xs:element name="source" type="atom:textType" minOccurs="0" maxOccurs="1" />
<xs:element name="summary" type="atom:textType" minOccurs="0" maxOccurs="1" />
<xs:element name="title" type="atom:textType" minOccurs="1" maxOccurs="1" />
<xs:element name="updated" type="atom:dateTimeType" minOccurs="1" maxOccurs="1" />
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
</xs:choice>
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:complexType>
<xs:complexType name="contentType" mixed="true">
<xs:annotation>
<xs:documentation>
The Atom content construct is defined in section 4.1.3 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="type" type="xs:string"/>
<xs:attribute name="src" type="xs:anyURI"/>
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:complexType>
<xs:complexType name="categoryType">
<xs:annotation>
<xs:documentation>
The Atom cagegory construct is defined in section 4.2.2 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:attribute name="term" type="xs:string" use="required"/>
<xs:attribute name="scheme" type="xs:anyURI" use="optional"/>
<xs:attribute name="label" type="xs:string" use="optional"/>
<xs:attributeGroup ref="atom:commonAttributes" />
</xs:complexType>
<xs:complexType name="generatorType">
<xs:annotation>
<xs:documentation>
The Atom generator element is defined in section 4.2.4 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="uri" use="optional" type="xs:anyURI" />
<xs:attribute name="version" use="optional" type="xs:string" />
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="iconType">
<xs:annotation>
<xs:documentation>
The Atom icon construct is defined in section 4.2.5 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:anyURI">
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="idType">
<xs:annotation>
<xs:documentation>
The Atom id construct is defined in section 4.2.6 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:anyURI">
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="linkType" mixed="true">
<xs:annotation>
<xs:documentation>
The Atom link construct is defined in section 3.4 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:attribute name="href" use="required" type="xs:anyURI" />
<xs:attribute name="rel" type="xs:string" use="optional"/>
<xs:attribute name="type" use="optional" type="xs:string" />
<xs:attribute name="hreflang" use="optional" type="xs:NMTOKEN" />
<xs:attribute name="title" use="optional" type="xs:string" />
<xs:attribute name="length" use="optional" type="xs:positiveInteger" />
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:complexType>
<xs:complexType name="logoType">
<xs:annotation>
<xs:documentation>
The Atom logo construct is defined in section 4.2.8 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:anyURI">
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="sourceType">
<xs:annotation>
<xs:documentation>
The Atom source construct is defined in section 4.2.11 of the format spec.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="author" type="atom:personType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="category" type="atom:categoryType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="contributor" type="atom:personType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="generator" type="atom:generatorType" minOccurs="0" maxOccurs="1"/>
<xs:element name="icon" type="atom:iconType" minOccurs="0" maxOccurs="1"/>
<xs:element name="id" type="atom:idType" minOccurs="0" maxOccurs="1"/>
<xs:element name="link" type="atom:linkType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="logo" type="atom:logoType" minOccurs="0" maxOccurs="1"/>
<xs:element name="rights" type="atom:textType" minOccurs="0" maxOccurs="1"/>
<xs:element name="subtitle" type="atom:textType" minOccurs="0" maxOccurs="1"/>
<xs:element name="title" type="atom:textType" minOccurs="0" maxOccurs="1"/>
<xs:element name="updated" type="atom:dateTimeType" minOccurs="0" maxOccurs="1"/>
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
</xs:choice>
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:complexType>
<xs:complexType name="uriType">
<xs:simpleContent>
<xs:extension base="xs:anyURI">
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="dateTimeType">
<xs:simpleContent>
<xs:extension base="xs:dateTime">
<xs:attributeGroup ref="atom:commonAttributes"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:attributeGroup name="commonAttributes">
<xs:attribute ref="xml:base" />
<xs:attribute ref="xml:lang" />
<xs:anyAttribute namespace="##other"/>
</xs:attributeGroup>
</xs:schema>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/data/jaxb"
xmlns="http://www.springframework.org/schema/data/jaxb"
xmlns:atom="http://www.w3.org/2005/Atom"
elementFormDefault="qualified">
<xs:import namespace="http://www.w3.org/2005/Atom" schemaLocation="atom.xsd" />
<xs:element name="page-request" type="pageRequest" />
<xs:element name="page" type="page" />
<xs:element name="sort" type="sort" />
<xs:element name="order" type="order" />
<xs:complexType name="pageRequest">
<xs:sequence>
<xs:element name="order" type="order" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="page" type="xs:integer" use="required" />
<xs:attribute name="size" type="xs:integer" use="required" />
</xs:complexType>
<xs:complexType name="page">
<xs:sequence>
<xs:element name="content" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:all />
</xs:complexType>
</xs:element>
<xs:element name="link" type="atom:linkType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="sort">
<xs:sequence>
<xs:element name="order" type="order" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="order">
<xs:attribute name="property" type="xs:string" />
<xs:attribute name="direction" type="direction" />
</xs:complexType>
<xs:simpleType name="direction">
<xs:restriction base="xs:string">
<xs:enumeration value="ASC" />
<xs:enumeration value="DESC" />
</xs:restriction>
</xs:simpleType>
</xs:schema>

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2012 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.domain.jaxb;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.hateoas.Link;
/**
* Unit test for custom JAXB conversions for Spring Data value objects.
*
* @author Oliver Gierke
*/
public class SpringDataJaxbUnitTests {
Marshaller marshaller;
Unmarshaller unmarshaller;
Sort sort = new Sort(Direction.ASC, "firstname", "lastname");
Pageable reference = new PageRequest(2, 15, sort);
Resource resource = new ClassPathResource("pageable.xml", this.getClass());
Resource schemaFile = new ClassPathResource("spring-data-jaxb.xsd", this.getClass());
Scanner scanner;
@Before
public void setUp() throws Exception {
JAXBContext context = JAXBContext.newInstance("org.springframework.data.domain.jaxb");
marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
unmarshaller = context.createUnmarshaller();
scanner = new Scanner(resource.getFile());
}
@After
public void tearDown() {
scanner.close();
}
@Test
public void usesCustomTypeAdapterForPageRequests() throws Exception {
StringWriter writer = new StringWriter();
Wrapper wrapper = new Wrapper();
wrapper.pageable = reference;
wrapper.sort = sort;
wrapper.pageableWithoutSort = new PageRequest(10, 20);
marshaller.marshal(wrapper, writer);
for (String line : writer.toString().split("\n")) {
assertThat(scanner.hasNextLine(), is(true));
assertThat(line, is(scanner.nextLine()));
}
}
@Test
public void readsPageRequest() throws Exception {
Object result = unmarshaller.unmarshal(resource.getFile());
assertThat(result, is(instanceOf(Wrapper.class)));
assertThat(((Wrapper) result).pageable, is(reference));
assertThat(((Wrapper) result).sort, is(sort));
}
@Test
public void writesPlainPage() throws Exception {
PageWrapper wrapper = new PageWrapper();
Content content = new Content();
content.name = "Foo";
wrapper.page = new PageImpl<Content>(Arrays.asList(content));
wrapper.pageWithLinks = new PageImpl<Content>(Arrays.asList(content));
marshaller.marshal(wrapper, new StringWriter());
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
static class Wrapper {
@XmlElement(name = "page-request", namespace = SpringDataJaxb.NAMESPACE)
Pageable pageable;
@XmlElement(name = "page-request-without-sort", namespace = SpringDataJaxb.NAMESPACE)
Pageable pageableWithoutSort;
@XmlElement(name = "sort", namespace = SpringDataJaxb.NAMESPACE)
Sort sort;
}
@XmlRootElement(name = "wrapper", namespace = SpringDataJaxb.NAMESPACE)
static class PageWrapper {
Page<Content> page;
@XmlElement(name = "page-with-links")
@XmlJavaTypeAdapter(LinkedPageAdapter.class)
Page<Content> pageWithLinks;
}
@XmlRootElement
static class Content {
@XmlAttribute
String name;
}
static class LinkedPageAdapter extends PageAdapter {
@Override
protected List<Link> getLinks(Page<?> source) {
return Arrays.asList(new Link(Link.REL_NEXT, "next"), new Link(Link.REL_PREVIOUS, "previous"));
}
}
}

View File

@@ -0,0 +1,3 @@
SpringDataJaxbUnitTests$Wrapper
SpringDataJaxbUnitTests$PageWrapper
SpringDataJaxbUnitTests$Content

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<wrapper xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sd="http://www.springframework.org/schema/data/jaxb">
<sd:page-request page="2" size="15">
<sd:order property="firstname" direction="ASC"/>
<sd:order property="lastname" direction="ASC"/>
</sd:page-request>
<sd:page-request-without-sort page="10" size="20"/>
<sd:sort>
<sd:order property="firstname" direction="ASC"/>
<sd:order property="lastname" direction="ASC"/>
</sd:sort>
</wrapper>

View File

@@ -8,6 +8,7 @@ Import-Template:
com.mysema.query.*;version="[2.2.0,3.0.0)";resolution:=optional,
javax.enterprise.*;version="${cdi.version:[=.=.=,+1.0.0)}";resolution:=optional,
javax.inject.*;version="[1.0.0,2.0.0)";resolution:=optional,
javax.xml.bind.*;version="0";resolution:=optional,
javax.xml.transform.*;version="0";resolution:=optional,
org.codehaus.jackson.*;version="${jackson.version:[=.=.=,+1.0.0)}";resolution:=optional,
org.springframework.aop.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional,
@@ -18,6 +19,7 @@ Import-Template:
org.springframework.expression.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional,
org.springframework.expression.spel.standard.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional,
org.springframework.expression.spel.support.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional,
org.springframework.hateoas.*;version="${org.springframework.hateoas.version:[=.=.=,+1.0.0)}";resolution:=optional,
org.springframework.oxm.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional,
org.springframework.transaction.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional,
org.springframework.util.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}",

View File

@@ -17,6 +17,7 @@
<org.springframework.version.30>3.0.7.RELEASE</org.springframework.version.30>
<org.springframework.version.40>4.0.0.RELEASE</org.springframework.version.40>
<org.springframework.version>[${org.springframework.version.30}, ${org.springframework.version.40})</org.springframework.version>
<org.springframework.hateoas.version>0.1.0.RELEASE</org.springframework.hateoas.version>
<org.hamcrest.version>1.2.1</org.hamcrest.version>
<bundlor.failOnWarnings>true</bundlor.failOnWarnings>
</properties>