Added query capability.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.repository.Repository;
|
||||
@@ -19,8 +18,8 @@ public interface RepositoryMetadata<R extends Repository<? extends Object, ? ext
|
||||
|
||||
E entityMetadata();
|
||||
|
||||
Method queryMethod(String key);
|
||||
RepositoryQueryMethod queryMethod(String key);
|
||||
|
||||
Map<String, Method> queryMethods();
|
||||
Map<String, RepositoryQueryMethod> queryMethods();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class RepositoryQueryMethod {
|
||||
|
||||
private static final Class[] SIMPLE_TYPES = new Class[]{
|
||||
String.class,
|
||||
Integer.class,
|
||||
Long.class,
|
||||
Boolean.class
|
||||
};
|
||||
private static final LocalVariableTableParameterNameDiscoverer nameLookup = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
private Method method;
|
||||
private Class<?>[] paramTypes;
|
||||
private String[] paramNames;
|
||||
|
||||
public RepositoryQueryMethod(Method method) {
|
||||
this.method = method;
|
||||
paramTypes = method.getParameterTypes();
|
||||
paramNames = nameLookup.getParameterNames(method);
|
||||
if (null == paramNames) {
|
||||
paramNames = new String[paramTypes.length];
|
||||
}
|
||||
Annotation[][] paramAnnos = method.getParameterAnnotations();
|
||||
for (int i = 0; i < paramAnnos.length; i++) {
|
||||
if (paramAnnos[i].length > 0) {
|
||||
for (Annotation anno : paramAnnos[i]) {
|
||||
if (Param.class.isAssignableFrom(anno.getClass())) {
|
||||
Param p = (Param) anno;
|
||||
paramNames[i] = p.value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (null == paramNames[i]) {
|
||||
paramNames[i] = "arg" + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Class<?>[] paramTypes() {
|
||||
return paramTypes;
|
||||
}
|
||||
|
||||
public String[] paramNames() {
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
public Method method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -139,4 +139,15 @@ public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "JpaAttributeMetadata{" +
|
||||
"name='" + name + '\'' +
|
||||
", attribute=" + attribute +
|
||||
", type=" + type +
|
||||
", field=" + field +
|
||||
", getter=" + getter +
|
||||
", setter=" + setter +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ public class JpaEntityMetadata implements EntityMetadata<JpaAttributeMetadata> {
|
||||
if (repositories.hasRepositoryFor(attrType)) {
|
||||
linkedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr));
|
||||
} else {
|
||||
embeddedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr));
|
||||
if (attr != idAttribute && attr != versionAttribute) {
|
||||
embeddedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,4 +75,14 @@ public class JpaEntityMetadata implements EntityMetadata<JpaAttributeMetadata> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "JpaEntityMetadata{" +
|
||||
"type=" + type +
|
||||
", idAttribute=" + idAttribute +
|
||||
", versionAttribute=" + versionAttribute +
|
||||
", embeddedAttributes=" + embeddedAttributes +
|
||||
", linkedAttributes=" + linkedAttributes +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryQueryMethod;
|
||||
import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -25,7 +26,7 @@ public class JpaRepositoryMetadata<R extends Repository<Object, Serializable>> i
|
||||
private final Class<?> repoClass;
|
||||
private final R repository;
|
||||
private final EntityInformation entityInfo;
|
||||
private final Map<String, Method> queryMethods = new HashMap<String, Method>();
|
||||
private final Map<String, RepositoryQueryMethod> queryMethods = new HashMap<String, RepositoryQueryMethod>();
|
||||
private JpaEntityMetadata entityMetadata;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@@ -46,7 +47,7 @@ public class JpaRepositoryMetadata<R extends Repository<Object, Serializable>> i
|
||||
@Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
String pathSeg = AnnotationUtils.findAnnotation(method, RestPathSegment.class).value();
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
queryMethods.put(pathSeg, method);
|
||||
queryMethods.put(pathSeg, new RepositoryQueryMethod(method));
|
||||
}
|
||||
},
|
||||
new ReflectionUtils.MethodFilter() {
|
||||
@@ -80,12 +81,23 @@ public class JpaRepositoryMetadata<R extends Repository<Object, Serializable>> i
|
||||
return entityMetadata;
|
||||
}
|
||||
|
||||
@Override public Method queryMethod(String key) {
|
||||
@Override public RepositoryQueryMethod queryMethod(String key) {
|
||||
return queryMethods.get(key);
|
||||
}
|
||||
|
||||
@Override public Map<String, Method> queryMethods() {
|
||||
@Override public Map<String, RepositoryQueryMethod> queryMethods() {
|
||||
return Collections.unmodifiableMap(queryMethods);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "JpaRepositoryMetadata{" +
|
||||
"name='" + name + '\'' +
|
||||
", repoClass=" + repoClass +
|
||||
", repository=" + repository +
|
||||
", entityInfo=" + entityInfo +
|
||||
", queryMethods=" + queryMethods +
|
||||
", entityMetadata=" + entityMetadata +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import javax.persistence.PersistenceContext
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.data.repository.CrudRepository
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata
|
||||
import org.springframework.data.rest.repository.RepositoryExporter
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata
|
||||
import org.springframework.data.rest.repository.test.Family
|
||||
import org.springframework.data.rest.repository.test.FamilyRepository
|
||||
import org.springframework.data.rest.repository.test.Person
|
||||
@@ -26,19 +27,23 @@ class JpaMetadataSpec extends Specification {
|
||||
@Autowired
|
||||
Collection<CrudRepository> repositories
|
||||
@Autowired
|
||||
JpaRepositoryMetadata repoMeta
|
||||
List<RepositoryExporter> exporters
|
||||
|
||||
RepositoryMetadata repositoryMetadataFor(name) {
|
||||
exporters.find { null != it.repositoryMetadataFor(name) }?.repositoryMetadataFor(name)
|
||||
}
|
||||
|
||||
def "finds repositories in ApplicationContext"() {
|
||||
|
||||
when: "find repo by String identifier"
|
||||
def repo = repoMeta.repositoryFor("person")
|
||||
def repo = repositoryMetadataFor("person").repository()
|
||||
|
||||
then:
|
||||
null != repo
|
||||
repo instanceof PersonRepository
|
||||
|
||||
when: "find repo by domain Class<?>"
|
||||
repo = repoMeta.repositoryFor(Family)
|
||||
repo = repositoryMetadataFor(Family).repository()
|
||||
|
||||
then:
|
||||
null != repo
|
||||
@@ -49,8 +54,8 @@ class JpaMetadataSpec extends Specification {
|
||||
def "provides entity metadata"() {
|
||||
|
||||
given:
|
||||
def personRepo = repoMeta.repositoryFor(Person)
|
||||
def familyRepo = repoMeta.repositoryFor(Family)
|
||||
def personRepo = repositoryMetadataFor(Person).repository()
|
||||
def familyRepo = repositoryMetadataFor(Family).repository()
|
||||
def johnDoe = personRepo.save(new Person("John Doe"))
|
||||
def janeDoe = personRepo.save(new Person("Jane Doe"))
|
||||
def doeFamily = familyRepo.save(new Family(
|
||||
@@ -59,13 +64,13 @@ class JpaMetadataSpec extends Specification {
|
||||
))
|
||||
|
||||
when:
|
||||
def personMeta = repoMeta.entityMetadataFor(Person)
|
||||
def familyMeta = repoMeta.entityMetadataFor(Family)
|
||||
def personMeta = repositoryMetadataFor(Person).entityMetadata()
|
||||
def familyMeta = repositoryMetadataFor(Family).entityMetadata()
|
||||
|
||||
then:
|
||||
personMeta.get("name", johnDoe) == "John Doe"
|
||||
familyMeta.get("surname", doeFamily) == "Doe"
|
||||
familyMeta.get("members", doeFamily).size() == 2
|
||||
personMeta.attribute("name").get(johnDoe) == "John Doe"
|
||||
familyMeta.attribute("surname").get(doeFamily) == "Doe"
|
||||
familyMeta.attribute("members").get(doeFamily).size() == 2
|
||||
personMeta.embeddedAttributes().size() == 1
|
||||
familyMeta.linkedAttributes().size() == 1
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
package org.springframework.data.rest.repository.test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestPathSegment("byName")
|
||||
public List<Person> findByName(String name);
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,6 @@
|
||||
|
||||
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
|
||||
|
||||
<bean id="jpaRepositoryMetadata" class="org.springframework.data.rest.repository.JpaRepositoryMetadata"/>
|
||||
<bean class="org.springframework.data.rest.repository.jpa.JpaRepositoryExporter"/>
|
||||
|
||||
</beans>
|
||||
@@ -17,8 +17,7 @@ dependencies {
|
||||
// Repository Exporter support
|
||||
compile project(":spring-data-rest-repository")
|
||||
|
||||
// Testing
|
||||
testRuntime "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
testRuntime "org.hsqldb:hsqldb:1.8.0.10"
|
||||
runtime "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
runtime "org.hsqldb:hsqldb:2.2.8"
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -23,8 +24,10 @@ import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
@@ -35,6 +38,7 @@ import org.springframework.data.rest.repository.RepositoryConstraintViolationExc
|
||||
import org.springframework.data.rest.repository.RepositoryExporter;
|
||||
import org.springframework.data.rest.repository.RepositoryExporterSupport;
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryQueryMethod;
|
||||
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
|
||||
import org.springframework.data.rest.repository.context.AfterLinkSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.AfterSaveEvent;
|
||||
@@ -61,6 +65,7 @@ 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 org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
@@ -224,13 +229,83 @@ public class RepositoryRestController
|
||||
while (iter.hasNext()) {
|
||||
Object o = iter.next();
|
||||
Serializable id = (Serializable) repoMeta.entityMetadata().idAttribute().get(o);
|
||||
links.add(new SimpleLink(o.getClass().getSimpleName(), buildUri(baseUri, repository, id.toString())));
|
||||
links.add(new SimpleLink(repository + "." + o.getClass().getSimpleName(),
|
||||
buildUri(baseUri, repository, id.toString())));
|
||||
}
|
||||
for (Map.Entry<String, RepositoryQueryMethod> entry : ((Map<String, RepositoryQueryMethod>) repoMeta.queryMethods())
|
||||
.entrySet()) {
|
||||
links.add(new SimpleLink(repository + "." + entry.getKey(),
|
||||
buildUri(baseUri, repository, "search", entry.getKey())));
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
model.addAttribute(RESOURCE, links);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/search/{query}",
|
||||
method = RequestMethod.GET,
|
||||
produces = {
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void query(WebRequest request,
|
||||
UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String query,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Repository repo = repoMeta.repository();
|
||||
RepositoryQueryMethod queryMethod = repoMeta.queryMethod(query);
|
||||
|
||||
Class<?>[] paramTypes = queryMethod.paramTypes();
|
||||
String[] paramNames = queryMethod.paramNames();
|
||||
Object[] paramVals = new Object[paramTypes.length];
|
||||
for (int i = 0; i < paramVals.length; i++) {
|
||||
String queryVal = request.getParameter(paramNames[i]);
|
||||
if (paramTypes[i].isAssignableFrom(String.class)) {
|
||||
paramVals[i] = queryVal;
|
||||
} else {
|
||||
paramVals[i] = conversionService.convert(queryVal, paramTypes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Object result = queryMethod.method().invoke(repo, paramVals);
|
||||
if (result instanceof Collection) {
|
||||
Collection coll = new ArrayList();
|
||||
for (Object o : (Collection) result) {
|
||||
RepositoryMetadata elemRepoMeta = repositoryMetadataFor(o.getClass());
|
||||
if (null != elemRepoMeta) {
|
||||
Map<String, Object> dto = extractPropertiesLinkAware(o, elemRepoMeta.entityMetadata(), baseUri);
|
||||
coll.add(dto);
|
||||
} else {
|
||||
coll.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
model.addAttribute(RESOURCE, coll);
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
} else {
|
||||
RepositoryMetadata elemRepoMeta = repositoryMetadataFor(result.getClass());
|
||||
if (null != elemRepoMeta) {
|
||||
Map<String, Object> dto = extractPropertiesLinkAware(result, elemRepoMeta.entityMetadata(), baseUri);
|
||||
model.addAttribute(RESOURCE, dto);
|
||||
} else {
|
||||
model.addAttribute(RESOURCE, result);
|
||||
}
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new DataRetrievalFailureException(e.getMessage(), e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new DataRetrievalFailureException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
|
||||
/**
|
||||
@@ -8,4 +11,8 @@ import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
*/
|
||||
@RestPathSegment("person")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestPathSegment("byName")
|
||||
public List<Person> findByName(@Param("nme") String name);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user