DATAREST-93 - Quite a lot of refactoring.

Integrated pagination resolving and closer integration of Spring Data Commons configuration.
This commit is contained in:
Oliver Gierke
2013-06-14 19:53:54 +02:00
parent a1c04a4db8
commit db2bbfca0e
73 changed files with 3168 additions and 1820 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ build/
.project
.settings
*.log
vf.gf.*

View File

@@ -10,7 +10,7 @@ ext {
// Spring
springVersion = "3.2.3.RELEASE"
hateoasVersion = "0.6.0.RELEASE"
hateoasVersion = "0.7.0.BUILD-SNAPSHOT"
springPluginVersion = "0.8.0.RELEASE"
springSecurityVersion = "3.1.3.RELEASE"
sdCommonsVersion = "1.6.0.BUILD-SNAPSHOT"
@@ -67,11 +67,11 @@ configure(allprojects) {
sourceSets.test.resources.srcDirs = ["src/test/resources", "src/test/java"]
repositories {
mavenLocal()
mavenCentral()
maven { url "http://oss.sonatype.org/content/repositories/releases" }
maven { url "http://m2.neo4j.org/releases" }
mavenLocal()
maven { url "http://repo.springsource.org/libs-snapshot" }
mavenCentral()
}
dependencies {

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-2013 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.rest.config;
import java.net.URI;
@@ -10,7 +25,9 @@ import org.springframework.util.Assert;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@SuppressWarnings("deprecation")
public class RepositoryRestConfiguration {
private URI baseUri = null;
@@ -340,5 +357,4 @@ public class RepositoryRestConfiguration {
Collections.addAll(exposeIdsFor, domainTypes);
return this;
}
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2013 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.rest.config;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
@@ -8,6 +23,7 @@ import java.util.Map;
/**
* @author Jon Brisbin
*/
@Deprecated
public class ResourceMapping {
private String rel;

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2013 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.rest.config;
import java.util.HashMap;
@@ -9,6 +24,7 @@ import java.util.Map;
*
* @author Jon Brisbin
*/
@SuppressWarnings("deprecation")
public class ResourceMappingConfiguration {
private final Map<Class<?>, ResourceMapping> resourceMappings = new HashMap<Class<?>, ResourceMapping>();

View File

@@ -1,68 +0,0 @@
package org.springframework.data.rest.repository;
import static org.springframework.data.rest.core.util.UriUtils.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
/**
* @author Jon Brisbin
*/
public class BaseUriAwareResource<T> extends Resource<T> {
@JsonIgnore
private URI baseUri;
public BaseUriAwareResource() {
}
public BaseUriAwareResource(T content, Link... links) {
super(content, links);
}
public BaseUriAwareResource(T content, Iterable<Link> links) {
super(content, links);
}
public URI getBaseUri() {
return baseUri;
}
public BaseUriAwareResource<T> setBaseUri(URI baseUri) {
this.baseUri = baseUri;
return this;
}
@Override public List<Link> getLinks() {
String baseUriStr = baseUri.toString();
List<Link> links = new ArrayList<Link>();
for(Link l : super.getLinks()) {
if(!l.getHref().startsWith(baseUriStr)
&& !l.getHref().startsWith("http")) {
links.add(new Link(buildUri(baseUri, l.getHref()).toString(), l.getRel()));
} else {
links.add(l);
}
}
return links;
}
@Override public Link getLink(String rel) {
Link l = super.getLink(rel);
if(null == l) {
return null;
}
if(!l.getHref().startsWith(baseUri.toString())
&& !l.getHref().startsWith("http")) {
return new Link(buildUri(baseUri, l.getHref()).toString(), l.getRel());
} else {
return l;
}
}
}

View File

@@ -1,73 +0,0 @@
package org.springframework.data.rest.repository;
import static org.springframework.data.rest.core.util.UriUtils.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
/**
* @author Jon Brisbin
*/
public class BaseUriAwareResources extends Resources<Resource<?>> {
@JsonIgnore
private URI baseUri;
public BaseUriAwareResources() {
}
public BaseUriAwareResources(Iterable<Resource<?>> content, Link... links) {
super(content, links);
}
public BaseUriAwareResources(Iterable<Resource<?>> content, Iterable<Link> links) {
super(content, links);
}
public URI getBaseUri() {
return baseUri;
}
public BaseUriAwareResources setBaseUri(URI baseUri) {
this.baseUri = baseUri;
return this;
}
@Override public Collection<Resource<?>> getContent() {
List<Resource<?>> resources = new ArrayList<Resource<?>>();
for(Resource<?> resource : super.getContent()) {
if(resource instanceof BaseUriAwareResource) {
resources.add(((BaseUriAwareResource<?>) resource).setBaseUri(baseUri));
} else {
resources.add(new BaseUriAwareResource<Object>(resource.getContent(), resource.getLinks()).setBaseUri(baseUri));
}
}
return resources;
}
@Override public Iterator<Resource<?>> iterator() {
return getContent().iterator();
}
@Override public List<Link> getLinks() {
List<Link> links = new ArrayList<Link>();
for(Link l : super.getLinks()) {
links.add(new Link(buildUri(baseUri, l.getHref()).toString(), l.getRel()));
}
return links;
}
@Override public Link getLink(String rel) {
Link l = super.getLink(rel);
return new Link(buildUri(baseUri, l.getHref()).toString(), l.getRel());
}
}

View File

@@ -1,85 +0,0 @@
package org.springframework.data.rest.repository;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Implementation of {@link Pageable} that is URL-aware.
*
* @author Jon Brisbin
*/
public class PagingAndSorting implements Pageable {
private final RepositoryRestConfiguration config;
private final PageRequest pageRequest;
public PagingAndSorting(RepositoryRestConfiguration config,
PageRequest pageRequest) {
this.config = config;
this.pageRequest = pageRequest;
}
/**
* Add the current sort parameters to the URI.
*
* @param urib
*
* @return
*/
public PagingAndSorting addSortParameters(UriComponentsBuilder urib) {
Sort sort = pageRequest.getSort();
if(null != sort) {
Iterator<Sort.Order> iter = sort.iterator();
while(iter.hasNext()) {
Sort.Order order = iter.next();
urib.queryParam(config.getSortParamName(), order.getProperty());
try {
urib.queryParam(URLEncoder.encode(order.getProperty() + ".dir", "ISO-8859-1"),
order.getDirection().toString().toLowerCase());
} catch(UnsupportedEncodingException ignored) {
// this should never happen
}
}
}
return this;
}
@Override public int getPageNumber() {
return pageRequest.getPageNumber();
}
@Override public int getPageSize() {
return pageRequest.getPageSize();
}
@Override public int getOffset() {
return pageRequest.getOffset();
}
@Override public Sort getSort() {
return pageRequest.getSort();
}
@Override public Pageable first() {
return pageRequest.first();
}
@Override public Pageable next() {
return pageRequest.next();
}
@Override public boolean hasPrevious() {
return pageRequest.hasPrevious();
}
@Override public Pageable previousOrFirst() {
return pageRequest.previousOrFirst();
}
}

View File

@@ -1,50 +1,51 @@
/*
* Copyright 2012-2013 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.rest.repository;
import java.net.URI;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* A Spring HATEOAS {@link Resource} subclass that holds a reference to the entity's {@link PersistentEntity} metadata.
*
*
* @author Jon Brisbin
*/
public class PersistentEntityResource<T> extends BaseUriAwareResource<T> {
public class PersistentEntityResource<T> extends Resource<T> {
@JsonIgnore
private final PersistentEntity<?, ?> persistentEntity;
@JsonIgnore private final PersistentEntity<?, ?> entity;
public static <T> PersistentEntityResource<T> wrap(PersistentEntity<?, ?> persistentEntity,
T obj,
URI baseUri) {
PersistentEntityResource<T> resource = new PersistentEntityResource<T>(persistentEntity, obj);
resource.setBaseUri(baseUri);
return resource;
}
public static <T> PersistentEntityResource<T> wrap(PersistentEntity<?, ?> entity, T obj) {
return new PersistentEntityResource<T>(entity, obj);
}
public PersistentEntityResource(PersistentEntity<?, ?> persistentEntity) {
this.persistentEntity = persistentEntity;
}
public PersistentEntityResource(PersistentEntity<?, ?> entity, T content, Link... links) {
this(entity, content, Arrays.asList(links));
}
public PersistentEntityResource(PersistentEntity<?, ?> persistentEntity,
T content,
Link... links) {
super(content, links);
this.persistentEntity = persistentEntity;
}
public PersistentEntityResource(PersistentEntity<?, ?> persistentEntity,
T content,
Iterable<Link> links) {
super(content, links);
this.persistentEntity = persistentEntity;
}
public PersistentEntity<?, ?> getPersistentEntity() {
return persistentEntity;
}
private PersistentEntityResource(PersistentEntity<?, ?> entity, T content, Iterable<Link> links) {
super(content, links);
this.entity = entity;
}
public PersistentEntity<?, ?> getPersistentEntity() {
return entity;
}
}

View File

@@ -1,33 +0,0 @@
package org.springframework.data.rest.repository.context;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.repository.support.Repositories;
/**
* @author Jon Brisbin
*/
public class RepositoriesFactoryBean implements FactoryBean<Repositories>,
ApplicationContextAware {
private ApplicationContext applicationContext;
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override public Repositories getObject() throws Exception {
return new Repositories(applicationContext);
}
@Override public Class<?> getObjectType() {
return Repositories.class;
}
@Override public boolean isSingleton() {
return false;
}
}

View File

@@ -1,13 +1,15 @@
package org.springframework.data.rest.repository.invoke;
import static org.springframework.util.ReflectionUtils.doWithMethods;
import static org.springframework.util.ReflectionUtils.invokeMethod;
import static org.springframework.util.ReflectionUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@@ -19,10 +21,13 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
/**
* @author Jon Brisbin
*/
@SuppressWarnings("deprecation")
public class RepositoryMethodInvoker implements PagingAndSortingRepository<Object, Serializable> {
private final Object repository;
private final Map<String, RepositoryMethod> queryMethods = new HashMap<String, RepositoryMethod>();
private final ConversionService conversionService;
private RepositoryMethod saveOne;
private RepositoryMethod saveSome;
private RepositoryMethod findOne;
@@ -38,8 +43,11 @@ public class RepositoryMethodInvoker implements PagingAndSortingRepository<Objec
private RepositoryMethod deleteAll;
public RepositoryMethodInvoker(Object repository,
RepositoryInformation repoInfo) {
RepositoryInformation repoInfo,
ConversionService conversionService) {
this.repository = repository;
this.conversionService = conversionService;
Class<?> repoType = repoInfo.getRepositoryInterface();
doWithMethods(repoType, new MethodCallback() {
@@ -219,5 +227,49 @@ public class RepositoryMethodInvoker implements PagingAndSortingRepository<Objec
public Object invokeQueryMethod(RepositoryMethod method, Object... params) {
return invokeMethod(method.getMethod(), repository, params);
}
public Object invokeQueryMethod(RepositoryMethod method, Pageable pageable, Map<String, String[]> rawParameters) {
return invokeQueryMethod(method, foo(method, pageable, rawParameters));
}
private Object[] foo(RepositoryMethod repoMethod, Pageable pageable, Map<String, String[]> rawParameters) {
List<MethodParameter> methodParams = repoMethod.getParameters();
if (methodParams.isEmpty()) {
return new Object[0];
}
Object[] paramValues = new Object[methodParams.size()];
for(int i = 0; i < paramValues.length; i++) {
MethodParameter param = methodParams.get(i);
Class<?> targetType = param.getParameterType();
if(Pageable.class.isAssignableFrom(targetType)) {
paramValues[i] = pageable;
} else if(Sort.class.isAssignableFrom(targetType)) {
paramValues[i] = pageable.getSort();
} else {
String paramName = repoMethod.getParameterNames().get(i);
String[] queryParamVals = rawParameters.get(paramName);
if(null == queryParamVals) {
if(paramName.startsWith("arg")) {
throw new IllegalArgumentException("No @Param annotation found on query method "
+ repoMethod.getMethod().getName()
+ " for parameter " + param.getParameterName());
} else {
throw new IllegalArgumentException("No query parameter specified for "
+ repoMethod.getMethod().getName() + " param '"
+ paramName + "'");
}
}
paramValues[i] = conversionService.convert(queryParamVals, targetType);
}
}
return paramValues;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2013 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.rest.repository.mapping;
/**
* A custom resource mapping for collection resources.
*
* @author Oliver Gierke
*/
public interface CollectionResourceMapping extends ResourceMapping {
String getSingleResourceRel();
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import org.springframework.util.Assert;
class CollectionResourceMappingBuilder implements InternalMappingBuilder {
private final CollectionResourceMapping mapping;
public CollectionResourceMappingBuilder(CollectionResourceMapping mapping) {
this.mapping = mapping;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.InternalMappingBuilder#withCollectionRel(java.lang.String)
*/
public CollectionResourceMappingBuilder withCollectionRel(String rel) {
SimpleCollectionResourceMapping newMapping = new SimpleCollectionResourceMapping(rel != null ? rel : mapping.getRel(),
mapping.getSingleResourceRel(), mapping.getPath(), mapping.isExported());
return new CollectionResourceMappingBuilder(newMapping);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.InternalMappingBuilder#withSingleRel(java.lang.String)
*/
@Override
public CollectionResourceMappingBuilder withSingleRel(String rel) {
SimpleCollectionResourceMapping newMapping = new SimpleCollectionResourceMapping(mapping.getRel(),
rel != null ? rel : mapping.getSingleResourceRel(), mapping.getPath(), mapping.isExported());
return new CollectionResourceMappingBuilder(newMapping);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.InternalMappingBuilder#withPath(java.lang.String)
*/
@Override
public CollectionResourceMappingBuilder withPath(String path) {
SimpleCollectionResourceMapping newMapping = new SimpleCollectionResourceMapping(mapping.getRel(),
mapping.getSingleResourceRel(), path != null ? path : mapping.getPath(), mapping.isExported());
return new CollectionResourceMappingBuilder(newMapping);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.InternalMappingBuilder#withExposed(java.lang.Boolean)
*/
@Override
public CollectionResourceMappingBuilder withExposed(Boolean exported) {
SimpleCollectionResourceMapping newMapping = new SimpleCollectionResourceMapping(mapping.getRel(),
mapping.getSingleResourceRel(), mapping.getPath(), exported != null ? exported : mapping.isExported());
return new CollectionResourceMappingBuilder(newMapping);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.InternalMappingBuilder#merge(org.springframework.data.rest.repository.mapping.CollectionResourceMapping)
*/
@Override
public InternalMappingBuilder merge(CollectionResourceMapping mapping) {
if (mapping == null) {
return this;
}
return withCollectionRel(mapping.getRel()). //
withSingleRel(mapping.getSingleResourceRel()). //
withPath(mapping.getPath()). //
withExposed(mapping.isExported());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.InternalMappingBuilder#getMapping()
*/
@Override
public CollectionResourceMapping getMapping() {
Assert.hasText(mapping.getRel(), "Rel must not be null or empty!");
return mapping;
}
/**
* @return the exported
*/
public Boolean isExported() {
return mapping.isExported();
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2013 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.rest.repository.mapping;
/**
* @author Oliver Gierke
*/
interface InternalMappingBuilder extends MappingBuilder {
InternalMappingBuilder merge(CollectionResourceMapping mapping);
CollectionResourceMapping getMapping();
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013 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.rest.repository.mapping;
/**
* SPI to allow users to register
*
* @author Oliver Gierke
*/
public interface MappingBuilder {
MappingBuilder withCollectionRel(String rel);
MappingBuilder withSingleRel(String rel);
MappingBuilder withPath(String path);
MappingBuilder withExposed(Boolean exposed);
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
public class RepositoryAwareResourceInformation implements ResourceMetadata {
private final Repositories repositories;
private final CollectionResourceMapping mapping;
private final ResourceMetadataProvider provider;
private final RepositoryInformation repositoryInterface;
/**
* @param repositories must not be {@literal null}.
* @param mapping must not be {@literal null}.
* @param provider must not be {@literal null}.
*/
public RepositoryAwareResourceInformation(Repositories repositories, CollectionResourceMapping mapping,
ResourceMetadataProvider provider, RepositoryInformation repositoryInterface) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(mapping, "ResourceMapping must not be null!");
Assert.notNull(provider, "ResourceMetadataProvider must not be null!");
this.repositories = repositories;
this.mapping = mapping;
this.provider = provider;
this.repositoryInterface = repositoryInterface;
}
public boolean isPrimary() {
return AnnotationUtils.findAnnotation(repositoryInterface.getRepositoryInterface(), Primary.class) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.DelegatingResourceInformation#isManaged(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public boolean isManaged(PersistentProperty<?> property) {
return repositories.hasRepositoryFor(property.getActualType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public ResourceMapping getMappingFor(PersistentProperty<?> property) {
return provider.getMappingFor(property);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMetadataProvider#hasMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public boolean isMapped(PersistentProperty<?> property) {
return provider.isMapped(property);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#isExported()
*/
@Override
public Boolean isExported() {
return mapping.isExported();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getCollectionRel()
*/
@Override
public String getRel() {
return mapping.getRel();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getSingleResourceRel()
*/
@Override
public String getSingleResourceRel() {
return mapping.getSingleResourceRel();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getPath()
*/
@Override
public String getPath() {
return mapping.getPath();
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.rest.repository.annotation.RestResource;
/**
* A {@link RepositoryMethodResourceMapping} created from a {@link Method}.
*
* @author Oliver Gierke
*/
public class RepositoryMethodResourceMapping implements ResourceMapping {
private final boolean isExported;
private final String rel;
private final String path;
/**
* Creates a new {@link RepositoryMethodResourceMapping} for the given {@link Method}.
*
* @param method must not be {@literal null}.
*/
public RepositoryMethodResourceMapping(Method method) {
RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class);
this.isExported = annotation != null ? annotation.exported() : true;
this.rel = annotation != null ? annotation.rel() : method.getName();
this.path = annotation != null ? annotation.path() : method.getName();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMapping#isExported()
*/
@Override
public Boolean isExported() {
return isExported;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMapping#getRel()
*/
@Override
public String getRel() {
return rel;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMapping#getPath()
*/
@Override
public String getPath() {
return path;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2013 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.rest.repository.mapping;
/**
* Mapping information for components to be exported as REST resources.
*
* @author Oliver Gierke
*/
public interface ResourceMapping {
public static ResourceMapping NO_MAPPING = new ResourceMapping() {
@Override
public Boolean isExported() {
return false;
}
@Override
public String getRel() {
return null;
}
@Override
public String getPath() {
return null;
}
};
/**
* Returns whether the component shall be exported at all.
*
* @return will never be {@literal null}.
*/
Boolean isExported();
/**
* Returns the relation for the resource exported.
*
* @return will never be {@literal null}.
*/
String getRel();
/**
* Returns the path the resource is exposed under.
*
* @return will never be {@literal null}.
*/
String getPath();
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.rest.repository.annotation.RestResource;
import org.springframework.data.rest.repository.support.RepositoriesUtils;
import org.springframework.hateoas.RelProvider;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author Oliver Gierke
*/
public class ResourceMappingFactory {
private final RelProvider relProvider;
public ResourceMappingFactory(RelProvider relProvider) {
this.relProvider = relProvider;
}
public CollectionResourceMapping getMappingForType(Class<?> type) {
return getMappingForType(type, new CollectionResourceMapping[0]);
}
public CollectionResourceMapping getMappingForType(Class<?> type, CollectionResourceMapping... manualMapping) {
Class<?> typeToInspect = getTypeToInspect(type);
InternalMappingBuilder mapping = getBaseMetadata(typeToInspect). //
merge(discoverConfig(typeToInspect));
if (type != typeToInspect) {
mapping = mapping.merge(discoverConfig(type));
}
for (CollectionResourceMapping externalMapping : manualMapping) {
mapping = mapping.merge(externalMapping);
}
return mapping.getMapping();
}
private static Class<?> getTypeToInspect(Class<?> type) {
if (!RepositoriesUtils.isRepositoryInterface(type)) {
return type;
}
return RepositoriesUtils.getDomainType(type);
}
private InternalMappingBuilder getBaseMetadata(Class<?> domainType) {
String path = StringUtils.uncapitalize(domainType.getSimpleName());
String defaultCollectionRel = relProvider.getCollectionResourceRelFor(domainType);
String defaultSingleRel = relProvider.getSingleResourceRelFor(domainType);
CollectionResourceMapping mapping = new SimpleCollectionResourceMapping(defaultCollectionRel, defaultSingleRel, path, true);
return new CollectionResourceMappingBuilder(mapping);
}
private static final CollectionResourceMapping discoverConfig(Class<?> type) {
RestResource resource = AnnotationUtils.findAnnotation(type, RestResource.class);
if (resource == null) {
return null;
}
return new AnnotationResourceMapping(resource);
}
/**
* {@link CollectionResourceMapping} based on an {@link RestResource} annotation.
*
* @author Oliver Gierke
*/
private static class AnnotationResourceMapping implements CollectionResourceMapping {
private final RestResource annotation;
/**
* Creates a new {@link AnnotationResourceMapping} for the given {@link RestResource}.
*
* @param annotation must not be {@literal null}.
*/
public AnnotationResourceMapping(RestResource annotation) {
Assert.notNull(annotation, "Annotation must not be null!");
this.annotation = annotation;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#isExported()
*/
@Override
public Boolean isExported() {
return annotation.exported();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getCollectionRel()
*/
@Override
public String getRel() {
return StringUtils.hasText(annotation.rel()) ? annotation.rel() : null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getSingleResourceRel()
*/
@Override
public String getSingleResourceRel() {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getPath()
*/
@Override
public String getPath() {
return StringUtils.hasText(annotation.path()) ? annotation.path() : null;
}
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.support.RepositoriesUtils;
import org.springframework.data.rest.repository.support.SimpleRelProvider;
import org.springframework.hateoas.RelProvider;
import org.springframework.util.Assert;
/**
* Central abstraction obtain {@link ResourceMetadata} and {@link ResourceMapping} instances for domain types and
* repositories.
*
* @author Oliver Gierke
*/
@SuppressWarnings("deprecation")
public class ResourceMappings implements ResourceMetadataProvider, Iterable<ResourceMetadata> {
private final RepositoryRestConfiguration config;
private final ResourceMappingFactory factory;
private final Repositories repositories;
private final Map<Class<?>, ResourceMetadata> cache = new HashMap<Class<?>, ResourceMetadata>();
private final Map<Class<?>, Map<String, ResourceMapping>> searchCache = new HashMap<Class<?>, Map<String, ResourceMapping>>();
/**
* Creates a new {@link ResourceMappings} using the given {@link RepositoryRestConfiguration} and {@link Repositories}
* .
*
* @param config
* @param repositories
*/
public ResourceMappings(RepositoryRestConfiguration config, Repositories repositories) {
this(config, repositories, new SimpleRelProvider());
}
/**
* Creates a new {@link ResourceMappings} from the given {@link RepositoryRestConfiguration}, {@link Repositories} and
* {@link RelProvider}.
*
* @param config must not be {@literal null}.
* @param repositories must not be {@literal null}.
* @param relProvider must not be {@literal null}.
*/
public ResourceMappings(RepositoryRestConfiguration config, Repositories repositories, RelProvider relProvider) {
Assert.notNull(config, "RepositoryRestConfiguration must not be null!");
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(relProvider, "RelProvider must not be null!");
this.config = config;
this.repositories = repositories;
this.factory = new ResourceMappingFactory(relProvider);
this.populateCache(repositories);
}
/**
* Returns a {@link ResourceMetadata} for the given type if available.
*
* @param type must not be {@literal null}.
* @return
*/
public ResourceMetadata getMappingFor(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
return cache.get(type);
}
private final void populateCache(Repositories repositories) {
for (Class<?> type : repositories) {
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(type);
Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface();
CollectionResourceMapping mapping = factory.getMappingForType(repositoryInterface, fromConfig(type),
fromConfig(repositoryInterface));
RepositoryAwareResourceInformation information = new RepositoryAwareResourceInformation(repositories, mapping,
this, repositoryInformation);
cache.put(repositoryInterface, information);
if (!cache.containsKey(type) || information.isPrimary()) {
cache.put(type, information);
}
}
}
/**
* Returns the {@link ResourceMapping}s for the search resources of the given type.
*
* @param type
* @return
*/
public Map<String, ResourceMapping> getSearchResourceMappings(Class<?> type) {
if (searchCache.containsKey(type)) {
return searchCache.get(type);
}
Class<?> domainType = RepositoriesUtils.getDomainType(type);
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainType);
Map<String, ResourceMapping> mappings = new HashMap<String, ResourceMapping>();
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
mappings.put(queryMethod.getName(), new RepositoryMethodResourceMapping(queryMethod));
}
searchCache.put(type, mappings);
return mappings;
}
/**
* Returns whether we have a {@link ResourceMapping} for the given type and it is exported.
*
* @param type
* @return
*/
public boolean exportsMappingFor(Class<?> type) {
if (!hasMappingFor(type)) {
return false;
}
ResourceMetadata metadata = getMappingFor(type);
return metadata.isExported();
}
/**
* Returns whether we have a {@link ResourceMapping} for the given type.
*
* @param type must not be {@literal null}.
* @return
*/
public boolean hasMappingFor(Class<?> type) {
if (cache.containsKey(type)) {
return true;
}
if (repositories.hasRepositoryFor(type)) {
return true;
}
if (RepositoriesUtils.isRepositoryInterface(type) && hasMappingFor(RepositoriesUtils.getDomainType(type))) {
return true;
}
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public ResourceMapping getMappingFor(PersistentProperty<?> property) {
return getMappingFor(property.getActualType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMetadataProvider#hasMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public boolean isMapped(PersistentProperty<?> property) {
ResourceMapping metadata = getMappingFor(property);
return metadata != null && metadata.isExported();
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<ResourceMetadata> iterator() {
return cache.values().iterator();
}
private CollectionResourceMapping fromConfig(Class<?> domainType) {
org.springframework.data.rest.config.ResourceMapping mapping = config.getResourceMappingForDomainType(domainType);
if (mapping == null) {
return null;
}
return new SimpleCollectionResourceMapping(mapping.getRel(), null, mapping.getPath(), mapping.isExported());
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import org.springframework.data.mapping.PersistentProperty;
/**
*
* @author Oliver Gierke
*/
public interface ResourceMetadata extends CollectionResourceMapping, ResourceMetadataProvider {
boolean isManaged(PersistentProperty<?> property);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import org.springframework.data.mapping.PersistentProperty;
/**
*
* @author Oliver Gierke
*/
public interface ResourceMetadataProvider {
boolean isMapped(PersistentProperty<?> property);
ResourceMapping getMappingFor(PersistentProperty<?> property);
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013 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.rest.repository.mapping;
public class SimpleCollectionResourceMapping implements CollectionResourceMapping {
private final String collectionRel;
private final String singleRel;
private final String path;
private final Boolean exported;
public SimpleCollectionResourceMapping(String relsAndPath) {
this(relsAndPath, relsAndPath, relsAndPath, true);
}
public SimpleCollectionResourceMapping(String collectionRel, String singleRel, String path, Boolean exported) {
this.collectionRel = collectionRel;
this.singleRel = singleRel;
this.path = path;
this.exported = exported;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getCollectionRel()
*/
public String getRel() {
return collectionRel;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getSingleResourceRel()
*/
public String getSingleResourceRel() {
return singleRel;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.CollectionResourceMapping#getPath()
*/
@Override
public String getPath() {
return path;
}
/**
* @return the exported
*/
public Boolean isExported() {
return exported;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2013 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.rest.repository.support;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AnnotationRepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
/**
*
* @author Oliver Gierke
*/
public class RepositoriesUtils {
public static Class<?> getDomainType(Class<?> repositoryType) {
if (!isRepositoryInterface(repositoryType)) {
return null;
}
return getMetadataFor(repositoryType).getDomainType();
}
public static boolean isRepositoryInterface(Class<?> type) {
return Repository.class.isAssignableFrom(type)
|| AnnotationUtils.findAnnotation(type, RepositoryDefinition.class) != null;
}
private static RepositoryMetadata getMetadataFor(Class<?> type) {
return Repository.class.isAssignableFrom(type) ? new DefaultRepositoryMetadata(type)
: new AnnotationRepositoryMetadata(type);
}
}

View File

@@ -19,6 +19,7 @@ import org.springframework.util.MultiValueMap;
/**
* @author Jon Brisbin
*/
@SuppressWarnings("deprecation")
public abstract class RepositoryInformationSupport {
protected Repositories repositories;
@@ -57,7 +58,7 @@ public abstract class RepositoryInformationSupport {
}
for(Class<?> domainType : repositories) {
RepositoryInformation repoInfo = findRepositoryInfoFor(domainType);
ResourceMapping mapping = getResourceMapping(config, repoInfo);
ResourceMapping mapping = getResourceMapping(config, repoInfo);
if(pathSegment.equals(mapping.getPath()) && mapping.isExported()) {
return repoInfo;
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2013 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.rest.repository.support;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.hateoas.RelProvider;
/**
*
* @author Oliver Gierke
*/
public class RepositoryRelProvider implements RelProvider {
private final ResourceMappings mappings;
/**
* @param repositories
* @param config
*/
public RepositoryRelProvider(ResourceMappings mappings) {
this.mappings = mappings;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.RelProvider#getCollectionResourceRelFor(java.lang.Class)
*/
@Override
public String getCollectionResourceRelFor(Class<?> type) {
return mappings.getMappingFor(type).getRel();
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.RelProvider#getSingleResourceRelFor(java.lang.Class)
*/
@Override
public String getSingleResourceRelFor(Class<?> type) {
return mappings.getMappingFor(type).getSingleResourceRel();
}
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Class<?> delimiter) {
return mappings.hasMappingFor(delimiter);
}
}

View File

@@ -17,6 +17,7 @@ import org.springframework.data.rest.repository.annotation.RestResource;
*
* @author Jon Brisbin
*/
@Deprecated
public abstract class ResourceMappingUtils {
protected ResourceMappingUtils() {

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2013 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.rest.repository.support;
import org.springframework.hateoas.RelProvider;
import org.springframework.util.StringUtils;
/**
*
* @author Oliver Gierke
*/
public class SimpleRelProvider implements RelProvider {
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Class<?> delimiter) {
return true;
}
/* (non-Javadoc)
* @see org.springframework.hateoas.RelProvider#getSingleResourceRelFor(java.lang.Class)
*/
@Override
public String getSingleResourceRelFor(Class<?> type) {
String collectionRel = getCollectionResourceRelFor(type);
return String.format("%s.%s", collectionRel, collectionRel);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.RelProvider#getCollectionResourceRelFor(java.lang.Class)
*/
@Override
public String getCollectionResourceRelFor(Class<?> type) {
return StringUtils.uncapitalize(type.getSimpleName());
}
}

View File

@@ -2,15 +2,13 @@ package org.springframework.data.rest.config;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.repository.domain.jpa.AnnotatedPersonRepository;
import org.springframework.data.rest.repository.domain.jpa.PersonRepository;
import org.springframework.data.rest.repository.domain.jpa.PlainPersonRepository;
import org.springframework.data.rest.repository.mapping.ResourceMapping;
import org.springframework.data.rest.repository.mapping.ResourceMappingFactory;
import org.springframework.data.rest.repository.support.SimpleRelProvider;
/**
* Ensure the {@link ResourceMapping} components convey the correct information.
@@ -18,46 +16,26 @@ import org.springframework.data.rest.repository.domain.jpa.PlainPersonRepository
* @author Jon Brisbin
*/
public class ResourceMappingUnitTests {
ResourceMappingFactory factory = new ResourceMappingFactory(new SimpleRelProvider());
@Test
public void shouldDetectDefaultRelAndPath() throws Exception {
ResourceMapping mapping = new ResourceMapping(
findRel(PlainPersonRepository.class),
findPath(PlainPersonRepository.class),
findExported(PlainPersonRepository.class)
);
assertThat(mapping.getRel(), is("plainPerson"));
assertThat(mapping.getPath(), is("plainPerson"));
assertThat(mapping.isExported(), is(true));
ResourceMapping newMapping = factory.getMappingForType(PlainPersonRepository.class);
assertThat(newMapping.getRel(), is("person"));
assertThat(newMapping.getPath(), is("person"));
assertThat(newMapping.isExported(), is(true));
}
@Test
public void shouldDetectAnnotatedRelAndPath() throws Exception {
ResourceMapping mapping = new ResourceMapping(
findRel(AnnotatedPersonRepository.class),
findPath(AnnotatedPersonRepository.class),
findExported(AnnotatedPersonRepository.class)
);
assertThat(mapping.getRel(), is("people"));
// The path is not set on the annotation so this should be the default from class name.
assertThat(mapping.getPath(), is("annotatedPerson"));
assertThat(mapping.isExported(), is(false));
ResourceMapping newMapping = factory.getMappingForType(AnnotatedPersonRepository.class);
assertThat(newMapping.getRel(), is("people"));
assertThat(newMapping.getPath(), is("person"));
assertThat(newMapping.isExported(), is(false));
}
@Test
public void shouldDetectAnnotatedRelAndPathOnMethod() throws Exception {
Method method = PersonRepository.class.getMethod("findByFirstName", String.class, Pageable.class);
ResourceMapping mapping = new ResourceMapping(
findRel(method),
findPath(method),
findExported(method)
);
assertThat(mapping.getRel(), is("firstname"));
assertThat(mapping.getPath(), is("firstname"));
assertThat(mapping.isExported(), is(true));
}
}

View File

@@ -19,6 +19,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RepositoryTestsConfig.class)
@SuppressWarnings("deprecation")
public class RepositoryRestConfigurationIntegrationTests {
@Autowired
@@ -26,7 +27,7 @@ public class RepositoryRestConfigurationIntegrationTests {
@Test
public void shouldProvideResourceMappingForConfiguredRepository() throws Exception {
ResourceMapping mapping = config.getResourceMappingForRepository(ConfiguredPersonRepository.class);
ResourceMapping mapping = config.getResourceMappingForRepository(ConfiguredPersonRepository.class);
assertThat(mapping, notNullValue());
assertThat(mapping.getRel(), is("people"));

View File

@@ -1,7 +1,5 @@
package org.springframework.data.rest.repository;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -14,7 +12,6 @@ import org.springframework.data.rest.repository.domain.jpa.ConfiguredPersonRepos
import org.springframework.data.rest.repository.domain.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.repository.domain.jpa.Person;
import org.springframework.data.rest.repository.domain.jpa.PersonRepository;
import org.springframework.data.rest.repository.json.PersistentEntityJackson2Module;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
@@ -31,7 +28,8 @@ public class RepositoryTestsConfig {
return new Repositories(appCtx);
}
@Bean public RepositoryRestConfiguration config() {
@SuppressWarnings("deprecation")
@Bean public RepositoryRestConfiguration config() {
RepositoryRestConfiguration config = new RepositoryRestConfiguration();
config.setResourceMappingForDomainType(Person.class)
@@ -63,15 +61,4 @@ public class RepositoryTestsConfig {
@Bean public UriDomainClassConverter uriDomainClassConverter() {
return new UriDomainClassConverter();
}
@Bean public Module persistentEntityModule() {
return new PersistentEntityJackson2Module(defaultConversionService());
}
@Bean public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(persistentEntityModule());
return mapper;
}
}

View File

@@ -1,11 +1,13 @@
package org.springframework.data.rest.repository.domain.jpa;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
/**
* A repository to manage {@link Person}s.
*
* @author Jon Brisbin
*/
@NoRepositoryBean
public interface ConfiguredPersonRepository extends CrudRepository<Person, Long> {
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-2013 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.rest.repository.domain.jpa;
import javax.persistence.EntityManagerFactory;
@@ -22,9 +37,10 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@Configuration
@ComponentScan(basePackageClasses = {JpaRepositoryConfig.class})
@ComponentScan
@EnableJpaRepositories
@EnableTransactionManagement
public class JpaRepositoryConfig {

View File

@@ -11,7 +11,7 @@ import org.springframework.stereotype.Component;
public class PersonLoader implements InitializingBean {
@Autowired
private PlainPersonRepository people;
private AnnotatedPersonRepository people;
@Override public void afterPropertiesSet() throws Exception {
people.save(new Person("John", "Doe"));

View File

@@ -1,11 +1,13 @@
package org.springframework.data.rest.repository.domain.jpa;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
/**
* A repository to manage {@link Person}s.
*
* @author Jon Brisbin
*/
@NoRepositoryBean
public interface PlainPersonRepository extends CrudRepository<Person, Long> {
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.rest.repository.annotation.RestResource;
import org.springframework.data.rest.repository.support.SimpleRelProvider;
/**
*
* @author Oliver Gierke
*/
public class RepositoryAwareResourceMappingFactoryUnitTests {
ResourceMappingFactory factory = new ResourceMappingFactory(new SimpleRelProvider());
@Test
public void foo() {
CollectionResourceMapping mapping = factory.getMappingForType(Person.class);
assertThat(mapping.getPath(), is("person"));
assertThat(mapping.getRel(), is("person"));
assertThat(mapping.getSingleResourceRel(), is("person.person"));
}
@Test
public void honorsAnnotatedMapping() {
CollectionResourceMapping mapping = factory.getMappingForType(AnnotatedPerson.class);
assertThat(mapping.getPath(), is("bar"));
assertThat(mapping.getRel(), is("foo"));
assertThat(mapping.getSingleResourceRel(), is("foo.foo"));
assertThat(mapping.isExported(), is(false));
}
static class Person {
}
@RestResource(path = "bar", rel = "foo", exported = false)
static class AnnotatedPerson {
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.repository.Repository;
import org.springframework.data.rest.repository.annotation.RestResource;
import org.springframework.data.rest.repository.support.SimpleRelProvider;
/**
*
* @author Oliver Gierke
*/
public class ResourceMappingFactoryUnitTests {
ResourceMappingFactory factory = new ResourceMappingFactory(new SimpleRelProvider());
@Test
public void foo() {
CollectionResourceMapping mapping = factory.getMappingForType(Person.class);
assertThat(mapping.getPath(), is("person"));
assertThat(mapping.getRel(), is("person"));
assertThat(mapping.getSingleResourceRel(), is("person.person"));
}
@Test
public void honorsAnnotatedMapping() {
CollectionResourceMapping mapping = factory.getMappingForType(AnnotatedPerson.class);
assertThat(mapping.getPath(), is("bar"));
assertThat(mapping.getRel(), is("foo"));
assertThat(mapping.getSingleResourceRel(), is("foo.foo"));
assertThat(mapping.isExported(), is(false));
}
@Test
public void honorsAnnotatedsMapping() {
CollectionResourceMapping mapping = factory.getMappingForType(PersonRepository.class);
assertThat(mapping.getPath(), is("bar"));
assertThat(mapping.getRel(), is("foo"));
assertThat(mapping.getSingleResourceRel(), is("foo.foo"));
assertThat(mapping.isExported(), is(false));
}
@Test
public void repositoryAnnotationTrumpsDomainTypeMapping() {
CollectionResourceMapping mapping = factory.getMappingForType(AnnotatedAnnotatedPersonRepository.class);
assertThat(mapping.getPath(), is("trumpsAll"));
assertThat(mapping.getRel(), is("foo"));
assertThat(mapping.getSingleResourceRel(), is("foo.foo"));
assertThat(mapping.isExported(), is(true));
}
static class Person {
}
@RestResource(path = "bar", rel = "foo", exported = false)
static class AnnotatedPerson {
}
interface PersonRepository extends Repository<AnnotatedPerson, Long> {
}
@RestResource(path = "trumpsAll")
interface AnnotatedAnnotatedPersonRepository extends Repository<AnnotatedPerson, Long> {
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2013 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.rest.repository.mapping;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.domain.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.repository.domain.jpa.Person;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration tests for {@link ResourceMappings}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JpaRepositoryConfig.class)
public class ResourceMappingsIntegrationTest {
@Autowired ListableBeanFactory factory;
ResourceMappings mappings;
@Before
public void setUp() {
Repositories repositories = new Repositories(factory);
this.mappings = new ResourceMappings(new RepositoryRestConfiguration(), repositories);
}
@Test
public void foo() {
assertThat(mappings, is(Matchers.<ResourceMetadata> iterableWithSize(2)));
assertThat(mappings.getMappingFor(Person.class).isExported(), is(false));
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.data" level="error" />
<root level="error">
<appender-ref ref="console" />
</root>
</configuration>

View File

@@ -1,137 +1,119 @@
/*
* Copyright 2012-2013 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.rest.webmvc;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.BaseUriAwareResource;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
import org.springframework.data.rest.repository.invoke.MethodParameterConversionService;
import org.springframework.data.rest.repository.support.ResourceMappingUtils;
import org.springframework.data.rest.webmvc.support.BaseUriLinkBuilder;
import org.springframework.data.rest.webmvc.support.ExceptionMessage;
import org.springframework.data.rest.webmvc.support.RepositoryConstraintViolationExceptionMessage;
import org.springframework.data.rest.webmvc.support.ValidationExceptionHandler;
import org.springframework.hateoas.*;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.*;
import static org.springframework.data.rest.core.util.UriUtils.buildUri;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@SuppressWarnings({"rawtypes"})
public class AbstractRepositoryRestController implements ApplicationContextAware,
InitializingBean {
@SuppressWarnings({ "rawtypes", "deprecation" })
class AbstractRepositoryRestController implements MessageSourceAware, InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(AbstractRepositoryRestController.class);
static final Resource<?> EMPTY_RESOURCE = new Resource<Object>(Collections.emptyList());
static final Resources<Resource<?>> EMPTY_RESOURCES = new Resources<Resource<?>>(Collections.<Resource<?>>emptyList());
static final Iterable<Resource<?>> EMPTY_RESOURCE_LIST = Collections.emptyList();
static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
protected final Logger LOG = LoggerFactory.getLogger(getClass());
protected final Repositories repositories;
protected final RepositoryRestConfiguration config;
protected final DomainClassConverter domainClassConverter;
protected final ConversionService conversionService;
protected final MethodParameterConversionService methodParameterConversionService;
protected final EntityLinks entityLinks;
protected ApplicationContext applicationContext;
@Autowired(required = false)
protected ValidationExceptionHandler handler;
@Autowired(required = false)
protected PlatformTransactionManager txMgr;
protected TransactionTemplate txTmpl;
private final PersistentEntityResourceAssembler<Object> perAssembler;
@Autowired
public AbstractRepositoryRestController(Repositories repositories,
RepositoryRestConfiguration config,
DomainClassConverter domainClassConverter,
ConversionService conversionService,
EntityLinks entityLinks) {
this.repositories = repositories;
this.config = config;
this.domainClassConverter = domainClassConverter;
this.conversionService = conversionService;
this.entityLinks = entityLinks;
this.methodParameterConversionService = new MethodParameterConversionService(conversionService);
@Autowired(required = false) private ValidationExceptionHandler handler;
@Autowired(required = false) private PlatformTransactionManager txMgr;
private MessageSource messageSource;
private PagedResourcesAssembler<Object> assembler;
public AbstractRepositoryRestController(PagedResourcesAssembler<Object> assembler, PersistentEntityResourceAssembler<Object> entityResourceAssembler) {
this.assembler = assembler;
this.perAssembler = entityResourceAssembler;
}
/*
* (non-Javadoc)
* @see org.springframework.context.MessageSourceAware#setMessageSource(org.springframework.context.MessageSource)
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
@Override
public void afterPropertiesSet() throws Exception {
if (null != txMgr) {
txTmpl = new TransactionTemplate(txMgr);
txTmpl.afterPropertiesSet();
}
// FIXME:
// if (null != txMgr) {
// txTmpl = new TransactionTemplate(txMgr);
// txTmpl.afterPropertiesSet();
// }
}
@ExceptionHandler({
NullPointerException.class
})
@ExceptionHandler({ NullPointerException.class })
@ResponseBody
public ResponseEntity<?> handleNPE(NullPointerException npe) {
return errorResponse(npe, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler({
ResourceNotFoundException.class
})
@ExceptionHandler({ ResourceNotFoundException.class })
@ResponseBody
public ResponseEntity<?> handleNotFound() {
return notFound();
}
@ExceptionHandler({
NoSuchMethodError.class,
HttpRequestMethodNotSupportedException.class
})
@ExceptionHandler({ NoSuchMethodError.class, HttpRequestMethodNotSupportedException.class })
@ResponseBody
public ResponseEntity<?> handleNoSuchMethod() {
return errorResponse(null, HttpStatus.METHOD_NOT_ALLOWED);
}
@ExceptionHandler({
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class
})
@ExceptionHandler({ HttpMessageNotReadableException.class, HttpMessageNotWritableException.class })
@ResponseBody
public ResponseEntity<ExceptionMessage> handleNotReadable(HttpMessageNotReadableException e) {
return badRequest(e);
@@ -139,16 +121,12 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
/**
* Handle failures commonly thrown from code tries to read incoming data and convert or cast it to the right type.
*
*
* @param t
* @return
*/
@ExceptionHandler({
InvocationTargetException.class,
IllegalArgumentException.class,
ClassCastException.class,
ConversionFailedException.class
})
@ExceptionHandler({ InvocationTargetException.class, IllegalArgumentException.class, ClassCastException.class,
ConversionFailedException.class })
@ResponseBody
public ResponseEntity handleMiscFailures(Throwable t) {
if (null != t.getCause() && t.getCause() instanceof ResourceNotFoundException) {
@@ -157,48 +135,22 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
return badRequest(t);
}
// @ExceptionHandler({
// RuntimeException.class
// })
// @ResponseBody
// public ResponseEntity maybeHandleValidationException(Locale locale,
// RuntimeException ex) {
// if(ResourceNotFoundException.class.isAssignableFrom(ex.getClass())) {
// return handleNotFound();
// }
//
// if(null != handler) {
// return handler.handleValidationException(ex,
// applicationContext,
// locale);
// } else {
// return response(null,
// ex,
// HttpStatus.BAD_REQUEST);
// }
// }
@ExceptionHandler({
RepositoryConstraintViolationException.class
})
@ExceptionHandler({ RepositoryConstraintViolationException.class })
@ResponseBody
public ResponseEntity handleRepositoryConstraintViolationException(Locale locale,
RepositoryConstraintViolationException rcve) {
return response(null,
new RepositoryConstraintViolationExceptionMessage(rcve, applicationContext, locale),
HttpStatus.BAD_REQUEST);
RepositoryConstraintViolationException rcve) {
return response(null, new RepositoryConstraintViolationExceptionMessage(rcve, messageSource, locale),
HttpStatus.BAD_REQUEST);
}
/**
* Send a 409 Conflict in case of concurrent modification.
*
*
* @param ex
* @return
*/
@ExceptionHandler({
OptimisticLockingFailureException.class,
DataIntegrityViolationException.class
})
@ExceptionHandler({ OptimisticLockingFailureException.class, DataIntegrityViolationException.class })
@ResponseBody
public ResponseEntity handleConflict(Exception ex) {
return errorResponse(null, ex, HttpStatus.CONFLICT);
@@ -220,14 +172,12 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
return errorResponse(headers, throwable, HttpStatus.BAD_REQUEST);
}
public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(T throwable,
HttpStatus status) {
public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(T throwable, HttpStatus status) {
return errorResponse(null, throwable, status);
}
public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(HttpHeaders headers,
T throwable,
HttpStatus status) {
public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(HttpHeaders headers, T throwable,
HttpStatus status) {
if (null != throwable && null != throwable.getMessage()) {
LOG.error(throwable.getMessage(), throwable);
return response(headers, new ExceptionMessage(throwable), status);
@@ -244,86 +194,6 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
return new ResponseEntity<T>(body, hdrs, status);
}
public <R extends Resource<?>> ResponseEntity<Resource<?>> resourceResponse(HttpHeaders headers,
R resource,
HttpStatus status) {
HttpHeaders hdrs = new HttpHeaders();
if (null != headers) {
hdrs.putAll(headers);
}
return new ResponseEntity<Resource<?>>(resource, hdrs, status);
}
protected void addQueryParameters(HttpServletRequest request,
UriComponentsBuilder builder) {
for (Enumeration<String> names = request.getParameterNames(); names.hasMoreElements(); ) {
String name = names.nextElement();
String value = request.getParameter(name);
if (name.equals(config.getPageParamName()) || name.equals(config.getLimitParamName())) {
continue;
}
builder.queryParam(name, value);
}
}
protected Link searchLink(RepositoryRestRequest repoRequest,
int pageIncrement,
String method,
String rel) {
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
UriComponentsBuilder ucb = UriComponentsBuilder.fromUri(
entityLinks.linkFor(repoRequest.getPersistentEntity().getType())
.slash("search")
.slash(method)
.toUri()
);
ucb.queryParam(config.getPageParamName(), Math.max(pageSort.getPageNumber() + pageIncrement, 1))
.queryParam(config.getLimitParamName(), pageSort.getPageSize());
addQueryParameters(repoRequest.getRequest(), ucb);
return new Link(ucb.build().toString(), rel);
}
protected Link entitiesPageLink(RepositoryRestRequest repoRequest,
int pageIncrement,
String rel) {
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
UriComponentsBuilder ucb = UriComponentsBuilder.fromUri(
entityLinks.linkFor(repoRequest.getPersistentEntity().getType())
.toUri()
);
if (null != repoRequest.getRequest().getParameter(config.getPageParamName())) {
ucb.queryParam(config.getPageParamName(), Math.max(pageSort.getPageNumber() + pageIncrement, 1))
.queryParam(config.getLimitParamName(), pageSort.getPageSize());
}
addQueryParameters(repoRequest.getRequest(), ucb);
return new Link(ucb.build().toString(), rel);
}
protected List<Link> queryMethodLinks(URI baseUri, Class<?> domainType) {
List<Link> links = new ArrayList<Link>();
RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(domainType);
ResourceMapping repoMapping = ResourceMappingUtils.merge(
repoInfo.getRepositoryInterface(),
config.getResourceMappingForRepository(repoInfo.getRepositoryInterface())
);
for (Method method : repoInfo.getQueryMethods()) {
LinkBuilder linkBuilder = BaseUriLinkBuilder.create(buildUri(baseUri, repoMapping.getPath(), "search"));
ResourceMapping methodMapping = ResourceMappingUtils.merge(method,
repoMapping.getResourceMappingFor(method.getName()));
if (!methodMapping.isExported()) {
continue;
}
links.add(linkBuilder.slash(methodMapping.getPath())
.withRel(repoMapping.getRel() + "." + methodMapping.getRel()));
}
return links;
}
protected Link resourceLink(RepositoryRestRequest repoRequest, Resource resource) {
ResourceMapping repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMapping entityMapping = repoRequest.getPersistentEntityResourceMapping();
@@ -333,89 +203,37 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
return new Link(selfLink.getHref(), rel);
}
@SuppressWarnings({"unchecked"})
protected Resources resultToResources(RepositoryRestRequest repoRequest,
Object result,
List<Link> links,
Link prevLink,
Link nextLink) {
@SuppressWarnings({ "unchecked" })
protected Resources resultToResources(Object result, Link baseLink) {
if (result instanceof Page) {
Page page = (Page) result;
PagedResources.PageMetadata pageMeta = pageMetadata(page);
if (page.hasPreviousPage() && null != prevLink) {
links.add(prevLink);
}
if (page.hasNextPage() && null != nextLink) {
links.add(nextLink);
}
if (page.hasContent()) {
return entitiesToResources(repoRequest, links, page);
} else {
return new PagedResources(Collections.emptyList(), pageMeta, links);
}
Page<Object> page = (Page<Object>) result;
return entitiesToResources(page, baseLink, assembler);
} else if (result instanceof Iterable) {
return entitiesToResources(repoRequest, links, (Iterable) result);
return entitiesToResources((Iterable<Object>) result);
} else if (null == result) {
return new Resources(EMPTY_RESOURCE_LIST);
} else {
PersistentEntityResource per = PersistentEntityResource.wrap(
repoRequest.getPersistentEntity(),
result,
repoRequest.getBaseUri()
);
BeanWrapper wrapper = BeanWrapper.create(result, conversionService);
Link selfLink = entityLinks.linkForSingleResource(
result.getClass(),
wrapper.getProperty(repoRequest.getPersistentEntity().getIdProperty())
)
.withSelfRel();
per.add(selfLink);
return new Resources(Collections.singletonList(per), links);
Resource<Object> resource = perAssembler.toResource(result);
return new Resources(Collections.singletonList(resource));
}
}
@SuppressWarnings({"unchecked"})
protected Resources entitiesToResources(RepositoryRestRequest repoRequest, List<Link> links, Page page) {
PagedResources.PageMetadata pageMeta = pageMetadata(page);
Resources<Object> resource = (Resources<Object>) entitiesToResources(repoRequest, links, page.getContent());
return new PagedResources<Object>(resource.getContent(), pageMeta, resource.getLinks());
protected Resources<? extends Resource<Object>> entitiesToResources(Page<Object> page, Link baseLink,
final PagedResourcesAssembler<Object> assembler) {
return assembler.toResource(page, perAssembler, baseLink);
}
@SuppressWarnings({"unchecked"})
protected Resources entitiesToResources(RepositoryRestRequest repoRequest, List<Link> links, Iterable entities) {
List<Resource<?>> resources = new ArrayList<Resource<?>>();
protected Resources<Resource<Object>> entitiesToResources(Iterable<Object> entities) {
List<Resource<Object>> resources = new ArrayList<Resource<Object>>();
for (Object obj : entities) {
if (null == obj) {
resources.add(null);
continue;
}
PersistentEntity persistentEntity = repositories.getPersistentEntity(obj.getClass());
if (null == persistentEntity) {
resources.add(new BaseUriAwareResource<Object>(obj)
.setBaseUri(repoRequest.getBaseUri()));
continue;
}
BeanWrapper wrapper = BeanWrapper.create(obj, conversionService);
PersistentEntityResource per = PersistentEntityResource.wrap(persistentEntity, obj, repoRequest.getBaseUri());
Link selfLink = entityLinks.linkForSingleResource(persistentEntity.getType(),
wrapper.getProperty(persistentEntity.getIdProperty()))
.withSelfRel();
per.add(selfLink);
resources.add(per);
resources.add(obj == null ? null : perAssembler.toResource(obj));
}
return new Resources(resources, links);
return new Resources<Resource<Object>>(resources);
}
protected PagedResources.PageMetadata pageMetadata(Page page) {
return new PagedResources.PageMetadata(
page.getNumberOfElements(),
page.getNumber() + 1,
page.getTotalElements(),
page.getTotalPages()
);
}
}

View File

@@ -1,9 +1,23 @@
/*
* Copyright 2012-2013 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.rest.webmvc;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.annotation.BaseURI;
@@ -15,14 +29,11 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class BaseUriMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private RepositoryRestConfiguration config;
public BaseUriMethodArgumentResolver() {
}
private final RepositoryRestConfiguration config;
public BaseUriMethodArgumentResolver(RepositoryRestConfiguration config) {
this.config = config;
@@ -34,7 +45,7 @@ public class BaseUriMethodArgumentResolver implements HandlerMethodArgumentResol
}
@Override
public Object resolveArgument(MethodParameter parameter,
public URI resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
@@ -48,5 +59,4 @@ public class BaseUriMethodArgumentResolver implements HandlerMethodArgumentResol
return config.getBaseUri();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2013 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.rest.webmvc;
import java.util.Collections;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
*
* @author Oliver Gierke
*/
public class ControllerUtils {
public static final Resource<?> EMPTY_RESOURCE = new Resource<Object>(Collections.emptyList());
public static final Resources<Resource<?>> EMPTY_RESOURCES = new Resources<Resource<?>>(
Collections.<Resource<?>> emptyList());
public static final Iterable<Resource<?>> EMPTY_RESOURCE_LIST = Collections.emptyList();
public static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
public static <R extends Resource<?>> ResponseEntity<Resource<?>> toResponseEntity(HttpHeaders headers, R resource,
HttpStatus status) {
HttpHeaders hdrs = new HttpHeaders();
if (null != headers) {
hdrs.putAll(headers);
}
return new ResponseEntity<Resource<?>>(resource, hdrs, status);
}
}

View File

@@ -1,144 +0,0 @@
package org.springframework.data.rest.webmvc;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.web.PageableDefaults;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* {@link HandlerMethodArgumentResolver} implementation responsible for inspecting a request for page and sort
* parameters for use by the repositories.
*
* @author Jon Brisbin
*/
@SuppressWarnings("deprecation")
public class PagingAndSortingMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final int DEFAULT_PAGE = 1; // We're 1-based, not 0-based
@Autowired
private RepositoryRestConfiguration config;
@Autowired
private RepositoryInformationHandlerMethodArgumentResolver repoInfoResolver;
public PagingAndSortingMethodArgumentResolver() {
}
public PagingAndSortingMethodArgumentResolver(RepositoryRestConfiguration config) {
this.config = config;
}
public RepositoryRestConfiguration getConfig() {
return config;
}
public RepositoryInformationHandlerMethodArgumentResolver getRepoInfoResolver() {
return repoInfoResolver;
}
public PagingAndSortingMethodArgumentResolver setRepoInfoResolver(RepositoryInformationHandlerMethodArgumentResolver repoInfoResolver) {
this.repoInfoResolver = repoInfoResolver;
return this;
}
@Override public boolean supportsParameter(MethodParameter parameter) {
return ClassUtils.isAssignable(parameter.getParameterType(), PagingAndSorting.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest();
PageRequest pr = null;
for(Annotation annotation : parameter.getParameterAnnotations()) {
if(annotation instanceof PageableDefaults) {
PageableDefaults defaults = (PageableDefaults)annotation;
pr = new PageRequest(defaults.pageNumber(), defaults.value());
break;
}
}
if(null == pr) {
int page = DEFAULT_PAGE;
String sPage = request.getParameter(config.getPageParamName());
if(StringUtils.hasText(sPage)) {
try {
page = Integer.parseInt(sPage);
} catch(NumberFormatException ignored) {
}
}
int limit = config.getDefaultPageSize();
String sLimit = request.getParameter(config.getLimitParamName());
if(StringUtils.hasText(sLimit)) {
try {
limit = Math.min(Integer.parseInt(sLimit), config.getMaxPageSize());
} catch(NumberFormatException ignored) {
}
}
Sort sort = null;
List<Sort.Order> orders = new ArrayList<Sort.Order>();
String[] orderValues = request.getParameterValues(config.getSortParamName());
if(null != orderValues) {
for(String orderParam : orderValues) {
String name = nameForParam(parameter,
mavContainer,
webRequest,
binderFactory,
orderParam);
String sortDir = request.getParameter(orderParam + ".dir");
Sort.Direction dir = (null != sortDir ? Sort.Direction.valueOf(sortDir.toUpperCase()) : Sort.Direction.ASC);
orders.add(new Sort.Order(dir, name));
}
if(!orders.isEmpty()) {
sort = new Sort(orders);
}
}
if(null != sort) {
pr = new PageRequest(page - 1, limit, sort);
} else {
pr = new PageRequest(page - 1, limit);
}
}
return new PagingAndSorting(config, pr);
}
private String nameForParam(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory,
String orderParam) throws Exception {
if(null == repoInfoResolver) {
return orderParam;
}
RepositoryInformation repoInfo = (RepositoryInformation)repoInfoResolver.resolveArgument(parameter,
mavContainer,
webRequest,
binderFactory);
ResourceMapping repoMapping = config.getResourceMappingForDomainType(repoInfo.getDomainType());
if(null != repoMapping) {
return repoMapping.getNameForPath(orderParam);
}
return orderParam;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2013 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.rest.webmvc;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.ResourceAssembler;
import org.springframework.util.Assert;
/**
*
* @author Oliver Gierke
*/
public class PersistentEntityResourceAssembler<T> implements ResourceAssembler<T, PersistentEntityResource<T>> {
private final Repositories repositories;
private final EntityLinks entityLinks;
/**
* Creates a new {@link PersistentEntityResourceAssembler}.
*
* @param repositories must not be {@literal null}.
* @param entityLinks must not be {@literal null}.
*/
public PersistentEntityResourceAssembler(Repositories repositories, EntityLinks entityLinks) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(entityLinks, "EntityLinks must not be null!");
this.repositories = repositories;
this.entityLinks = entityLinks;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.ResourceAssembler#toResource(java.lang.Object)
*/
@Override
public PersistentEntityResource<T> toResource(T instance) {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(instance.getClass());
PersistentEntityResource<T> resource = PersistentEntityResource.wrap(entity, instance);
resource.add(getSelfLinkFor(instance));
return resource;
}
public Link getSelfLinkFor(Object instance) {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(instance.getClass());
BeanWrapper<?, Object> wrapper = BeanWrapper.create(instance, null);
Object id = wrapper.getProperty(entity.getIdProperty());
return entityLinks.linkForSingleResource(entity.getType(), id).withSelfRel();
}
}

View File

@@ -49,8 +49,7 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha
}
Object obj = converter.read(domainType, request);
return new PersistentEntityResource<Object>(repoRequest.getPersistentEntity(),
obj);
return new PersistentEntityResource<Object>(repoRequest.getPersistentEntity(), obj);
}
return null;

View File

@@ -1,11 +1,25 @@
/*
* Copyright 2012-2013 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.rest.webmvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -15,30 +29,29 @@ import static org.springframework.data.rest.repository.support.ResourceMappingUt
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@RestController
@SuppressWarnings("deprecation")
public class RepositoryController extends AbstractRepositoryRestController {
private final Repositories repositories;
private final RepositoryRestConfiguration config;
private final EntityLinks entityLinks;
@Autowired
public RepositoryController(Repositories repositories,
RepositoryRestConfiguration config,
DomainClassConverter<?> domainClassConverter,
ConversionService conversionService,
EntityLinks entityLinks) {
super(repositories,
config,
domainClassConverter,
conversionService,
entityLinks);
public RepositoryController(Repositories repositories, RepositoryRestConfiguration config, EntityLinks entityLinks,
PagedResourcesAssembler<Object> assembler, PersistentEntityResourceAssembler<Object> perAssembler) {
super(assembler, perAssembler);
this.repositories = repositories;
this.config = config;
this.entityLinks = entityLinks;
}
@RequestMapping(
value = "/",
method = RequestMethod.GET,
produces = {
"application/json",
"application/x-spring-data-compact+json"
}
)
@RequestMapping(value = "/", method = RequestMethod.GET, //
produces = { "application/json", "application/x-spring-data-compact+json" })
@ResponseBody
public RepositoryLinksResource listRepositories() throws ResourceNotFoundException {
RepositoryLinksResource resource = new RepositoryLinksResource();
@@ -50,5 +63,4 @@ public class RepositoryController extends AbstractRepositoryRestController {
}
return resource;
}
}

View File

@@ -1,34 +1,21 @@
/*
* Copyright 2013 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.rest.webmvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.context.*;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.json.JsonSchema;
import org.springframework.data.rest.repository.json.PersistentEntityToJsonSchemaConverter;
import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.hateoas.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import java.io.Serializable;
import java.net.URI;
@@ -36,109 +23,147 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.context.AfterCreateEvent;
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
import org.springframework.data.rest.repository.context.AfterSaveEvent;
import org.springframework.data.rest.repository.context.BeforeCreateEvent;
import org.springframework.data.rest.repository.context.BeforeDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.data.rest.webmvc.json.JsonSchema;
import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@SuppressWarnings({"rawtypes"})
public class RepositoryEntityController extends AbstractRepositoryRestController {
@RestController
@SuppressWarnings("deprecation")
class RepositoryEntityController extends AbstractRepositoryRestController implements
ApplicationEventPublisherAware {
private static final String BASE_MAPPING = "/{repository}";
@Autowired
private DomainObjectMerger domainObjectMerger;
@Autowired
private PersistentEntityToJsonSchemaConverter jsonSchemaConverter;
public RepositoryEntityController(Repositories repositories,
RepositoryRestConfiguration config,
DomainClassConverter domainClassConverter,
ConversionService conversionService,
EntityLinks entityLinks) {
super(repositories,
config,
domainClassConverter,
conversionService,
entityLinks);
private final EntityLinks entityLinks;
private final PersistentEntityResourceAssembler<Object> perAssembler;
private final RepositoryRestConfiguration config;
private final DomainClassConverter<?> converter;
private final ConversionService conversionService;
private final TransactionOperations txOperations;
private ApplicationEventPublisher publisher;
@Autowired private DomainObjectMerger domainObjectMerger;
@Autowired private PersistentEntityToJsonSchemaConverter jsonSchemaConverter;
@Autowired
public RepositoryEntityController(Repositories repositories, RepositoryRestConfiguration config,
EntityLinks entityLinks, PagedResourcesAssembler<Object> assembler,
PersistentEntityResourceAssembler<Object> perAssembler, DomainClassConverter<?> converter,
@Qualifier("defaultConversionService") ConversionService conversionService) {
super(assembler, perAssembler);
this.entityLinks = entityLinks;
this.perAssembler = perAssembler;
this.config = config;
this.converter = converter;
this.conversionService = conversionService;
this.txOperations = null;
}
@RequestMapping(
value = BASE_MAPPING + "/schema",
method = RequestMethod.GET,
produces = {
"application/schema+json"
}
)
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@RequestMapping(value = BASE_MAPPING + "/schema", method = RequestMethod.GET,
produces = { "application/schema+json" })
@ResponseBody
public JsonSchema schema(RepositoryRestRequest repoRequest) {
return jsonSchemaConverter.convert(repoRequest.getPersistentEntity().getType());
}
@RequestMapping(
value = BASE_MAPPING,
method = RequestMethod.GET,
produces = {
"application/json",
"application/x-spring-data-verbose+json"
}
)
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json" })
@ResponseBody
public Resources listEntities(final RepositoryRestRequest repoRequest)
public Resources<?> listEntities(final RepositoryRestRequest request, Pageable pageable)
throws ResourceNotFoundException {
List<Link> links = new ArrayList<Link>();
Iterable<?> results;
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
RepositoryMethodInvoker repoMethodInvoker = request.getRepositoryMethodInvoker();
if (null == repoMethodInvoker) {
throw new ResourceNotFoundException();
}
boolean hasSortParams = (null != repoRequest.getRequest().getParameter(config.getSortParamName()));
if (repoMethodInvoker.hasFindAllPageable()) {
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
results = repoMethodInvoker.findAll(new PageRequest(pageSort.getPageNumber(),
pageSort.getPageSize(),
pageSort.getSort()));
} else if (repoMethodInvoker.hasFindAllSorted() && hasSortParams) {
results = repoMethodInvoker.findAll(repoRequest.getPagingAndSorting().getSort());
results = repoMethodInvoker.findAll(pageable);
} else if (repoMethodInvoker.hasFindAllSorted()) {
results = repoMethodInvoker.findAll(pageable.getSort());
} else if (repoMethodInvoker.hasFindAll()) {
results = repoMethodInvoker.findAll();
} else {
throw new ResourceNotFoundException();
}
ResourceMapping repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMapping repoMapping = request.getRepositoryResourceMapping();
if (!repoMethodInvoker.getQueryMethods().isEmpty()) {
links.add(entityLinks.linkForSingleResource(repoRequest.getPersistentEntity().getType(), "search")
.withRel(repoMapping.getRel() + ".search"));
links.add(entityLinks.linkForSingleResource(request.getPersistentEntity().getType(), "search").withRel(
repoMapping.getRel() + ".search"));
}
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
Link prevLink = null;
Link nextLink = null;
if (results instanceof Page) {
if (((Page) results).hasPreviousPage() && pageSort.getPageNumber() > 0) {
prevLink = entitiesPageLink(repoRequest, 0, "page.previous");
}
if (((Page) results).hasNextPage()) {
nextLink = entitiesPageLink(repoRequest, 1, "page.next");
}
}
return resultToResources(repoRequest, results, links, prevLink, nextLink);
Link baseLink = request.getRepositoryLink();
Resources<?> resources = resultToResources(results, baseLink);
resources.add(links);
return resources;
}
@SuppressWarnings({"unchecked"})
@RequestMapping(
value = BASE_MAPPING,
method = RequestMethod.GET,
produces = {
"application/x-spring-data-compact+json",
"text/uri-list"
}
)
@SuppressWarnings({ "unchecked" })
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = {
"application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public Resources listEntitiesCompact(final RepositoryRestRequest repoRequest)
public Resources<?> listEntitiesCompact(final RepositoryRestRequest repoRequest, Pageable pageable)
throws ResourceNotFoundException {
Resources resources = listEntities(repoRequest);
Resources<?> resources = listEntities(repoRequest, pageable);
List<Link> links = new ArrayList<Link>(resources.getLinks());
for (Resource<?> resource : ((Resources<Resource<?>>) resources).getContent()) {
@@ -146,169 +171,115 @@ public class RepositoryEntityController extends AbstractRepositoryRestController
links.add(resourceLink(repoRequest, persistentEntityResource));
}
if (resources instanceof PagedResources) {
return new PagedResources(Collections.emptyList(), ((PagedResources) resources).getMetadata(), links);
return new PagedResources<Object>(Collections.emptyList(), ((PagedResources<?>) resources).getMetadata(), links);
} else {
return new Resources(Collections.emptyList(), links);
return new Resources<Object>(Collections.emptyList(), links);
}
}
@SuppressWarnings({"unchecked"})
@RequestMapping(
value = BASE_MAPPING,
method = RequestMethod.POST,
consumes = {
"application/json"
},
produces = {
"application/json",
"text/uri-list"
}
)
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST, consumes = { "application/json" }, produces = {
"application/json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> createNewEntity(RepositoryRestRequest repoRequest,
PersistentEntityResource<?> incoming) {
PersistentEntityResource<?> incoming) {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasSaveOne()) {
throw new NoSuchMethodError();
}
applicationContext.publishEvent(new BeforeCreateEvent(incoming.getContent()));
publisher.publishEvent(new BeforeCreateEvent(incoming.getContent()));
Object obj = repoMethodInvoker.save(incoming.getContent());
applicationContext.publishEvent(new AfterCreateEvent(obj));
publisher.publishEvent(new AfterCreateEvent(obj));
BeanWrapper wrapper = BeanWrapper.create(obj, conversionService);
Link selfLink = entityLinks.linkForSingleResource(
repoRequest.getPersistentEntity().getType(),
wrapper.getProperty(repoRequest.getPersistentEntity().getIdProperty())
).withSelfRel();
Link selfLink = perAssembler.getSelfLinkFor(obj);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(selfLink.getHref()));
if (config.isReturnBodyOnCreate()) {
return resourceResponse(
headers,
new PersistentEntityResource<Object>(
repoRequest.getPersistentEntity(),
obj,
selfLink
).setBaseUri(repoRequest.getBaseUri()),
HttpStatus.CREATED
);
return ControllerUtils.toResponseEntity(headers, perAssembler.toResource(obj), HttpStatus.CREATED);
} else {
return resourceResponse(headers, null, HttpStatus.CREATED);
return ControllerUtils.toResponseEntity(headers, null, HttpStatus.CREATED);
}
}
@SuppressWarnings({"unchecked"})
@RequestMapping(
value = BASE_MAPPING + "/{id}",
method = RequestMethod.GET,
produces = {
"application/json",
"application/x-spring-data-compact+json",
"text/uri-list"
}
)
/**
* {@code GET /{repository}/{id}}
*
* @param repoRequest
* @param id
* @return
* @throws ResourceNotFoundException
*/
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public Resource<?> getSingleEntity(RepositoryRestRequest repoRequest,
@PathVariable String id) throws ResourceNotFoundException {
public Resource<?> getSingleEntity(RepositoryRestRequest repoRequest, @PathVariable String id)
throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasFindOne()) {
throw new ResourceNotFoundException();
}
Object domainObj = domainClassConverter.convert(
id,
STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType())
);
Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
if (null == domainObj) {
throw new ResourceNotFoundException();
}
PersistentEntityResource per = PersistentEntityResource.wrap(repoRequest.getPersistentEntity(),
domainObj,
repoRequest.getBaseUri());
BeanWrapper wrapper = BeanWrapper.create(domainObj, conversionService);
Link selfLink = entityLinks.linkForSingleResource(
repoRequest.getPersistentEntity().getType(),
wrapper.getProperty(repoRequest.getPersistentEntity().getIdProperty())
).withSelfRel();
per.add(selfLink);
return per;
return perAssembler.toResource(domainObj);
}
@SuppressWarnings({"unchecked"})
@RequestMapping(
value = BASE_MAPPING + "/{id}",
method = RequestMethod.PUT,
consumes = {
"application/json"
},
produces = {
"application/json",
"text/uri-list"
}
)
/**
* {@code PUT /{repository}/{id}} - Updates an existing entity or creates one at exactly that place.
*
* @param repoRequest
* @param incoming
* @param id
* @return
* @throws ResourceNotFoundException
*/
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT, consumes = { "application/json" },
produces = { "application/json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> updateEntity(RepositoryRestRequest repoRequest,
PersistentEntityResource<?> incoming,
@PathVariable String id) throws ResourceNotFoundException {
PersistentEntityResource<Object> incoming, @PathVariable String id) throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasSaveOne() || !repoMethodInvoker.hasFindOne()) {
throw new NoSuchMethodError();
}
Object domainObj = domainClassConverter.convert(
id,
STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType())
);
Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
if (null == domainObj) {
BeanWrapper incomingWrapper = BeanWrapper.create(incoming.getContent(), conversionService);
PersistentProperty idProp = incoming.getPersistentEntity().getIdProperty();
BeanWrapper<?, Object> incomingWrapper = BeanWrapper.create(incoming.getContent(), conversionService);
PersistentProperty<?> idProp = incoming.getPersistentEntity().getIdProperty();
incomingWrapper.setProperty(idProp, conversionService.convert(id, idProp.getType()));
return createNewEntity(repoRequest, incoming);
}
domainObjectMerger.merge(incoming.getContent(), domainObj);
applicationContext.publishEvent(new BeforeSaveEvent(incoming.getContent()));
publisher.publishEvent(new BeforeSaveEvent(incoming.getContent()));
Object obj = repoMethodInvoker.save(domainObj);
applicationContext.publishEvent(new AfterSaveEvent(obj));
publisher.publishEvent(new AfterSaveEvent(obj));
if (config.isReturnBodyOnUpdate()) {
PersistentEntityResource per = PersistentEntityResource.wrap(repoRequest.getPersistentEntity(),
obj,
repoRequest.getBaseUri());
BeanWrapper wrapper = BeanWrapper.create(obj, conversionService);
Link selfLink = entityLinks.linkForSingleResource(repoRequest.getPersistentEntity().getType(),
wrapper.getProperty(repoRequest.getPersistentEntity()
.getIdProperty()))
.withSelfRel();
per.add(selfLink);
return resourceResponse(null,
per,
HttpStatus.OK);
return ControllerUtils.toResponseEntity(null, perAssembler.toResource(obj), HttpStatus.OK);
} else {
return resourceResponse(null,
null,
HttpStatus.NO_CONTENT);
return ControllerUtils.toResponseEntity(null, null, HttpStatus.NO_CONTENT);
}
}
@SuppressWarnings({"unchecked"})
@RequestMapping(
value = BASE_MAPPING + "/{id}",
method = RequestMethod.DELETE
)
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.DELETE)
@ResponseBody
public ResponseEntity<?> deleteEntity(final RepositoryRestRequest repoRequest,
@PathVariable final String id)
public ResponseEntity<?> deleteEntity(final RepositoryRestRequest repoRequest, @PathVariable final String id)
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || (!repoMethodInvoker.hasFindOne()
&& !(repoMethodInvoker.hasDeleteOne() || repoMethodInvoker.hasDeleteOneById()))) {
if (null == repoMethodInvoker
|| (!repoMethodInvoker.hasFindOne() && !(repoMethodInvoker.hasDeleteOne() || repoMethodInvoker
.hasDeleteOneById()))) {
throw new HttpRequestMethodNotSupportedException("DELETE");
}
ResourceMapping methodMapping = repoRequest.getRepositoryResourceMapping().getResourceMappingFor("delete");
@@ -316,22 +287,20 @@ public class RepositoryEntityController extends AbstractRepositoryRestController
throw new HttpRequestMethodNotSupportedException("DELETE");
}
final Object domainObj = domainClassConverter.convert(id,
STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity()
.getType()));
final Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
if (null == domainObj) {
throw new ResourceNotFoundException();
}
applicationContext.publishEvent(new BeforeDeleteEvent(domainObj));
publisher.publishEvent(new BeforeDeleteEvent(domainObj));
TransactionCallbackWithoutResult callback = new TransactionCallbackWithoutResult() {
@Override
@SuppressWarnings({ "unchecked" })
protected void doInTransactionWithoutResult(TransactionStatus status) {
if (repoMethodInvoker.hasDeleteOneById()) {
Class<? extends Serializable> idType = (Class<? extends Serializable>) repoRequest.getPersistentEntity()
.getIdProperty()
.getType();
.getIdProperty().getType();
final Serializable idVal = conversionService.convert(id, idType);
repoMethodInvoker.delete(idVal);
} else if (repoMethodInvoker.hasDeleteOne()) {
@@ -339,12 +308,16 @@ public class RepositoryEntityController extends AbstractRepositoryRestController
}
}
};
if (null != txTmpl) {
txTmpl.execute(callback);
// FIXME
if (txOperations != null) {
txOperations.execute(callback);
} else {
callback.doInTransaction(null);
}
applicationContext.publishEvent(new AfterDeleteEvent(domainObj));
publisher.publishEvent(new AfterDeleteEvent(domainObj));
return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-2013 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.rest.webmvc;
import static org.springframework.util.ClassUtils.*;
@@ -15,27 +30,31 @@ import org.springframework.web.util.UrlPathHelper;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class RepositoryInformationHandlerMethodArgumentResolver
extends RepositoryInformationSupport
implements HandlerMethodArgumentResolver {
public class RepositoryInformationHandlerMethodArgumentResolver extends RepositoryInformationSupport implements
HandlerMethodArgumentResolver {
@Override public boolean supportsParameter(MethodParameter parameter) {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return isAssignable(parameter.getParameterType(), RepositoryInformation.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
public RepositoryInformation resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String requestUri = new UrlPathHelper().getLookupPathForRequest(request);
if(requestUri.startsWith("/")) {
requestUri = requestUri.substring(1);
}
String[] parts = requestUri.split("/");
if(parts.length == 0) {
// Root request
return null;
@@ -43,6 +62,4 @@ public class RepositoryInformationHandlerMethodArgumentResolver
return findRepositoryInfoFor(parts[0]);
}
}

View File

@@ -1,7 +1,23 @@
/*
* Copyright 2012-2013 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.rest.webmvc;
import static org.springframework.data.rest.core.util.UriUtils.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import java.net.URI;
import java.util.ArrayList;
@@ -10,7 +26,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.convert.ConversionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@@ -26,7 +44,7 @@ import org.springframework.data.rest.repository.context.AfterLinkSaveEvent;
import org.springframework.data.rest.repository.context.BeforeLinkDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.hateoas.EntityLinks;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.http.HttpHeaders;
@@ -43,21 +61,39 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Jon Brisbin
* @author Oliver Gierke
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class RepositoryPropertyReferenceController extends AbstractRepositoryRestController {
@RestController
@SuppressWarnings({"unchecked", "deprecation"})
public class RepositoryPropertyReferenceController extends AbstractRepositoryRestController implements ApplicationEventPublisherAware {
private static final String BASE_MAPPING = "/{repository}/{id}/{property}";
private final Repositories repositories;
private final RepositoryRestConfiguration config;
private final PersistentEntityResourceAssembler<Object> perAssembler;
private final DomainClassConverter<?> converter;
public RepositoryPropertyReferenceController(Repositories repositories,
RepositoryRestConfiguration config,
DomainClassConverter<?> domainClassConverter,
ConversionService conversionService,
EntityLinks entityLinks) {
super(repositories,
config,
domainClassConverter,
conversionService,
entityLinks);
private ApplicationEventPublisher publisher;
@Autowired
public RepositoryPropertyReferenceController(Repositories repositories, RepositoryRestConfiguration config,
DomainClassConverter<?> domainClassConverter, PagedResourcesAssembler<Object> assembler,
PersistentEntityResourceAssembler<Object> perAssembler) {
super(assembler, perAssembler);
this.repositories = repositories;
this.perAssembler = perAssembler;
this.config = config;
this.converter = domainClassConverter;
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@RequestMapping(
@@ -76,51 +112,36 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
final HttpHeaders headers = new HttpHeaders();
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
@Override public Resource<?> apply(ReferencedProperty prop) {
if(null == prop.propertyValue) {
throw new ResourceNotFoundException();
}
if(prop.property.isCollectionLike()) {
List<Resource<?>> resources = new ArrayList<Resource<?>>();
PersistentEntity entity = repositories.getPersistentEntity(prop.propertyType);
for(Object obj : ((Iterable)prop.propertyValue)) {
BeanWrapper wrapper = BeanWrapper.create(obj, conversionService);
PersistentEntityResource per = PersistentEntityResource.wrap(entity, obj, repoRequest.getBaseUri());
Link selfLink = entityLinks.linkForSingleResource(entity.getType(),
wrapper.getProperty(entity.getIdProperty()))
.withSelfRel();
per.add(selfLink);
resources.add(per);
for(Object obj : ((Iterable<Object>) prop.propertyValue)) {
resources.add(perAssembler.toResource(obj));
}
return new Resource<Object>(resources);
} else if(prop.property.isMap()) {
Map<Object, Resource<?>> resources = new HashMap<Object, Resource<?>>();
PersistentEntity entity = repositories.getPersistentEntity(prop.propertyType);
for(Map.Entry<Object, Object> entry : ((Map<Object, Object>)prop.propertyValue).entrySet()) {
PersistentEntityResource per = PersistentEntityResource.wrap(entity,
entry.getValue(),
repoRequest.getBaseUri());
BeanWrapper wrapper = BeanWrapper.create(entry.getValue(), conversionService);
Link selfLink = entityLinks.linkForSingleResource(entity.getType(),
wrapper.getProperty(entity.getIdProperty()))
.withSelfRel();
per.add(selfLink);
resources.put(entry.getKey(), per);
for(Map.Entry<Object, Object> entry : ((Map<Object, Object>) prop.propertyValue).entrySet()) {
resources.put(entry.getKey(), perAssembler.toResource(entry.getValue()));
}
return new Resource<Object>(resources);
} else {
PersistentEntityResource per = PersistentEntityResource.wrap(repositories.getPersistentEntity(prop.propertyType),
prop.propertyValue,
repoRequest.getBaseUri());
BeanWrapper wrapper = BeanWrapper.create(prop.propertyValue, conversionService);
Link selfLink = entityLinks.linkForSingleResource(prop.propertyType,
wrapper.getProperty(prop.entity.getIdProperty()))
.withSelfRel();
per.add(selfLink);
headers.set("Content-Location", selfLink.getHref());
return new Resource<Object>(per);
PersistentEntityResource<Object> resource = perAssembler.toResource(prop.propertyValue);
headers.set("Content-Location", resource.getId().getHref());
return resource;
}
}
};
@@ -128,7 +149,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
id,
property,
handler);
return resourceResponse(headers, responseResource, HttpStatus.OK);
return ControllerUtils.toResponseEntity(headers, responseResource, HttpStatus.OK);
}
@RequestMapping(
@@ -158,9 +179,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
prop.wrapper.setProperty(prop.property, null);
}
applicationContext.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
publisher.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
applicationContext.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
publisher.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
return null;
}
};
@@ -175,7 +196,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
}
return resourceResponse(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT);
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT);
}
@RequestMapping(
@@ -201,31 +222,29 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
throw new ResourceNotFoundException();
}
if(prop.property.isCollectionLike()) {
PersistentEntity entity = repositories.getPersistentEntity(prop.propertyType);
for(Object obj : ((Iterable)prop.propertyValue)) {
BeanWrapper propValWrapper = BeanWrapper.create(obj, conversionService);
for(Object obj : ((Iterable<?>)prop.propertyValue)) {
BeanWrapper<?, Object> propValWrapper = BeanWrapper.create(obj, null);
String sId = propValWrapper.getProperty(prop.entity.getIdProperty()).toString();
if(propertyId.equals(sId)) {
PersistentEntityResource per = PersistentEntityResource.wrap(entity, obj, repoRequest.getBaseUri());
Link selfLink = entityLinks.linkForSingleResource(entity.getType(), sId).withSelfRel();
per.add(selfLink);
headers.set("Content-Location", selfLink.getHref());
return per;
PersistentEntityResource<Object> resource = perAssembler.toResource(obj);
headers.set("Content-Location", resource.getId().getHref());
return resource;
}
}
} else if(prop.property.isMap()) {
PersistentEntity entity = repositories.getPersistentEntity(prop.propertyType);
for(Map.Entry<Object, Object> entry : ((Map<Object, Object>)prop.propertyValue).entrySet()) {
BeanWrapper propValWrapper = BeanWrapper.create(entry.getValue(), conversionService);
BeanWrapper<?, Object> propValWrapper = BeanWrapper.create(entry.getValue(), null);
String sId = propValWrapper.getProperty(prop.entity.getIdProperty()).toString();
if(propertyId.equals(sId)) {
PersistentEntityResource per = PersistentEntityResource.wrap(entity,
entry.getValue(),
repoRequest.getBaseUri());
Link selfLink = entityLinks.linkForSingleResource(entity.getType(), sId).withSelfRel();
per.add(selfLink);
headers.set("Content-Location", selfLink.getHref());
return per;
PersistentEntityResource<Object> resource = perAssembler.toResource(entry.getValue());
headers.set("Content-Location", resource.getId().getHref());
return resource;
}
}
} else {
@@ -234,11 +253,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
throw new IllegalArgumentException(new ResourceNotFoundException());
}
};
Resource<?> responseResource = doWithReferencedProperty(repoRequest,
id,
property,
handler);
return resourceResponse(headers, responseResource, HttpStatus.OK);
Resource<?> responseResource = doWithReferencedProperty(repoRequest, id, property, handler);
return ControllerUtils.toResponseEntity(headers, responseResource, HttpStatus.OK);
}
@RequestMapping(
@@ -263,7 +280,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
ResourceMapping entityMapping = repoRequest.getPersistentEntityResourceMapping();
String propName = entityMapping.getNameForPath(property);
ResourceMapping propMapping = entityMapping.getResourceMappingFor(entityMapping.getNameForPath(property));
PersistentProperty persistentProp = repoRequest.getPersistentEntity().getPersistentProperty(propName);
PersistentProperty<?> persistentProp = repoRequest.getPersistentEntity().getPersistentProperty(propName);
Class<?> propType = (persistentProp.isCollectionLike() || persistentProp.isMap()
? persistentProp.getComponentType()
: persistentProp.getType());
@@ -290,15 +307,14 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
} else if(resource.getContent() instanceof Map) {
for(Map.Entry<Object, Resource<?>> entry : ((Map<Object, Resource<?>>)resource.getContent()).entrySet()) {
Link l = new Link(entry.getValue().getLink("self").getHref(), conversionService.convert(entry.getKey(),
String.class));
Link l = new Link(entry.getValue().getLink("self").getHref(), entry.getKey().toString());
links.add(l);
}
} else {
links.add(new Link(entityBaseUri.toString(), propRel));
}
return resourceResponse(null, new Resource<Object>(EMPTY_RESOURCE_LIST, links), HttpStatus.OK);
return ControllerUtils.toResponseEntity(null, new Resource<Object>(EMPTY_RESOURCE_LIST, links), HttpStatus.OK);
}
@RequestMapping(
@@ -326,9 +342,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
@Override public Resource<?> apply(ReferencedProperty prop) {
if(prop.property.isCollectionLike()) {
Collection coll = new ArrayList();
Collection<Object> coll = new ArrayList<Object>();
if("POST".equals(repoRequest.getRequest().getMethod())) {
coll.addAll((Collection)prop.propertyValue);
coll.addAll((Collection<Object>)prop.propertyValue);
}
for(Link l : incoming.getLinks()) {
Object propVal = loadPropertyValue(prop.propertyType, l.getHref());
@@ -336,9 +352,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
prop.wrapper.setProperty(prop.property, coll);
} else if(prop.property.isMap()) {
Map m = new HashMap();
Map<String, Object> m = new HashMap<String, Object>();
if("POST".equals(repoRequest.getRequest().getMethod())) {
m.putAll((Map)prop.propertyValue);
m.putAll((Map<String, Object>)prop.propertyValue);
}
for(Link l : incoming.getLinks()) {
Object propVal = loadPropertyValue(prop.propertyType, l.getHref());
@@ -358,9 +374,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
prop.wrapper.setProperty(prop.property, propVal);
}
applicationContext.publishEvent(new BeforeLinkSaveEvent(prop.wrapper.getBean(), prop.propertyValue));
publisher.publishEvent(new BeforeLinkSaveEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
applicationContext.publishEvent(new AfterLinkSaveEvent(result, prop.propertyValue));
publisher.publishEvent(new AfterLinkSaveEvent(result, prop.propertyValue));
return null;
}
};
@@ -368,7 +384,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
id,
property,
handler);
return resourceResponse(null, EMPTY_RESOURCE, HttpStatus.CREATED);
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.CREATED);
}
@RequestMapping(
@@ -392,20 +408,20 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
return null;
}
if(prop.property.isCollectionLike()) {
Collection coll = new ArrayList();
for(Object obj : (Collection)prop.propertyValue) {
BeanWrapper propValWrapper = BeanWrapper.create(obj, conversionService);
String s = (String)propValWrapper.getProperty(prop.entity.getIdProperty(), String.class, false);
Collection<Object> coll = new ArrayList<Object>();
for(Object obj : (Collection<Object>) prop.propertyValue) {
BeanWrapper<?, Object> propValWrapper = BeanWrapper.create(obj, null);
String s = propValWrapper.getProperty(prop.entity.getIdProperty()).toString();
if(!propertyId.equals(s)) {
coll.add(obj);
}
}
prop.wrapper.setProperty(prop.property, coll);
} else if(prop.property.isMap()) {
Map m = new HashMap();
Map<Object, Object> m = new HashMap<Object, Object>();
for(Map.Entry<Object, Object> entry : ((Map<Object, Object>)prop.propertyValue).entrySet()) {
BeanWrapper propValWrapper = BeanWrapper.create(entry.getValue(), conversionService);
String s = (String)propValWrapper.getProperty(prop.entity.getIdProperty(), String.class, false);
BeanWrapper<?, Object> propValWrapper = BeanWrapper.create(entry.getValue(), null);
String s = propValWrapper.getProperty(prop.entity.getIdProperty()).toString();
if(!propertyId.equals(s)) {
m.put(entry.getKey(), entry.getValue());
}
@@ -415,9 +431,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
prop.wrapper.setProperty(prop.property, null);
}
applicationContext.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
publisher.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
applicationContext.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
publisher.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
return null;
}
};
@@ -426,7 +442,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
property,
handler);
return resourceResponse(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT);
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT);
}
private Link propertyReferenceLink(Resource<?> resource,
@@ -439,7 +455,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
private Object loadPropertyValue(Class<?> type, String href) {
String id = href.substring(href.lastIndexOf('/') + 1);
return domainClassConverter.convert(id,
return converter.convert(id,
STRING_TYPE,
TypeDescriptor.valueOf(type));
}
@@ -454,21 +470,20 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
throw new NoSuchMethodException();
}
Object domainObj = domainClassConverter.convert(id,
STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity()
.getType()));
Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
if(null == domainObj) {
throw new ResourceNotFoundException();
}
String propertyName = repoRequest.getPersistentEntityResourceMapping().getNameForPath(propertyPath);
PersistentProperty prop = repoRequest.getPersistentEntity().getPersistentProperty(propertyName);
PersistentProperty<?> prop = repoRequest.getPersistentEntity().getPersistentProperty(propertyName);
if(null == prop) {
throw new ResourceNotFoundException();
}
BeanWrapper wrapper = BeanWrapper.create(domainObj, conversionService);
BeanWrapper<?, Object> wrapper = BeanWrapper.create(domainObj, null);
Object propVal = wrapper.getProperty(prop);
return handler.apply(new ReferencedProperty(prop,
@@ -477,15 +492,17 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
private class ReferencedProperty {
final PersistentEntity entity;
final PersistentProperty property;
final Class<?> propertyType;
final Object propertyValue;
final BeanWrapper wrapper;
final PersistentEntity<?, ?> entity;
final PersistentProperty<?> property;
final Class<?> propertyType;
final Object propertyValue;
final BeanWrapper<?, ?>wrapper;
private ReferencedProperty(PersistentProperty property,
private ReferencedProperty(PersistentProperty<?> property,
Object propertyValue,
BeanWrapper wrapper) {
BeanWrapper<?, ?> wrapper) {
this.property = property;
this.propertyValue = propertyValue;
this.wrapper = wrapper;

View File

@@ -1,19 +1,19 @@
package org.springframework.data.rest.webmvc;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import static org.springframework.util.StringUtils.*;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.support.JpaHelper;
import org.springframework.http.MediaType;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
@@ -36,9 +36,12 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
private RepositoryRestConfiguration config;
@Autowired(required = false)
private JpaHelper jpaHelper;
private final ResourceMappings mappings;
public RepositoryRestHandlerMapping() {
public RepositoryRestHandlerMapping(ResourceMappings mappings) {
setOrder(Ordered.LOWEST_PRECEDENCE);
this.mappings = mappings;
}
@Override
@@ -81,11 +84,9 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
// Root request
return super.lookupHandlerMethod(lookupPath, request);
}
for(Class<?> domainType : repositories) {
RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(domainType);
ResourceMapping mapping = getResourceMapping(config, repoInfo);
if(mapping.getPath().equals(parts[0]) && mapping.isExported()) {
if (mappings.exportsMappingFor(domainType)) {
return super.lookupHandlerMethod(lookupPath, request);
}
}
@@ -94,10 +95,7 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
}
@Override protected boolean isHandler(Class<?> beanType) {
return (RepositoryController.class.isAssignableFrom(beanType)
|| RepositoryEntityController.class.isAssignableFrom(beanType)
|| RepositoryPropertyReferenceController.class.isAssignableFrom(beanType)
|| RepositorySearchController.class.isAssignableFrom(beanType));
return AnnotationUtils.findAnnotation(beanType, RestController.class) != null;
}
@Override protected void extendInterceptors(List<Object> interceptors) {

View File

@@ -1,34 +1,44 @@
/*
* Copyright 2012-2013 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.rest.webmvc;
import static org.springframework.data.rest.core.util.UriUtils.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import java.net.URI;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.data.domain.Page;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.hateoas.Link;
import org.springframework.web.util.UriComponentsBuilder;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@SuppressWarnings("deprecation")
class RepositoryRestRequest {
private final RepositoryRestConfiguration config;
private final HttpServletRequest request;
private final PagingAndSorting pagingAndSorting;
private final URI baseUri;
private final RepositoryInformation repoInfo;
private final ResourceMapping repoMapping;
private final Link repoLink;
private final Object repository;
@@ -39,14 +49,11 @@ class RepositoryRestRequest {
public RepositoryRestRequest(RepositoryRestConfiguration config,
Repositories repositories,
HttpServletRequest request,
PagingAndSorting pagingAndSorting,
URI baseUri,
RepositoryInformation repoInfo) {
this.config = config;
RepositoryInformation repoInfo,
ConversionService conversionService) {
this.request = request;
this.pagingAndSorting = pagingAndSorting;
this.baseUri = baseUri;
this.repoInfo = repoInfo;
this.repoMapping = getResourceMapping(config, repoInfo);
if(null == repoMapping || !repoMapping.isExported()) {
this.repoLink = null;
@@ -58,7 +65,7 @@ class RepositoryRestRequest {
this.repoLink = new Link(buildUri(baseUri, repoMapping.getPath()).toString(), repoMapping.getRel());
this.repository = repositories.getRepositoryFor(repoInfo.getDomainType());
this.persistentEntity = repositories.getPersistentEntity(repoInfo.getDomainType());
this.repoMethodInvoker = new RepositoryMethodInvoker(repository, repoInfo);
this.repoMethodInvoker = new RepositoryMethodInvoker(repository, repoInfo, conversionService);
this.entityMapping = getResourceMapping(config, persistentEntity);
}
}
@@ -67,18 +74,10 @@ class RepositoryRestRequest {
return request;
}
PagingAndSorting getPagingAndSorting() {
return pagingAndSorting;
}
URI getBaseUri() {
return baseUri;
}
RepositoryInformation getRepositoryInformation() {
return repoInfo;
}
ResourceMapping getRepositoryResourceMapping() {
return repoMapping;
}
@@ -87,10 +86,6 @@ class RepositoryRestRequest {
return repoLink;
}
Object getRepository() {
return repository;
}
RepositoryMethodInvoker getRepositoryMethodInvoker() {
return repoMethodInvoker;
}
@@ -102,40 +97,4 @@ class RepositoryRestRequest {
ResourceMapping getPersistentEntityResourceMapping() {
return entityMapping;
}
void addNextLink(Page<?> page, List<Link> links) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(baseUri);
// Add existing query parameters
addQueryParameters(request, builder);
builder.queryParam(config.getPageParamName(), page.getNumber() + 1)
.queryParam(config.getLimitParamName(), pagingAndSorting.getPageSize());
links.add(new Link(builder.build().toString(), "page.next"));
}
void addPrevLink(Page<?> page, List<Link> links) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(baseUri);
// Add existing query parameters
addQueryParameters(request, builder);
builder.queryParam(config.getPageParamName(), page.getNumber() - 1)
.queryParam(config.getLimitParamName(), pagingAndSorting.getPageSize());
links.add(new Link(builder.build().toString(), "page.previous"));
}
private void addQueryParameters(HttpServletRequest request,
UriComponentsBuilder builder) {
for(Enumeration<String> names = request.getParameterNames(); names.hasMoreElements(); ) {
String name = names.nextElement();
String value = request.getParameter(name);
if(name.equals(config.getPageParamName()) || name.equals(config.getLimitParamName())) {
continue;
}
builder.queryParam(name, value);
}
}
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-2013 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.rest.webmvc;
import java.net.URI;
@@ -5,10 +20,10 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -16,9 +31,12 @@ import org.springframework.web.method.support.ModelAndViewContainer;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class RepositoryRestRequestHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final ConversionService conversionService;
@Autowired
private RepositoryRestConfiguration config;
@Autowired
@@ -26,11 +44,14 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
@Autowired
private RepositoryInformationHandlerMethodArgumentResolver repoInfoResolver;
@Autowired
private PagingAndSortingMethodArgumentResolver pagingAndSortingResolver;
@Autowired
private BaseUriMethodArgumentResolver baseUriResolver;
public RepositoryRestRequestHandlerMethodArgumentResolver(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override public boolean supportsParameter(MethodParameter parameter) {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return RepositoryRestRequest.class.isAssignableFrom(parameter.getParameterType());
}
@@ -39,25 +60,11 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
PagingAndSorting pagingAndSorting = (PagingAndSorting)pagingAndSortingResolver.resolveArgument(parameter,
mavContainer,
webRequest,
binderFactory);
RepositoryInformation repoInfo = (RepositoryInformation)repoInfoResolver.resolveArgument(parameter,
mavContainer,
webRequest,
binderFactory);
URI baseUri = (URI)baseUriResolver.resolveArgument(parameter,
mavContainer,
webRequest,
binderFactory);
URI baseUri = (URI) baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
RepositoryInformation repoInfo = repoInfoResolver.resolveArgument(parameter, mavContainer, webRequest,
binderFactory);
return new RepositoryRestRequest(config,
repositories,
webRequest.getNativeRequest(HttpServletRequest.class),
pagingAndSorting,
baseUri,
repoInfo);
return new RepositoryRestRequest(config, repositories, webRequest.getNativeRequest(HttpServletRequest.class), baseUri, repoInfo, conversionService);
}
}

View File

@@ -1,26 +1,45 @@
/*
* Copyright 2013 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.rest.webmvc;
import static org.springframework.data.rest.core.util.UriUtils.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.rest.repository.invoke.RepositoryMethod;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.hateoas.EntityLinks;
import org.springframework.data.rest.repository.support.ResourceMappingUtils;
import org.springframework.data.rest.webmvc.support.BaseUriLinkBuilder;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
@@ -31,21 +50,27 @@ import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class RepositorySearchController extends AbstractRepositoryRestController {
@RestController
@SuppressWarnings("deprecation")
class RepositorySearchController extends AbstractRepositoryRestController {
private static final String BASE_MAPPING = "/{repository}/search";
private final Repositories repositories;
private final RepositoryRestConfiguration config;
@Autowired
public RepositorySearchController(Repositories repositories,
RepositoryRestConfiguration config,
DomainClassConverter<?> domainClassConverter,
ConversionService conversionService,
EntityLinks entityLinks) {
super(repositories,
config,
domainClassConverter,
conversionService,
entityLinks);
PagedResourcesAssembler<Object> assembler,
PersistentEntityResourceAssembler<Object> perAssembler) {
super(assembler, perAssembler);
this.repositories = repositories;
this.config = config;
}
@RequestMapping(
@@ -66,6 +91,24 @@ public class RepositorySearchController extends AbstractRepositoryRestController
}
return new Resource<Object>(Collections.emptyList(), links);
}
protected List<Link> queryMethodLinks(URI baseUri, Class<?> domainType) {
List<Link> links = new ArrayList<Link>();
RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(domainType);
ResourceMapping repoMapping = ResourceMappingUtils.merge(repoInfo.getRepositoryInterface(),
config.getResourceMappingForRepository(repoInfo.getRepositoryInterface()));
for (Method method : repoInfo.getQueryMethods()) {
LinkBuilder linkBuilder = BaseUriLinkBuilder.create(buildUri(baseUri, repoMapping.getPath(), "search"));
ResourceMapping methodMapping = ResourceMappingUtils.merge(method,
repoMapping.getResourceMappingFor(method.getName()));
if (!methodMapping.isExported()) {
continue;
}
links
.add(linkBuilder.slash(methodMapping.getPath()).withRel(repoMapping.getRel() + "." + methodMapping.getRel()));
}
return links;
}
@RequestMapping(
value = BASE_MAPPING + "/{method}",
@@ -78,7 +121,7 @@ public class RepositorySearchController extends AbstractRepositoryRestController
@ResponseBody
public ResourceSupport query(final RepositoryRestRequest repoRequest,
@PathVariable String repository,
@PathVariable String method)
@PathVariable String method, Pageable pageable)
throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if(repoMethodInvoker.getQueryMethods().isEmpty()) {
@@ -100,50 +143,15 @@ public class RepositorySearchController extends AbstractRepositoryRestController
throw new ResourceNotFoundException();
}
}
Map<String, String[]> rawParameters = repoRequest.getRequest().getParameterMap();
Object result = repoMethodInvoker.invokeQueryMethod(repoMethod, pageable, rawParameters);
Link baseLink = linkTo(methodOn(RepositorySearchController.class). //
queryCompact(repoRequest, repository, methodName, pageable)).withSelfRel();
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
List<MethodParameter> methodParams = repoMethod.getParameters();
Object[] paramValues = new Object[methodParams.size()];
if(!methodParams.isEmpty()) {
for(int i = 0; i < paramValues.length; i++) {
MethodParameter param = methodParams.get(i);
if(Pageable.class.isAssignableFrom(param.getParameterType())) {
paramValues[i] = new PageRequest(pageSort.getPageNumber(),
pageSort.getPageSize(),
pageSort.getSort());
} else if(Sort.class.isAssignableFrom(param.getParameterType())) {
paramValues[i] = pageSort.getSort();
} else {
String paramName = repoMethod.getParameterNames().get(i);
String[] queryParamVals = repoRequest.getRequest().getParameterValues(paramName);
if(null == queryParamVals) {
if(paramName.startsWith("arg")) {
throw new IllegalArgumentException("No @Param annotation found on query method "
+ repoMethod.getMethod().getName()
+ " for parameter " + param.getParameterName());
} else {
throw new IllegalArgumentException("No query parameter specified for "
+ repoMethod.getMethod().getName() + " param '"
+ paramName + "'");
}
}
paramValues[i] = methodParameterConversionService.convert(queryParamVals, param);
}
}
}
Object result = repoMethodInvoker.invokeQueryMethod(repoMethod, paramValues);
Link prevLink = null;
Link nextLink = null;
if(result instanceof Page) {
if(((Page<?>)result).hasPreviousPage() && pageSort.getPageNumber() > 0) {
prevLink = searchLink(repoRequest, 0, method, "page.previous");
}
if(((Page<?>)result).hasNextPage()) {
nextLink = searchLink(repoRequest, 1, method, "page.next");
}
}
return resultToResources(repoRequest, result, new ArrayList<Link>(), prevLink, nextLink);
return resultToResources(result, baseLink);
}
@RequestMapping(
@@ -156,11 +164,12 @@ public class RepositorySearchController extends AbstractRepositoryRestController
@ResponseBody
public ResourceSupport queryCompact(RepositoryRestRequest repoRequest,
@PathVariable String repository,
@PathVariable String method)
@PathVariable String method,
Pageable pageable)
throws ResourceNotFoundException {
List<Link> links = new ArrayList<Link>();
ResourceSupport resource = query(repoRequest, repository, method);
ResourceSupport resource = query(repoRequest, repository, method, pageable);
links.addAll(resource.getLinks());
if(resource instanceof Resources && ((Resources<?>) resource).getContent() != null) {

View File

@@ -24,6 +24,7 @@ import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceProcessor;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.mvc.HeaderLinksResponseEntity;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
@@ -58,6 +59,7 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler
private final HandlerMethodReturnValueHandler delegate;
private final List<ProcessorWrapper> processors;
private boolean rootLinksAsHeaders = false;
/**
* Creates a new {@link ResourceProcessorHandlerMethodReturnValueHandler} using the given delegate to eventually
@@ -95,6 +97,13 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler
Collections.sort(this.processors, AnnotationAwareOrderComparator.INSTANCE);
}
/**
* @param rootLinksAsHeaders the rootLinksAsHeaders to set
*/
public void setRootLinksAsHeaders(boolean rootLinksAsHeaders) {
this.rootLinksAsHeaders = rootLinksAsHeaders;
}
/*
* (non-Javadoc)
@@ -159,14 +168,14 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler
ReflectionUtils.setField(CONTENT_FIELD, resources, result);
}
Object result = invokeProcessorsFor(value, targetType);
ResourceSupport result = (ResourceSupport) invokeProcessorsFor(value, targetType);
delegate.handleReturnValue(rewrapResult(result, returnValue), returnType, mavContainer, webRequest);
}
/**
* Invokes all registered {@link ResourceProcessor}s registered for the given {@link TypeInformation}.
*
* @param value the object to process
* @param value the object to process
* @param targetType
* @return
*/
@@ -192,19 +201,28 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler
* @param originalValue the original input value.
* @return
*/
static Object rewrapResult(Object newBody, Object originalValue) {
Object rewrapResult(ResourceSupport newBody, Object originalValue) {
if (!(originalValue instanceof HttpEntity)) {
return newBody;
}
HttpEntity<ResourceSupport> entity = null;
if (originalValue instanceof ResponseEntity) {
ResponseEntity<?> source = (ResponseEntity<?>) originalValue;
return new ResponseEntity<Object>(newBody, source.getHeaders(), source.getStatusCode());
entity = new ResponseEntity<ResourceSupport>(newBody, source.getHeaders(), source.getStatusCode());
} else {
HttpEntity<?> source = (HttpEntity<?>) originalValue;
return new HttpEntity<Object>(newBody, source.getHeaders());
entity = new HttpEntity<ResourceSupport>(newBody, source.getHeaders());
}
return addLinksToHeaderWrapper(entity);
}
private HttpEntity<?> addLinksToHeaderWrapper(HttpEntity<ResourceSupport> entity) {
return rootLinksAsHeaders ? HeaderLinksResponseEntity.wrap(entity) : entity;
}
/**

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013 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.rest.webmvc;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* @author Oliver Gierke
*/
@Documented
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
public @interface RestController {
}

View File

@@ -1,47 +1,65 @@
/*
* Copyright 2012-2013 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.rest.webmvc.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.convert.ISO8601DateConverter;
import org.springframework.data.rest.convert.UUIDConverter;
import org.springframework.data.rest.repository.UriDomainClassConverter;
import org.springframework.data.rest.repository.context.AnnotatedHandlerBeanPostProcessor;
import org.springframework.data.rest.repository.context.RepositoriesFactoryBean;
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener;
import org.springframework.data.rest.repository.json.Jackson2DatatypeHelper;
import org.springframework.data.rest.repository.json.PersistentEntityJackson2Module;
import org.springframework.data.rest.repository.json.PersistentEntityToJsonSchemaConverter;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.data.rest.webmvc.BaseUriMethodArgumentResolver;
import org.springframework.data.rest.webmvc.PagingAndSortingMethodArgumentResolver;
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler;
import org.springframework.data.rest.webmvc.PersistentEntityResourceHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.RepositoryController;
import org.springframework.data.rest.webmvc.RepositoryEntityController;
import org.springframework.data.rest.webmvc.RepositoryInformationHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.RepositoryPropertyReferenceController;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
import org.springframework.data.rest.webmvc.RepositoryRestRequestHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.RepositorySearchController;
import org.springframework.data.rest.webmvc.RestController;
import org.springframework.data.rest.webmvc.ServerHttpRequestMethodArgumentResolver;
import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter;
import org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module;
import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter;
import org.springframework.data.rest.webmvc.support.JpaHelper;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.data.rest.webmvc.support.ValidationExceptionHandler;
import org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.RelProvider;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@@ -51,33 +69,41 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* Main application configuration for Spring Data REST. To customize how the exporter works, subclass this and override
* any of the {@literal configure*} methods.
* <p/>
* Any XML files located in the classpath under the {@literal META-INF/spring-data-rest/} path will be automatically
* found and loaded into this {@link org.springframework.context.ApplicationContext}.
*
*
* @author Jon Brisbin
* @author Oliver Gierke
*/
@Configuration
@ComponentScan(basePackageClasses = RestController.class, includeFilters = @Filter(RestController.class), useDefaultFilters = false)
@ImportResource("classpath*:META-INF/spring-data-rest/**/*.xml")
public class RepositoryRestMvcConfiguration {
public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebConfiguration {
private static final boolean IS_JAVAX_VALIDATION_AVAILABLE = ClassUtils.isPresent(
"javax.validation.ConstraintViolationException",
RepositoryRestMvcConfiguration.class.getClassLoader()
);
private static final boolean IS_JPA_AVAILABLE = ClassUtils.isPresent(
"javax.persistence.EntityManager",
RepositoryRestMvcConfiguration.class.getClassLoader()
);
"javax.validation.ConstraintViolationException", RepositoryRestMvcConfiguration.class.getClassLoader());
private static final boolean IS_JPA_AVAILABLE = ClassUtils.isPresent("javax.persistence.EntityManager",
RepositoryRestMvcConfiguration.class.getClassLoader());
@Bean public RepositoriesFactoryBean repositories() {
return new RepositoriesFactoryBean();
@Autowired ListableBeanFactory beanFactory;
@Bean
public Repositories repositories() {
return new Repositories(beanFactory);
}
@Bean public DefaultFormattingConversionService defaultConversionService() {
@Bean
@Qualifier
public DefaultFormattingConversionService defaultConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(UUIDConverter.INSTANCE);
conversionService.addConverter(ISO8601DateConverter.INSTANCE);
@@ -85,34 +111,41 @@ public class RepositoryRestMvcConfiguration {
return conversionService;
}
@Bean public DomainClassConverter<?> domainClassConverter() {
@Bean
public DomainClassConverter<?> domainClassConverter() {
return new DomainClassConverter<DefaultFormattingConversionService>(defaultConversionService());
}
@Bean public UriDomainClassConverter uriDomainClassConverter() {
@Bean
public UriDomainClassConverter uriDomainClassConverter() {
return new UriDomainClassConverter();
}
/**
* {@link org.springframework.context.ApplicationListener} implementation for invoking {@link
* org.springframework.validation.Validator} instances assigned to specific domain types.
* {@link org.springframework.context.ApplicationListener} implementation for invoking
* {@link org.springframework.validation.Validator} instances assigned to specific domain types.
*/
@Bean public ValidatingRepositoryEventListener validatingRepositoryEventListener() {
@Bean
public ValidatingRepositoryEventListener validatingRepositoryEventListener() {
ValidatingRepositoryEventListener listener = new ValidatingRepositoryEventListener();
configureValidatingRepositoryEventListener(listener);
return listener;
}
@Bean @Lazy public ValidationExceptionHandler validationExceptionHandler() {
if(IS_JAVAX_VALIDATION_AVAILABLE) {
@Bean
@Lazy
public ValidationExceptionHandler validationExceptionHandler() {
if (IS_JAVAX_VALIDATION_AVAILABLE) {
return new ValidationExceptionHandler();
} else {
return null;
}
}
@Bean @Lazy public JpaHelper jpaHelper() {
if(IS_JPA_AVAILABLE) {
@Bean
@Lazy
public JpaHelper jpaHelper() {
if (IS_JPA_AVAILABLE) {
return new JpaHelper();
} else {
return null;
@@ -122,167 +155,95 @@ public class RepositoryRestMvcConfiguration {
/**
* Main configuration for the REST exporter.
*/
@Bean public RepositoryRestConfiguration config() {
@Bean
public RepositoryRestConfiguration config() {
RepositoryRestConfiguration config = new RepositoryRestConfiguration();
configureRepositoryRestConfiguration(config);
return config;
}
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} to turn beans annotated as {@link
* org.springframework.data.rest.repository.annotation.RepositoryEventHandler}s.
*
* {@link org.springframework.beans.factory.config.BeanPostProcessor} to turn beans annotated as
* {@link org.springframework.data.rest.repository.annotation.RepositoryEventHandler}s.
*
* @return
*/
@Bean public AnnotatedHandlerBeanPostProcessor annotatedHandlerBeanPostProcessor() {
@Bean
public AnnotatedHandlerBeanPostProcessor annotatedHandlerBeanPostProcessor() {
return new AnnotatedHandlerBeanPostProcessor();
}
/**
* For merging incoming objects materialized from JSON with existing domain objects loaded from the repository.
*
*
* @return
*
* @throws Exception
*/
@Bean public DomainObjectMerger domainObjectMerger() throws Exception {
return new DomainObjectMerger(
repositories().getObject(),
defaultConversionService()
);
}
/**
* The controller that handles top-level requests for listing what repositories are available.
*
* @return
*
* @throws Exception
*/
@Bean public RepositoryController repositoryController() throws Exception {
return new RepositoryController(
repositories().getObject(),
config(),
domainClassConverter(),
defaultConversionService(),
entityLinks()
);
}
/**
* The controller responsible for handling requests to display or those that modify an entity.
*
* @return
*
* @throws Exception
*/
@Bean public RepositoryEntityController repositoryEntityController() throws Exception {
return new RepositoryEntityController(
repositories().getObject(),
config(),
domainClassConverter(),
defaultConversionService(),
entityLinks()
);
}
/**
* The controller responsible for managing links of property references.
*
* @return
*
* @throws Exception
*/
@Bean public RepositoryPropertyReferenceController propertyReferenceController() throws Exception {
return new RepositoryPropertyReferenceController(
repositories().getObject(),
config(),
domainClassConverter(),
defaultConversionService(),
entityLinks()
);
}
/**
* The controller responsible for performing searches.
*
* @return
*
* @throws Exception
*/
@Bean public RepositorySearchController repositorySearchController() throws Exception {
return new RepositorySearchController(
repositories().getObject(),
config(),
domainClassConverter(),
defaultConversionService(),
entityLinks()
);
@Bean
public DomainObjectMerger domainObjectMerger() throws Exception {
return new DomainObjectMerger(repositories(), defaultConversionService());
}
/**
* Resolves the base {@link java.net.URI} under which this application is configured.
*
*
* @return
*/
@Bean public BaseUriMethodArgumentResolver baseUriMethodArgumentResolver() {
return new BaseUriMethodArgumentResolver();
@Bean
public BaseUriMethodArgumentResolver baseUriMethodArgumentResolver() {
return new BaseUriMethodArgumentResolver(config());
}
/**
* Resolves the {@link org.springframework.data.repository.core.RepositoryInformation} for this request.
*
*
* @return
*/
@Bean public RepositoryInformationHandlerMethodArgumentResolver repoInfoMethodArgumentResolver() {
@Bean
public RepositoryInformationHandlerMethodArgumentResolver repoInfoMethodArgumentResolver() {
return new RepositoryInformationHandlerMethodArgumentResolver();
}
/**
* Resolves the paging and sorting information from the query parameters based on the current configuration settings.
*
* Turns an {@link javax.servlet.http.HttpServletRequest} into a
* {@link org.springframework.http.server.ServerHttpRequest}.
*
* @return
*/
@Bean public PagingAndSortingMethodArgumentResolver pagingAndSortingMethodArgumentResolver() {
return new PagingAndSortingMethodArgumentResolver();
}
/**
* Turns an {@link javax.servlet.http.HttpServletRequest} into a {@link org.springframework.http.server.ServerHttpRequest}.
*
* @return
*/
@Bean public ServerHttpRequestMethodArgumentResolver serverHttpRequestMethodArgumentResolver() {
@Bean
public ServerHttpRequestMethodArgumentResolver serverHttpRequestMethodArgumentResolver() {
return new ServerHttpRequestMethodArgumentResolver();
}
/**
* A convenience resolver that pulls together all the information needed to service a request.
*
*
* @return
*/
@Bean public RepositoryRestRequestHandlerMethodArgumentResolver repoRequestArgumentResolver() {
return new RepositoryRestRequestHandlerMethodArgumentResolver();
@Bean
public RepositoryRestRequestHandlerMethodArgumentResolver repoRequestArgumentResolver() {
return new RepositoryRestRequestHandlerMethodArgumentResolver(defaultConversionService());
}
/**
* A special {@link org.springframework.hateoas.EntityLinks} implementation that takes repository and current
* configuration into account when generating links.
*
*
* @return
*
* @throws Exception
*/
@Bean public EntityLinks entityLinks() throws Exception {
return new RepositoryEntityLinks(repositories().getObject(), config());
@Bean
public EntityLinks entityLinks() {
return new RepositoryEntityLinks(repositories(), resourceMappings(), config());
}
/**
* Reads incoming JSON into an entity.
*
*
* @return
*/
@Bean public PersistentEntityResourceHandlerMethodArgumentResolver persistentEntityArgumentResolver() {
@Bean
public PersistentEntityResourceHandlerMethodArgumentResolver persistentEntityArgumentResolver() {
List<HttpMessageConverter<?>> messageConverters = defaultMessageConverters();
configureHttpMessageConverters(messageConverters);
@@ -290,20 +251,22 @@ public class RepositoryRestMvcConfiguration {
}
/**
* Turns a domain class into a {@link org.springframework.data.rest.repository.json.JsonSchema}.
*
* Turns a domain class into a {@link org.springframework.data.rest.webmvc.json.JsonSchema}.
*
* @return
*/
@Bean public PersistentEntityToJsonSchemaConverter jsonSchemaConverter() {
@Bean
public PersistentEntityToJsonSchemaConverter jsonSchemaConverter() {
return new PersistentEntityToJsonSchemaConverter();
}
/**
* The Jackson {@link ObjectMapper} used internally.
*
*
* @return
*/
@Bean public ObjectMapper objectMapper() {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// Our special PersistentEntityResource Module
@@ -317,37 +280,43 @@ public class RepositoryRestMvcConfiguration {
/**
* The {@link HttpMessageConverter} used by Spring MVC to read and write JSON data.
*
*
* @return
*/
@Bean public MappingJackson2HttpMessageConverter jacksonHttpMessageConverter() {
@Bean
public MappingJackson2HttpMessageConverter jacksonHttpMessageConverter() {
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
jacksonConverter.setObjectMapper(objectMapper());
jacksonConverter.setSupportedMediaTypes(Arrays.asList(
MediaType.APPLICATION_JSON,
MediaType.valueOf("application/schema+json"),
MediaType.valueOf("application/x-spring-data-verbose+json"),
MediaType.valueOf("application/x-spring-data-compact+json")
));
jacksonConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,
MediaType.valueOf("application/schema+json"), MediaType.valueOf("application/x-spring-data-verbose+json"),
MediaType.valueOf("application/x-spring-data-compact+json")));
return jacksonConverter;
}
/**
* The {@link HttpMessageConverter} used to create {@literal text/uri-list} responses.
*
*
* @return
*/
@Bean public UriListHttpMessageConverter uriListHttpMessageConverter() {
@Bean
public UriListHttpMessageConverter uriListHttpMessageConverter() {
return new UriListHttpMessageConverter();
}
@Bean
public PersistentEntityResourceAssembler<Object> persistentEntityResourceAssembler() {
return new PersistentEntityResourceAssembler<Object>(repositories(), entityLinks());
}
/**
* Special {@link org.springframework.web.servlet.HandlerAdapter} that only recognizes handler methods defined in
* the provided controller classes.
*
* Special {@link org.springframework.web.servlet.HandlerAdapter} that only recognizes handler methods defined in the
* provided controller classes.
*
* @return
*/
@Bean public RequestMappingHandlerAdapter repositoryExporterHandlerAdapter() {
@Bean
public RequestMappingHandlerAdapter repositoryExporterHandlerAdapter() {
List<HttpMessageConverter<?>> messageConverters = defaultMessageConverters();
configureHttpMessageConverters(messageConverters);
@@ -359,30 +328,47 @@ public class RepositoryRestMvcConfiguration {
}
/**
* Special {@link org.springframework.web.servlet.HandlerMapping} that only recognizes handler methods defined in
* the provided controller classes.
*
* Special {@link org.springframework.web.servlet.HandlerMapping} that only recognizes handler methods defined in the
* provided controller classes.
*
* @return
*/
@Bean public RequestMappingHandlerMapping repositoryExporterHandlerMapping() {
return new RepositoryRestHandlerMapping();
@Bean
public RequestMappingHandlerMapping repositoryExporterHandlerMapping() {
return new RepositoryRestHandlerMapping(resourceMappings());
}
@Bean
public ResourceMappings resourceMappings() {
Repositories repositories = repositories();
RepositoryRestConfiguration config = config();
try {
RelProvider relProvider = beanFactory.getBean(RelProvider.class);
return new ResourceMappings(config, repositories, relProvider);
} catch (NoSuchBeanDefinitionException e) {
return new ResourceMappings(config, repositories);
}
}
/**
* Jackson module responsible for intelligently serializing and deserializing JSON that corresponds to an entity.
*
*
* @return
*/
@Bean public Module persistentEntityJackson2Module() {
return new PersistentEntityJackson2Module(defaultConversionService());
@Bean
public Module persistentEntityJackson2Module() {
return new PersistentEntityJackson2Module(resourceMappings());
}
/**
* Bean for looking up methods annotated with {@link org.springframework.web.bind.annotation.ExceptionHandler}.
*
*
* @return
*/
@Bean public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
@Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
ExceptionHandlerExceptionResolver er = new ExceptionHandlerExceptionResolver();
er.setCustomArgumentResolvers(defaultMethodArgumentResolvers());
@@ -403,67 +389,51 @@ public class RepositoryRestMvcConfiguration {
}
private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
return Arrays.asList(baseUriMethodArgumentResolver(),
pagingAndSortingMethodArgumentResolver(),
serverHttpRequestMethodArgumentResolver(),
repoInfoMethodArgumentResolver(),
repoRequestArgumentResolver(),
persistentEntityArgumentResolver());
return Arrays.asList(baseUriMethodArgumentResolver(), pageableResolver(), sortResolver(),
serverHttpRequestMethodArgumentResolver(), repoInfoMethodArgumentResolver(), repoRequestArgumentResolver(),
persistentEntityArgumentResolver());
}
/**
* Override this method to add additional configuration.
*
* @param config
* Main configuration bean.
*
* @param config Main configuration bean.
*/
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
}
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {}
/**
* Override this method to add your own converters.
*
* @param conversionService
* Default ConversionService bean.
*
* @param conversionService Default ConversionService bean.
*/
protected void configureConversionService(ConfigurableConversionService conversionService) {
}
protected void configureConversionService(ConfigurableConversionService conversionService) {}
/**
* Override this method to add validators manually.
*
* @param validatingListener
* The {@link org.springframework.context.ApplicationListener} responsible for invoking {@link
* org.springframework.validation.Validator} instances.
*
* @param validatingListener The {@link org.springframework.context.ApplicationListener} responsible for invoking
* {@link org.springframework.validation.Validator} instances.
*/
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
}
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {}
/**
* Configure the {@link ExceptionHandlerExceptionResolver}.
*
* @param exceptionResolver
* The default exception resolver on which you can add custom argument resolvers.
*
* @param exceptionResolver The default exception resolver on which you can add custom argument resolvers.
*/
protected void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {
}
protected void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {}
/**
* Configure the available {@link HttpMessageConverter}s by adding your own.
*
* @param messageConverters
* The converters to be used by the system.
*
* @param messageConverters The converters to be used by the system.
*/
protected void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
}
protected void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {}
/**
* Configure the Jackson {@link ObjectMapper} directly.
*
* @param objectMapper
* The {@literal ObjectMapper} to be used by the system.
*
* @param objectMapper The {@literal ObjectMapper} to be used by the system.
*/
protected void configureJacksonObjectMapper(ObjectMapper objectMapper) {
}
protected void configureJacksonObjectMapper(ObjectMapper objectMapper) {}
}

View File

@@ -1,4 +1,4 @@
package org.springframework.data.rest.repository.json;
package org.springframework.data.rest.webmvc.json;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -1,4 +1,4 @@
package org.springframework.data.rest.repository.json;
package org.springframework.data.rest.webmvc.json;
import java.util.ArrayList;
import java.util.HashMap;

View File

@@ -1,8 +1,6 @@
package org.springframework.data.rest.repository.json;
package org.springframework.data.rest.webmvc.json;
import static org.springframework.beans.BeanUtils.*;
import static org.springframework.data.rest.core.util.UriUtils.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import java.io.IOException;
import java.net.URI;
@@ -15,6 +13,28 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.UriDomainClassConverter;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder;
import org.springframework.hateoas.Link;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -26,26 +46,6 @@ import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.UriDomainClassConverter;
import org.springframework.hateoas.Link;
import org.springframework.http.converter.HttpMessageNotReadableException;
/**
* @author Jon Brisbin
@@ -55,54 +55,45 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
private static final long serialVersionUID = -7289265674870906323L;
private static final Logger LOG = LoggerFactory.getLogger(PersistentEntityJackson2Module.class);
private static final TypeDescriptor URI_TYPE = TypeDescriptor.valueOf(URI.class);
private final ConversionService conversionService;
@Autowired
private Repositories repositories;
@Autowired
private RepositoryRestConfiguration config;
@Autowired
private UriDomainClassConverter uriDomainClassConverter;
private final ResourceMappings mappings;
public PersistentEntityJackson2Module(ConversionService conversionService) {
public PersistentEntityJackson2Module(ResourceMappings resourceMappings) {
super(new Version(1, 1, 0, "BUILD-SNAPSHOT", "org.springframework.data.rest", "jackson-module"));
this.conversionService = conversionService;
this.mappings = resourceMappings;
addSerializer(new ResourceSerializer());
}
public static boolean maybeAddAssociationLink(Repositories repositories,
RepositoryRestConfiguration config,
URI baseEntityUri,
RepositoryInformation repoInfo,
ResourceMapping entityMapping,
ResourceMapping propertyMapping,
public static boolean maybeAddAssociationLink(RepositoryLinkBuilder builder,
ResourceMappings mappings,
PersistentProperty<?> persistentProperty,
List<Link> links) {
Class<?> propertyType = persistentProperty.getType();
if(persistentProperty.isCollectionLike() || persistentProperty.isArray()) {
propertyType = persistentProperty.getComponentType();
}
String propertyPath = (null != propertyMapping
? propertyMapping.getPath()
: persistentProperty.getName());
// In case a property mapping is specified but no path is set
if(null == propertyPath) {
propertyPath = persistentProperty.getName();
}
String propertyRel = formatRel(config, repoInfo, persistentProperty);
if(repositories.hasRepositoryFor(propertyType)) {
// This is a managed type, generate a Link
RepositoryInformation linkedRepoInfo = repositories.getRepositoryInformationFor(propertyType);
ResourceMapping linkedRepoMapping = getResourceMapping(config, linkedRepoInfo);
if(linkedRepoMapping.isExported()) {
URI uri = buildUri(baseEntityUri, propertyPath);
Link l = new Link(uri.toString(), propertyRel);
links.add(l);
// This is an association. We added a Link.
return true;
}
Assert.isTrue(persistentProperty.isAssociation(), "PersistentProperty must be an association!");
ResourceMetadata metadata = mappings.getMappingFor(persistentProperty.getOwner().getType());
if (!metadata.isManaged(persistentProperty)) {
return false;
}
metadata = mappings.getMappingFor(persistentProperty.getActualType());
if(metadata.isExported()) {
String propertyRel = String.format("%s.%s", metadata.getSingleResourceRel(), persistentProperty.getName());
links.add(builder.slash(persistentProperty.getName()).withRel(propertyRel));
// This is an association. We added a Link.
return true;
}
// This is not an association. No Link was added.
return false;
}
@@ -136,8 +127,9 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
DeserializationContext ctxt) throws IOException,
JsonProcessingException {
Object entity = instantiateClass(getValueClass());
BeanWrapper<?, Object> wrapper = BeanWrapper.create(entity, conversionService);
ResourceMapping domainMapping = config.getResourceMappingForDomainType(getValueClass());
BeanWrapper<?, Object> wrapper = BeanWrapper.create(entity, null);
ResourceMetadata metadata = mappings.getMappingFor(getValueClass());
for(JsonToken tok = jp.nextToken(); tok != JsonToken.END_OBJECT; tok = jp.nextToken()) {
String name = jp.getCurrentName();
@@ -160,18 +152,7 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
PersistentProperty<?> persistentProperty = persistentEntity.getPersistentProperty(name);
if(null == persistentProperty) {
String errMsg = "Property '" + name + "' not found for entity " + getValueClass().getName();
if(null == domainMapping) {
throw new HttpMessageNotReadableException(errMsg);
}
String propertyName = domainMapping.getNameForPath(name);
if(null == propertyName) {
throw new HttpMessageNotReadableException(errMsg);
}
persistentProperty = persistentEntity.getPersistentProperty(propertyName);
if(null == persistentProperty) {
throw new HttpMessageNotReadableException(errMsg);
}
continue;
}
Object val = null;
@@ -199,7 +180,7 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
// The method of doing that varies based on the type of the property.
if(persistentProperty.isCollectionLike()) {
Class<? extends Collection<?>> ctype = (Class<? extends Collection<?>>) persistentProperty.getType();
Collection<Object> c = (Collection<Object>) wrapper.getProperty(persistentProperty, ctype, false);
Collection<Object> c = (Collection<Object>) wrapper.getProperty(persistentProperty);
if(null == c || c == Collections.EMPTY_LIST || c == Collections.EMPTY_SET) {
if(Collection.class.isAssignableFrom(ctype)) {
c = new ArrayList<Object>();
@@ -222,7 +203,7 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
}
} else if(persistentProperty.isMap()) {
Class<? extends Map<?, ?>> mtype = (Class<? extends Map<?, ?>>)persistentProperty.getType();
Map<Object, Object> m = (Map<Object, Object>) wrapper.getProperty(persistentProperty, mtype, false);
Map<Object, Object> m = (Map<Object, Object>) wrapper.getProperty(persistentProperty);
if(null == m || m == Collections.EMPTY_MAP) {
m = new HashMap<Object, Object>();
}
@@ -278,18 +259,13 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
Object obj = resource.getContent();
final PersistentEntity persistentEntity = resource.getPersistentEntity();
final ResourceMapping entityMapping = getResourceMapping(config, persistentEntity);
final PersistentEntity entity = resource.getPersistentEntity();
final RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(persistentEntity.getType());
final ResourceMapping repoMapping = getResourceMapping(config, repoInfo);
final BeanWrapper wrapper = BeanWrapper.create(obj, conversionService);
final Object entityId = wrapper.getProperty(persistentEntity.getIdProperty());
final URI baseEntityUri = buildUri(resource.getBaseUri(),
repoMapping.getPath(),
entityId.toString());
final BeanWrapper wrapper = BeanWrapper.create(obj, null);
final Object entityId = wrapper.getProperty(entity.getIdProperty());
final ResourceMappings mappings = new ResourceMappings(config, repositories);
final ResourceMetadata metadata = mappings.getMappingFor(entity.getType());
final RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, config.getBaseUri()).slash(entityId);
final List<Link> links = new ArrayList<Link>();
// Start with ResourceProcessor-added links
@@ -297,32 +273,23 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
jgen.writeStartObject();
try {
persistentEntity.doWithProperties(new PropertyHandler() {
@Override public void doWithPersistentProperty(PersistentProperty persistentProperty) {
if(persistentProperty.isIdProperty() && !config.isIdExposedFor(persistentEntity.getType())) {
entity.doWithProperties(new PropertyHandler() {
@Override public void doWithPersistentProperty(PersistentProperty property) {
boolean idAvailableAndShallNotBeExposed = property.isIdProperty() && !config.isIdExposedFor(entity.getType());
if(idAvailableAndShallNotBeExposed) {
return;
}
ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName());
if(null != propertyMapping && !propertyMapping.isExported()) {
return;
}
if(persistentProperty.isEntity() && maybeAddAssociationLink(repositories,
config,
baseEntityUri,
repoInfo,
entityMapping,
propertyMapping,
persistentProperty,
links)) {
if (property.isEntity() && maybeAddAssociationLink(builder, mappings, property, links)) {
return;
}
// Property is a normal or non-managed property.
String propertyName = (null != propertyMapping ? propertyMapping.getPath() : persistentProperty.getName());
Object propertyValue = wrapper.getProperty(persistentProperty);
Object propertyValue = wrapper.getProperty(property);
try {
jgen.writeObjectField(propertyName, propertyValue);
jgen.writeObjectField(property.getName(), propertyValue);
} catch(IOException e) {
throw new IllegalStateException(e);
}
@@ -330,27 +297,22 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
});
// Add associations as links
persistentEntity.doWithAssociations(new AssociationHandler() {
entity.doWithAssociations(new AssociationHandler() {
@Override public void doWithAssociation(Association association) {
PersistentProperty persistentProperty = association.getInverse();
ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName());
if(null != propertyMapping && !propertyMapping.isExported()) {
PersistentProperty property = association.getInverse();
if(!mappings.isMapped(property)) {
return;
}
if(maybeAddAssociationLink(repositories,
config,
baseEntityUri,
repoInfo,
entityMapping,
propertyMapping,
persistentProperty,
links)) {
if (maybeAddAssociationLink(builder, mappings, property, links)) {
return;
}
// Association Link was not added, probably because this isn't a managed type. Add value of property inline.
Object propertyValue = wrapper.getProperty(persistentProperty);
Object propertyValue = wrapper.getProperty(property);
try {
jgen.writeObjectField(persistentProperty.getName(), propertyValue);
jgen.writeObjectField(property.getName(), propertyValue);
} catch(IOException e) {
throw new IllegalStateException(e);
}

View File

@@ -1,15 +1,13 @@
package org.springframework.data.rest.repository.json;
package org.springframework.data.rest.webmvc.json;
import static org.springframework.data.rest.core.util.UriUtils.*;
import static org.springframework.data.rest.repository.json.PersistentEntityJackson2Module.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import static org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.*;
import static org.springframework.util.StringUtils.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.validation.constraints.NotNull;
@@ -21,10 +19,11 @@ import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.annotation.Description;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.repository.support.RepositoryInformationSupport;
import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder;
import org.springframework.hateoas.Link;
/**
@@ -38,11 +37,15 @@ public class PersistentEntityToJsonSchemaConverter
private static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
private static final TypeDescriptor SCHEMA_TYPE = TypeDescriptor.valueOf(JsonSchema.class);
private Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
private ResourceMappings mappings;
@Override public void afterPropertiesSet() throws Exception {
for(Class<?> domainType : repositories) {
for(Class<?> domainType : repositories) {
convertiblePairs.add(new ConvertiblePair(domainType, JsonSchema.class));
}
this.mappings = new ResourceMappings(config, repositories);
}
@Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
@@ -60,15 +63,13 @@ public class PersistentEntityToJsonSchemaConverter
@SuppressWarnings({"unchecked", "rawtypes"})
@Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity((Class<?>)source);
final RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(persistentEntity.getType());
final ResourceMapping repoMapping = getResourceMapping(config, repoInfo);
final ResourceMapping entityMapping = getResourceMapping(config, persistentEntity);
final URI baseEntityUri = buildUri(config.getBaseUri(), repoMapping.getPath(), "{id}");
final ResourceMetadata metadata = mappings.getMappingFor(persistentEntity.getClass());
String entityDesc = persistentEntity.getType().isAnnotationPresent(Description.class)
? ((Description)persistentEntity.getType().getAnnotation(Description.class)).value()
: null;
final JsonSchema jsonSchema = new JsonSchema(persistentEntity.getName(), entityDesc);
persistentEntity.doWithProperties(new PropertyHandler() {
@Override public void doWithPersistentProperty(PersistentProperty persistentProperty) {
@@ -95,23 +96,19 @@ public class PersistentEntityToJsonSchemaConverter
});
final List<Link> links = new ArrayList<Link>();
persistentEntity.doWithAssociations(new AssociationHandler() {
@Override public void doWithAssociation(Association association) {
PersistentProperty persistentProperty = association.getInverse();
ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName());
if(null != propertyMapping && !propertyMapping.isExported()) {
if(!metadata.isMapped(persistentProperty)) {
return;
}
maybeAddAssociationLink(repositories,
config,
baseEntityUri,
repoInfo,
entityMapping,
propertyMapping,
persistentProperty,
links);
RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, config.getBaseUri()).slash("{id}");
maybeAddAssociationLink(builder, mappings, persistentProperty, links);
}
});
jsonSchema.add(links);
return jsonSchema;

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2013 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.
*/
/**
*
* @author Oliver Gierke
*/
package org.springframework.data.rest.webmvc.json;

View File

@@ -1,131 +1,83 @@
package org.springframework.data.rest.webmvc.support;
import static org.springframework.data.rest.core.util.UriUtils.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import java.net.URI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.webmvc.RepositoryController;
import org.springframework.hateoas.Identifiable;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.core.AbstractEntityLinks;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.util.Assert;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class RepositoryEntityLinks extends AbstractEntityLinks {
private final Repositories repositories;
private final Repositories repositories;
private final ResourceMappings mappings;
private final RepositoryRestConfiguration config;
@Autowired
public RepositoryEntityLinks(Repositories repositories,
RepositoryRestConfiguration config) {
public RepositoryEntityLinks(Repositories repositories, ResourceMappings mappings, RepositoryRestConfiguration config) {
Assert.notNull(repositories, "Repositories must not be null!");
this.repositories = repositories;
this.mappings = mappings;
this.config = config;
}
@Override public boolean supports(Class<?> delimiter) {
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(delimiter);
return (null != persistentEntity);
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Class<?> delimiter) {
return repositories.hasRepositoryFor(delimiter);
}
@Override public LinkBuilder linkFor(Class<?> type) {
RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(type);
if(null == repoInfo) {
throw new IllegalArgumentException(type + " is not managed by any repository.");
}
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(type);
if(null == persistentEntity) {
throw new IllegalArgumentException(type + " is not managed by any repository.");
}
return new PersistentEntityLinkBuilder(config.getBaseUri(), repoInfo, persistentEntity);
/*
* (non-Javadoc)
* @see org.springframework.hateoas.EntityLinks#linkFor(java.lang.Class)
*/
@Override
public LinkBuilder linkFor(Class<?> type) {
ResourceMetadata metadata = mappings.getMappingFor(type);
return new RepositoryLinkBuilder(metadata, config.getBaseUri());
}
@Override public LinkBuilder linkFor(Class<?> type, Object... parameters) {
/*
* (non-Javadoc)
* @see org.springframework.hateoas.EntityLinks#linkFor(java.lang.Class, java.lang.Object[])
*/
@Override
public LinkBuilder linkFor(Class<?> type, Object... parameters) {
return linkFor(type);
}
@Override public Link linkToCollectionResource(Class<?> type) {
RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(type);
if(null == repoInfo) {
throw new IllegalArgumentException(type + " is not managed by any repository.");
}
ResourceMapping mapping = getResourceMapping(config, repoInfo);
return linkFor(type).withRel(mapping.getRel());
}
@Override public Link linkToSingleResource(Class<?> type, Object id) {
RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(type);
if(null == repoInfo) {
throw new IllegalArgumentException(type + " is not managed by any repository.");
}
ResourceMapping repoMapping = getResourceMapping(config, repoInfo);
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(type);
ResourceMapping entityMapping = getResourceMapping(config, persistentEntity);
return linkFor(type).slash(id).withRel(repoMapping.getRel() + "." + entityMapping.getRel());
}
private class PersistentEntityLinkBuilder implements LinkBuilder {
/*
* (non-Javadoc)
* @see org.springframework.hateoas.EntityLinks#linkToCollectionResource(java.lang.Class)
*/
@Override
public Link linkToCollectionResource(Class<?> type) {
private final UriComponentsBuilder builder;
private final ResourceMapping repoMapping;
private final ResourceMapping entityMapping;
private PersistentEntityLinkBuilder(URI baseUri,
RepositoryInformation repoInfo,
PersistentEntity<?, ?> persistentEntity) {
this.repoMapping = getResourceMapping(config, repoInfo);
this.entityMapping = getResourceMapping(config, persistentEntity);
if(null == baseUri) {
URI u = ControllerLinkBuilder.linkTo(RepositoryController.class).toUri();
if(u.toString().endsWith("/")) {
String s = u.toString();
baseUri = URI.create(s.substring(0, s.lastIndexOf('/')));
} else {
baseUri = u;
}
}
this.builder = UriComponentsBuilder.fromUri(buildUri(baseUri, repoMapping.getPath()));
}
@Override public LinkBuilder slash(Object object) {
String path = String.format("%s", object);
if(object instanceof PersistentProperty) {
String propName = ((PersistentProperty<?>) object).getName();
if(entityMapping.hasResourceMappingFor(propName)) {
path = entityMapping.getResourceMappingFor(propName).getPath();
}
}
builder.pathSegment(path);
return this;
}
@Override public LinkBuilder slash(Identifiable<?> identifiable) {
return slash(identifiable.getId());
}
@Override public URI toUri() {
return builder.build().toUri();
}
@Override public Link withRel(String rel) {
return new Link(builder.build().toUriString(), rel);
}
@Override public Link withSelfRel() {
return withRel("self");
}
ResourceMetadata metadata = mappings.getMappingFor(type);
return linkFor(type).withRel(metadata.getRel());
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.EntityLinks#linkToSingleResource(java.lang.Class, java.lang.Object)
*/
@Override
public Link linkToSingleResource(Class<?> type, Object id) {
ResourceMetadata metadata = mappings.getMappingFor(type);
return linkFor(type).slash(id).withRel(metadata.getSingleResourceRel());
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2013 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.rest.webmvc.support;
import java.net.URI;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.RepositoryController;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.core.LinkBuilderSupport;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.web.util.UriComponentsBuilder;
public class RepositoryLinkBuilder extends LinkBuilderSupport<RepositoryLinkBuilder> {
private final ResourceMetadata metadata;
public RepositoryLinkBuilder(ResourceMetadata metadata, URI baseUri) {
this(metadata, prepareBuilder(baseUri, metadata));
}
private RepositoryLinkBuilder(ResourceMetadata metadata, UriComponentsBuilder builder) {
super(builder);
this.metadata = metadata;
}
private static UriComponentsBuilder prepareBuilder(URI baseUri, ResourceMetadata metadata) {
UriComponentsBuilder builder = baseUri != null ? UriComponentsBuilder.fromUri(baseUri) : ControllerLinkBuilder.linkTo(RepositoryController.class).toUriComponentsBuilder();
return builder.path(metadata.getPath());
}
@Override
public RepositoryLinkBuilder slash(Object object) {
if (object instanceof PersistentProperty) {
return slash((PersistentProperty<?>) object);
}
return super.slash(object);
}
public RepositoryLinkBuilder slash(PersistentProperty<?> property) {
String propName = property.getName();
if (metadata.isManaged(property)) {
return slash(metadata.getMappingFor(property).getPath());
} else {
return slash(propName);
}
}
public Link withResourceRel() {
return withRel(metadata.getRel());
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder)
*/
@Override
protected RepositoryLinkBuilder createNewInstance(UriComponentsBuilder builder) {
return new RepositoryLinkBuilder(this.metadata, builder);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.LinkBuilderSupport#getThis()
*/
@Override
protected RepositoryLinkBuilder getThis() {
return this;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2013 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.rest.webmvc;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.core.DefaultLinkDiscoverer;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
/**
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@Transactional
@ContextConfiguration(classes = RepositoryRestMvcConfiguration.class)
public abstract class AbstractWebIntegrationTests {
@Autowired WebApplicationContext context;
protected MockMvc mvc;
LinkDiscoverer links = new DefaultLinkDiscoverer();
@Before
public void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
protected MockHttpServletResponse request(String href, MediaType contentType) throws Exception {
return mvc.perform(get(href).accept(contentType)). //
andExpect(status().isOk()). //
andExpect(content().contentType(MediaType.APPLICATION_JSON)). //
andReturn().getResponse();
}
protected MockHttpServletResponse request(Link link) throws Exception {
return request(link.getHref());
}
protected MockHttpServletResponse request(String href) throws Exception {
return request(href, MediaType.APPLICATION_JSON);
}
protected Link assertHasLinkWithRel(String rel, MockHttpServletResponse response) throws Exception {
String content = response.getContentAsString();
Link link = links.findLinkWithRel(rel, content);
assertThat("Expected to find link with rel " + rel + " but found none in " + content + "!", link,
is(notNullValue()));
return link;
}
protected void assertDoesNotHaveLinkWithRel(String rel, MockHttpServletResponse response) throws Exception {
String content = response.getContentAsString();
Link link = links.findLinkWithRel(rel, content);
assertThat("Expected not to find link with rel " + rel + " but found " + link + "!", link, is(nullValue()));
}
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-2013 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.rest.webmvc;
import static org.hamcrest.MatcherAssert.*;
@@ -7,18 +22,25 @@ import java.net.URI;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.rest.webmvc.annotation.BaseURI;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class CustomMethodArgumentResolverTests extends AbstractJMockTests {
@RunWith(MockitoJUnitRunner.class)
public class CustomMethodArgumentResolverTests {
static final MethodParameter BASE_URI;
static final MethodParameter PAGE_SORT;
@@ -30,7 +52,7 @@ public class CustomMethodArgumentResolverTests extends AbstractJMockTests {
0
);
PAGE_SORT = MethodParameter.forMethodOrConstructor(
Methods.class.getDeclaredMethod("pagingAndSorting", PagingAndSorting.class),
Methods.class.getDeclaredMethod("pagingAndSorting", Pageable.class),
0
);
} catch(NoSuchMethodException e) {
@@ -43,15 +65,17 @@ public class CustomMethodArgumentResolverTests extends AbstractJMockTests {
.setBaseUri(URI.create("http://localhost:8080"));
private final BaseUriMethodArgumentResolver baseUriResolver =
new BaseUriMethodArgumentResolver(config);
private final PagingAndSortingMethodArgumentResolver pageSortResolver =
new PagingAndSortingMethodArgumentResolver(config);
private final PageableHandlerMethodArgumentResolver pageSortResolver =
new PageableHandlerMethodArgumentResolver();
private ModelAndViewContainer mavContainer;
private WebDataBinderFactory webDataBinderFactory;
@Mock WebDataBinderFactory webDataBinderFactory;
@Before
public void setup() {
mavContainer = new ModelAndViewContainer();
webDataBinderFactory = context.mock(WebDataBinderFactory.class);
pageSortResolver.setOneIndexedParameters(true);
pageSortResolver.setFallbackPageable(new PageRequest(1, 5));
}
@Test
@@ -80,7 +104,7 @@ public class CustomMethodArgumentResolverTests extends AbstractJMockTests {
is(true));
// Resolve Page and Sort information
PagingAndSorting pageSort = (PagingAndSorting)pageSortResolver.resolveArgument(
Pageable pageSort = pageSortResolver.resolveArgument(
PAGE_SORT,
mavContainer,
new ServletWebRequest(Requests.PAGE_REQUEST),
@@ -92,15 +116,14 @@ public class CustomMethodArgumentResolverTests extends AbstractJMockTests {
is(1));
assertThat("Finds limit parameter value",
pageSort.getPageSize(),
is(5));
is(10));
}
static class Methods {
void baseUri(@BaseURI URI baseUri) {
}
void pagingAndSorting(PagingAndSorting pageSort) {
void pagingAndSorting(Pageable pageSort) {
}
}
}

View File

@@ -12,7 +12,7 @@ public abstract class Requests {
static {
PAGE_REQUEST.setParameter("page", "2");
PAGE_REQUEST.setParameter("limit", "5");
PAGE_REQUEST.setParameter("size", "10");
}
private Requests() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 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.
@@ -17,6 +17,7 @@ package org.springframework.data.rest.webmvc;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.rest.webmvc.HttpEntityMatcher.*;
import static org.springframework.util.ReflectionUtils.*;
@@ -28,13 +29,18 @@ import java.util.List;
import java.util.Map;
import org.hamcrest.Matcher;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.MethodParameter;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceProcessor;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.mvc.HeaderLinksResponseEntity;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -48,7 +54,8 @@ import org.springframework.web.method.support.ModelAndViewContainer;
* @author Oliver Gierke
* @author Jon Brisbin
*/
public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests extends AbstractJMockTests {
@RunWith(MockitoJUnitRunner.class)
public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests {
static final Resource<String> FOO = new Resource<String>("foo");
static final Resources<Resource<String>> FOOS = new Resources<Resource<String>>(
@@ -90,12 +97,11 @@ public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests extends A
});
}
HandlerMethodReturnValueHandler delegate;
@Mock HandlerMethodReturnValueHandler delegate;
List<ResourceProcessor<?>> resourceProcessors;
@Before
public void setUp() {
delegate = context.mock(HandlerMethodReturnValueHandler.class);
resourceProcessors = new ArrayList<ResourceProcessor<?>>();
}
@@ -188,6 +194,20 @@ public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests extends A
invokeReturnValueHandler("resourceEntity", is(LONG_20), LONG_10_RES);
}
@Test
public void usesHeaderLinksResponseEntityIfConfigured() throws Exception {
Resource<String> resource = new Resource<String>("foo", new Link("href", "rel"));
MethodParameter parameter = METHOD_PARAMS.get("resource");
ResourceProcessorHandlerMethodReturnValueHandler handler = new ResourceProcessorHandlerMethodReturnValueHandler(delegate, resourceProcessors);
handler.setRootLinksAsHeaders(true);
handler.handleReturnValue(resource, parameter, null, null);
verify(delegate, times(1)).handleReturnValue(Mockito.any(HeaderLinksResponseEntity.class), eq(parameter),
Mockito.any(ModelAndViewContainer.class), Mockito.any(NativeWebRequest.class));
}
// Helpers ---------------------------------------------------------//
private void invokeReturnValueHandler(String method,
@@ -195,12 +215,9 @@ public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests extends A
Object returnValue) throws Exception {
final MethodParameter methodParam = METHOD_PARAMS.get(method);
context.checking(new Expectations() {{
oneOf(delegate).handleReturnValue(with(matcher),
with(methodParam),
with(aNull(ModelAndViewContainer.class)),
with(aNull(NativeWebRequest.class)));
}});
if (methodParam == null) {
throw new IllegalArgumentException("Invalid method!");
}
HandlerMethodReturnValueHandler handler = new ResourceProcessorHandlerMethodReturnValueHandler(
delegate,
@@ -209,13 +226,10 @@ public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests extends A
handler.handleReturnValue(returnValue, methodParam, null, null);
}
private void assertSupport(final boolean value) {
final MethodParameter parameter = context.mock(MethodParameter.class);
private void assertSupport(boolean value) {
context.checking(new Expectations() {{
oneOf(delegate).supportsReturnType(parameter);
will(returnValue(value));
}});
final MethodParameter parameter = Mockito.mock(MethodParameter.class);
when(delegate.supportsReturnType(Mockito.any(MethodParameter.class))).thenReturn(value);
HandlerMethodReturnValueHandler handler = new ResourceProcessorHandlerMethodReturnValueHandler(
delegate,

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2013 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.rest.webmvc.config;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.hateoas.core.DefaultRelProvider;
/**
* @author Oliver Gierke
*/
public class RepositoryRestMvConfigurationIntegrationTests {
static AbstractApplicationContext context;
@BeforeClass
public static void setUp() {
context = new AnnotationConfigApplicationContext(ExtendingConfiguration.class);
}
@AfterClass
public static void tearDown() {
if (context != null) {
context.close();
}
}
@Test
public void foo() {
context.getBean(PageableHandlerMethodArgumentResolver.class);
}
@Configuration
static class ExtendingConfiguration extends RepositoryRestMvcConfiguration {
@Bean
public DefaultRelProvider relProvider() {
return new DefaultRelProvider();
}
}
}

View File

@@ -1,6 +1,20 @@
/*
* Copyright 2012-2013 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.rest.webmvc.jpa;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
@@ -9,20 +23,19 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaDialect;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@Configuration
@ComponentScan(basePackageClasses = JpaRepositoryConfig.class)
@ComponentScan
@EnableJpaRepositories
@EnableTransactionManagement
public class JpaRepositoryConfig {
@@ -32,7 +45,7 @@ public class JpaRepositoryConfig {
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean public EntityManagerFactory entityManagerFactory() {
@Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.HSQL);
vendorAdapter.setGenerateDdl(true);
@@ -41,20 +54,12 @@ public class JpaRepositoryConfig {
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getClass().getPackage().getName());
factory.setDataSource(dataSource());
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean public JpaDialect jpaDialect() {
return new HibernateJpaDialect();
return factory;
}
@Bean public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
return new JpaTransactionManager();
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2013 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.rest.webmvc.jpa;
import org.junit.Test;
import org.springframework.data.rest.webmvc.AbstractWebIntegrationTests;
import org.springframework.hateoas.Link;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
/**
* Web integration tests specific to JPA.
*
* @author Oliver Gierke
*/
@ContextConfiguration(classes = JpaRepositoryConfig.class)
public class JpaWebTests extends AbstractWebIntegrationTests {
@Test
public void accessPersons() throws Exception {
MockHttpServletResponse response = request("/people?page=0&size=1");
Link nextLink = assertHasLinkWithRel(Link.REL_NEXT, response);
assertDoesNotHaveLinkWithRel(Link.REL_PREVIOUS, response);
response = request(nextLink);
assertHasLinkWithRel(Link.REL_PREVIOUS, response);
nextLink = assertHasLinkWithRel(Link.REL_NEXT, response);
response = request(nextLink);
assertHasLinkWithRel(Link.REL_PREVIOUS, response);
assertDoesNotHaveLinkWithRel(Link.REL_NEXT, response);
}
}

View File

@@ -1,36 +1,37 @@
package org.springframework.data.rest.repository.json;
package org.springframework.data.rest.webmvc.json;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.io.StringWriter;
import java.util.Collections;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.RepositoryTestsConfig;
import org.springframework.data.rest.repository.domain.jpa.Person;
import org.springframework.data.rest.repository.domain.jpa.PersonRepository;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.PersonRepository;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.core.DefaultLinkDiscoverer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.UriTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Jon Brisbin
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RepositoryTestsConfig.class)
@Transactional
public class PersistentEntitySerializationTests {
private static final String PERSON_JSON_IN = "{\"firstName\": \"John\",\"lastName\": \"Doe\"}";
@@ -38,22 +39,19 @@ public class PersistentEntitySerializationTests {
@Autowired ObjectMapper mapper;
@Autowired Repositories repositories;
@Autowired PersonRepository people;
public static Matcher<Link> isLinkWithHref(final String href) {
return new BaseMatcher<Link>() {
@Override public boolean matches(Object item) {
return (item instanceof Link && href.equals(((Link)item).getHref()));
}
@Override public void describeTo(Description description) {
description.appendText(href);
}
};
LinkDiscoverer linkDiscoverer;
@Before
public void setUp() {
linkDiscoverer = new DefaultLinkDiscoverer();
}
@Test
public void deserializesPersonEntity() throws IOException {
Person p = mapper.readValue(PERSON_JSON_IN, Person.class);
assertThat(p.getFirstName(), is("John"));
assertThat(p.getLastName(), is("Doe"));
assertThat(p.getSiblings(), is(Collections.EMPTY_LIST));
@@ -62,13 +60,18 @@ public class PersistentEntitySerializationTests {
@Test
public void serializesPersonEntity() throws IOException, InterruptedException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(Person.class);
Person person = people.save(new Person("John", "Doe"));
mapper.writeValue(out, PersistentEntityResource.wrap(persistentEntity, person, URI.create("http://localhost")));
out.flush();
String s = new String(out.toByteArray());
assertThat("Siblings Link looks correct", JsonPath.read(s, "$links[0].href").toString(), endsWith("/2/siblings"));
StringWriter writer = new StringWriter();
mapper.writeValue(writer, PersistentEntityResource.wrap(persistentEntity, person));
String s = writer.toString();
Link fatherLink = linkDiscoverer.findLinkWithRel("father", s);
assertThat(fatherLink.getHref(), endsWith(new UriTemplate("/{id}/father").expand(person.getId()).toString()));
Link siblingLink = linkDiscoverer.findLinkWithRel("siblings", s);
assertThat(siblingLink.getHref(), endsWith(new UriTemplate("/{id}/siblings").expand(person.getId()).toString()));
}
}

View File

@@ -0,0 +1,82 @@
package org.springframework.data.rest.webmvc.json;
import java.net.URI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.UriDomainClassConverter;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.PersonRepository;
import org.springframework.format.support.DefaultFormattingConversionService;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Jon Brisbin
*/
@Configuration
@Import({JpaRepositoryConfig.class})
@SuppressWarnings("deprecation")
public class RepositoryTestsConfig {
@Autowired
private ApplicationContext appCtx;
@Bean public Repositories repositories() {
return new Repositories(appCtx);
}
@Bean public RepositoryRestConfiguration config() {
RepositoryRestConfiguration config = new RepositoryRestConfiguration();
config.setResourceMappingForDomainType(Person.class)
.setRel("person");
// config.setResourceMappingForRepository(ConfiguredPersonRepository.class)
// .setRel("people")
// .setPath("people")
// .setExported(false);
config.setResourceMappingForRepository(PersonRepository.class)
.setRel("people")
.setPath("people")
.addResourceMappingFor("findByFirstName")
.setRel("firstname")
.setPath("firstname");
config.setBaseUri(URI.create("http://localhost:8080"));
return config;
}
@Bean public DefaultFormattingConversionService defaultConversionService() {
return new DefaultFormattingConversionService();
}
@Bean public DomainClassConverter<?> domainClassConverter() {
return new DomainClassConverter<DefaultFormattingConversionService>(defaultConversionService());
}
@Bean public UriDomainClassConverter uriDomainClassConverter() {
return new UriDomainClassConverter();
}
@Bean public Module persistentEntityModule() {
return new PersistentEntityJackson2Module(new ResourceMappings(config(), repositories()));
}
@Bean public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(persistentEntityModule());
return mapper;
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="info" />
<root level="error">
<appender-ref ref="console" />
</root>
</configuration>