Incorporated recent changes to Spring Data Commons and dependent projects that obsoleted the need to manage domain object metadata within Spring Data REST. Required updating to the latest snapshots available for spring-data-commons and spring-data-jpa.
Additional changes include: * Re-wrote the monolithic Controller into separate controller classes that have a more narrow focus. * Implemented common functionality as a `HandlerMethodArgumentResolver` rather than as a helper method in a controller class. * Re-implemented JSONP functionality as an HttpMessageConverter rather than inline within a controller class. * Updated to Jackson 2 for all JSON handling. * By relying on spring-data-commons, spring-data-rest now handles all supported Repository types: JPA, MongoDB, and GemFire. Added support for MongoDB and GemFire repositories by relying on spring-data-commons to provide the metadata rather than maintaining internal metadata information that is store-specific. Replaced Spock spec tests with JMock unit and integration tests. Started integrating Jetty 8 into the testing so MVC testing can be done against a live server.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,4 +6,4 @@ build/
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
.log
|
||||
*.log
|
||||
|
||||
337
build.gradle
337
build.gradle
@@ -1,52 +1,107 @@
|
||||
// spring-data-rest - Spring Data REST Exporter
|
||||
apply plugin: "base"
|
||||
|
||||
allprojects {
|
||||
ext {
|
||||
gradleScriptDir = "${rootProject.projectDir}/gradle"
|
||||
|
||||
// Logging
|
||||
slf4jVersion = "1.7.2"
|
||||
logbackVersion = "1.0.7"
|
||||
|
||||
// Spring
|
||||
springVersion = "3.1.3.RELEASE"
|
||||
//springVersion = "3.2.0.RELEASE"
|
||||
hateoasVersion = "0.4.0.BUILD-SNAPSHOT"
|
||||
springPluginVersion = "0.8.0.BUILD-SNAPSHOT"
|
||||
springSecurityVersion = "3.1.3.RELEASE"
|
||||
sdCommonsVersion = "1.5.0.BUILD-SNAPSHOT"
|
||||
sdJpaVersion = "1.3.0.BUILD-SNAPSHOT"
|
||||
sdMongoVersion = "1.2.0.BUILD-SNAPSHOT"
|
||||
sdGemfireVersion = "1.3.0.BUILD-SNAPSHOT"
|
||||
|
||||
// Libraries
|
||||
guavaVersion = "13.0.1"
|
||||
jacksonVersion = "2.1.2"
|
||||
jodaVersion = "2.1"
|
||||
hibernateVersion = "4.1.7.Final"
|
||||
hibernateValidatorVersion = "4.3.0.Final"
|
||||
|
||||
// Supporting libraries
|
||||
cglibVersion = "2.2.2"
|
||||
|
||||
// Testing
|
||||
junitVersion = "4.11"
|
||||
hamcrestVersion = "1.3"
|
||||
jmockVersion = "2.6.0-RC2"
|
||||
jettyVersion = "8.1.8.v20121106"
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url "http://repo.springsource.org/plugins-release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.build.gradle:docbook-reference-plugin:0.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
configure(allprojects) {
|
||||
apply plugin: "java"
|
||||
apply plugin: "maven"
|
||||
apply plugin: "idea"
|
||||
apply plugin: "eclipse"
|
||||
apply plugin: "maven"
|
||||
apply from: "${gradleScriptDir}/ide.gradle"
|
||||
|
||||
group = "org.springframework.data"
|
||||
version = "$sdRestVersion"
|
||||
|
||||
configurations.all {
|
||||
exclude group: "commons-logging"
|
||||
exclude module: "slf4j-log4j12"
|
||||
}
|
||||
|
||||
project.sourceCompatibility = 1.6
|
||||
project.targetCompatibility = 1.6
|
||||
|
||||
[compileJava, compileTestJava]*.options*.compilerArgs = ["-Xlint:none", "-g"]
|
||||
|
||||
sourceSets.test.resources.srcDirs = ["src/test/resources", "src/test/java"]
|
||||
|
||||
repositories {
|
||||
//maven { url "http://repo.springsource.org/libs-snapshot" }
|
||||
//maven { url "http://repo.springsource.org/libs-milestone" }
|
||||
maven { url "http://repo.springsource.org/libs-release" }
|
||||
//maven { url "http://repo.springsource.org/libs-milestone" }
|
||||
maven { url "http://repo.springsource.org/libs-snapshot" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Logging
|
||||
compile "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
runtime "org.slf4j:jcl-over-slf4j:$slf4jVersion"
|
||||
|
||||
// Testing
|
||||
testCompile "junit:junit-dep:$junitVersion"
|
||||
testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
testCompile "org.jmock:jmock-junit4:$jmockVersion"
|
||||
testCompile "org.jmock:jmock-legacy:$jmockVersion"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
testRuntime "org.springframework:spring-context-support:$springVersion"
|
||||
testRuntime "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
}
|
||||
}
|
||||
|
||||
configure(subprojects) { subproject ->
|
||||
apply plugin: "java"
|
||||
apply plugin: "groovy"
|
||||
apply from: "${rootProject.projectDir}/maven.gradle"
|
||||
|
||||
configurations {
|
||||
compile.extendsFrom providedCompile
|
||||
}
|
||||
|
||||
[compileJava, compileTestJava]*.options*.compilerArgs = ["-Xlint:unchecked"]
|
||||
|
||||
project.sourceCompatibility = 1.6
|
||||
project.targetCompatibility = 1.6
|
||||
apply from: "${gradleScriptDir}/maven.gradle"
|
||||
|
||||
javadoc {
|
||||
options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
|
||||
options.author = true
|
||||
options.header = subproject.name
|
||||
//options.overview = "${projectDir}/src/main/java/overview.html"
|
||||
//options.overview = "${projectDir}/src/api/overview.html"
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier = "sources"
|
||||
from sourceSets.main.allJava
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
classifier = "javadoc"
|
||||
from javadoc
|
||||
@@ -56,68 +111,230 @@ configure(subprojects) { subproject ->
|
||||
archives sourcesJar
|
||||
archives javadocJar
|
||||
}
|
||||
}
|
||||
|
||||
project("spring-data-rest-core") {
|
||||
description = "Spring Data REST core components."
|
||||
|
||||
configurations {
|
||||
compile.extendsFrom providedCompile
|
||||
}
|
||||
|
||||
dependencies {
|
||||
groovy "org.codehaus.groovy:groovy:$groovyVersion"
|
||||
|
||||
// Logging
|
||||
compile "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
runtime "org.slf4j:jcl-over-slf4j:$slf4jVersion"
|
||||
runtime "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
|
||||
// Jackson JSON
|
||||
compile "org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion"
|
||||
// Google Guava
|
||||
compile "com.google.guava:guava:$guavaVersion"
|
||||
|
||||
// Spring
|
||||
compile("org.springframework:spring-beans:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-context:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-core:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-orm:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-tx:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-web:$springVersion") { force = true }
|
||||
runtime "cglib:cglib-nodep:$cglibVersion"
|
||||
|
||||
// Testing
|
||||
testCompile("org.codehaus.groovy:groovy-all:$groovyVersion") { force = true }
|
||||
testCompile "org.spockframework:spock-core:$spockVersion"
|
||||
testCompile "org.spockframework:spock-spring:$spockVersion"
|
||||
testCompile "org.hamcrest:hamcrest-library:1.3"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
testRuntime "org.springframework:spring-context-support:$springVersion"
|
||||
testCompile "org.mockito:mockito-core:1.8.5"
|
||||
}
|
||||
// Spring HATEOAS
|
||||
compile("org.springframework.hateoas:spring-hateoas:$hateoasVersion") {
|
||||
exclude module: "spring-webmvc"
|
||||
}
|
||||
|
||||
// Spring Data Commons
|
||||
providedCompile("org.springframework.data:spring-data-commons:$sdCommonsVersion") {
|
||||
exclude module: "slf4j-api"
|
||||
exclude module: "jcl-over-slf4j"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project("spring-data-rest-repository") {
|
||||
description = "Spring Data REST Repository integration."
|
||||
|
||||
dependencies {
|
||||
// Exporter core
|
||||
compile project(":spring-data-rest-core")
|
||||
|
||||
// JSR-305
|
||||
compile "com.google.code.findbugs:jsr305:2.0.1"
|
||||
|
||||
// JODA
|
||||
compile("joda-time:joda-time:$jodaVersion", optional)
|
||||
|
||||
// Jackson JSON
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
|
||||
compile("com.fasterxml.jackson.datatype:jackson-datatype-joda:$jacksonVersion", optional)
|
||||
compile("com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:$jacksonVersion", optional)
|
||||
|
||||
// ROME
|
||||
//compile "rome:rome:1.0"
|
||||
|
||||
// Spring
|
||||
compile("org.springframework:spring-tx:$springVersion") { force = true }
|
||||
|
||||
// JPA
|
||||
compile("org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final", optional)
|
||||
|
||||
// Spring Plugin
|
||||
compile "org.springframework.plugin:spring-plugin-core:$springPluginVersion"
|
||||
|
||||
// Spring Data
|
||||
compile("org.springframework.data:spring-data-jpa:$sdJpaVersion", optional)
|
||||
compile("org.springframework.data:spring-data-mongodb:$sdMongoVersion", optional)
|
||||
|
||||
// JSR 303 Validation
|
||||
compile "javax.validation:validation-api:1.0.0.GA"
|
||||
|
||||
// Testing
|
||||
testCompile "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
testCompile "org.hsqldb:hsqldb:2.2.8"
|
||||
testRuntime "org.hibernate:hibernate-validator:$hibernateValidatorVersion"
|
||||
}
|
||||
}
|
||||
|
||||
project("spring-data-rest-webmvc") {
|
||||
description = "Spring Data REST MVC components."
|
||||
|
||||
dependencies {
|
||||
// Repository Exporter support
|
||||
compile project(":spring-data-rest-repository")
|
||||
|
||||
// Spring
|
||||
compile("org.springframework:spring-webmvc:$springVersion") { force = true }
|
||||
|
||||
// APIS
|
||||
compile("javax.servlet:javax.servlet-api:3.0.1", provided)
|
||||
|
||||
// Testing
|
||||
testCompile "org.eclipse.jetty:jetty-servlet:$jettyVersion"
|
||||
testCompile "org.eclipse.jetty:jetty-webapp:$jettyVersion"
|
||||
testCompile "org.mozilla:rhino:1.7R4"
|
||||
}
|
||||
}
|
||||
|
||||
project("spring-data-rest-example") {
|
||||
apply plugin: "war"
|
||||
description = "Spring Data REST example web application."
|
||||
|
||||
dependencies {
|
||||
compile project(":spring-data-rest-webmvc")
|
||||
|
||||
// Logging
|
||||
runtime "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
|
||||
// JPA
|
||||
compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final"
|
||||
|
||||
// Spring Data
|
||||
compile "org.springframework.data:spring-data-jpa:$sdJpaVersion"
|
||||
compile "org.springframework.data:spring-data-mongodb:$sdMongoVersion"
|
||||
compile "org.springframework.data:spring-data-gemfire:$sdGemfireVersion"
|
||||
|
||||
// Spring Security
|
||||
compile "org.springframework.security:spring-security-config:$springSecurityVersion"
|
||||
compile "org.springframework.security:spring-security-web:$springSecurityVersion"
|
||||
|
||||
// JODA
|
||||
compile "joda-time:joda-time:$jodaVersion"
|
||||
|
||||
// Jackson
|
||||
compile "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jacksonVersion"
|
||||
compile "com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:$jacksonVersion"
|
||||
|
||||
// Hibernate
|
||||
runtime "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
runtime "org.hibernate:hibernate-validator:$hibernateValidatorVersion"
|
||||
|
||||
// HSQL
|
||||
runtime "org.hsqldb:hsqldb:2.2.8"
|
||||
}
|
||||
}
|
||||
|
||||
configure(rootProject) {
|
||||
apply plugin: "docbook-reference"
|
||||
|
||||
task javadoc(type: Javadoc) {
|
||||
title = "Spring Data REST ${version} API"
|
||||
source subprojects.collect { project -> project.sourceSets.main.allJava }
|
||||
classpath = files(subprojects.collect { project -> project.sourceSets.main.compileClasspath })
|
||||
destinationDir = new File(buildDir, "javadoc")
|
||||
description = "Spring Data REST Exporter"
|
||||
|
||||
reference {
|
||||
sourceDir = file("src/reference/docbook")
|
||||
pdfFilename = "spring-data-rest-reference.pdf"
|
||||
}
|
||||
|
||||
// don"t publish the default jar for the root project
|
||||
configurations.archives.artifacts.clear()
|
||||
|
||||
task api(type: Javadoc) {
|
||||
group = "Documentation"
|
||||
description = "Generates aggregated Javadoc API documentation."
|
||||
title = "${rootProject.description} ${version} API"
|
||||
|
||||
options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
|
||||
options.author = true
|
||||
options.header = "Spring Data REST ${version} API"
|
||||
//options.overview = "${projectDir}/src/main/java/overview.html"
|
||||
options.header = rootProject.description
|
||||
options.overview = "src/api/overview.html"
|
||||
options.splitIndex = true
|
||||
options.linksOffline "http://docs.oracle.com/javase/6/docs/api/", "http://docs.oracle.com/javase/6/docs/api/"
|
||||
options.linksOffline "http://static.springsource.org/spring/docs/3.1.x/javadoc-api/", "http://static.springsource.org/spring/docs/3.1.x/javadoc-api/"
|
||||
options.linksOffline "http://static.springsource.org/spring-data/data-commons/docs/current/api/", "http://static.springsource.org/spring-data/data-commons/docs/current/api/"
|
||||
|
||||
source subprojects.collect { project ->
|
||||
project.sourceSets.main.allJava
|
||||
}
|
||||
|
||||
destinationDir = new File(buildDir, "api")
|
||||
classpath = files(subprojects.collect { project ->
|
||||
project.sourceSets.main.compileClasspath
|
||||
})
|
||||
maxMemory = "1024m"
|
||||
}
|
||||
|
||||
}
|
||||
task docsZip(type: Zip) {
|
||||
group = "Distribution"
|
||||
baseName = "spring-data-rest"
|
||||
classifier = "docs"
|
||||
description = "Builds -${classifier} archive containing api and reference " +
|
||||
"for deployment to http://static.springframework.org/spring-data-rest/docs."
|
||||
|
||||
task wrapper(type: Wrapper) { gradleVersion = "1.1" }
|
||||
|
||||
idea {
|
||||
module {
|
||||
downloadJavadoc = false
|
||||
downloadSources = true
|
||||
from("src/dist") {
|
||||
include "changelog.txt"
|
||||
}
|
||||
from(api) {
|
||||
into "api"
|
||||
}
|
||||
from(reference) {
|
||||
into "reference"
|
||||
}
|
||||
}
|
||||
project {
|
||||
ipr {
|
||||
withXml { xml ->
|
||||
xml.node.component.find { it.@name == "VcsDirectoryMappings" }.mapping.@vcs = "Git"
|
||||
xml.node.component.find { it.@name == "ProjectRootManager" }.output.@url = "file://\$PROJECT_DIR\$/build"
|
||||
|
||||
dependencies {
|
||||
// For integration testing
|
||||
testCompile project(":spring-data-rest-core")
|
||||
testCompile project(":spring-data-rest-repository")
|
||||
testCompile project(":spring-data-rest-webmvc")
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
downloadJavadoc = false
|
||||
downloadSources = true
|
||||
}
|
||||
project {
|
||||
ipr {
|
||||
withXml { xml ->
|
||||
xml.node.component.find { it.@name == "VcsDirectoryMappings" }.mapping.@vcs = "Git"
|
||||
xml.node.component.find { it.@name == "ProjectRootManager" }.output.@url = "file://\$PROJECT_DIR\$/build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
description = "Generates gradlew[.bat] scripts"
|
||||
gradleVersion = "1.3"
|
||||
|
||||
doLast() {
|
||||
def gradleOpts = "-XX:MaxPermSize=1024m -Xmx1024m"
|
||||
def gradleBatOpts = "$gradleOpts -XX:MaxHeapSize=256m"
|
||||
File wrapperFile = file("gradlew")
|
||||
wrapperFile.text = wrapperFile.text.replace("DEFAULT_JVM_OPTS=",
|
||||
"GRADLE_OPTS=\"$gradleOpts \$GRADLE_OPTS\"\nDEFAULT_JVM_OPTS=")
|
||||
File wrapperBatFile = file("gradlew.bat")
|
||||
wrapperBatFile.text = wrapperBatFile.text.replace("set DEFAULT_JVM_OPTS=",
|
||||
"set GRADLE_OPTS=$gradleBatOpts %GRADLE_OPTS%\nset DEFAULT_JVM_OPTS=")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Adding Spring Data REST to an existing Spring MVC Application
|
||||
|
||||
If you have an existing Spring MVC application and you'd like to integrate Spring Data REST, it's actually very easy.
|
||||
|
||||
Somewhere in your Spring MVC configuration (most likely where you configure your MVC resources) add a bean reference to the JavaConfig class that is responsible for configuring the `RepositoryRestController`. The class name is `org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration`. In XML this would look like:
|
||||
|
||||
<bean class="org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration"/>
|
||||
|
||||
When your ApplicationContext comes across this bean definition it will bootstrap the necessary Spring MVC resources to fully-configure the controller for exporting the Repositories it finds in that ApplicationContext and any parent contexts.
|
||||
|
||||
### More on required configuration
|
||||
|
||||
There are a couple Spring MVC resources that Spring Data REST depends on that must be configured correctly for it to work inside an existing Spring MVC application. We've tried to isolate those resources from whatever similar resources already exist within your application, but it may be that you want to customize some of the behavior of Spring Data REST by modifying these MVC components.
|
||||
|
||||
The most important things that we configure especially for use by Spring Data REST include:
|
||||
|
||||
#### RepositoryRestHandlerMapping
|
||||
|
||||
We register a custom `HandlerMapping` instance that responds only to the `RepositoryRestController` and only if a path is meant to be handled by Spring Data REST. In order to keep paths that are meant to be handled by your application separate from those handled by Spring Data REST, this custom HandlerMapping inspects the URL path and checks to see if a Repository has been exported under that name. If it has, it allows the request to be handled by Spring Data REST. If there is no Repository exported under that name, it returns `null`, which just means "let other HandlerMapping instances try to service this request".
|
||||
|
||||
Basically this means that Spring Data REST will always be first in line when it comes time to map a URL path and your existing application will never get a chance to service a request that is meant for a Repository. For example, if you have a Repository exported under the name "person", then all requests to your application that start with "/person" will be handled by Spring Data REST and your application will never see that request. If your Repository is exported under a different name, however (like "people"), then requests to "/people" will go to Spring Data REST and requests to "/person" will be handled by your application.
|
||||
@@ -1,40 +0,0 @@
|
||||
# Customizing the JSON output
|
||||
|
||||
Sometimes in your application you need to provide links to other resources from a particular entity. For example, a
|
||||
`Customer` response might be enriched with links to a current shopping cart, or links to manage resources related to
|
||||
that entity. Spring Data REST provides integration with [Spring HATEOAS](https://github.com/SpringSource/spring-hateoas)
|
||||
and provides an extension hook for users to alter the representation of resources going out to the client.
|
||||
|
||||
### The ResourceProcessor interface
|
||||
|
||||
Spring HATEOAS defines a `ResourceProcessor<?>` interface for processing entities. All beans of type
|
||||
`ResourceProcessor<Resource<T>>` will be automatically picked up by the Spring Data REST exporter and triggered when
|
||||
serializing an entity of type `T`. For example, to define a processor for a `Person` entity, add a `@Bean` to your
|
||||
ApplicationContext like the following (which is taken from the Spring Data REST tests):
|
||||
|
||||
@Bean public ResourceProcessor<Resource<Person>> personProcessor() {
|
||||
return new ResourceProcessor<Resource<Person>>() {
|
||||
@Override public Resource<Person> process(Resource<Person> resource) {
|
||||
resource.add(new Link("http://localhost:8080/people", "added-link"));
|
||||
return resource;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
### Adding Links
|
||||
|
||||
It's possible to add links to the default representation of an entity by simply calling `resource.add(Link)` like the
|
||||
example above. Any links you add to the `Resource` will be added to the final output.
|
||||
|
||||
### Customizing the representation
|
||||
|
||||
The Spring Data REST exporter executes any discovered `ResourceProcessor`s before it creates the output representation.
|
||||
It does this by registering a `Converter<Entity, Resource>` instance with an internal `ConversionService`. This is the
|
||||
component responsible for creating the links to referenced entities (e.g. those objects under the "links" property in
|
||||
the object's JSON representation). It takes an `@Entity` and iterates over its properties, creating links for those
|
||||
properties that are managed by a `Repository` and copying across any embedded or simple properties.
|
||||
|
||||
If your project needs to have output in a different format, however, it's possible to completely replace the default
|
||||
outgoing JSON representation with your own. If you register your own `ConversionService` in the ApplicationContext and
|
||||
register your own `Converter<Person, Resource>`, then you can return a `Resource` implementation of your choosing.
|
||||
@@ -1,56 +0,0 @@
|
||||
# Adding custom (de)serializers to Jackson's ObjectMapper
|
||||
|
||||
Sometimes the behavior of the Spring Data REST's ObjectMapper, which has been specially configured to use intelligent serializers that can turn domain objects into links and back again, may not handle your domain model correctly. There are so many ways one can structure your data that you may find your own domain model isn't being translated to JSON correctly. It's also sometimes not practical in these cases to try and support a complex domain model in a generic way. Sometimes, depending on the complexity, it's not even possible to offer a generic solution.
|
||||
|
||||
So to accommodate the largest percentage of the use cases, Spring Data REST tries very hard to render your object graph correctly. It will try and serialize unmanaged beans as normal POJOs and it will try and create links to managed beans where that's necessary. But if your domain model doesn't easily lend itself to reading or writing plain JSON, you may want to configure Jackson's ObjectMapper with your own custom type mappings and (de)serializers.
|
||||
|
||||
### Abstract class registration
|
||||
|
||||
One key configuration point you might need to hook into is when you're using an abstract class (or an interface) in your domain model. Jackson won't know by default what implementation to create for an interface. Take the following example:
|
||||
|
||||
@Entity
|
||||
public class MyEntity {
|
||||
|
||||
@OneToMany
|
||||
private List<MyInterface> interfaces;
|
||||
|
||||
}
|
||||
|
||||
In a default configuration, Jackson has no idea what class to instantiate when POSTing new data to the exporter. This is something you'll need to tell Jackson either through an annotation, or, more cleanly, by registering a type mapping using a [Module](http://wiki.fasterxml.com/JacksonFeatureModules).
|
||||
|
||||
Any `Module` bean declared within the scope of your `ApplicationContext` will be picked up by the exporter and registered with its `ObjectMapper`. To add this special abstract class type mapping, create a `Module` bean and in the `setupModule` method, add an appropriate `TypeResolver`:
|
||||
|
||||
public class MyCustomModule extends SimpleModule {
|
||||
|
||||
private MyCustomModule() {
|
||||
super("MyCustomModule", new Version(1, 0, 0, "SNAPSHOT"));
|
||||
}
|
||||
|
||||
@Override public void setupModule(SetupContext context) {
|
||||
context.addAbstractTypeResolver(
|
||||
new SimpleAbstractTypeResolver().addMapping(MyInterface.class, MyInterfaceImpl.class)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Once you have access to the `SetupContext` object in your `Module`, you can do all sorts of cool things to configure Jackon's JSON mapping. You can read more about how `Module`s work on Jackson's wiki: [http://wiki.fasterxml.com/JacksonFeatureModules](http://wiki.fasterxml.com/JacksonFeatureModules)
|
||||
|
||||
### Adding custom serializers for domain types
|
||||
|
||||
If you want to (de)serialize a domain type in a special way, you can register your own implementations with Jackson's `ObjectMapper` and the Spring Data REST exporter will transparently handle those domain objects correctly.
|
||||
|
||||
To add serializers, from your `setupModule` method implementation, do something like the following:
|
||||
|
||||
@Override public void setupModule(SetupContext context) {
|
||||
SimpleSerializers serializers = new SimpleSerializers();
|
||||
SimpleDeserializers deserializers = new SimpleDeserializers();
|
||||
|
||||
serializers.addSerializer(MyEntity.class, new MyEntitySerializer());
|
||||
deserializers.addDeserializer(MyEntity.class, mew MyEntityDeserializer());
|
||||
|
||||
context.addSerializers(serializers);
|
||||
context.addDeserializers(deserializers);
|
||||
}
|
||||
|
||||
Now Spring Data REST will correctly handle your domain objects in case they are too complex for the 80% generic use case that Spring Data REST tries to cover.
|
||||
@@ -1,148 +0,0 @@
|
||||
# Configuring the REST URL path
|
||||
|
||||
Configuring the segments of the URL path under which the resources of a JPA Repository are exported is simple. You just add an annotation at the class level and/or at the query method level.
|
||||
|
||||
By default, the exporter will expose your CrudRepository using the class name stripped of the word "Repository". So a Repository defined as follows:
|
||||
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {}
|
||||
|
||||
Will, by default, be exposed under the URL:
|
||||
|
||||
http://localhost:8080/person/
|
||||
|
||||
To change how the Repository is exported, add a `@RestResource` annotation at the class level:
|
||||
|
||||
@RestResource(path = "people")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {}
|
||||
|
||||
Now the Repository will be accessible under the URL:
|
||||
|
||||
http://localhost:8080/people/
|
||||
|
||||
If you have query methods defined, those also default to be exposed by their name:
|
||||
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
public List<Person> findByName(String name);
|
||||
|
||||
}
|
||||
|
||||
This would be exposed under the URL:
|
||||
|
||||
http://localhost:8080/person/search/findByName
|
||||
|
||||
_NOTE: All query method resources are exposed under the resource `search`._
|
||||
|
||||
To change the segment of the URL under which this query method is exposed, use the `@RestResource` annotation again:
|
||||
|
||||
@RestResource(path = "people")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestResource(path = "names")
|
||||
public List<Person> findByName(String name);
|
||||
|
||||
}
|
||||
|
||||
Now this query method will be exposed under the URL:
|
||||
|
||||
http://localhost:8080/people/search/names
|
||||
|
||||
### Handling rels
|
||||
|
||||
Since these resources are all discoverable, you can also affect how the "rel" attribute is displayed in the links sent out by the exporter.
|
||||
|
||||
For instance, in the default configuration, if you issue a request to `http://localhost:8080/person/search` to find out what query methods are exposed, you'll get back a list of links:
|
||||
|
||||
{
|
||||
"_links" : [ {
|
||||
"rel" : "person.findByName",
|
||||
"href" : "http://localhost:8080/person/search/findByName"
|
||||
} ]
|
||||
}
|
||||
|
||||
To change the rel value, use the `rel` property on the `@RestResource` annotation:
|
||||
|
||||
@RestResource(path = "people")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestResource(path = "names", rel = "names")
|
||||
public List<Person> findByName(String name);
|
||||
|
||||
}
|
||||
|
||||
This would result in a link value of:
|
||||
|
||||
{
|
||||
"_links" : [ {
|
||||
"rel" : "person.names",
|
||||
"href" : "http://localhost:8080/people/search/names"
|
||||
} ]
|
||||
}
|
||||
|
||||
The Repository's rel can also be changed by using the `@RestResource` property:
|
||||
|
||||
@RestResource(path = "people", rel = "people")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestResource(path = "names", rel = "names")
|
||||
public List<Person> findByName(String name);
|
||||
|
||||
}
|
||||
|
||||
This would result in a link value of:
|
||||
|
||||
{
|
||||
"_links" : [ {
|
||||
"rel" : "people.names",
|
||||
"href" : "http://localhost:8080/people/search/names"
|
||||
} ]
|
||||
}
|
||||
|
||||
### Hiding certain Repositories, query methods, or fields
|
||||
|
||||
You may not want a certain Repository, a query method on a Repository, or a field of your entity to be exported at all. To tell the exporter to not export these items, annotate them with `@RestResource` and set `exported = false`.
|
||||
|
||||
For example, to skip exporting a Repository:
|
||||
|
||||
@RestResource(exported = false)
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
}
|
||||
|
||||
To skip exporting a query method:
|
||||
|
||||
@RestResource(path = "people", rel = "people")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestResource(exported = false)
|
||||
public List<Person> findByName(String name);
|
||||
|
||||
}
|
||||
|
||||
Or to skip exporting a field:
|
||||
|
||||
@Entity
|
||||
public class Person {
|
||||
@Id @GeneratedValue private Long id;
|
||||
@OneToMany
|
||||
@RestResource(exported = false)
|
||||
private Map<String, Profile> profiles;
|
||||
}
|
||||
|
||||
### Hiding Repository CRUD methods
|
||||
|
||||
If you don't want to expose a save or delete method on your `CrudRepository`, you can use the `@RestResource(exported = false)` setting by overriding the method you want to turn off and placing the annotation on the overriden version. For example, to prevent HTTP users from invoking the delete methods of `CrudRepository`, override all of them and add the annotation to the overriden methods.
|
||||
|
||||
@RestResource(path = "people", rel = "people")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
void delete(Long id);
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
void delete(Person entity);
|
||||
|
||||
}
|
||||
|
||||
NOTE: It is important that you override _both_ delete methods as the exporter currently uses a somewhat naive algorithm for determing which CRUD method to use in the interest of faster runtime performance. It's not currently possible to turn off the version of delete which takes an ID but leave exported the version that takes an entity instance. For the time being, you can either export the delete methods or not. If you want turn them off, then just keep in mind you have to annotate both versions with `exported = false`.
|
||||
@@ -1,43 +0,0 @@
|
||||
# Example API usage with curl
|
||||
|
||||
Here is some example usage of the REST API with `curl`. First we'll add a `Family`:
|
||||
|
||||
$ curl -v -d '{"surname" : "Doe"}' -H "Content-Type: application/json" http://localhost:8080/family
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Location: http://localhost:8080/family/1
|
||||
Content-Length: 0
|
||||
|
||||
Now we'll add a `Person`:
|
||||
|
||||
$ curl -v -d '{"name" : "John Doe"}' -H "Content-Type: application/json" http://localhost:8080/people
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Location: http://localhost:8080/people/1
|
||||
Content-Length: 0
|
||||
|
||||
Now we'll add this person to the "Doe" family we added above:
|
||||
|
||||
$ curl -v -d 'http://localhost:8080/people/1' -H "Content-Type: text/uri-list" http://localhost:8080/family/1/members
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Content-Length: 0
|
||||
|
||||
Notice that we don't return a `Location` when we add items to a referenced collection because we can add N numbers of items (here we're just adding one) so the `Location` header wouldn't be very meaningful as you couldn't match which URL you POSTed with the corresponding URL in the header.
|
||||
|
||||
Now that we have some links created, let's query them so our user agent can keep track of them:
|
||||
|
||||
$ curl -v http://localhost:8080/family/1/members
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json;charset=ISO-8859-1
|
||||
Content-Length: 118
|
||||
|
||||
{
|
||||
"_links" : [ {
|
||||
"rel" : "family.Family.Person.1",
|
||||
"href" : "http://localhost:8080/family/1/members/1"
|
||||
} ]
|
||||
}
|
||||
|
||||
We can continue adding other top-level entities by sending JSON data and can add links to referenced entities by sending `text/uri-list` data with the URIs to those other top-level entities.
|
||||
@@ -1,62 +0,0 @@
|
||||
# Embedded Entity references in complex object graphs
|
||||
|
||||
Sometimes it's necessary to populate the incoming JSON with references to pre-existing @Entity objects. Often, this is because of referential integrity constraints. Consider the following relationship between two entities:
|
||||
|
||||
@Entity
|
||||
public class Person {
|
||||
// ... person's properties
|
||||
}
|
||||
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@OneToOne(optional = false)
|
||||
private Person person;
|
||||
|
||||
}
|
||||
|
||||
Because of the `optional = false` on the `@OneToOne` annotation, I have to include a reference to an existing `Person` entity if I want to create a new `Address`.
|
||||
|
||||
If you pull up the list of `Person` links using your user agent (Javascript, for instance), you'll want to save the link object that refers to the `Person` instance you're interested in. For example, if I list the `Person`s in the database and use the compact JSON format:
|
||||
|
||||
curl -v -H "Accept: application/x-spring-data-compact+json" http://localhost:8080/people
|
||||
|
||||
I'll get back the link objects I need to reference this entity again (this link is the same as the "self" link that appears in other places):
|
||||
|
||||
{
|
||||
"links" : [ {
|
||||
"rel" : "people.Person",
|
||||
"href" : "http://localhost:8080/people/2"
|
||||
}, {
|
||||
"rel" : "people.Person",
|
||||
"href" : "http://localhost:8080/people/1"
|
||||
}, {
|
||||
"rel" : "people.search",
|
||||
"href" : "http://localhost:8080/people/search"
|
||||
} ],
|
||||
"content" : [ ],
|
||||
"page" : {
|
||||
"number" : 1,
|
||||
"size" : 20,
|
||||
"totalPages" : 1,
|
||||
"totalElements" : 2
|
||||
}
|
||||
}
|
||||
|
||||
If you present the user with, for example, a drop-down combo box of the `people.Person` type links, and keep track of their selection, then you could just include that object in a new JSON object something like the following:
|
||||
|
||||
{
|
||||
"postalCode": "12345",
|
||||
"province": "MO",
|
||||
"lines": ["1 W 1st St."],
|
||||
"city": "Univille",
|
||||
"person": {
|
||||
"rel" : "people.Person",
|
||||
"href" : "http://localhost:8080/people/1"
|
||||
}
|
||||
}
|
||||
|
||||
You'll remember from the entity definition that the relationship between `Address` and `Person` is not optional. In that case, you'd simply include a JSON link object that refers to the entity you're interested in wherever that entity is supposed to appear. In this case, as the value of the "person" property. You can now POST this JSON to the server to create a new `Address` instance, with the "person" properly populated:
|
||||
|
||||
curl -v -X POST -d '...json data...' http://localhost:8080/address
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
# Handling ApplicationEvents in the REST Exporter
|
||||
|
||||
There are six different events that the REST exporter emits throughout the process of working with an entity. Those are:
|
||||
|
||||
* BeforeSaveEvent
|
||||
* AfterSaveEvent
|
||||
* BeforeLinkSaveEvent
|
||||
* AfterLinkSaveEvent
|
||||
* BeforeDeleteEvent
|
||||
* AfterDeleteEvent
|
||||
|
||||
### ApplicationListener
|
||||
|
||||
There is an abstract class you can subclass which listens for these kinds of events and calls
|
||||
the appropriate method based on the event type. You just override the methods for
|
||||
the events you're interested in.
|
||||
|
||||
public class BeforeSaveEventListener extends AbstractRepositoryEventListener {
|
||||
|
||||
@Override public void onBeforeSave(Object entity) {
|
||||
... logic to handle inspecting the entity before the Repository saves it
|
||||
}
|
||||
|
||||
@Override public void onAfterDelete(Object entity) {
|
||||
... send a message that this entity has been deleted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
One thing to note with this approach, however, is that it makes no distinction based on
|
||||
the type of the entity. You'll have to inspect that yourself.
|
||||
|
||||
### Annotated Handler
|
||||
|
||||
Another approach is to use an annotated handler, which does filter events based on domain type.
|
||||
|
||||
To declare a handler, create a POJO and put the `@RepositoryEventHandler` annotation on it.
|
||||
This tells the classpath scanner that this class needs to be inspected for handler methods.
|
||||
|
||||
Once it finds a class with this annotation, it iterates over the exposed methods and looks for
|
||||
annotations that correspond to the event you're interested in. For example, to handle BeforeSaveEvents
|
||||
in an annotated POJO for different kinds of domain types, you'd define your class like this:
|
||||
|
||||
@RepositoryEventHandler
|
||||
public class PersonEventHandler {
|
||||
|
||||
@HandleBeforeSave(Person.class) public void handlePersonSave(Person p) {
|
||||
... you can now deal with Person in a type-safe way
|
||||
}
|
||||
|
||||
@HandleBeforeSave(Profile.class) public void handleProfileSave(Profile p) {
|
||||
... you can now deal with Profile in a type-safe way
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
You can also declare the domain type at the class level:
|
||||
|
||||
@RepositoryEventHandler(Person.class)
|
||||
public class PersonEventHandler {
|
||||
|
||||
@HandleBeforeSave public void handleBeforeSave(Person p) {
|
||||
...
|
||||
}
|
||||
|
||||
@HandleAfterDelete public void handleAfterDelete(Person p) {
|
||||
...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
To actually get your handler invoked, however, you need to declare an instance of it in your
|
||||
ApplicationContext. The classpath scanner will look for event handlers and build up information
|
||||
about them, but it won't actually wire a handler to accept events unless there's an instance of
|
||||
it declared in your ApplicationContext.
|
||||
|
||||
(In JavaConfig style):
|
||||
|
||||
@Configuration
|
||||
public class RepositoryConfiguration {
|
||||
|
||||
@Bean PersonEventHandler personEventHandler() {
|
||||
return new PersonEventHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
When you have your beans properly declared, you need to declare an instance of the ApplicationListener.
|
||||
You can pass the base package of the packages you want searched for handlers in the constructor.
|
||||
|
||||
@Configuration
|
||||
public class RepositoryConfiguration {
|
||||
|
||||
@Bean PersonEventHandler personEventHandler() {
|
||||
return new PersonEventHandler();
|
||||
}
|
||||
|
||||
@Bean AnnotatedHandlerRepositoryEventListener repositoryEventListener() {
|
||||
return new AnnotatedHandlerRepositoryEventListener("com.mycompany.repository.handlers");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
(In XML style):
|
||||
|
||||
<bean class="com.mycompany.repository.handlers.PersonEventHandler"/>
|
||||
|
||||
<bean class="org.springframework.data.rest.repository.context.AnnotatedHandlerRepositoryEventListener">
|
||||
<property name="basePackage" value="com.mycompany.repository.handlers"/>
|
||||
</bean>
|
||||
50
doc/jsonp.md
50
doc/jsonp.md
@@ -1,50 +0,0 @@
|
||||
# JSONP Support in Spring Data REST
|
||||
|
||||
Spring Data REST supports [JSONP](http://en.wikipedia.org/wiki/JSONP) for doing safe cross-domain Ajax. JSONP support is integrated into the exporter so all you need to do to take advantage of it is pass the appropriate URL parameter. The default parameter is `callback`. So to get query method results wrapped with a call to your Javascript function, add `?callback=my_jsonp_callback` to the URL:
|
||||
|
||||
curl -v http://localhost:8080/people/search/findByName?name=John+Doe&callback=my_json_callback
|
||||
|
||||
Which will result in:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/javascript
|
||||
Content-Length: ...
|
||||
|
||||
my_jsonp_callback({
|
||||
"results": [ ... ],
|
||||
"_links": [ ... ]
|
||||
})
|
||||
|
||||
### Configuring the URL parameter
|
||||
|
||||
To configure what URL parameter is used, set the `jsonpParamName` property on your `org.springframework.data.rest.webmvc.RepositoryRestConfiguration` bean definition. In JavaConfig this would look like:
|
||||
|
||||
@Bean public RepositoryRestConfiguration restConfig() {
|
||||
return new RepositoryRestConfiguration().
|
||||
setJsonpParamName("jsonp");
|
||||
}
|
||||
|
||||
This would mean the above URL would become:
|
||||
|
||||
curl -v http://localhost:8080/people/search/findByName?name=John+Doe&jsonp=my_json_callback
|
||||
|
||||
## JSONP-E Handling Errors
|
||||
|
||||
It's usually not possible to easily handle server errors with JSONP. This is because many JSONP frameworks use a script tag insertion to perform cross-domain Ajax. If the JSONP request results in an HTTP 400 Bad Request, for example, no javascript will be evaluated because the page is considered in error.
|
||||
|
||||
To deftly handle server errors using JSONP, you need to set a value on the `jsonpOnErrParamName` REST exporter configuration property (which is defaulted to `null`, which means don't handle errors). If this value is set, the exception handling code will look for a URL query string parameter of that name and use that javascript function to call as the error handler. The way it does this is by changing the HTTP status code from, for example 400, to 200 (OK). It then wraps the error message with a call to your javascript function and sends the original HTTP status code as the first parameter.
|
||||
|
||||
For example, if a call to POST a new entity results in a validation error, the server will return a 400 Bad Request. If the `jsonpOnErrParamName` is specified and you send that URL parameter, it will instead return a 200 and call your javascript function. Assuming I have `jsonpOnErrParamName` set to "errback", I would trigger this error handling like this:
|
||||
|
||||
curl -v -d '...bad json data...' http://localhost:8080/people?errback=my_jsonp_error_handler
|
||||
|
||||
Which would result in:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/javascript
|
||||
Content-Length: ...
|
||||
|
||||
my_jsonp_error_handler(400, {
|
||||
"message": "Validation failed on property 'name'!",
|
||||
"cause": { ... }
|
||||
})
|
||||
301
doc/main_wiki.md
301
doc/main_wiki.md
@@ -1,301 +0,0 @@
|
||||
# Spring Data JPA Repository Web Exporter
|
||||
|
||||
The Spring Data JPA Repository Web Exporter allows you to export your [JPA Repositories](http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/#jpa.repositories) as a RESTful web application. The exporter exposes the CRUD methods of a [CrudRepository](http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/repository/CrudRepository.html) for doing basic entity management. Relationships can also be managed between linked entities. The exporter is deployed as a traditional Spring MVC Controller, which means all the traditional Spring MVC tools are available to work with the Web Exporter (like Spring Security, for instance).
|
||||
|
||||
### Installation
|
||||
|
||||
#### Servlet environment
|
||||
|
||||
Deployment of the Spring Data Web Exporter is extremely flexible. You can build a WAR file for deploying in a Servlet 2.5 or Servlet 3.0 environment. You can drop the spring-data-rest-webmvc.war artifact into an existing Servlet 3.0 application.
|
||||
|
||||
Start by cloning the base web application project: [https://github.com/SpringSource/spring-data-rest-webmvc](https://github.com/SpringSource/spring-data-rest-webmvc). This sample application contains a `web.xml` file in `src/main/webapp/WEB-INF/servlet-2.5-web.xml` for deployment to pre-servlet-3 containers. The prefered way to configure the exporter, though, is using the XML-free Servlet 3.0 version. Tomcat 7 and Jetty 8 both support deploying this project directly.
|
||||
|
||||
git clone https://github.com/SpringSource/spring-data-rest-webmvc.git
|
||||
cd spring-data-rest-webmvc
|
||||
./gradlew war
|
||||
|
||||
Deploy the built WAR file to your servlet container:
|
||||
|
||||
cp build/libs/spring-data-rest-webmvc-1.0.0.RELEASE.war $TOMCAT_HOME/webapps/data.war
|
||||
cd $TOMCAT_HOME
|
||||
bin/catalina.sh run
|
||||
|
||||
You can also run the project directly a Tomcat web container embedded in the build:
|
||||
|
||||
./gradlew tomcatRun
|
||||
|
||||
The WAR file has a couple example domain classes and exposes a couple repositories by default. You can verify that this configuration is working by issuing an HTTP GET to the root of the web application:
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/
|
||||
|
||||
In return, you should see:
|
||||
|
||||
> GET /data/ HTTP/1.1
|
||||
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8r zlib/1.2.3
|
||||
> Host: localhost:8080
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: application/json;charset=ISO-8859-1
|
||||
< Content-Length: 257
|
||||
<
|
||||
{
|
||||
"links" : [ {
|
||||
"rel" : "address",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/address"
|
||||
}, {
|
||||
"rel" : "person",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person"
|
||||
}, {
|
||||
"rel" : "profile",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/profile"
|
||||
} ]
|
||||
}
|
||||
|
||||
### Export Repositories
|
||||
|
||||
The preferred method to configure the Spring Data REST Exporter is to use the JavaConfig annotations. There is an example ApplicationConfig in the example application you can follow. You want to make sure the configuration class with the `@EnableJpaRepositores` annotation on it is loaded by the servlet context loader. Either use the special `RepositoryRestExporterServlet` or a `DispatcherServlet` the the appropriate `contextConfigLocation` set (refer to the `RepositoryRestExporterServlet` for more information).
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = ApplicationConfig.class)
|
||||
@EnableJpaRepositories
|
||||
@EnableTransactionManagement
|
||||
public class ApplicationConfig {
|
||||
|
||||
@Bean public DataSource dataSource() {
|
||||
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
|
||||
return builder.setType(EmbeddedDatabaseType.HSQL).build();
|
||||
}
|
||||
|
||||
@Bean public EntityManagerFactory entityManagerFactory() {
|
||||
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
|
||||
vendorAdapter.setDatabase(Database.HSQL);
|
||||
vendorAdapter.setGenerateDdl(true);
|
||||
|
||||
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
|
||||
factory.setJpaVendorAdapter(vendorAdapter);
|
||||
factory.setPackagesToScan(getClass().getPackage().getName());
|
||||
factory.setDataSource(dataSource());
|
||||
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
return factory.getObject();
|
||||
}
|
||||
|
||||
@Bean public JpaDialect jpaDialect() {
|
||||
return new HibernateJpaDialect();
|
||||
}
|
||||
|
||||
@Bean public PlatformTransactionManager transactionManager() {
|
||||
JpaTransactionManager txManager = new JpaTransactionManager();
|
||||
txManager.setEntityManagerFactory(entityManagerFactory());
|
||||
return txManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Your `WebApplicationInitializer` class would look like this:
|
||||
|
||||
public class RestExporterWebInitializer implements WebApplicationInitializer {
|
||||
|
||||
@Override public void onStartup(ServletContext ctx) throws ServletException {
|
||||
|
||||
AnnotationConfigWebApplicationContext rootCtx = new AnnotationConfigWebApplicationContext();
|
||||
rootCtx.register(ApplicationConfig.class);
|
||||
|
||||
ctx.addListener(new ContextLoaderListener(rootCtx));
|
||||
|
||||
RepositoryRestExporterServlet exporter = new RepositoryRestExporterServlet();
|
||||
|
||||
ServletRegistration.Dynamic reg = ctx.addServlet("rest-exporter", exporter);
|
||||
reg.setLoadOnStartup(1);
|
||||
reg.addMapping("/*");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
The REST exporter will also load any XML config files it finds under the path `META-INF/spring-data-rest/*-export.xml`. If you have XML configuration (Spring Integration configuration, for example), then just put your XML files in this location and they will also be bootstrapped in the ApplicationContext.
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/data/jpa
|
||||
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
|
||||
|
||||
<import resource="shared.xml"/>
|
||||
|
||||
<bean id="beforeSavePersonValidator" class="org.springframework.data.rest.example.PersonValidator"/>
|
||||
|
||||
</beans>
|
||||
|
||||
### Including your domain artifacts
|
||||
|
||||
To expose your domain objects (your JPA entities, Repositories) and Spring configuration using the web exporter, you need to copy those resources to the web exporter's `WEB-INF/lib` or `WEB-INF/classes` directory. There are potentially other ways to deploy these artifacts without modifying the web exporter's WAR file, but those methods are considerably more complicated and prone to classpath problems. The easiest and most reliable way to deploy your user artifacts are by deploying them alongside the web exporter's artifacts.
|
||||
|
||||
### Exposing your repositories
|
||||
|
||||
By default, any repositories found are exported using the bean name of the repository in the Spring configuration (minus the word "Repository", if it appears in the bean name).
|
||||
|
||||
If you have a JPA entity in your domain model that looks like...
|
||||
|
||||
@Entity
|
||||
public class Person {
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
private String name;
|
||||
@Version
|
||||
private Long version;
|
||||
@OneToMany
|
||||
private List<Address> addresses;
|
||||
@OneToMany
|
||||
private Map<String, Profile> profiles;
|
||||
}
|
||||
|
||||
...and an appropriate CrudRepository interface defined like...
|
||||
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
}
|
||||
|
||||
...your PersonRepository will by default be declared in the ApplicationContext with a bean name of "personRepository". The web exporter will strip the word "Repository" from it and expose a resource named "person". The resulting URL of this repository (assuming the exporter webapp is deployed at context path `/data` in your servlet container) will be `http://localhost:8080/spring-data-rest-webmvc/person`.
|
||||
|
||||
You can configure under what path, or whether a resource is exported at all, by using the `@RestResource` annotation. Details are here: [Configuring the REST URL path](../wiki/Configuring-the-REST-URL-path)
|
||||
|
||||
### Using the rest-shell
|
||||
|
||||
There is a command-line utility to make REST interaction easier. It includes history support and has helper commands to reduce the amount of typing you need to do to effect interactions with your REST services. It's called the `rest-shell`. You can download the binary package or the source from GitHub here: [https://github.com/jbrisbin/rest-shell](https://github.com/jbrisbin/rest-shell).
|
||||
|
||||
### Discoverability
|
||||
|
||||
The Web Exporter implements some aspects of the [HATEOAS](http://en.wikipedia.org/wiki/HATEOAS) methodology. That means all the services of the web exporter are discoverable and exposed to the client using links.
|
||||
|
||||
If you issue an HTTP request to the root of the exporter:
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/
|
||||
|
||||
You'll get back a chunk of JSON that points your user agent to the locations of the exported repositories:
|
||||
|
||||
{
|
||||
"links" : [{
|
||||
"rel" : "person",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person"
|
||||
}]
|
||||
}
|
||||
|
||||
The "rel" of the link will match the exported name of the repository. Your application should keep track of this rel value as the key to this repository.
|
||||
|
||||
Similarly, if you issue a GET to `http://localhost:8080/spring-data-rest-webmvc/person`, you should get back a list of entities exported at this resource (as returned by the CrudRepository.findAll method). See the wiki for more information about the paging and sorting options.
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/person
|
||||
|
||||
{
|
||||
"content": [ ],
|
||||
"links" : [ {
|
||||
"rel" : "person.search",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person/search"
|
||||
} ]
|
||||
}
|
||||
|
||||
The default "rel" of these links will be the rel of the repository plus a dot '.' plus the simple class name of the entity managed by this repository. The rel value can be configured using the `@RestResource` annotation, discussed on [Configuring the REST URL path](../wiki/Configuring-the-REST-URL-path).
|
||||
|
||||
Following these links will give your user agent a chunk of JSON that represents the entity. Besides properly handling nested objects and simple values, the web exporter will show relationships between entities using links just like those presented previously.
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/person/1
|
||||
|
||||
{
|
||||
"name" : "John Doe",
|
||||
"links" : [ {
|
||||
"rel" : "profiles",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person/1/profiles"
|
||||
}, {
|
||||
"rel" : "addresses",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person/1/addresses"
|
||||
}, {
|
||||
"rel" : "self",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person/1"
|
||||
} ],
|
||||
"version" : 1
|
||||
}
|
||||
|
||||
This entity has a simple String value called "name", and two relationships to other entities ("profiles", and "addresses"). Note that the "rel" value of the link corresponds to the property name of the @Entity.
|
||||
|
||||
The "self" link will always point to the resource for this entity. Use the "self" link to access the entity itself if you wish to update or delete the entity.
|
||||
|
||||
Following the links for the "profiles" property gives us a list of links to the actual entities that are referenced by this relationship:
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/person/1/profiles
|
||||
|
||||
{
|
||||
"profiles" : [ {
|
||||
"rel" : "twitter",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person/1/profiles/1"
|
||||
}, {
|
||||
"rel" : "facebook",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person/1/profiles/2"
|
||||
} ]
|
||||
}
|
||||
|
||||
In this case, the "profiles" property is a Map, so the "rel" value of the links is the key in the Map. The resource link, however, does not use the Map key in the URL. It is consistent with all other links to child resources and uses the ID of the child entity as the last component of the URL.
|
||||
|
||||
Retrieving the linked entity gives us a JSON representation of the entity, as well as the "self" link necessary to update and delete the entity.
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/person/1/profiles/1
|
||||
|
||||
{
|
||||
"links" : [ {
|
||||
"rel" : "self",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/profile/1"
|
||||
} ],
|
||||
"type" : "twitter",
|
||||
"url" : "#!/johndoe"
|
||||
}
|
||||
|
||||
### Updating relationships
|
||||
|
||||
To maintain a relationship between two entities, access the resource of the relationship by using the id of the entity as the last element in the resource path. For example, to add a link to a Profile with id 3 to a Person with id 1, issue a POST to the "profiles" resource and include in the body of the request a list of resource paths to entities you want to link to (make sure to use the [special Content-Type "text/uri-list"](http://www.ietf.org/rfc/rfc2483.txt) which, as the name implies, is a representation of a list of URIs):
|
||||
|
||||
curl -v -X POST -H "Content-Type: text/uri-list" -d "http://localhost:8080/spring-data-rest-webmvc/profile/3" http://localhost:8080/spring-data-rest-webmvc/person/1/profiles
|
||||
|
||||
You can also delete a relationship by issuing a DELETE request to the resource path that represents the relationship between parent and child entities. For example, to delete a relationship between a Profile entity with an id of 2 and a Person with an id of 1:
|
||||
|
||||
curl -v -X DELETE http://localhost:8080/spring-data-rest-webmvc/person/1/profiles/2
|
||||
|
||||
### Calling Query methods
|
||||
|
||||
Starting with Spring Data REST 1.0.0.M2, the exporter exposes Repository query methods under the special URL path `/repository/search/*`.
|
||||
|
||||
To see what query methods are exported, issue a GET request to the entity resource URL and add the segment "search". You'll get back a list of links to the exported search methods.
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/person/search
|
||||
|
||||
{
|
||||
"links" : [ {
|
||||
"rel" : "person.findByName",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person/search/findByName"
|
||||
} ]
|
||||
}
|
||||
|
||||
To query for entities using this search method, add a query parameter to the URL. The response will be a list of links to the top-level URL for that resource.
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/person/search/findByName?name=John+Doe
|
||||
|
||||
[ {
|
||||
"rel" : "person.Person",
|
||||
"href" : "http://localhost:8080/spring-data-rest-webmvc/person/1"
|
||||
} ]
|
||||
|
||||
To change the URL under which the query method is exported or set the name of the query parameter containing the search term, use the `@RestResource` annotation.
|
||||
|
||||
@RestResource(path = "people")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestResource(path = "name", rel = "names")
|
||||
public List<Person> findByName(@Param("name") String name);
|
||||
|
||||
}
|
||||
|
||||
This changes the path the PersonRepository is exported under to `/people`, changes the rel of the search URL to `people.names`, changes the path under with the query method is exported to `/name`, and sets the query parameter containing the search term to `name`. To search the Repository using this method, issue a GET request.
|
||||
|
||||
curl -v http://localhost:8080/spring-data-rest-webmvc/people/search/name?name=John+Doe
|
||||
@@ -1,53 +0,0 @@
|
||||
# Paging and Sorting
|
||||
|
||||
_This documents Spring Data REST's usage of the Spring Data Repository paging and sorting abstractions. To familiarize yourself with those features, please see the Spring Data documentation for the Repository implementation you're using._
|
||||
|
||||
## Paging
|
||||
|
||||
Rather than return everything from a large result set, Spring Data REST recognizes some URL parameters that will influence the page size and starting page number.
|
||||
|
||||
To add paging support to your Repositories, you need to extend the `PagingAndSortingRepository<T,ID>` interface rather than the basic `CrudRepository<T,ID>` interface. This adds methods that accept a `Pageable` to control the number and page of results returned.
|
||||
|
||||
public Page findAll(Pageable pageable);
|
||||
|
||||
If you extend `PagingAndSortingRepository<T,ID>` and access the list of all entities, you'll get links to the first 20 entities. To set the page size to any other number, add a `limit` parameter:
|
||||
|
||||
http://localhost:8080/people/?limit=50
|
||||
|
||||
This will set the page size to 50.
|
||||
|
||||
To use paging in your own query methods, you need to change the method signature to accept an additional `Pageable` parameter and return a `Page` rather than a `List`. For example, the following query method will be exported to `/people/search/nameStartsWith` and will support paging:
|
||||
|
||||
@RestResource(path = "nameStartsWith", rel = "nameStartsWith")
|
||||
public Page findByNameStartsWith(@Param("name") String name, Pageable p);
|
||||
|
||||
The Spring Data REST exporter will recognize the returned `Page` and give you the results in the body of the response, just as it would with a non-paged response, but additional links will be added to the resource to represent the "previous" and "next" pages of data.
|
||||
|
||||
### Previous and Next Links
|
||||
|
||||
Each paged response will return links to the previous and next pages of results based on the current page. If you are currently at the first page of results, however, no "previous" link will be rendered. The same is true for the last page of results: no "next" link will be rendered if you are on the last page of results. The "rel" value of the link will end with ".next" for next links and ".prev" for previous links.
|
||||
|
||||
{
|
||||
"rel" : "people.next",
|
||||
"href" : "http://localhost:8080/people?page=2&limit=20"
|
||||
}
|
||||
|
||||
### Header metadata when paging
|
||||
|
||||
As a convenience to the user agent, Spring Data REST sets a few special HTTP headers when doing paging. To help the UA understand where it is within the entire set of available pages, three headers are set when returning paged responses:
|
||||
|
||||
x-springdata-meta-total-count: 125
|
||||
x-springdata-meta-current-page: 1
|
||||
x-springdata-meta-total-pages: 7
|
||||
|
||||
The UA can use these values to keep track of where the paging stands in relation to the entire result set. This information is useful if you are providing a Javascript slider, for instance. You would be able to easily set the number of "notches" in the slider to the total number of pages and easily indicate to the user exactly where the current page of data falls in the context of the whole.
|
||||
|
||||
## Sorting
|
||||
|
||||
Spring Data REST also recognizes sorting parameters that will use the Repository sorting support.
|
||||
|
||||
To have your results sorted on a particular property, add a `sort` URL parameter with the name of the property you want to sort the results on. You can control the direction of the sort by specifying a URL parameter composed of the property name plus `.dir` and setting that value to either `asc` or `desc`. The following would use the `findByNameStartsWith` query method defined on the `PersonRepository` for all `Person` entities with names starting with the letter "K" and add sort data that orders the results on the `name` property in descending order:
|
||||
|
||||
curl -v http://localhost:8080/people/search/nameStartsWith?name=K&sort=name&name.dir=desc
|
||||
|
||||
To sort the results by more than one property, keep adding as many `sort=PROPERTY` parameters as you need. They will be added to the `Pageable` in the order they appear in the query string.
|
||||
@@ -1,41 +0,0 @@
|
||||
# Validation in Spring Data REST
|
||||
|
||||
Integrating validation with the Spring Data REST Exporter is as easy as simply defining an instance of a [Validator](http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/validation/Validator.html). There is an ApplicationListener that that looks for these Validator instances on startup and wires them to the correct RepositoryEvent based on the bean name.
|
||||
|
||||
For example, to validate entities before they are saved to the Repository, you only need to define a Validator instance in your ApplicationContext with a name that starts with "beforeSave".
|
||||
|
||||
<!--
|
||||
This validator will be picked up automatically. The default configuration is to look at the bean name
|
||||
and figure out what event you're interested in. This validator is interested in 'beforeSave' events
|
||||
because the word 'beforeSave' appears in the first part of the bean name. It recognizes:
|
||||
|
||||
- beforeSave
|
||||
- afterSave
|
||||
- beforeDelete
|
||||
- afterDelete
|
||||
- beforeLinkSave
|
||||
- afterLinkSave
|
||||
|
||||
What you put after that doesn't matter, you just need to make the bean name unique, of course.
|
||||
-->
|
||||
<bean id="beforeSavePersonValidator" class="com.mycompany.domain.validators.PersonValidator"/>
|
||||
|
||||
All the events dicussed in [Handling ApplicationEvents in the REST Exporter](wiki/Handling-ApplicationEvents-in-the-REST-Exporter) can be validated.
|
||||
|
||||
If any errors are found during validation, a [RepositoryConstraintViolationException](blob/master/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryConstraintViolationException.java) will be thrown, resulting in a 400 Bad Request.
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
If you need a little more control over how the Validators are wired, you can instantiate a [ValidatingRepositoryEventListener](blob/master/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java) yourself and use a Map of Validators to their event names:
|
||||
|
||||
<bean class="org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener">
|
||||
<property name="validators">
|
||||
<map>
|
||||
<entry key="beforeSave">
|
||||
<list>
|
||||
<bean class="org.springframework.data.rest.test.webmvc.PersonValidator"/>
|
||||
</list>
|
||||
</entry>
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
@@ -1,26 +1 @@
|
||||
# Logging
|
||||
slf4jVersion = 1.6.6
|
||||
logbackVersion = 1.0.6
|
||||
|
||||
# Libraries
|
||||
springVersion = 3.1.2.RELEASE
|
||||
cglibVersion = 2.2.2
|
||||
|
||||
# Languages
|
||||
groovyVersion = 1.8.8
|
||||
|
||||
# Supporting libraries
|
||||
sdCommonsVersion = 1.4.0.RELEASE
|
||||
sdJpaVersion = 1.2.0.RELEASE
|
||||
hateoasVersion = 0.3.0.RELEASE
|
||||
jacksonVersion = 1.9.10
|
||||
hibernateVersion = 4.1.6.Final
|
||||
|
||||
# Testing
|
||||
spockVersion = 0.6-groovy-1.8
|
||||
|
||||
## OSGi ranges
|
||||
spring.range = "[3.0.7, 4.0.0)"
|
||||
jackson.range = "[1.9, 2.0.0)"
|
||||
|
||||
sdRestVersion = 1.1.0.BUILD-SNAPSHOT
|
||||
version = 1.1.0.BUILD-SNAPSHOT
|
||||
|
||||
0
gradle/ide.gradle
Normal file
0
gradle/ide.gradle
Normal file
65
gradle/maven.gradle
Normal file
65
gradle/maven.gradle
Normal file
@@ -0,0 +1,65 @@
|
||||
apply plugin: 'maven'
|
||||
|
||||
ext.optionalDeps = []
|
||||
ext.providedDeps = []
|
||||
|
||||
ext.optional = { optionalDeps << it }
|
||||
ext.provided = { providedDeps << it }
|
||||
|
||||
install {
|
||||
repositories.mavenInstaller {
|
||||
customizePom(pom, project)
|
||||
}
|
||||
}
|
||||
|
||||
def customizePom(pom, gradleProject) {
|
||||
pom.whenConfigured { generatedPom ->
|
||||
// respect 'optional' and 'provided' dependencies
|
||||
gradleProject.optionalDeps.each { dep ->
|
||||
generatedPom.dependencies.find { it.artifactId == dep.name }?.optional = true
|
||||
}
|
||||
gradleProject.providedDeps.each { dep ->
|
||||
generatedPom.dependencies.find { it.artifactId == dep.name }?.scope = 'provided'
|
||||
}
|
||||
|
||||
// eliminate test-scoped dependencies (no need in maven central poms)
|
||||
generatedPom.dependencies.removeAll { dep ->
|
||||
dep.scope == 'test'
|
||||
}
|
||||
|
||||
// add all items necessary for maven central publication
|
||||
generatedPom.project {
|
||||
name = "Spring Data REST"
|
||||
description = "Directly export Spring Data-managed PersistentEntities to the web."
|
||||
url = 'http://github.com/SpringSource/spring-data-rest'
|
||||
organization {
|
||||
name = 'SpringSource'
|
||||
url = 'http://www.springsource.org/spring-data/rest'
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache Software License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
distribution 'repo'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = 'http://github.com/SpringSource/spring-data-rest'
|
||||
connection = 'scm:git:git://github.com/SpringSource/spring-data-rest'
|
||||
developerConnection = 'scm:git:git://github.com/SpringSource/spring-data-rest'
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'jbrisbin'
|
||||
name = 'Jon Brisbin'
|
||||
email = 'jbrisbin@vmware.com'
|
||||
}
|
||||
developer {
|
||||
id = 'ogierke'
|
||||
name = 'Oliver Gierke'
|
||||
email = 'ogierke@vmware.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Wed Aug 15 14:17:51 CDT 2012
|
||||
#Wed Dec 05 08:31:21 CST 2012
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.1-bin.zip
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.3-bin.zip
|
||||
|
||||
7
gradlew
vendored
7
gradlew
vendored
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -7,6 +7,7 @@
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
GRADLE_OPTS="-XX:MaxPermSize=1024m -Xmx1024m $GRADLE_OPTS"
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
@@ -61,9 +62,9 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
181
gradlew.bat
vendored
181
gradlew.bat
vendored
@@ -1,90 +1,91 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set GRADLE_OPTS=-XX:MaxPermSize=1024m -Xmx1024m -XX:MaxHeapSize=256m %GRADLE_OPTS%
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
60
maven.gradle
60
maven.gradle
@@ -1,60 +0,0 @@
|
||||
apply plugin: 'maven'
|
||||
|
||||
ext.optionalDeps = []
|
||||
ext.providedDeps = []
|
||||
|
||||
ext.optional = { optionalDeps << it }
|
||||
ext.provided = { providedDeps << it }
|
||||
|
||||
install {
|
||||
repositories.mavenInstaller {
|
||||
customizePom(pom, project)
|
||||
}
|
||||
}
|
||||
|
||||
def customizePom(pom, gradleProject) {
|
||||
pom.whenConfigured { generatedPom ->
|
||||
// respect 'optional' and 'provided' dependencies
|
||||
gradleProject.optionalDeps.each { dep ->
|
||||
generatedPom.dependencies.find { it.artifactId == dep.name }?.optional = true
|
||||
}
|
||||
gradleProject.providedDeps.each { dep ->
|
||||
generatedPom.dependencies.find { it.artifactId == dep.name }?.scope = 'provided'
|
||||
}
|
||||
|
||||
// eliminate test-scoped dependencies (no need in maven central poms)
|
||||
generatedPom.dependencies.removeAll { dep ->
|
||||
dep.scope == 'test'
|
||||
}
|
||||
|
||||
// add all items necessary for maven central publication
|
||||
generatedPom.project {
|
||||
name = "Spring Data REST"
|
||||
description = "Directly export Spring Data managed JPA Entities to the web."
|
||||
url = 'http://github.com/SpringSource/spring-data-rest'
|
||||
organization {
|
||||
name = 'SpringSource'
|
||||
url = 'http://www.springsource.org/spring-data/rest'
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache Software License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
distribution 'repo'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = 'http://github.com/SpringSource/spring-data-rest'
|
||||
connection = 'scm:git:git://github.com/SpringSource/spring-data-rest'
|
||||
developerConnection = 'scm:git:git://github.com/SpringSource/spring-data-rest'
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'jbrisbin'
|
||||
name = 'Jon Brisbin'
|
||||
email = 'jbrisbin@vmware.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
rootProject.name = "spring-data-rest"
|
||||
|
||||
include "spring-data-rest-core",
|
||||
"spring-data-rest-repository",
|
||||
"spring-data-rest-webmvc"
|
||||
"spring-data-rest-webmvc",
|
||||
"spring-data-rest-example"
|
||||
@@ -1,6 +0,0 @@
|
||||
dependencies {
|
||||
|
||||
// Google Guava
|
||||
compile "com.google.guava:guava:12.0"
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.springframework.data.rest.core.convert;
|
||||
package org.springframework.data.rest.convert;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
@@ -7,14 +7,13 @@ import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
/**
|
||||
* This {@link ConversionService} implementation delegates the actual conversion the ConversionService if finds in its
|
||||
* internal List that claims to be able to convert a given class. It will roll through the internal <code>Stack</code>
|
||||
* of <code>ConversionService</code>s until it finds one that can convert the given type.
|
||||
* This {@link ConversionService} implementation delegates the actual conversion to the {@literal ConversionService} it
|
||||
* finds in its internal {@link Stack} that claims to be able to convert a given class. It will roll through the
|
||||
* {@literal ConversionService}s until it finds one that can convert the given type.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class DelegatingConversionService
|
||||
implements ConversionService {
|
||||
public class DelegatingConversionService implements ConversionService {
|
||||
|
||||
private Stack<ConversionService> conversionServices = new Stack<ConversionService>();
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.springframework.data.rest.convert;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class ISO8601DateConverter implements ConditionalGenericConverter,
|
||||
Converter<String[], Date> {
|
||||
|
||||
public static final ConditionalGenericConverter INSTANCE = new ISO8601DateConverter();
|
||||
|
||||
private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS = new HashSet<ConvertiblePair>();
|
||||
|
||||
static {
|
||||
CONVERTIBLE_PAIRS.add(new ConvertiblePair(String.class, Date.class));
|
||||
CONVERTIBLE_PAIRS.add(new ConvertiblePair(Date.class, String.class));
|
||||
}
|
||||
|
||||
@Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if(String.class.isAssignableFrom(sourceType.getType())) {
|
||||
return Date.class.isAssignableFrom(targetType.getType());
|
||||
}
|
||||
|
||||
return Date.class.isAssignableFrom(sourceType.getType())
|
||||
&& String.class.isAssignableFrom(targetType.getType());
|
||||
}
|
||||
|
||||
@Override public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return CONVERTIBLE_PAIRS;
|
||||
}
|
||||
|
||||
@Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
DateFormat dateFmt = iso8601DateFormat();
|
||||
if(String.class.isAssignableFrom(sourceType.getType())) {
|
||||
return dateFmt.format(source);
|
||||
} else {
|
||||
try {
|
||||
return dateFmt.parse(source.toString());
|
||||
} catch(ParseException e) {
|
||||
throw new ConversionFailedException(sourceType, targetType, source, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Date convert(String[] source) {
|
||||
if(source.length > 0) {
|
||||
try {
|
||||
return iso8601DateFormat().parse(source[0]);
|
||||
} catch(ParseException e) {
|
||||
throw new ConversionFailedException(
|
||||
TypeDescriptor.valueOf(String[].class),
|
||||
TypeDescriptor.valueOf(Date.class),
|
||||
source[0],
|
||||
new IllegalArgumentException("Source does not conform to ISO8601 date format (YYYY-MM-DDTHH:MM:SS-0000")
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private DateFormat iso8601DateFormat() {
|
||||
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.springframework.data.rest.convert;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
|
||||
/**
|
||||
* For converting a {@link UUID} into a {@link String}.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class UUIDConverter implements ConditionalGenericConverter {
|
||||
|
||||
public static final UUIDConverter INSTANCE = new UUIDConverter();
|
||||
private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS = new HashSet<ConvertiblePair>();
|
||||
|
||||
static {
|
||||
CONVERTIBLE_PAIRS.add(new ConvertiblePair(String.class, UUID.class));
|
||||
CONVERTIBLE_PAIRS.add(new ConvertiblePair(UUID.class, String.class));
|
||||
}
|
||||
|
||||
@Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if(String.class.isAssignableFrom(sourceType.getType())) {
|
||||
return UUID.class.isAssignableFrom(targetType.getType());
|
||||
}
|
||||
|
||||
return UUID.class.isAssignableFrom(sourceType.getType())
|
||||
&& String.class.isAssignableFrom(targetType.getType());
|
||||
}
|
||||
|
||||
@Override public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return CONVERTIBLE_PAIRS;
|
||||
}
|
||||
|
||||
@Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if(String.class.isAssignableFrom(sourceType.getType())) {
|
||||
return UUID.fromString(source.toString());
|
||||
} else {
|
||||
return source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* {@link org.springframework.core.convert.ConversionService} and {@link org.springframework.core.convert.converter.Converter} integration for Spring Data REST.
|
||||
*/
|
||||
package org.springframework.data.rest.convert;
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
/**
|
||||
* Generic interface used as a callback in any place you need extensibility.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface Handler<T, V> {
|
||||
|
||||
/**
|
||||
* Accept an argument and possibly produce a result.
|
||||
*
|
||||
* @param t
|
||||
* arg
|
||||
*
|
||||
* @return Some object or {@literal null} if no result.
|
||||
*/
|
||||
V handle(T t);
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for turning {@link URI}s into real objects.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public interface UriResolver<T> {
|
||||
|
||||
/**
|
||||
* Take a {@link URI} and resolve it to an actual object.
|
||||
*
|
||||
* @param baseUri
|
||||
* The base URI that this resource is relative to.
|
||||
* @param uri
|
||||
* The URI id of the resource.
|
||||
*
|
||||
* @return The resolved object or {@literal null} if not found.
|
||||
*/
|
||||
T resolve(URI baseUri, URI uri);
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.springframework.data.rest.core.convert;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class StringToUUIDConverter
|
||||
implements Converter<String, UUID> {
|
||||
@Override public UUID convert(String s) {
|
||||
return (null != s ? UUID.fromString(s) : null);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.springframework.data.rest.core.convert;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class UUIDToStringConverter
|
||||
implements Converter<UUID, String> {
|
||||
@Override public String convert(UUID uuid) {
|
||||
return (null != uuid ? uuid.toString() : null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Core components used across Spring Data REST.
|
||||
*/
|
||||
package org.springframework.data.rest.core;
|
||||
@@ -1,213 +0,0 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class BeanUtils {
|
||||
|
||||
private BeanUtils() {
|
||||
}
|
||||
|
||||
public static ConfigurableConversionService CONVERSION_SERVICE = new DefaultConversionService();
|
||||
|
||||
private static final LoadingCache<Object[], Field> fields = CacheBuilder.newBuilder().build(
|
||||
new CacheLoader<Object[], Field>() {
|
||||
@Override public Field load(Object[] key)
|
||||
throws Exception {
|
||||
Class<?> clazz = (Class<?>)key[0];
|
||||
String name = (String)key[1];
|
||||
Field f = ReflectionUtils.findField(clazz, name);
|
||||
if(null != f) {
|
||||
ReflectionUtils.makeAccessible(f);
|
||||
return f;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Field " + clazz.getName() + "." + name + " not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
private static final LoadingCache<Object[], Method> methods = CacheBuilder.newBuilder().build(
|
||||
new CacheLoader<Object[], Method>() {
|
||||
@Override public Method load(Object[] key)
|
||||
throws Exception {
|
||||
Class<?> clazz = (Class<?>)key[0];
|
||||
String name = (String)key[1];
|
||||
Integer paramCnt = key.length == 3 ? (Integer)key[2] : 0;
|
||||
|
||||
for(Method m : clazz.getDeclaredMethods()) {
|
||||
if(m.getName().equals(name)) {
|
||||
if(m.getParameterTypes().length == paramCnt) {
|
||||
ReflectionUtils.makeAccessible(m);
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Method " + clazz.getName() + "." + name + " not found");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
public static boolean hasProperty(String property, Object... objs) {
|
||||
for(Object obj : objs) {
|
||||
if(obj instanceof Map) {
|
||||
return ((Map)obj).containsKey(property);
|
||||
}
|
||||
Class<?> type = obj.getClass();
|
||||
try {
|
||||
if(FluentBeanUtils.isFluentBean(type)) {
|
||||
return null != methods.get(new Object[]{type, property});
|
||||
} else {
|
||||
if(null == methods.get(new Object[]{type, "get" + StringUtils.capitalize(property)})) {
|
||||
return null != fields.get(new Object[]{type, property});
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch(UncheckedExecutionException e) {
|
||||
if(e.getCause().getClass() == IllegalArgumentException.class) {
|
||||
return false;
|
||||
} else {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
} catch(ExecutionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static <T> T findFirst(Class<T> clazz, List<?> stack) {
|
||||
for(Object o : stack) {
|
||||
if(ClassUtils.isAssignable(clazz, o.getClass())) {
|
||||
return (T)o;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static Object findFirst(Object o, Object... objs) {
|
||||
for(Object obj : objs) {
|
||||
if(o == obj || null != o && o.equals(obj)) {
|
||||
return obj;
|
||||
} else if(obj instanceof List) {
|
||||
return Collections.binarySearch((List)obj, o);
|
||||
} else if(obj instanceof Object[]) {
|
||||
return Arrays.binarySearch((Object[])obj, o);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object findFirst(String property, Object... objs) {
|
||||
for(Object obj : objs) {
|
||||
if(obj instanceof Map) {
|
||||
return ((Map)obj).get(property);
|
||||
}
|
||||
Class<?> type = obj.getClass();
|
||||
try {
|
||||
Field f = fields.get(new Object[]{type, property});
|
||||
if(FluentBeanUtils.isFluentBean(type)) {
|
||||
return FluentBeanUtils.get(property, obj);
|
||||
} else {
|
||||
Method getter = methods.get(new Object[]{type, "get" + StringUtils.capitalize(property)});
|
||||
try {
|
||||
if(null != getter) {
|
||||
return getter.invoke(obj);
|
||||
} else {
|
||||
return f.get(obj);
|
||||
}
|
||||
} catch(IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch(InvocationTargetException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
} catch(IllegalArgumentException e) {
|
||||
} catch(ExecutionException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean containsType(Class<?> type, List<Object> objs) {
|
||||
return containsType(type, objs.toArray());
|
||||
}
|
||||
|
||||
public static boolean containsType(Class<?> type, Object[] objs) {
|
||||
for(Object obj : objs) {
|
||||
if(null != obj && ClassUtils.isAssignable(obj.getClass(), type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static Object invoke(String methodName, Object target, Object... args) {
|
||||
return invoke(methodName, target, Object.class, args);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static <T> T invoke(String methodName, Object target, Class<T> returnType, Object... args) {
|
||||
if(null == target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = target.getClass();
|
||||
try {
|
||||
Method m = methods.get(new Object[]{type, methodName, args.length});
|
||||
List<Object> newArgs = new ArrayList<Object>(args.length);
|
||||
Class<?>[] paramTypes = m.getParameterTypes();
|
||||
for(int i = 0; i < args.length; i++) {
|
||||
Object o = args[i];
|
||||
Class<?> oType = o.getClass();
|
||||
Class<?> pType = paramTypes[i];
|
||||
if(!ClassUtils.isAssignable(oType, pType)) {
|
||||
newArgs.add(CONVERSION_SERVICE.convert(o, pType));
|
||||
} else {
|
||||
newArgs.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
Object rtnVal = m.invoke(target, newArgs.toArray());
|
||||
if((returnType != Void.TYPE || returnType != Object.class)
|
||||
&& null != rtnVal
|
||||
&& !ClassUtils.isAssignable(returnType, rtnVal.getClass())) {
|
||||
return CONVERSION_SERVICE.convert(rtnVal, returnType);
|
||||
} else {
|
||||
return (T)rtnVal;
|
||||
}
|
||||
} catch(IllegalArgumentException e) {
|
||||
} catch(Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Helper methods for dealing with the metadata of "fluent" beans.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class FluentBeanUtils {
|
||||
|
||||
private static final LoadingCache<Class<?>, Metadata> metadata = CacheBuilder.newBuilder().build(
|
||||
new CacheLoader<Class<?>, Metadata>() {
|
||||
@Override public Metadata load(Class<?> type)
|
||||
throws Exception {
|
||||
final Metadata meta = new Metadata();
|
||||
ReflectionUtils.doWithFields(
|
||||
type,
|
||||
new ReflectionUtils.FieldCallback() {
|
||||
@Override public void doWith(Field field)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
final String fname = field.getName();
|
||||
if(!fname.startsWith("_")) {
|
||||
ReflectionUtils.doWithMethods(field.getDeclaringClass(), new ReflectionUtils.MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
if(method.getName().equals(fname)) {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
if(method.getParameterTypes().length == 0) {
|
||||
meta.getters.put(fname, method);
|
||||
} else if(method.getParameterTypes().length == 1) {
|
||||
meta.setters.put(fname, method);
|
||||
}
|
||||
meta.fieldNames.add(fname);
|
||||
}
|
||||
}
|
||||
});
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
meta.fields.put(fname, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Interrogate a bean and collect {@link Metadata} on it.
|
||||
*
|
||||
* @param targetType
|
||||
* The type to interrogate.
|
||||
*
|
||||
* @return {@link Metadata} for the fluent bean.
|
||||
*/
|
||||
public static Metadata metadata(Class<?> targetType) {
|
||||
try {
|
||||
return metadata.get(targetType);
|
||||
} catch(ExecutionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property of a fluent bean.
|
||||
*
|
||||
* @param property
|
||||
* Name of the property to set.
|
||||
* @param value
|
||||
* Value of the property.
|
||||
* @param bean
|
||||
* Bean on which to set this property.
|
||||
*
|
||||
* @return Usually {@literal null} but will return whatever the "setter" returns, which could be {@this} or something
|
||||
* else.
|
||||
*/
|
||||
public static Object set(String property, Object value, Object bean) {
|
||||
if(null == bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = bean.getClass();
|
||||
try {
|
||||
Method setter = metadata.get(type).setters.get(property);
|
||||
if(null != setter) {
|
||||
return setter.invoke(bean, value);
|
||||
}
|
||||
|
||||
Field f = metadata.get(type).fields.get(property);
|
||||
if(null == f) {
|
||||
return null;
|
||||
}
|
||||
|
||||
f.set(bean, value);
|
||||
|
||||
return bean;
|
||||
} catch(Throwable t) {
|
||||
throw new IllegalArgumentException(t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a property.
|
||||
*
|
||||
* @param property
|
||||
* Name of the property.
|
||||
* @param bean
|
||||
* Bean of which to get the property.
|
||||
*
|
||||
* @return Value of the property. Could be {@literal null}
|
||||
*/
|
||||
public static Object get(String property, Object bean) {
|
||||
if(null == bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = bean.getClass();
|
||||
try {
|
||||
Method getter = metadata.get(type).getters.get(property);
|
||||
if(null != getter) {
|
||||
return getter.invoke(bean);
|
||||
}
|
||||
|
||||
Field f = metadata.get(type).fields.get(property);
|
||||
if(null == f) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return f.get(bean);
|
||||
} catch(Throwable t) {
|
||||
throw new IllegalStateException(t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a given type looks like a fluent bean. That means it has methods whose names exactly correspond
|
||||
* to a field of the same name. A "getter" is that method which is named the same as the field and has 0 parameters.
|
||||
* The "setter" is that method which is named the same as the field and has a single argument.
|
||||
*
|
||||
* @param type
|
||||
* The class to inspect.
|
||||
*
|
||||
* @return {@literal true} if this looks like a fluent bean, {@literal false} otherwise.
|
||||
*/
|
||||
public static boolean isFluentBean(Class<?> type) {
|
||||
try {
|
||||
return metadata.get(type).getters.size() > 0;
|
||||
} catch(ExecutionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Metadata {
|
||||
List<String> fieldNames = new ArrayList<String>();
|
||||
Map<String, Field> fields = new HashMap<String, Field>();
|
||||
Map<String, Method> getters = new HashMap<String, Method>();
|
||||
Map<String, Method> setters = new HashMap<String, Method>();
|
||||
|
||||
public List<String> fieldNames() {
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
public Map<String, Method> getters() {
|
||||
return getters;
|
||||
}
|
||||
|
||||
public Map<String, Method> setters() {
|
||||
return setters;
|
||||
}
|
||||
|
||||
public Map<String, Field> fields() {
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RestHelper<T> {
|
||||
|
||||
public HttpStatus status;
|
||||
public HttpHeaders headers = new HttpHeaders();
|
||||
public T body;
|
||||
|
||||
private RestHelper(T body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static <T> RestHelper<T> resource(T body) {
|
||||
return new RestHelper<T>(body);
|
||||
}
|
||||
|
||||
public RestHelper<T> header(String key, String value) {
|
||||
headers.add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestHelper<T> status(HttpStatus status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpEntity<T> asHttpEntity() {
|
||||
return new HttpEntity<T>(body, headers);
|
||||
}
|
||||
|
||||
public ResponseEntity<T> asResponseEntity() {
|
||||
return new ResponseEntity<T>(body, headers, status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import com.google.common.base.Function;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@@ -37,7 +37,7 @@ public abstract class UriUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link Handler} for each segment in the {@link URI}.
|
||||
* Execute the given {@link Function} for each segment in the {@link URI}.
|
||||
* <p>e.g. given a URI of {@literal http://localhost:8080/data/person/1} and a base URI of {@code
|
||||
* http://localhost:8080/data}, this method will explode the URI into it's components, as compared to the base URI.
|
||||
* The result would be: the given handler gets called twice, once passing a relative {@link URI} of "person" and a
|
||||
@@ -47,19 +47,19 @@ public abstract class UriUtils {
|
||||
* @param baseUri
|
||||
* base {@link URI}
|
||||
* @param uri
|
||||
* {@link URI} to explode and iteratre over.
|
||||
* {@link URI} to explode and iterate over.
|
||||
* @param handler
|
||||
* {@link Handler} to call for each segment of the URI's path.
|
||||
* {@link Function} to call for each segment of the URI's path.
|
||||
* @param <V>
|
||||
* Return type of the handler.
|
||||
*
|
||||
* @return Handler return value, or possibly {@literal null}.
|
||||
*/
|
||||
public static <V> V foreach(URI baseUri, URI uri, Handler<URI, V> handler) {
|
||||
public static <V> V foreach(URI baseUri, URI uri, Function<URI, V> handler) {
|
||||
List<URI> uris = explode(baseUri, uri);
|
||||
V v = null;
|
||||
for(URI u : uris) {
|
||||
v = handler.handle(u);
|
||||
v = handler.apply(u);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@@ -163,8 +163,9 @@ public abstract class UriUtils {
|
||||
* Just the path portion of the {@link URI}, but with any trailing slash "/" removed.
|
||||
*
|
||||
* @param uri
|
||||
* path URI
|
||||
*
|
||||
* @return
|
||||
* @return the path portion of the URI, but with any trailing slash removed
|
||||
*/
|
||||
public static String path(URI uri) {
|
||||
if(null == uri) {
|
||||
@@ -197,9 +198,11 @@ public abstract class UriUtils {
|
||||
* Create a new {@link URI} out of the components.
|
||||
*
|
||||
* @param baseUri
|
||||
* The base URI these path segments are relative to.
|
||||
* @param pathSegments
|
||||
* The path segments to add to the given base URI.
|
||||
*
|
||||
* @return
|
||||
* @return A new URI built from the given base URI and additional path segments.
|
||||
*/
|
||||
public static URI buildUri(URI baseUri, String... pathSegments) {
|
||||
return UriComponentsBuilder.fromUri(baseUri).pathSegment(pathSegments).build().toUri();
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package org.springframework.data.rest.core.spec
|
||||
|
||||
import org.springframework.data.rest.core.util.UriUtils
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
class UriUtilsSpec extends Specification {
|
||||
|
||||
def "merges URIs correctly"() {
|
||||
|
||||
given:
|
||||
// (absolute) URI of the base resource
|
||||
def baseUri = new URI("http://localhost:8080/baseUrl")
|
||||
// (relative) URI of the top-level Resource
|
||||
def uri2 = new URI("resource")
|
||||
// (relative) URI of the second-level Resource
|
||||
def uri3 = new URI("1")
|
||||
// (fragment) URI of the bottom-level Resource
|
||||
def uri4 = new URI("count")
|
||||
|
||||
when:
|
||||
def uri5 = UriUtils.merge(baseUri, uri2, uri3, uri4)
|
||||
|
||||
then:
|
||||
uri5.toString() == "http://localhost:8080/baseUrl/resource/1/count"
|
||||
|
||||
}
|
||||
|
||||
def "explodes URIs correctly"() {
|
||||
|
||||
given:
|
||||
// (absolute) URI of the base resource
|
||||
def baseUri = new URI("http://localhost:8080/baseUrl")
|
||||
// (absolute) URI of the full resource to get a path to
|
||||
def resourceUri = new URI("http://localhost:8080/baseUrl/resource/1/property")
|
||||
|
||||
when:
|
||||
def uris = UriUtils.explode(baseUri, resourceUri)
|
||||
|
||||
then:
|
||||
uris.size() == 3
|
||||
uris[2].path == "property"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.data.rest;
|
||||
|
||||
import org.jmock.integration.junit4.JMock;
|
||||
import org.jmock.integration.junit4.JUnit4Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Abstract base classes for JUnit tests that use JMock.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@RunWith(JMock.class)
|
||||
public abstract class AbstractJMockTests {
|
||||
|
||||
protected JUnit4Mockery context = new JUnit4Mockery() {{
|
||||
setImposteriser(ClassImposteriser.INSTANCE);
|
||||
}};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.springframework.data.rest.convert;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.rest.AbstractJMockTests;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
|
||||
/**
|
||||
* Tests to ensure the {@link DelegatingConversionService} properly delegates conversions to the {@link
|
||||
* org.springframework.core.convert.ConversionService} that is appropriate for the given source and return types.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class DelegatingConversionServiceUnitTests extends AbstractJMockTests {
|
||||
|
||||
private static final UUID RANDOM_UUID = UUID.fromString("9deccfd7-f892-4e26-a4d5-c92893392e78");
|
||||
|
||||
private ConversionService conversionService;
|
||||
private DelegatingConversionService delegatingConversionService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
conversionService = context.mock(ConversionService.class);
|
||||
|
||||
DefaultFormattingConversionService cs = new DefaultFormattingConversionService(false);
|
||||
cs.addConverter(UUIDConverter.INSTANCE);
|
||||
|
||||
delegatingConversionService = new DelegatingConversionService(
|
||||
conversionService,
|
||||
cs
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
allowing(conversionService).canConvert(String.class, UUID.class);
|
||||
will(returnValue(false));
|
||||
allowing(conversionService).canConvert(UUID.class, String.class);
|
||||
will(returnValue(false));
|
||||
|
||||
// Ensure the first ConversionService is never asked to convert this String into a UUID
|
||||
never(conversionService).convert(with(any(String.class)), with(UUID.class));
|
||||
never(conversionService).convert(with(any(UUID.class)), with(String.class));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDelegateToProperConversionService() throws Exception {
|
||||
assertThat(delegatingConversionService.canConvert(String.class, UUID.class), is(true));
|
||||
assertThat(delegatingConversionService.convert(RANDOM_UUID.toString(), UUID.class), is(RANDOM_UUID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConvertUUIDToString() throws Exception {
|
||||
assertThat(delegatingConversionService.canConvert(UUID.class, String.class), is(true));
|
||||
assertThat(delegatingConversionService.convert(RANDOM_UUID, String.class), is(RANDOM_UUID.toString()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests to verify that {@link UriUtils} can manipulate {@link URI}s.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class UriUtilsUnitTests {
|
||||
|
||||
private static final String BASE_URI_STR = "http://localhost:8080/data";
|
||||
private static final URI BASE_URI = URI.create(BASE_URI_STR);
|
||||
|
||||
private static final String PERSON_2LVL_STR = BASE_URI_STR + "/person/1";
|
||||
private static final URI PERSON_2LVL_URI = URI.create(PERSON_2LVL_STR);
|
||||
|
||||
@Test
|
||||
public void shouldValidateBaseURI() throws Exception {
|
||||
URI uri = new URI(BASE_URI + "/person/1");
|
||||
|
||||
assertThat(UriUtils.validBaseUri(BASE_URI, uri), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldIterateOverPathElements() throws Exception {
|
||||
final List<String> paths = new ArrayList<String>();
|
||||
Function<URI, Void> fn = new Function<URI, Void>() {
|
||||
@Override public Void apply(URI uri) {
|
||||
paths.add(uri.getPath());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
UriUtils.foreach(BASE_URI, PERSON_2LVL_URI, fn);
|
||||
|
||||
assertThat(paths, hasSize(2));
|
||||
assertThat(paths, contains("person", "1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldExplodeRelativeURI() throws Exception {
|
||||
Stack<URI> uris = UriUtils.explode(BASE_URI, PERSON_2LVL_URI);
|
||||
|
||||
assertThat(uris, hasSize(2));
|
||||
assertThat(uris, contains(URI.create("person"), URI.create("1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMergeDifferentURIsIntoOne() throws Exception {
|
||||
String qrystr = "?queryParam=testValue";
|
||||
|
||||
URI uriWithQuery = URI.create(qrystr);
|
||||
URI uriWithPath = URI.create("person/1");
|
||||
|
||||
URI uri = UriUtils.merge(BASE_URI, uriWithPath, uriWithQuery);
|
||||
|
||||
assertThat(uri.toString(), is(PERSON_2LVL_STR + qrystr));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldStripTrailingSlashFromPath() throws Exception {
|
||||
URI uri = URI.create("person/");
|
||||
|
||||
String path = UriUtils.path(uri);
|
||||
|
||||
assertThat(path, is("person"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldStripTheLastPathSegmentFromAURI() throws Exception {
|
||||
URI uri = UriUtils.tail(BASE_URI, PERSON_2LVL_URI);
|
||||
|
||||
assertThat(uri, is(URI.create("1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldBuildURIFromPathSegments() throws Exception {
|
||||
URI uri = UriUtils.buildUri(BASE_URI, "person", "1");
|
||||
|
||||
assertThat(uri, is(PERSON_2LVL_URI));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,8 +8,7 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.data.services" level="DEBUG"/>
|
||||
<logger name="org.springframework" level="INFO"/>
|
||||
<logger name="org.springframework.data.rest" level="DEBUG"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.springframework.data.rest.example;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@ImportResource("classpath:")
|
||||
public class RestExporterExampleConfig {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.springframework.data.rest.example;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.data.rest.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.example.jpa.Person;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@ImportResource("classpath:META-INF/spring/security-config.xml")
|
||||
public class RestExporterExampleRestConfig extends RepositoryRestMvcConfiguration {
|
||||
|
||||
@Override protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
|
||||
config.addResourceMappingForDomainType(Person.class)
|
||||
.addResourceMappingFor("lastName")
|
||||
.setPath("surname");
|
||||
config.addResourceMappingForDomainType(Person.class)
|
||||
.addResourceMappingFor("siblings")
|
||||
.setRel("siblings")
|
||||
.setPath("siblings");
|
||||
}
|
||||
|
||||
// @Bean public ResourceProcessor<Resource<?>> globalResourceProcessor() {
|
||||
// return new ResourceProcessor<Resource<?>>() {
|
||||
// @Override public Resource<?> process(Resource<?> resource) {
|
||||
// resource.add(new Link("href", "rel"));
|
||||
// return resource;
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.springframework.data.rest.example;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRegistration;
|
||||
|
||||
import org.springframework.data.rest.example.gemfire.GemfireRepositoryConfig;
|
||||
import org.springframework.data.rest.example.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.data.rest.example.mongodb.MongoDbRepositoryConfig;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestDispatcherServlet;
|
||||
import org.springframework.web.WebApplicationInitializer;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RestExporterWebInitializer implements WebApplicationInitializer {
|
||||
|
||||
@Override public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
AnnotationConfigWebApplicationContext rootCtx = new AnnotationConfigWebApplicationContext();
|
||||
rootCtx.register(
|
||||
JpaRepositoryConfig.class,
|
||||
MongoDbRepositoryConfig.class,
|
||||
GemfireRepositoryConfig.class
|
||||
);
|
||||
|
||||
servletContext.addListener(new ContextLoaderListener(rootCtx));
|
||||
servletContext.addFilter("springSecurity", DelegatingFilterProxy.class);
|
||||
servletContext.getFilterRegistration("springSecurity").addMappingForUrlPatterns(
|
||||
EnumSet.of(DispatcherType.REQUEST),
|
||||
false,
|
||||
"/*"
|
||||
);
|
||||
|
||||
AnnotationConfigWebApplicationContext webCtx = new AnnotationConfigWebApplicationContext();
|
||||
webCtx.register(RestExporterExampleRestConfig.class);
|
||||
|
||||
RepositoryRestDispatcherServlet dispatcherServlet = new RepositoryRestDispatcherServlet(webCtx);
|
||||
ServletRegistration.Dynamic reg = servletContext.addServlet("rest-exporter", dispatcherServlet);
|
||||
reg.setLoadOnStartup(1);
|
||||
reg.addMapping("/*");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
|
||||
|
||||
/**
|
||||
* Spring JavaConfig configuration class to setup a Spring container and infrastructure components.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
*/
|
||||
@Configuration
|
||||
@ImportResource("classpath:META-INF/spring/cache-config.xml")
|
||||
@EnableGemfireRepositories
|
||||
public class GemfireRepositoryConfig {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.core;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
/**
|
||||
* Base class for persistent classes.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class AbstractPersistentEntity {
|
||||
|
||||
@Id
|
||||
private final Long id;
|
||||
|
||||
/**
|
||||
* Returns the identifier of the entity.
|
||||
*
|
||||
* @return the id
|
||||
*/
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
protected AbstractPersistentEntity(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
protected AbstractPersistentEntity() {
|
||||
this.id = null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AbstractPersistentEntity that = (AbstractPersistentEntity) obj;
|
||||
|
||||
return this.id.equals(that.getId());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id == null ? 0 : id.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.core;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An address.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class Address {
|
||||
|
||||
private final String street, city, country;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Address} from the given street, city and country.
|
||||
*
|
||||
* @param street must not be {@literal null} or empty.
|
||||
* @param city must not be {@literal null} or empty.
|
||||
* @param country must not be {@literal null} or empty.
|
||||
*/
|
||||
public Address(String street, String city, String country) {
|
||||
|
||||
Assert.hasText(street, "Street must not be null or empty!");
|
||||
Assert.hasText(city, "City must not be null or empty!");
|
||||
Assert.hasText(country, "Country must not be null or empty!");
|
||||
|
||||
this.street = street;
|
||||
this.city = city;
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the current {@link Address} instance which is a new entity in terms of persistence.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Address getCopy() {
|
||||
return new Address(this.street, this.city, this.country);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the street.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the city.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the country.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.core;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import org.springframework.data.gemfire.mapping.Region;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* A customer.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
*/
|
||||
@Region
|
||||
public class Customer extends AbstractPersistentEntity {
|
||||
private EmailAddress emailAddress;
|
||||
private String firstname, lastname;
|
||||
private Set<Address> addresses = new HashSet<Address>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link Customer} from the given parameters.
|
||||
*
|
||||
* @param id
|
||||
* the unique id;
|
||||
* @param emailAddress
|
||||
* must not be {@literal null} or empty.
|
||||
* @param firstname
|
||||
* must not be {@literal null} or empty.
|
||||
* @param lastname
|
||||
* must not be {@literal null} or empty.
|
||||
*/
|
||||
public Customer(Long id, EmailAddress emailAddress, String firstname, String lastname) {
|
||||
super(id);
|
||||
Assert.hasText(firstname);
|
||||
Assert.hasText(lastname);
|
||||
Assert.notNull(emailAddress);
|
||||
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
|
||||
protected Customer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link Address} to the {@link Customer}.
|
||||
*
|
||||
* @param address
|
||||
* must not be {@literal null}.
|
||||
*/
|
||||
public void add(Address address) {
|
||||
|
||||
Assert.notNull(address);
|
||||
this.addresses.add(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the firstname of the {@link Customer}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getFirstname() {
|
||||
return firstname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the firstname of the {@link Customer}.
|
||||
*
|
||||
* @param firstname
|
||||
*/
|
||||
public void setFirstname(String firstname) {
|
||||
this.firstname = firstname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lastname of the {@link Customer}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getLastname() {
|
||||
return lastname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lastname of the {@link Customer}.
|
||||
*
|
||||
* @param lastname
|
||||
*/
|
||||
public void setLastname(String lastname) {
|
||||
this.lastname = lastname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link EmailAddress} of the {@link Customer}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public EmailAddress getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the emailAddress of the {@link Customer}.
|
||||
*
|
||||
* @param emailAddress
|
||||
*/
|
||||
public void setEmailAddress(EmailAddress emailAddress) {
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Customer}'s addresses.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<Address> getAddresses() {
|
||||
return Collections.unmodifiableSet(addresses);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* Repository interface to access {@link Customer}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
*/
|
||||
|
||||
public interface CustomerRepository extends CrudRepository<Customer, Long> {
|
||||
|
||||
/**
|
||||
* Finds all {@link Customer}s with the given lastname.
|
||||
*
|
||||
* @param lastname
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<Customer> findByLastname(@Param("lastname") String lastname);
|
||||
|
||||
/**
|
||||
* Finds the Customer with the given {@link EmailAddress}.
|
||||
*
|
||||
* @param emailAddress
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Customer findByEmailAddress(@Param("email") EmailAddress emailAddress);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.core;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Value object to represent email addresses.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
public final class EmailAddress {
|
||||
|
||||
private static final String EMAIL_REGEX = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
|
||||
private static final Pattern PATTERN = Pattern.compile(EMAIL_REGEX);
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EmailAddress} from the given {@link String} representation.
|
||||
*
|
||||
* @param emailAddress
|
||||
* must not be {@literal null} or empty.
|
||||
*/
|
||||
@JsonCreator
|
||||
public EmailAddress(String emailAddress) {
|
||||
Assert.isTrue(isValid(emailAddress), "Invalid email address!");
|
||||
this.value = emailAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link String} is a valid {@link EmailAddress} which means you can safely instantiate
|
||||
* the
|
||||
* class.
|
||||
*
|
||||
* @param candidate
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static boolean isValid(String candidate) {
|
||||
return candidate == null ? false : PATTERN.matcher(candidate).matches();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if(this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!(obj instanceof EmailAddress)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EmailAddress that = (EmailAddress)obj;
|
||||
return this.value.equals(that.value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Component
|
||||
static class EmailAddressToStringConverter implements Converter<EmailAddress, String> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public String convert(EmailAddress source) {
|
||||
return source == null ? null : source.value;
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
static class StringToEmailAddressConverter implements Converter<String, EmailAddress> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
public EmailAddress convert(String source) {
|
||||
return StringUtils.hasText(source) ? new EmailAddress(source) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.core;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.gemfire.mapping.Region;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* A product.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
*/
|
||||
@Region
|
||||
public class Product extends AbstractPersistentEntity {
|
||||
|
||||
private String name, description;
|
||||
private BigDecimal price;
|
||||
private Map<String, String> attributes = new HashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link Product} with the given name.
|
||||
*
|
||||
* @param id
|
||||
* a unique Id
|
||||
* @param name
|
||||
* must not be {@literal null} or empty.
|
||||
* @param price
|
||||
* must not be {@literal null} or less than or equal to zero.
|
||||
*/
|
||||
public Product(Long id, String name, BigDecimal price) {
|
||||
this(id, name, price, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Product} from the given name and description.
|
||||
*
|
||||
* @param id
|
||||
* a unique Id
|
||||
* @param name
|
||||
* must not be {@literal null} or empty.
|
||||
* @param price
|
||||
* must not be {@literal null} or less than or equal to zero.
|
||||
* @param description
|
||||
*/
|
||||
@PersistenceConstructor
|
||||
public Product(Long id, String name, BigDecimal price, String description) {
|
||||
super(id);
|
||||
Assert.hasText(name, "Name must not be null or empty!");
|
||||
Assert.isTrue(BigDecimal.ZERO.compareTo(price) < 0, "Price must be greater than zero!");
|
||||
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
protected Product() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attribute with the given name to the given value.
|
||||
*
|
||||
* @param name
|
||||
* must not be {@literal null} or empty.
|
||||
* @param value
|
||||
*/
|
||||
public void setAttribute(String name, String value) {
|
||||
|
||||
Assert.hasText(name);
|
||||
|
||||
if(value == null) {
|
||||
this.attributes.remove(value);
|
||||
} else {
|
||||
this.attributes.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Product}'s name.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Product}'s description.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the custom attributes of the {@link Product}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
return Collections.unmodifiableMap(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the price of the {@link Product}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public BigDecimal getPrice() {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.gemfire.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
|
||||
/**
|
||||
* Repository interface to access {@link Product}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David TuranskiGem
|
||||
*/
|
||||
public interface ProductRepository extends CrudRepository<Product, Long> {
|
||||
|
||||
/**
|
||||
* Returns a list of {@link Product}s having a description which contains the given snippet.
|
||||
* @param the search string
|
||||
* @return
|
||||
*/
|
||||
|
||||
List<Product> findByDescriptionContaining(String description);
|
||||
|
||||
/**
|
||||
* Returns all {@link Product}s having the given attribute value.
|
||||
* @param attribute
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
@Query("SELECT * FROM /Product where attributes[$1] = $2")
|
||||
List<Product> findByAttributes(String key, String value);
|
||||
|
||||
List<Product> findByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.order;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.springframework.data.rest.example.gemfire.core.Product;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class LineItem {
|
||||
|
||||
private BigDecimal price;
|
||||
private int amount;
|
||||
private Long productId;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LineItem} for the given {@link Product}.
|
||||
* @param product must not be {@literal null}.
|
||||
*/
|
||||
public LineItem(Product product) {
|
||||
this(product, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LineItem} for the given {@link Product} and amount.
|
||||
* @param product must not be {@literal null}.
|
||||
* @param amount
|
||||
*/
|
||||
public LineItem(Product product, int amount) {
|
||||
Assert.notNull(product, "The given Product must not be null!");
|
||||
Assert.isTrue(amount > 0, "The amount of Products to be bought must be greater than 0!");
|
||||
|
||||
this.productId = product.getId();
|
||||
this.amount = amount;
|
||||
this.price = product.getPrice();
|
||||
}
|
||||
|
||||
protected LineItem() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the {@link Product} the {@link LineItem} refers to.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Long getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of {@link Product}s to be ordered.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the price a single unit of the {@link LineItem}'s product.
|
||||
*
|
||||
* @return the price
|
||||
*/
|
||||
public BigDecimal getUnitPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total for the {@link LineItem}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public BigDecimal getTotal() {
|
||||
return price.multiply(BigDecimal.valueOf(amount));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.order;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.gemfire.mapping.Region;
|
||||
import org.springframework.data.rest.example.gemfire.core.AbstractPersistentEntity;
|
||||
import org.springframework.data.rest.example.gemfire.core.Address;
|
||||
import org.springframework.data.rest.example.gemfire.core.Customer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
*/
|
||||
@Region
|
||||
public class Order extends AbstractPersistentEntity {
|
||||
|
||||
private Long customerId;
|
||||
private Address billingAddress;
|
||||
private Address shippingAddress;
|
||||
private Set<LineItem> lineItems = new HashSet<LineItem>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link Order} for the given {@link Customer}.
|
||||
*
|
||||
* @param customer
|
||||
* must not be {@literal null}.
|
||||
*/
|
||||
public Order(Long id, Long customerId, Address shippingAddress) {
|
||||
super(id);
|
||||
Assert.notNull(customerId);
|
||||
Assert.notNull(shippingAddress);
|
||||
|
||||
this.customerId = customerId;
|
||||
this.shippingAddress = shippingAddress;
|
||||
}
|
||||
|
||||
protected Order() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link LineItem} to the {@link Order}.
|
||||
*
|
||||
* @param lineItem
|
||||
*/
|
||||
public void add(LineItem lineItem) {
|
||||
this.lineItems.add(lineItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the {@link Customer} who placed the {@link Order}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the billing {@link Address} for this order.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Address getBillingAddress() {
|
||||
return billingAddress != null ? billingAddress : shippingAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shipping {@link Address} for this order;
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Address getShippingAddress() {
|
||||
return shippingAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link LineItem}s currently belonging to the {@link Order}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<LineItem> getLineItems() {
|
||||
return Collections.unmodifiableSet(lineItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total of the {@link Order}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public BigDecimal getTotal() {
|
||||
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
|
||||
for(LineItem item : lineItems) {
|
||||
total = total.add(item.getTotal());
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.example.gemfire.order;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
*/
|
||||
public interface OrderRepository extends CrudRepository<Order, Long> {
|
||||
List<Order> findByCustomerId(Long customerId);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.springframework.data.rest.repository.test;
|
||||
package org.springframework.data.rest.example.jpa;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
@@ -7,7 +7,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.orm.jpa.JpaDialect;
|
||||
@@ -23,10 +22,10 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = {ApplicationConfig.class})
|
||||
@ComponentScan(basePackageClasses = JpaRepositoryConfig.class)
|
||||
@EnableJpaRepositories
|
||||
@EnableTransactionManagement
|
||||
public class ApplicationConfig {
|
||||
public class JpaRepositoryConfig {
|
||||
|
||||
@Bean public DataSource dataSource() {
|
||||
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
|
||||
@@ -42,7 +41,6 @@ public class ApplicationConfig {
|
||||
factory.setJpaVendorAdapter(vendorAdapter);
|
||||
factory.setPackagesToScan(getClass().getPackage().getName());
|
||||
factory.setDataSource(dataSource());
|
||||
factory.setPersistenceXmlLocation("/JpaMetadataSpec-persistence.xml");
|
||||
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
@@ -59,8 +57,4 @@ public class ApplicationConfig {
|
||||
return txManager;
|
||||
}
|
||||
|
||||
@Bean public JpaRepositoryExporter jpaRepositoryExporter() {
|
||||
return new JpaRepositoryExporter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package org.springframework.data.rest.example.jpa;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.PrePersist;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.springframework.data.rest.repository.annotation.Description;
|
||||
|
||||
/**
|
||||
* An entity that represents a person.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
private Long id;
|
||||
@Description("A person's first name")
|
||||
private String firstName;
|
||||
@Description("A person's last name")
|
||||
private String lastName;
|
||||
@Description("A person's siblings")
|
||||
private List<Person> siblings = Collections.emptyList();
|
||||
private Person father;
|
||||
@Description("Timestamp this person object was created")
|
||||
private Date created;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
@Id @GeneratedValue public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Person addSibling(Person p) {
|
||||
if(siblings == Collections.EMPTY_LIST) {
|
||||
siblings = new ArrayList<Person>();
|
||||
}
|
||||
siblings.add(p);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ManyToMany public List<Person> getSiblings() {
|
||||
return siblings;
|
||||
}
|
||||
|
||||
public void setSiblings(List<Person> siblings) {
|
||||
this.siblings = siblings;
|
||||
}
|
||||
|
||||
@ManyToOne public Person getFather() {
|
||||
return father;
|
||||
}
|
||||
|
||||
public void setFather(Person father) {
|
||||
this.father = father;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
}
|
||||
|
||||
@PrePersist
|
||||
private void prePersist() {
|
||||
this.created = Calendar.getInstance().getTime();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.springframework.data.rest.example.jpa;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Component
|
||||
public class PersonLoader implements InitializingBean {
|
||||
|
||||
@Autowired
|
||||
PersonRepository people;
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
Person billyBob = people.save(new Person("Billy Bob", "Thornton"));
|
||||
|
||||
Person john = new Person("John", "Doe");
|
||||
Person jane = new Person("Jane", "Doe");
|
||||
john.addSibling(jane);
|
||||
john.setFather(billyBob);
|
||||
jane.addSibling(john);
|
||||
jane.setFather(billyBob);
|
||||
|
||||
people.save(Arrays.asList(john, jane));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.springframework.data.rest.example.jpa;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.convert.ISO8601DateConverter;
|
||||
import org.springframework.data.rest.repository.annotation.ConvertWith;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
|
||||
/**
|
||||
* A repository to manage {@link Person}s.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@RestResource(rel = "people", path = "people")
|
||||
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
|
||||
|
||||
@RestResource(rel = "firstname", path = "firstname")
|
||||
public Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
|
||||
|
||||
public Person findFirstPersonByFirstName(@Param("firstName") String firstName);
|
||||
|
||||
public Page<Person> findByCreatedGreaterThan(@Param("date") Date date, Pageable pageable);
|
||||
|
||||
@Query("select p from Person p where p.created > :date")
|
||||
public Page<Person> findByCreatedUsingISO8601Date(@Param("date")
|
||||
@ConvertWith(
|
||||
ISO8601DateConverter.class)
|
||||
Date date,
|
||||
Pageable pageable);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.springframework.data.rest.example.mongodb;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import com.mongodb.Mongo;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
|
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = MongoDbRepositoryConfig.class)
|
||||
@EnableMongoRepositories
|
||||
public class MongoDbRepositoryConfig {
|
||||
|
||||
@Bean public MongoDbFactory mongoDbFactory() throws UnknownHostException {
|
||||
return new SimpleMongoDbFactory(new Mongo("localhost"), "spring-data-rest-example");
|
||||
}
|
||||
|
||||
@Bean public MongoTemplate mongoTemplate() throws UnknownHostException {
|
||||
return new MongoTemplate(mongoDbFactory());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.springframework.data.rest.example.mongodb;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Document
|
||||
public class Profile {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
private Long person;
|
||||
private String type;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Profile setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Long getPerson() {
|
||||
return person;
|
||||
}
|
||||
|
||||
public Profile setPerson(Long person) {
|
||||
this.person = person;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Profile setType(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.rest.example.mongodb;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public interface ProfileRepository extends CrudRepository<Profile, String> {
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
|
||||
<persistence-unit name="jpa.sample">
|
||||
<class>org.springframework.data.rest.repository.test.Person</class>
|
||||
<class>org.springframework.data.rest.repository.test.Family</class>
|
||||
<properties>
|
||||
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
|
||||
<property name="hibernate.connection.url" value="jdbc:hsqldb:mem:spring"/>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<gfe:cache use-bean-factory-locator="false"/>
|
||||
|
||||
<gfe:replicated-region id="Customer"/>
|
||||
<gfe:replicated-region id="Order"/>
|
||||
<gfe:replicated-region id="Product"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:security="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
|
||||
|
||||
|
||||
|
||||
</beans>
|
||||
@@ -8,9 +8,7 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.data.rest.auditlog" level="INFO"/>
|
||||
<logger name="org.springframework.data.rest" level="DEBUG"/>
|
||||
<logger name="org.springframework.data" level="INFO"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
@@ -1,24 +0,0 @@
|
||||
dependencies {
|
||||
|
||||
// Spring
|
||||
//compile("org.springframework:spring-orm:$springVersion") { force = true }
|
||||
//compile("org.springframework:spring-oxm:$springVersion") { force = true }
|
||||
|
||||
// JPA
|
||||
compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final"
|
||||
|
||||
// Spring Data
|
||||
//compile "org.springframework.data:spring-data-commons-core:$sdCommonsVersion"
|
||||
compile "org.springframework.data:spring-data-jpa:$sdJpaVersion"
|
||||
|
||||
// Spring HATEOAS
|
||||
compile "org.springframework.hateoas:spring-hateoas:$hateoasVersion"
|
||||
|
||||
// Exporter core
|
||||
compile project(":spring-data-rest-core")
|
||||
|
||||
// Testing
|
||||
testCompile "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
testCompile "org.hsqldb:hsqldb:2.2.8"
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
package org.springframework.data.rest.config;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -10,15 +11,10 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Central configuration helper class for the REST exporter. If something within the REST exporter is configurable,
|
||||
* there is a property here you can use to set the value.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RepositoryRestConfiguration {
|
||||
|
||||
public static final RepositoryRestConfiguration DEFAULT = new RepositoryRestConfiguration();
|
||||
|
||||
private URI baseUri = null;
|
||||
private int defaultPageSize = 20;
|
||||
private String pageParamName = "page";
|
||||
@@ -30,11 +26,14 @@ public class RepositoryRestConfiguration {
|
||||
private Map<Class<?>, Class<?>> typeMappings = Collections.emptyMap();
|
||||
private MediaType defaultMediaType = MediaType.APPLICATION_JSON;
|
||||
private boolean dumpErrors = true;
|
||||
private List<Class<?>> exposeIdsFor = new ArrayList<Class<?>>();
|
||||
private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration();
|
||||
private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration();
|
||||
|
||||
/**
|
||||
* The base URI against which the exporter should calculate its links.
|
||||
*
|
||||
* @return
|
||||
* @return The base URI.
|
||||
*/
|
||||
public URI getBaseUri() {
|
||||
return baseUri;
|
||||
@@ -44,6 +43,7 @@ public class RepositoryRestConfiguration {
|
||||
* The base URI against which the exporter should calculate its links.
|
||||
*
|
||||
* @param baseUri
|
||||
* The base URI.
|
||||
*/
|
||||
public RepositoryRestConfiguration setBaseUri(URI baseUri) {
|
||||
Assert.notNull(baseUri, "The baseUri cannot be null.");
|
||||
@@ -54,7 +54,7 @@ public class RepositoryRestConfiguration {
|
||||
/**
|
||||
* Get the default size of {@link org.springframework.data.domain.Pageable}s. Default is 20.
|
||||
*
|
||||
* @return
|
||||
* @return The default page size.
|
||||
*/
|
||||
public int getDefaultPageSize() {
|
||||
return defaultPageSize;
|
||||
@@ -64,8 +64,9 @@ public class RepositoryRestConfiguration {
|
||||
* Set the default size of {@link org.springframework.data.domain.Pageable}s.
|
||||
*
|
||||
* @param defaultPageSize
|
||||
* The default page size.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setDefaultPageSize(int defaultPageSize) {
|
||||
Assert.isTrue((defaultPageSize > 0), "Page size must be greater than 0.");
|
||||
@@ -76,7 +77,7 @@ public class RepositoryRestConfiguration {
|
||||
/**
|
||||
* Get the name of the URL query string parameter that indicates what page to return. Default is 'page'.
|
||||
*
|
||||
* @return
|
||||
* @return Name of the query parameter used to indicate the page number to return.
|
||||
*/
|
||||
public String getPageParamName() {
|
||||
return pageParamName;
|
||||
@@ -86,8 +87,9 @@ public class RepositoryRestConfiguration {
|
||||
* Set the name of the URL query string parameter that indicates what page to return.
|
||||
*
|
||||
* @param pageParamName
|
||||
* Name of the query parameter used to indicate the page number to return.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setPageParamName(String pageParamName) {
|
||||
Assert.notNull(pageParamName, "Page param name cannot be null.");
|
||||
@@ -99,7 +101,7 @@ public class RepositoryRestConfiguration {
|
||||
* Get the name of the URL query string parameter that indicates how many results to return at once. Default is
|
||||
* 'limit'.
|
||||
*
|
||||
* @return
|
||||
* @return Name of the query parameter used to indicate the maximum number of entries to return at a time.
|
||||
*/
|
||||
public String getLimitParamName() {
|
||||
return limitParamName;
|
||||
@@ -109,8 +111,9 @@ public class RepositoryRestConfiguration {
|
||||
* Set the name of the URL query string parameter that indicates how many results to return at once.
|
||||
*
|
||||
* @param limitParamName
|
||||
* Name of the query parameter used to indicate the maximum number of entries to return at a time.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setLimitParamName(String limitParamName) {
|
||||
Assert.notNull(limitParamName, "Limit param name cannot be null.");
|
||||
@@ -121,7 +124,7 @@ public class RepositoryRestConfiguration {
|
||||
/**
|
||||
* Get the name of the URL query string parameter that indicates what direction to sort results. Default is 'sort'.
|
||||
*
|
||||
* @return
|
||||
* @return Name of the query string parameter used to indicate what field to sort on.
|
||||
*/
|
||||
public String getSortParamName() {
|
||||
return sortParamName;
|
||||
@@ -131,8 +134,9 @@ public class RepositoryRestConfiguration {
|
||||
* Set the name of the URL query string parameter that indicates what direction to sort results.
|
||||
*
|
||||
* @param sortParamName
|
||||
* Name of the query string parameter used to indicate what field to sort on.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setSortParamName(String sortParamName) {
|
||||
Assert.notNull(sortParamName, "Sort param name cannot be null.");
|
||||
@@ -143,7 +147,7 @@ public class RepositoryRestConfiguration {
|
||||
/**
|
||||
* Get the list of custom {@link HttpMessageConverter}s to be used to convert user input to objects and visa versa.
|
||||
*
|
||||
* @return
|
||||
* @return List of custom {@literal HttpMessageConverter}s.
|
||||
*/
|
||||
public List<HttpMessageConverter<?>> getCustomConverters() {
|
||||
return customConverters;
|
||||
@@ -153,8 +157,9 @@ public class RepositoryRestConfiguration {
|
||||
* Set the list of custom {@link HttpMessageConverter}s to be used to convert user input to objects and visa versa.
|
||||
*
|
||||
* @param customConverters
|
||||
* List of custom {@literal HttpMessageConverter}s.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setCustomConverters(List<HttpMessageConverter<?>> customConverters) {
|
||||
Assert.notNull(customConverters, "Custom converters list cannot be null.");
|
||||
@@ -166,7 +171,7 @@ public class RepositoryRestConfiguration {
|
||||
* Get the list of domain type to repository implementation mappings that will help the exporters narrow down the
|
||||
* correct {@link org.springframework.data.repository.Repository} to return for a given domain type.
|
||||
*
|
||||
* @return
|
||||
* @return A {@link Map} of domain type to repository mappings.
|
||||
*/
|
||||
public Map<Class<?>, Class<?>> getDomainTypeToRepositoryMappings() {
|
||||
return typeMappings;
|
||||
@@ -177,8 +182,9 @@ public class RepositoryRestConfiguration {
|
||||
* correct {@link org.springframework.data.repository.Repository} to return for a given domain type.
|
||||
*
|
||||
* @param typeMappings
|
||||
* A {@link Map} of domain type to repository mappings.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setDomainTypeToRepositoryMappings(Map<Class<?>, Class<?>> typeMappings) {
|
||||
this.typeMappings = typeMappings;
|
||||
@@ -189,7 +195,7 @@ public class RepositoryRestConfiguration {
|
||||
* Get the name of the URL query string parameter that indicates the name of the javascript function to use as the
|
||||
* JSONP wrapper for results.
|
||||
*
|
||||
* @return
|
||||
* @return Name of the query string parameter used to indicate the JSONP callback function.
|
||||
*/
|
||||
public String getJsonpParamName() {
|
||||
return jsonpParamName;
|
||||
@@ -200,8 +206,9 @@ public class RepositoryRestConfiguration {
|
||||
* JSONP wrapper for results.
|
||||
*
|
||||
* @param jsonpParamName
|
||||
* Name of the query string parameter used to indicate the JSONP callback function.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setJsonpParamName(String jsonpParamName) {
|
||||
this.jsonpParamName = jsonpParamName;
|
||||
@@ -212,7 +219,8 @@ public class RepositoryRestConfiguration {
|
||||
* Get the name of the URL query string parameter that indicates the name of the javascript function to use as the
|
||||
* error handler JSONP wrapper for errors.
|
||||
*
|
||||
* @return
|
||||
* @return Name of the query string parameter used to indicate what javascript function to use as the JSONP error
|
||||
* response.
|
||||
*/
|
||||
public String getJsonpOnErrParamName() {
|
||||
return jsonpOnErrParamName;
|
||||
@@ -223,8 +231,10 @@ public class RepositoryRestConfiguration {
|
||||
* error handler JSONP wrapper for errors.
|
||||
*
|
||||
* @param jsonpOnErrParamName
|
||||
* Name of the query string parameter used to indicate what javascript function to use as the JSONP error
|
||||
* response.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setJsonpOnErrParamName(String jsonpOnErrParamName) {
|
||||
this.jsonpOnErrParamName = jsonpOnErrParamName;
|
||||
@@ -234,7 +244,7 @@ public class RepositoryRestConfiguration {
|
||||
/**
|
||||
* Get the {@link MediaType} to use as a default when none is specified.
|
||||
*
|
||||
* @return
|
||||
* @return Default content type if none has been specified.
|
||||
*/
|
||||
public MediaType getDefaultMediaType() {
|
||||
return defaultMediaType;
|
||||
@@ -244,8 +254,9 @@ public class RepositoryRestConfiguration {
|
||||
* Set the {@link MediaType} to use as a default when none is specified.
|
||||
*
|
||||
* @param defaultMediaType
|
||||
* Default content type if none has been specified.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setDefaultMediaType(MediaType defaultMediaType) {
|
||||
this.defaultMediaType = defaultMediaType;
|
||||
@@ -255,7 +266,7 @@ public class RepositoryRestConfiguration {
|
||||
/**
|
||||
* Should exception messages be logged to the body of the response in a JSON object?
|
||||
*
|
||||
* @return
|
||||
* @return Flag indicating whether exception messages are logged to the body of the response.
|
||||
*/
|
||||
public boolean isDumpErrors() {
|
||||
return dumpErrors;
|
||||
@@ -265,12 +276,106 @@ public class RepositoryRestConfiguration {
|
||||
* Set whether exception messages should be logged to the body of the response as a JSON object.
|
||||
*
|
||||
* @param dumpErrors
|
||||
* Flag indicating whether exception messages are logged to the body of the response.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setDumpErrors(boolean dumpErrors) {
|
||||
this.dumpErrors = dumpErrors;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start configuration a {@link ResourceMapping} for a specific domain type.
|
||||
*
|
||||
* @param domainType
|
||||
* The {@link Class} of the domain type to configure a mapping for.
|
||||
*
|
||||
* @return A new {@link ResourceMapping} for configuring how a domain type is mapped.
|
||||
*/
|
||||
public ResourceMapping addResourceMappingForDomainType(Class<?> domainType) {
|
||||
return domainMappings.addResourceMappingFor(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ResourceMapping} for a specific domain type.
|
||||
*
|
||||
* @param domainType
|
||||
* The {@link Class} of the domain type.
|
||||
*
|
||||
* @return A {@link ResourceMapping} for that domain type or {@literal null} if none exists.
|
||||
*/
|
||||
public ResourceMapping getResourceMappingForDomainType(Class<?> domainType) {
|
||||
return domainMappings.getResourceMappingFor(domainType);
|
||||
}
|
||||
|
||||
public boolean hasResourceMappingForDomainType(Class<?> domainType) {
|
||||
return domainMappings.hasResourceMappingFor(domainType);
|
||||
}
|
||||
|
||||
public ResourceMappingConfiguration getDomainTypesResourceMappingConfiguration() {
|
||||
return domainMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start configuration a {@link ResourceMapping} for a specific repository interface.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* The {@link Class} of the repository interface to configure a mapping for.
|
||||
*
|
||||
* @return A new {@link ResourceMapping} for configuring how a repository interface is mapped.
|
||||
*/
|
||||
public ResourceMapping setResourceMappingForRepository(Class<?> repositoryInterface) {
|
||||
return repoMappings.addResourceMappingFor(repositoryInterface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ResourceMapping} for a specific repository interface.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* The {@link Class} of the repository interface.
|
||||
*
|
||||
* @return A {@link ResourceMapping} for that repository interface or {@literal null} if none exists.
|
||||
*/
|
||||
public ResourceMapping getResourceMappingForRepository(Class<?> repositoryInterface) {
|
||||
return repoMappings.getResourceMappingFor(repositoryInterface);
|
||||
}
|
||||
|
||||
public boolean hasResourceMappingForRepository(Class<?> repositoryInterface) {
|
||||
return repoMappings.hasResourceMappingFor(repositoryInterface);
|
||||
}
|
||||
|
||||
public ResourceMapping findRepositoryMappingForPath(String path) {
|
||||
Class<?> type = repoMappings.findTypeForPath(path);
|
||||
if(null == type) {
|
||||
return null;
|
||||
}
|
||||
return repoMappings.getResourceMappingFor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we expose the ID property for this domain type?
|
||||
*
|
||||
* @param domainType
|
||||
* The domain type we may need to expose the ID for.
|
||||
*
|
||||
* @return {@literal true} is the ID is to be exposed, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean isIdExposedFor(Class<?> domainType) {
|
||||
return exposeIdsFor.contains(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of domain types for which we will expose the ID value as a normal property.
|
||||
*
|
||||
* @param domainTypes
|
||||
* Array of types to expose IDs for.
|
||||
*
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration exposeIdsFor(Class<?>... domainTypes) {
|
||||
Collections.addAll(exposeIdsFor, domainTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.springframework.data.rest.config;
|
||||
|
||||
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class ResourceMapping {
|
||||
|
||||
private String rel;
|
||||
private String path;
|
||||
private boolean exported = true;
|
||||
private final Map<String, ResourceMapping> resourceMappings = new HashMap<String, ResourceMapping>();
|
||||
|
||||
public ResourceMapping() {
|
||||
}
|
||||
|
||||
public ResourceMapping(Class<?> type) {
|
||||
rel = findRel(type);
|
||||
path = findPath(type);
|
||||
exported = findExported(type);
|
||||
}
|
||||
|
||||
public ResourceMapping(String rel, String path) {
|
||||
this.rel = rel;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public ResourceMapping(String rel, String path, boolean exported) {
|
||||
this.rel = rel;
|
||||
this.path = path;
|
||||
this.exported = exported;
|
||||
}
|
||||
|
||||
public String getRel() {
|
||||
return rel;
|
||||
}
|
||||
|
||||
public ResourceMapping setRel(String rel) {
|
||||
this.rel = rel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public ResourceMapping setPath(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isExported() {
|
||||
return exported;
|
||||
}
|
||||
|
||||
public ResourceMapping setExported(boolean exported) {
|
||||
this.exported = exported;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResourceMapping addResourceMappings(Map<String, ResourceMapping> mappings) {
|
||||
if(null == mappings) {
|
||||
return this;
|
||||
}
|
||||
|
||||
resourceMappings.putAll(mappings);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResourceMapping addResourceMappingFor(String name) {
|
||||
ResourceMapping rm = new ResourceMapping();
|
||||
resourceMappings.put(name, rm);
|
||||
return rm;
|
||||
}
|
||||
|
||||
public ResourceMapping getResourceMappingFor(String name) {
|
||||
return resourceMappings.get(name);
|
||||
}
|
||||
|
||||
public boolean hasResourceMappingFor(String name) {
|
||||
return resourceMappings.containsKey(name);
|
||||
}
|
||||
|
||||
public Map<String, ResourceMapping> getResourceMappings() {
|
||||
return resourceMappings;
|
||||
}
|
||||
|
||||
public String getNameForPath(String path) {
|
||||
for(Map.Entry<String, ResourceMapping> mapping : resourceMappings.entrySet()) {
|
||||
if(mapping.getValue().getPath().equals(path)) {
|
||||
return mapping.getKey();
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "ResourceMapping{" +
|
||||
"rel='" + rel + '\'' +
|
||||
", path='" + path + '\'' +
|
||||
", exported=" + exported +
|
||||
", resourceMappings=" + resourceMappings +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.data.rest.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages the {@link ResourceMapping} configurations for any resources being exported. This includes domain entities
|
||||
* and repositories.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class ResourceMappingConfiguration {
|
||||
|
||||
private final Map<Class<?>, ResourceMapping> resourceMappings = new HashMap<Class<?>, ResourceMapping>();
|
||||
|
||||
public ResourceMapping addResourceMappingFor(Class<?> type) {
|
||||
ResourceMapping rm = resourceMappings.get(type);
|
||||
if(null == rm) {
|
||||
rm = new ResourceMapping(type);
|
||||
resourceMappings.put(type, rm);
|
||||
}
|
||||
return rm;
|
||||
}
|
||||
|
||||
public ResourceMapping getResourceMappingFor(Class<?> type) {
|
||||
return resourceMappings.get(type);
|
||||
}
|
||||
|
||||
public boolean hasResourceMappingFor(Class<?> type) {
|
||||
return resourceMappings.containsKey(type);
|
||||
}
|
||||
|
||||
public Class<?> findTypeForPath(String path) {
|
||||
if(null == path) {
|
||||
return null;
|
||||
}
|
||||
for(Map.Entry<Class<?>, ResourceMapping> entry : resourceMappings.entrySet()) {
|
||||
if(path.equals(entry.getValue().getPath())) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Encapsulates necessary information about an attribute of a generic entity.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface AttributeMetadata {
|
||||
|
||||
/**
|
||||
* Name of the attribute.
|
||||
*
|
||||
* @return name of the attribute.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* The type of this attribute.
|
||||
*
|
||||
* @return type of this attribute.
|
||||
*/
|
||||
Class<?> type();
|
||||
|
||||
/**
|
||||
* The type of this map's key, if it's map-like.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> keyType();
|
||||
|
||||
/**
|
||||
* The element type of this attribute, if this attribute is a "plural"-like attribute (a Collection, Map, etc...).
|
||||
*
|
||||
* @return Class of element type or {@literal null} if not a plural attribute.
|
||||
*/
|
||||
Class<?> elementType();
|
||||
|
||||
/**
|
||||
* Whether this attribute can be nulled or not.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isNullable();
|
||||
|
||||
/**
|
||||
* Can this attribute look like a {@link Collection}?
|
||||
*
|
||||
* @return {@literal true} if attribute is a Collection, {@literal false} otherwise.
|
||||
*/
|
||||
boolean isCollectionLike();
|
||||
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Collection}.
|
||||
*
|
||||
* @param target
|
||||
* The entity to inspect for this attribute.
|
||||
*
|
||||
* @return attribute value as a {@link Collection}
|
||||
*/
|
||||
Collection<?> asCollection(Object target);
|
||||
|
||||
/**
|
||||
* Can this attribute look like a {@link Set}?
|
||||
*
|
||||
* @return {@literal true} if attribute is a Set, {@literal false} otherwise.
|
||||
*/
|
||||
boolean isSetLike();
|
||||
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Set}.
|
||||
*
|
||||
* @param target
|
||||
* The entity to inspect for this attribute.
|
||||
*
|
||||
* @return attribute value as a {@link Set}
|
||||
*/
|
||||
Set<?> asSet(Object target);
|
||||
|
||||
/**
|
||||
* Can this attribute look like a {@link Map}?
|
||||
*
|
||||
* @return {@literal true} if attribute is a Map, {@literal false} otherwise.
|
||||
*/
|
||||
boolean isMapLike();
|
||||
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Map}.
|
||||
*
|
||||
* @param target
|
||||
* The entity to inspect for this attribute.
|
||||
*
|
||||
* @return attribute value as a {@link Map}
|
||||
*/
|
||||
Map asMap(Object target);
|
||||
|
||||
/**
|
||||
* Does this attribute have the given annotation on it?
|
||||
*
|
||||
* @param annoType
|
||||
* The type of annotation to search for.
|
||||
*
|
||||
* @return {@literal true} if this annotation exists on this attribute, {@literal false} otherwise.
|
||||
*/
|
||||
boolean hasAnnotation(Class<? extends Annotation> annoType);
|
||||
|
||||
/**
|
||||
* Get the given annotation.
|
||||
*
|
||||
* @param annoType
|
||||
* The type of annotation to get.
|
||||
* @param <A>
|
||||
*
|
||||
* @return The annotation, or {@literal null} if it doesn't exist.
|
||||
*/
|
||||
<A extends Annotation> A annotation(Class<A> annoType);
|
||||
|
||||
/**
|
||||
* Get the path of this attribute.
|
||||
*
|
||||
* @param target
|
||||
* The entity to inspect for this attribute.
|
||||
*
|
||||
* @return attribute value
|
||||
*/
|
||||
Object get(Object target);
|
||||
|
||||
/**
|
||||
* Set the path of this attribute.
|
||||
*
|
||||
* @param value
|
||||
* Value to set on this attribute.
|
||||
* @param target
|
||||
* The entity to set this attribute's value on.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
AttributeMetadata set(Object value, Object target);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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)) {
|
||||
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())) {
|
||||
return new Link(buildUri(baseUri, l.getHref()).toString(), l.getRel());
|
||||
} else {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encapsulates necessary metadata about a generic entity.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface EntityMetadata<A extends AttributeMetadata> {
|
||||
|
||||
/**
|
||||
* The class of this entity.
|
||||
*
|
||||
* @return Type of this domain class.
|
||||
*/
|
||||
Class<?> type();
|
||||
|
||||
/**
|
||||
* A Map of attribute metadata keyed on the attribute's name.
|
||||
*
|
||||
* @return Attributes that do not involve relationships.
|
||||
*/
|
||||
Map<String, A> embeddedAttributes();
|
||||
|
||||
/**
|
||||
* A Map of linked attribute metadata keyed on the attribute's name.
|
||||
*
|
||||
* @return Attributes that involve relationships.
|
||||
*/
|
||||
Map<String, A> linkedAttributes();
|
||||
|
||||
/**
|
||||
* The {@link AttributeMetadata} representing the ID of the entity.
|
||||
*
|
||||
* @return {@link AttributeMetadata} for the ID.
|
||||
*/
|
||||
A idAttribute();
|
||||
|
||||
/**
|
||||
* The {@link AttributeMetadata} representing the version of the entity, if applicable.
|
||||
*
|
||||
* @return {@link AttributeMetadata} or {@literal null} if no version attributes exists.
|
||||
*/
|
||||
A versionAttribute();
|
||||
|
||||
/**
|
||||
* Get {@link AttributeMetadata} by name.
|
||||
*
|
||||
* @param name
|
||||
* The name of the attribute.
|
||||
*
|
||||
* @return {@link AttributeMetadata} or {@literal null} if that attribute doesn't exist.
|
||||
*/
|
||||
A attribute(String name);
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class PagingMetadata {
|
||||
|
||||
private int number = 0;
|
||||
private int size = 0;
|
||||
private int totalPages = 0;
|
||||
private long totalElements = 0;
|
||||
|
||||
public PagingMetadata() {
|
||||
}
|
||||
|
||||
public PagingMetadata(int number,
|
||||
int size,
|
||||
int totalPages,
|
||||
long totalElements) {
|
||||
this.number = number;
|
||||
this.size = size;
|
||||
this.totalPages = totalPages;
|
||||
this.totalElements = totalElements;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public PagingMetadata setNumber(int number) {
|
||||
this.number = number;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public PagingMetadata setSize(int size) {
|
||||
this.size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTotalPages() {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
public PagingMetadata setTotalPages(int totalPages) {
|
||||
this.totalPages = totalPages;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getTotalElements() {
|
||||
return totalElements;
|
||||
}
|
||||
|
||||
public PagingMetadata setTotalElements(long totalElements) {
|
||||
this.totalElements = totalElements;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resource;
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
@JsonIgnore
|
||||
private final PersistentEntity<T, ?> persistentEntity;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
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 PersistentEntityResource(PersistentEntity<T, ?> persistentEntity) {
|
||||
this.persistentEntity = persistentEntity;
|
||||
}
|
||||
|
||||
public PersistentEntityResource(PersistentEntity<T, ?> persistentEntity,
|
||||
T content,
|
||||
Link... links) {
|
||||
super(content, links);
|
||||
this.persistentEntity = persistentEntity;
|
||||
}
|
||||
|
||||
public PersistentEntityResource(PersistentEntity<T, ?> persistentEntity,
|
||||
T content,
|
||||
Iterable<Link> links) {
|
||||
super(content, links);
|
||||
this.persistentEntity = persistentEntity;
|
||||
}
|
||||
|
||||
public PersistentEntity<T, ?> getPersistentEntity() {
|
||||
return persistentEntity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,10 +4,11 @@ import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.validation.Errors;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* Exception that is thrown when a Spring {@link org.springframework.validation.Validator} throws an error.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RepositoryConstraintViolationException
|
||||
extends DataIntegrityViolationException {
|
||||
public class RepositoryConstraintViolationException extends DataIntegrityViolationException {
|
||||
|
||||
private Errors errors;
|
||||
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstract class that contains the basic functionality that any exporter will need
|
||||
* to export a Repository implementation.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class RepositoryExporter<R extends RepositoryExporter<? super R, M, E>, M extends RepositoryMetadata<E>, E extends EntityMetadata<? extends AttributeMetadata>>
|
||||
implements ApplicationContextAware,
|
||||
InitializingBean {
|
||||
|
||||
protected ApplicationContext applicationContext;
|
||||
protected Repositories repositories;
|
||||
protected Map<String, M> repositoryMetadata;
|
||||
protected List<String> exportOnlyTheseClasses = Collections.emptyList();
|
||||
protected Map<Class<?>, Class<?>> domainTypeMappings = new HashMap<Class<?>, Class<?>>();
|
||||
|
||||
/**
|
||||
* Get the list of class names of Repositories to export.
|
||||
*
|
||||
* @return a List of class names to export
|
||||
*/
|
||||
public List<String> getExportOnlyTheseClasses() {
|
||||
return exportOnlyTheseClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the class names of only those Repositories you want exported.
|
||||
* Default is to export all found Repositories.
|
||||
*
|
||||
* @param exportOnlyTheseClasses
|
||||
* {@link List} of class names to export.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public R setExportOnlyTheseClasses(List<String> exportOnlyTheseClasses) {
|
||||
this.exportOnlyTheseClasses = exportOnlyTheseClasses;
|
||||
return (R)this;
|
||||
}
|
||||
|
||||
public Map<Class<?>, Class<?>> getDomainTypeMappings() {
|
||||
return domainTypeMappings;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public R setDomainTypeMappings(Map<Class<?>, Class<?>> domainTypeMappings) {
|
||||
this.domainTypeMappings = domainTypeMappings;
|
||||
return (R)this;
|
||||
}
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of Repository names being exported.
|
||||
*
|
||||
* @return {@link List} of class names to export.
|
||||
*/
|
||||
public Set<String> repositoryNames() {
|
||||
refresh();
|
||||
return repositoryMetadata.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a Repository being exporter that supports this domain type?
|
||||
*
|
||||
* @param domainType
|
||||
* Type of the domain class.
|
||||
*
|
||||
* @return {@literal true} if a Repository is being exported, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean hasRepositoryFor(Class<?> domainType) {
|
||||
refresh();
|
||||
for(M repoMeta : repositoryMetadata.values()) {
|
||||
if(repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RepositoryMetadata for the Repository responsible for this domain type.
|
||||
*
|
||||
* @param domainType
|
||||
* Type of the domain class.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} instance
|
||||
*/
|
||||
public M repositoryMetadataFor(Class<?> domainType) {
|
||||
refresh();
|
||||
// Look for an exact match
|
||||
for(M repoMeta : repositoryMetadata.values()) {
|
||||
if(repoMeta.domainType() == domainType) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
// Didn't find an exact match, look for domain type mapping
|
||||
Class<?> repoClass = domainTypeMappings.get(domainType);
|
||||
if(null != repoClass) {
|
||||
for(M repoMeta : repositoryMetadata.values()) {
|
||||
if(repoMeta.repositoryClass() == repoClass) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Didn't find a mapping, look for a superclass
|
||||
for(M repoMeta : repositoryMetadata.values()) {
|
||||
if(repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link RepositoryMetadata} for the Repository exported under the given name.
|
||||
*
|
||||
* @param name
|
||||
* Name a Repository would be exported under.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} instance
|
||||
*/
|
||||
public M repositoryMetadataFor(String name) {
|
||||
refresh();
|
||||
return repositoryMetadata.get(name);
|
||||
}
|
||||
|
||||
protected abstract M createRepositoryMetadata(String name,
|
||||
Class<?> domainType,
|
||||
Class<?> repoClass,
|
||||
Repositories repositories);
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public void refresh() {
|
||||
if(null != repositories) {
|
||||
return;
|
||||
}
|
||||
repositories = new Repositories(applicationContext);
|
||||
repositoryMetadata = new HashMap<String, M>();
|
||||
for(Class<?> domainType : repositories) {
|
||||
if(exportOnlyTheseClasses.isEmpty() || exportOnlyTheseClasses.contains(domainType.getName())) {
|
||||
Class<?> repoClass = repositories.getRepositoryInformationFor(domainType).getRepositoryInterface();
|
||||
String name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", ""));
|
||||
RestResource resourceAnno = repoClass.getAnnotation(RestResource.class);
|
||||
boolean exported = true;
|
||||
if(null != resourceAnno) {
|
||||
if(StringUtils.hasText(resourceAnno.path())) {
|
||||
name = resourceAnno.path();
|
||||
}
|
||||
exported = resourceAnno.exported();
|
||||
}
|
||||
if(exported) {
|
||||
repositoryMetadata.put(name, createRepositoryMetadata(name, domainType, repoClass, repositories));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Abstract class used as a helper for those classes that need access to the exported repositories.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupport<? super S>> {
|
||||
|
||||
protected List<RepositoryExporter> repositoryExporters = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Get a List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @return Exported {@link RepositoryExporter}s.
|
||||
*/
|
||||
public List<RepositoryExporter> getRepositoryExporters() {
|
||||
return repositoryExporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @param repositoryExporters
|
||||
* Export this {@link List} of {@link RepositoryExporter}s.
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setRepositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
this.repositoryExporters = repositoryExporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @return Exported {@link RepositoryExporter}s.
|
||||
*/
|
||||
public List<RepositoryExporter> repositoryExporters() {
|
||||
return repositoryExporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @param repositoryExporters
|
||||
* Export this {@link List} of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public S repositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
setRepositoryExporters(repositoryExporters);
|
||||
return (S)this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link RepositoryExporter}s to use.
|
||||
*
|
||||
* @param repositoryExporter
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public S repositoryExporters(RepositoryExporter... repositoryExporter) {
|
||||
setRepositoryExporters(Arrays.asList(repositoryExporter));
|
||||
return (S)this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a Repository exist for this name?
|
||||
*
|
||||
* @param name
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public boolean hasRepositoryMetadataFor(String name) {
|
||||
try {
|
||||
return (null != repositoryMetadataFor(name));
|
||||
} catch(RepositoryNotFoundException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there a Repository responsible for this domain type?
|
||||
*
|
||||
* @param domainType
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasRepositoryMetadataFor(Class<?> domainType) {
|
||||
try {
|
||||
return (null != repositoryMetadataFor(domainType));
|
||||
} catch(RepositoryNotFoundException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find {@link RepositoryMetadata} for the {@link org.springframework.data.repository.Repository} exported under this
|
||||
* name.
|
||||
*
|
||||
* @param name
|
||||
* URL segment name.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} or {@literal null} if none found.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(String name) {
|
||||
for(RepositoryExporter exporter : repositoryExporters) {
|
||||
RepositoryMetadata repoMeta = exporter.repositoryMetadataFor(name);
|
||||
if(null != repoMeta) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
throw new RepositoryNotFoundException("No repository found for name " + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the {@link RepositoryMetadata} for the {@link org.springframework.data.repository.Repository} responsible for
|
||||
* the given domain type.
|
||||
*
|
||||
* @param domainType
|
||||
* Type of the domain class.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} or {@literal null} if none found.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(Class<?> domainType) {
|
||||
for(RepositoryExporter exporter : repositoryExporters) {
|
||||
RepositoryMetadata repoMeta = exporter.repositoryMetadataFor(domainType);
|
||||
if(null != repoMeta) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
throw new RepositoryNotFoundException("No repository found for type " + domainType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the {@link RepositoryMetadata} for an attribute of an entity which is possibly managed by a {@link
|
||||
* org.springframework.data.repository.Repository}.
|
||||
*
|
||||
* @param attrMeta
|
||||
* {@link AttributeMetadata} of a possibly-managed entity.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} or {@literal null} if none found.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(AttributeMetadata attrMeta) {
|
||||
if(null != attrMeta.elementType()) {
|
||||
return repositoryMetadataFor(attrMeta.elementType());
|
||||
} else {
|
||||
return repositoryMetadataFor(attrMeta.type());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.rest.repository.invoke.CrudMethod;
|
||||
import org.springframework.data.rest.repository.invoke.RepositoryQueryMethod;
|
||||
|
||||
/**
|
||||
* Encapsulates necessary metadata about a {@link Repository}.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface RepositoryMetadata<E extends EntityMetadata<? extends AttributeMetadata>> {
|
||||
|
||||
/**
|
||||
* The name this {@link Repository} is exported under.
|
||||
*
|
||||
* @return Name used in the URL for this Repository.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Get the string value to be used as part of a link {@literal rel} attribute.
|
||||
*
|
||||
* @return Rel value used in links.
|
||||
*/
|
||||
String rel();
|
||||
|
||||
/**
|
||||
* The type of domain object this {@link Repository} is repsonsible for.
|
||||
*
|
||||
* @return Type of the domain class.
|
||||
*/
|
||||
Class<?> domainType();
|
||||
|
||||
/**
|
||||
* The Class of the {@link Repository} subinterface.
|
||||
*
|
||||
* @return Type of the Repository being proxied.
|
||||
*/
|
||||
Class<?> repositoryClass();
|
||||
|
||||
/**
|
||||
* The {@link Repository} instance.
|
||||
*
|
||||
* @return The actual {@link Repository} instance.
|
||||
*/
|
||||
CrudRepository<Object, Serializable> repository();
|
||||
|
||||
/**
|
||||
* The {@link EntityMetadata} associated with the domain type of this {@literal Repository}.
|
||||
*
|
||||
* @return EntityMetadata associated with this Repository's domain type.
|
||||
*/
|
||||
E entityMetadata();
|
||||
|
||||
/**
|
||||
* Get a {@link org.springframework.data.rest.repository.invoke.RepositoryQueryMethod} by key.
|
||||
*
|
||||
* @param key
|
||||
* Segment of the URL to find a query method for.
|
||||
*
|
||||
* @return Found {@link org.springframework.data.rest.repository.invoke.RepositoryQueryMethod} or {@literal null} if
|
||||
* none found.
|
||||
*/
|
||||
RepositoryQueryMethod queryMethod(String key);
|
||||
|
||||
/**
|
||||
* Get a Map of all {@link RepositoryQueryMethod}s, keyed by name.
|
||||
*
|
||||
* @return All query methods for this Repository.
|
||||
*/
|
||||
Map<String, RepositoryQueryMethod> queryMethods();
|
||||
|
||||
/**
|
||||
* Does this Repository all this method to be exported?
|
||||
*
|
||||
* @param method
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Boolean exportsMethod(CrudMethod method);
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryNotFoundException
|
||||
extends DataAccessResourceFailureException {
|
||||
|
||||
public RepositoryNotFoundException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public RepositoryNotFoundException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.repository.support.DomainClassConverter;
|
||||
import org.springframework.data.rest.repository.support.RepositoryInformationSupport;
|
||||
|
||||
/**
|
||||
* A {@link ConditionalGenericConverter} that can convert a {@link URI} domain entity.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class UriDomainClassConverter
|
||||
extends RepositoryInformationSupport
|
||||
implements ConditionalGenericConverter,
|
||||
InitializingBean {
|
||||
|
||||
private static TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
|
||||
|
||||
@Autowired
|
||||
private DomainClassConverter domainClassConverter;
|
||||
private Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
for(Class<?> domainType : repositories) {
|
||||
convertiblePairs.add(new ConvertiblePair(URI.class, domainType));
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return URI.class.isAssignableFrom(sourceType.getType())
|
||||
&& (null != repositories.getPersistentEntity(targetType.getType()));
|
||||
}
|
||||
|
||||
@Override public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return convertiblePairs;
|
||||
}
|
||||
|
||||
@Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
PersistentEntity entity = repositories.getPersistentEntity(targetType.getType());
|
||||
if(null == entity || !domainClassConverter.matches(STRING_TYPE, targetType)) {
|
||||
throw new ConversionFailedException(
|
||||
sourceType,
|
||||
targetType,
|
||||
source,
|
||||
new IllegalArgumentException("No PersistentEntity information available for " + targetType.getType())
|
||||
);
|
||||
}
|
||||
|
||||
URI uri = (URI)source;
|
||||
String[] parts = uri.getPath().split("/");
|
||||
if(parts.length < 2) {
|
||||
throw new ConversionFailedException(
|
||||
sourceType,
|
||||
targetType,
|
||||
source,
|
||||
new IllegalArgumentException("Cannot resolve URI " + uri + ". Is it local or remote? Only local URIs are resolvable.")
|
||||
);
|
||||
}
|
||||
|
||||
return domainClassConverter.convert(parts[parts.length - 1], STRING_TYPE, targetType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.core.UriResolver;
|
||||
import org.springframework.data.rest.core.util.UriUtils;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class UriToDomainObjectUriResolver
|
||||
extends RepositoryExporterSupport<UriToDomainObjectUriResolver>
|
||||
implements UriResolver<Object> {
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<ConversionService> conversionServices = Arrays.<ConversionService>asList(new DefaultFormattingConversionService());
|
||||
|
||||
public List<ConversionService> getConversionServices() {
|
||||
return conversionServices;
|
||||
}
|
||||
|
||||
public UriToDomainObjectUriResolver setConversionServices(List<ConversionService> conversionServices) {
|
||||
this.conversionServices = conversionServices;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override public Object resolve(URI baseUri, URI uri) {
|
||||
URI relativeUri = baseUri.relativize(uri);
|
||||
Stack<URI> uris = UriUtils.explode(baseUri, relativeUri);
|
||||
|
||||
if(uris.size() < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String repoName = UriUtils.path(uris.get(0));
|
||||
String sId = UriUtils.path(uris.get(1));
|
||||
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repoName);
|
||||
|
||||
CrudRepository repo;
|
||||
if(null == (repo = repoMeta.repository())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EntityMetadata entityMeta;
|
||||
if(null == (entityMeta = repoMeta.entityMetadata())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<? extends Serializable> idType = (Class<? extends Serializable>)entityMeta.idAttribute().type();
|
||||
Serializable serId = null;
|
||||
if(ClassUtils.isAssignable(idType, String.class)) {
|
||||
serId = sId;
|
||||
} else {
|
||||
for(ConversionService cs : conversionServices) {
|
||||
if(cs.canConvert(String.class, idType)) {
|
||||
serId = cs.convert(sId, idType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return repo.findOne(serId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.validation.AbstractErrors;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.FieldError;
|
||||
@@ -15,16 +21,16 @@ import org.springframework.validation.ObjectError;
|
||||
*/
|
||||
public class ValidationErrors extends AbstractErrors {
|
||||
|
||||
private String name;
|
||||
private Object entity;
|
||||
private EntityMetadata entityMetadata;
|
||||
private String name;
|
||||
private Object entity;
|
||||
private PersistentEntity persistentEntity;
|
||||
private List<ObjectError> globalErrors = new ArrayList<ObjectError>();
|
||||
private List<FieldError> fieldErrors = new ArrayList<FieldError>();
|
||||
|
||||
public ValidationErrors(String name, Object entity, EntityMetadata entityMetadata) {
|
||||
public ValidationErrors(String name, Object entity, PersistentEntity persistentEntity) {
|
||||
this.name = name;
|
||||
this.entity = entity;
|
||||
this.entityMetadata = entityMetadata;
|
||||
this.persistentEntity = persistentEntity;
|
||||
}
|
||||
|
||||
@Override public String getObjectName() {
|
||||
@@ -58,6 +64,21 @@ public class ValidationErrors extends AbstractErrors {
|
||||
}
|
||||
|
||||
@Override public Object getFieldValue(String field) {
|
||||
return entityMetadata.attribute(field).get(entity);
|
||||
PersistentProperty prop = (null != persistentEntity ? persistentEntity.getPersistentProperty(field) : null);
|
||||
if(null == prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Method getter = prop.getGetter();
|
||||
if(null != getter) {
|
||||
return invokeMethod(getter, entity);
|
||||
}
|
||||
Field fld = prop.getField();
|
||||
if(null != fld) {
|
||||
return getField(fld, entity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.springframework.data.rest.repository.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
@@ -9,11 +8,12 @@ import java.lang.annotation.Target;
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.FIELD,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeRenderResource {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
public @interface Description {
|
||||
String value();
|
||||
}
|
||||
@@ -7,9 +7,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* Denotes a component that should handle the {@literal afterDelete} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterDelete {
|
||||
|
||||
@@ -7,9 +7,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal afterLinkDelete} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterLinkDelete {
|
||||
|
||||
@@ -7,9 +7,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* Denotes a component that should handle the {@literal afterLinkSave} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterLinkSave {
|
||||
|
||||
@@ -7,9 +7,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* Denotes a component that should handle the {@literal afterSave} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterSave {
|
||||
|
||||
@@ -7,9 +7,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* Denotes a component that should handle the {@literal beforeDelete} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeDelete {
|
||||
|
||||
@@ -7,9 +7,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal beforeLinkDelete} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeLinkDelete {
|
||||
|
||||
@@ -7,9 +7,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* Denotes a component that should handle the {@literal beforeLinkSave} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeLinkSave {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.springframework.data.rest.repository.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeRenderResources {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -7,9 +7,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* Denotes a component that should handle the {@literal beforeSave} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({
|
||||
ElementType.TYPE,
|
||||
ElementType.METHOD
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeSave {
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.lang.annotation.Target;
|
||||
* Annotate a {@link org.springframework.data.repository.Repository} with this to influence how it is exported and what
|
||||
* the value of the {@literal rel} attribute will be in links.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({
|
||||
ElementType.FIELD,
|
||||
@@ -21,10 +21,25 @@ import java.lang.annotation.Target;
|
||||
@Inherited
|
||||
public @interface RestResource {
|
||||
|
||||
/**
|
||||
* Flag indicating whether this resource is exported at all.
|
||||
*
|
||||
* @return {@literal true} if the resource is to be exported, {@literal false} otherwise.
|
||||
*/
|
||||
boolean exported() default true;
|
||||
|
||||
/**
|
||||
* The path segment under which this resource is to be exported.
|
||||
*
|
||||
* @return A valid path segment.
|
||||
*/
|
||||
String path() default "";
|
||||
|
||||
/**
|
||||
* The rel value to use when generating links to this resource.
|
||||
*
|
||||
* @return A valid rel value.
|
||||
*/
|
||||
String rel() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,55 +1,51 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import java.util.List;
|
||||
import static org.springframework.core.GenericTypeResolver.*;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.data.rest.repository.RepositoryExporter;
|
||||
import org.springframework.data.rest.repository.RepositoryExporterSupport;
|
||||
|
||||
/**
|
||||
* Abstract class that listens for generic {@link RepositoryEvent}s and dispatches them to a specific
|
||||
* method based on the event type.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class AbstractRepositoryEventListener<T extends AbstractRepositoryEventListener<? super T>>
|
||||
extends RepositoryExporterSupport<T>
|
||||
implements ApplicationListener<RepositoryEvent>,
|
||||
ApplicationContextAware {
|
||||
public abstract class AbstractRepositoryEventListener<T> implements ApplicationListener<RepositoryEvent>,
|
||||
ApplicationContextAware {
|
||||
|
||||
private final Class<?> INTERESTED_TYPE = resolveTypeArgument(getClass(), AbstractRepositoryEventListener.class);
|
||||
protected ApplicationContext applicationContext;
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setRepositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
super.setRepositoryExporters(repositoryExporters);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override public final void onApplicationEvent(RepositoryEvent event) {
|
||||
Class<?> srcType = event.getSource().getClass();
|
||||
if(null != INTERESTED_TYPE && !INTERESTED_TYPE.isAssignableFrom(srcType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(event instanceof BeforeSaveEvent) {
|
||||
onBeforeSave(event.getSource());
|
||||
onBeforeSave((T)event.getSource());
|
||||
} else if(event instanceof AfterSaveEvent) {
|
||||
onAfterSave(event.getSource());
|
||||
onAfterSave((T)event.getSource());
|
||||
} else if(event instanceof BeforeLinkSaveEvent) {
|
||||
onBeforeLinkSave(event.getSource(), ((BeforeLinkSaveEvent)event).getLinked());
|
||||
onBeforeLinkSave((T)event.getSource(), ((BeforeLinkSaveEvent)event).getLinked());
|
||||
} else if(event instanceof AfterLinkSaveEvent) {
|
||||
onAfterLinkSave(event.getSource(), ((AfterLinkSaveEvent)event).getLinked());
|
||||
onAfterLinkSave((T)event.getSource(), ((AfterLinkSaveEvent)event).getLinked());
|
||||
} else if(event instanceof BeforeLinkDeleteEvent) {
|
||||
onBeforeLinkDelete(event.getSource(), ((BeforeLinkDeleteEvent)event).getLinked());
|
||||
onBeforeLinkDelete((T)event.getSource(), ((BeforeLinkDeleteEvent)event).getLinked());
|
||||
} else if(event instanceof AfterLinkDeleteEvent) {
|
||||
onAfterLinkDelete(event.getSource(), ((AfterLinkDeleteEvent)event).getLinked());
|
||||
onAfterLinkDelete((T)event.getSource(), ((AfterLinkDeleteEvent)event).getLinked());
|
||||
} else if(event instanceof BeforeDeleteEvent) {
|
||||
onBeforeDelete(event.getSource());
|
||||
onBeforeDelete((T)event.getSource());
|
||||
} else if(event instanceof AfterDeleteEvent) {
|
||||
onAfterDelete(event.getSource());
|
||||
onAfterDelete((T)event.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,68 +53,80 @@ public abstract class AbstractRepositoryEventListener<T extends AbstractReposito
|
||||
* Override this method if you are interested in {@literal beforeSave} events.
|
||||
*
|
||||
* @param entity
|
||||
* The entity being saved.
|
||||
*/
|
||||
protected void onBeforeSave(Object entity) {
|
||||
protected void onBeforeSave(T entity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterSave} events.
|
||||
*
|
||||
* @param entity
|
||||
* The entity that was just saved.
|
||||
*/
|
||||
protected void onAfterSave(Object entity) {
|
||||
protected void onAfterSave(T entity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeLinkSave} events.
|
||||
*
|
||||
* @param parent
|
||||
* The parent entity to which the child object is linked.
|
||||
* @param linked
|
||||
* The linked, child entity.
|
||||
*/
|
||||
protected void onBeforeLinkSave(Object parent, Object linked) {
|
||||
protected void onBeforeLinkSave(T parent, Object linked) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterLinkSave} events.
|
||||
*
|
||||
* @param parent
|
||||
* The parent entity to which the child object is linked.
|
||||
* @param linked
|
||||
* The linked, child entity.
|
||||
*/
|
||||
protected void onAfterLinkSave(Object parent, Object linked) {
|
||||
protected void onAfterLinkSave(T parent, Object linked) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeLinkDelete} events.
|
||||
*
|
||||
* @param parent
|
||||
* The parent entity to which the child object is linked.
|
||||
* @param linked
|
||||
* The linked, child entity.
|
||||
*/
|
||||
protected void onBeforeLinkDelete(Object parent, Object linked) {
|
||||
protected void onBeforeLinkDelete(T parent, Object linked) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterLinkDelete} events.
|
||||
*
|
||||
* @param parent
|
||||
* The parent entity to which the child object is linked.
|
||||
* @param linked
|
||||
* The linked, child entity.
|
||||
*/
|
||||
protected void onAfterLinkDelete(Object parent, Object linked) {
|
||||
protected void onAfterLinkDelete(T parent, Object linked) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeDelete} events.
|
||||
*
|
||||
* @param entity
|
||||
* The entity that is being deleted.
|
||||
*/
|
||||
protected void onBeforeDelete(Object entity) {
|
||||
protected void onBeforeDelete(T entity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterDelete} events.
|
||||
*
|
||||
* @param entity
|
||||
* The entity that was just deleted.
|
||||
*/
|
||||
protected void onAfterDelete(Object entity) {
|
||||
protected void onAfterDelete(T entity) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class AfterSaveEvent
|
||||
extends RepositoryEvent {
|
||||
public class AfterSaveEvent extends RepositoryEvent {
|
||||
public AfterSaveEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterLinkDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeSave;
|
||||
import org.springframework.data.rest.repository.annotation.RepositoryEventHandler;
|
||||
@@ -96,6 +98,8 @@ public class AnnotatedHandlerBeanPostProcessor implements ApplicationListener<Re
|
||||
inspect(targetType, bean, method, HandleAfterLinkSave.class, AfterLinkSaveEvent.class);
|
||||
inspect(targetType, bean, method, HandleBeforeDelete.class, BeforeDeleteEvent.class);
|
||||
inspect(targetType, bean, method, HandleAfterDelete.class, AfterDeleteEvent.class);
|
||||
inspect(targetType, bean, method, HandleBeforeLinkDelete.class, BeforeLinkDeleteEvent.class);
|
||||
inspect(targetType, bean, method, HandleAfterLinkDelete.class, AfterLinkDeleteEvent.class);
|
||||
}
|
||||
},
|
||||
new ReflectionUtils.MethodFilter() {
|
||||
@@ -129,8 +133,8 @@ public class AnnotatedHandlerBeanPostProcessor implements ApplicationListener<Re
|
||||
}
|
||||
for(Class<?> type : targetTypes) {
|
||||
EventHandlerMethod m = new EventHandlerMethod(type, handler, method);
|
||||
if(LOG.isInfoEnabled()) {
|
||||
LOG.info("Annotated handler method found: " + m);
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Annotated handler method found: " + m);
|
||||
}
|
||||
handlerMethods.put(eventType, m);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeSaveEvent
|
||||
extends RepositoryEvent {
|
||||
public class BeforeSaveEvent extends RepositoryEvent {
|
||||
public BeforeSaveEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* An event to encapsulate an exception occurring anywhere within the REST exporter.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class ExceptionEvent extends RepositoryEvent {
|
||||
public ExceptionEvent(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source of this exception event.
|
||||
*
|
||||
* @return The {@link Throwable} that is the source of this exception event.
|
||||
*/
|
||||
public Throwable getException() {
|
||||
return (Throwable)getSource();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user