diff --git a/pom.xml b/pom.xml
index 1a5300236..b862fb3d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@
3.2.0.BUILD-SNAPSHOT
1.5.0.BUILD-SNAPSHOT
- 4.2.0.Final
+ 4.3.5.Final
false
@@ -74,13 +74,6 @@
-
- org.hibernate.javax.persistence
- hibernate-jpa-2.0-api
- 1.0.1.Final
- test
-
-
org.hibernate
hibernate-entitymanager
@@ -119,7 +112,7 @@
org.hsqldb
hsqldb
- 2.2.8
+ 2.3.2
test
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/Description.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/Description.java
index 373cafeae..f65002de1 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/Description.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/Description.java
@@ -27,7 +27,7 @@ import java.lang.annotation.Target;
* @author Jon Brisbin
* @author Oliver Gierke
*/
-@Target({ ElementType.FIELD, ElementType.METHOD })
+@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Description {
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/MetadataConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/MetadataConfiguration.java
new file mode 100644
index 000000000..95c5a01f3
--- /dev/null
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/MetadataConfiguration.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014 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.core.config;
+
+/**
+ * Configuration for metadata exposure.
+ *
+ * @author Oliver Gierke
+ */
+public class MetadataConfiguration {
+
+ private boolean omitUnresolvableDescriptionKeys = true;
+ private boolean alpsEnabled = true;
+
+ /**
+ * Configures whether to omit documentation attributes for unresolvable resource bundle keys. Defaults to
+ * {@literal true}, which means that an unsuccessful attempt to resolve the message will cause no documentation entry
+ * to be rendered for the metadata resources.
+ *
+ * @param omitUnresolvableDescriptionKeys whether to omit documentation attributes for unresolvable resource bundle
+ * keys.
+ */
+ public void setOmitUnresolvableDescriptionKeys(boolean omitUnresolvableDescriptionKeys) {
+ this.omitUnresolvableDescriptionKeys = omitUnresolvableDescriptionKeys;
+ }
+
+ /**
+ * Returns whether to omit documentation attributes for unresolvable resource bundle keys.
+ *
+ * @return the omitUnresolvableDescriptionKeys
+ */
+ public boolean omitUnresolvableDescriptionKeys() {
+ return omitUnresolvableDescriptionKeys;
+ }
+
+ /**
+ * Configures whether to expose the ALPS resources.
+ *
+ * @param alpsEnabled the alpsEnabled to set
+ */
+ public void setAlpsEnabled(boolean enableAlps) {
+ this.alpsEnabled = enableAlps;
+ }
+
+ /**
+ * Returns whether the ALPS resources are exposed.
+ *
+ * @return the alpsEnabled
+ */
+ public boolean alpsEnabled() {
+ return alpsEnabled;
+ }
+}
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfiguration.java
index 9175fe344..03147f335 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfiguration.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfiguration.java
@@ -17,6 +17,7 @@ package org.springframework.data.rest.core.config;
import java.util.HashMap;
import java.util.Map;
+import java.util.Map.Entry;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.rest.core.projection.ProjectionDefinitions;
@@ -142,6 +143,27 @@ public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
return false;
}
+ /**
+ * Returns all projections registered for the given source type.
+ *
+ * @param sourceType must not be {@literal null}.
+ * @return
+ */
+ public Map> getProjectionsFor(Class> sourceType) {
+
+ Assert.notNull(sourceType, "Source type must not be null!");
+
+ Map> result = new HashMap>();
+
+ for (Entry> entry : projectionDefinitions.entrySet()) {
+ if (entry.getKey().sourceType.equals(sourceType)) {
+ result.put(entry.getKey().name, entry.getValue());
+ }
+ }
+
+ return result;
+ }
+
/**
* Value object to define lookup keys for projections.
*
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java
index c0088b830..85ce70ea4 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java
@@ -45,23 +45,29 @@ public class RepositoryRestConfiguration {
private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration();
private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration();
private final ProjectionDefinitionConfiguration projectionConfiguration;
+ private final MetadataConfiguration metadataConfiguration;
/**
* Creates a new default {@link RepositoryRestConfiguration}.
*/
public RepositoryRestConfiguration() {
- this(new ProjectionDefinitionConfiguration());
+ this(new ProjectionDefinitionConfiguration(), new MetadataConfiguration());
}
/**
* Creates a new {@link RepositoryRestConfiguration} with the given {@link ProjectionDefinitionConfiguration}.
*
* @param projectionConfiguration must not be {@literal null}.
+ * @param metadataConfiguration must not be {@literal null}.
*/
- public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionConfiguration) {
+ public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionConfiguration,
+ MetadataConfiguration metadataConfiguration) {
Assert.notNull(projectionConfiguration, "ProjectionDefinitionConfiguration must not be null!");
+ Assert.notNull(metadataConfiguration, "MetadataConfiguration must not be null!");
+
this.projectionConfiguration = projectionConfiguration;
+ this.metadataConfiguration = metadataConfiguration;
}
/**
@@ -76,14 +82,25 @@ public class RepositoryRestConfiguration {
/**
* The base URI against which the exporter should calculate its links.
*
- * @param baseUri The base URI.
+ * @param baseUri must not be {@literal null}.
*/
public RepositoryRestConfiguration setBaseUri(URI baseUri) {
- Assert.notNull(baseUri, "The baseUri cannot be null.");
+ Assert.notNull(baseUri, "The base URI cannot be null.");
this.baseUri = baseUri;
return this;
}
+ /**
+ * The base URI against which the exporter should calculate its links.
+ *
+ * @param baseUri must not be {@literal null}.
+ */
+ public RepositoryRestConfiguration setBaseUri(String baseUri) {
+ Assert.notNull(baseUri, "The base URI cannot be null.");
+ this.baseUri = URI.create(baseUri);
+ return this;
+ }
+
/**
* Get the default size of {@link org.springframework.data.domain.Pageable}s. Default is 20.
*
@@ -381,4 +398,13 @@ public class RepositoryRestConfiguration {
public ProjectionDefinitionConfiguration projectionConfiguration() {
return projectionConfiguration;
}
+
+ /**
+ * Returns the {@link MetadataConfiguration} to customize metadata exposure.
+ *
+ * @return
+ */
+ public MetadataConfiguration metadataConfiguration() {
+ return metadataConfiguration;
+ }
}
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/AnnotationBasedResourceDescription.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/AnnotationBasedResourceDescription.java
index 5d2f9674f..b73d75cb7 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/AnnotationBasedResourceDescription.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/AnnotationBasedResourceDescription.java
@@ -15,6 +15,7 @@
*/
package org.springframework.data.rest.core.mapping;
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.rest.core.annotation.Description;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
@@ -26,7 +27,7 @@ import org.springframework.util.StringUtils;
*
* @author Oliver Gierke
*/
-class AnnotationBasedResourceDescription extends ResolvableResourceDescriptionSupport {
+public class AnnotationBasedResourceDescription extends ResolvableResourceDescriptionSupport {
private final String message;
private final ResourceDescription fallback;
@@ -37,7 +38,7 @@ class AnnotationBasedResourceDescription extends ResolvableResourceDescriptionSu
* @param description must not be {@literal null}.
* @param fallback must not be {@literal null}.
*/
- AnnotationBasedResourceDescription(Description description, ResourceDescription fallback) {
+ public AnnotationBasedResourceDescription(Description description, ResourceDescription fallback) {
Assert.notNull(description, "Description must not be null!");
Assert.notNull(fallback, "Fallback resource description must not be null!");
@@ -46,9 +47,28 @@ class AnnotationBasedResourceDescription extends ResolvableResourceDescriptionSu
this.fallback = fallback;
}
- /**
- * @return the message
+ public AnnotationBasedResourceDescription(Class> type, ResourceDescription fallback) {
+
+ Description description = AnnotationUtils.findAnnotation(type, Description.class);
+
+ this.message = description == null ? null : description.value();
+ this.fallback = fallback;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.context.MessageSourceResolvable#getCodes()
*/
+ @Override
+ public String[] getCodes() {
+ return fallback.getCodes();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.core.mapping.ResourceDescription#getMessage()
+ */
+ @Override
public String getMessage() {
return StringUtils.hasText(message) ? message : fallback.getMessage();
}
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MethodResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MethodResourceMapping.java
index c33242d87..fb8a66964 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MethodResourceMapping.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MethodResourceMapping.java
@@ -16,7 +16,6 @@
package org.springframework.data.rest.core.mapping;
import java.lang.reflect.Method;
-import java.util.List;
/**
* A {@link ResourceMapping} that is backed by a {@link Method}.
@@ -33,9 +32,9 @@ public interface MethodResourceMapping extends ResourceMapping {
Method getMethod();
/**
- * Returns the names of the parameters the method exposes.
+ * Returns {@link ParameterMetadata} instances for all named parameters.
*
* @return
*/
- List getParameterNames();
+ ParametersMetadata getParametersMetadata();
}
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ParameterMetadata.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ParameterMetadata.java
new file mode 100644
index 000000000..b675647ee
--- /dev/null
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ParameterMetadata.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2014 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.core.mapping;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.data.rest.core.annotation.Description;
+import org.springframework.util.Assert;
+
+/**
+ * Value object to capture metadata for query method parameters.
+ *
+ * @author Oliver Gierke
+ */
+public final class ParameterMetadata {
+
+ private final String name;
+ private final ResourceDescription description;
+
+ /**
+ * Creates a new {@link ParameterMetadata} for the given {@link MethodParameter} and base rel.
+ *
+ * @param name must not be {@literal null} or empty.
+ * @param baseRel must not be {@literal null} or empty.
+ */
+ public ParameterMetadata(MethodParameter parameter, String baseRel) {
+
+ this.name = parameter.getParameterName();
+
+ Assert.hasText(name, "Parameter must not be null or empty!");
+ Assert.hasText(baseRel, "Method rel must not be null!");
+
+ ResourceDescription fallback = TypedResourceDescription.defaultFor(baseRel.concat(".").concat(name),
+ parameter.getParameterType());
+ Description annotation = parameter.getParameterAnnotation(Description.class);
+
+ this.description = annotation == null ? fallback : new AnnotationBasedResourceDescription(annotation, fallback);
+ }
+
+ /**
+ * Return sthe name of the method parameter.
+ *
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the description for the method parameter.
+ *
+ * @return the description
+ */
+ public ResourceDescription getDescription() {
+ return description;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof ParameterMetadata)) {
+ return false;
+ }
+
+ ParameterMetadata that = (ParameterMetadata) obj;
+ return this.name.equals(that.name) && this.description.equals(that.description);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+
+ result += 31 * name.hashCode();
+ result += 31 * description.hashCode();
+
+ return result;
+ }
+}
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ParametersMetadata.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ParametersMetadata.java
new file mode 100644
index 000000000..4951d7dc9
--- /dev/null
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ParametersMetadata.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 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.core.mapping;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.springframework.util.Assert;
+
+/**
+ * Value object for a list of {@link ParameterMetadata} instances.
+ *
+ * @author Oliver Gierke
+ */
+public class ParametersMetadata implements Iterable {
+
+ private final List parameterMetadata;
+
+ /**
+ * Creates a new {@link ParametersMetadata} instance for the given {@link ParameterMetadata} instances.
+ *
+ * @param parameterMetadata must not be {@literal null}.
+ */
+ ParametersMetadata(List parameterMetadata) {
+
+ Assert.notNull(parameterMetadata, "Parameter metadata must not be null!");
+
+ this.parameterMetadata = parameterMetadata;
+ }
+
+ /**
+ * Returns all parameter names.
+ *
+ * @return
+ */
+ public List getParameterNames() {
+
+ List names = new ArrayList(parameterMetadata.size());
+
+ for (ParameterMetadata metadata : parameterMetadata) {
+ names.add(metadata.getName());
+ }
+
+ return names;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Iterable#iterator()
+ */
+ @Override
+ public Iterator iterator() {
+ return parameterMetadata.iterator();
+ }
+}
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java
index eb875f79b..c5b1e1340 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java
@@ -47,7 +47,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
private final Method method;
private final boolean paging;
- private final List parameterNames;
+ private final List parameterMetadata;
/**
* Creates a new {@link RepositoryMethodResourceMapping} for the given {@link Method}.
@@ -61,24 +61,24 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
Assert.notNull(resourceMapping, "ResourceMapping must not be null!");
RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class);
+ String resourceRel = resourceMapping.getRel();
this.isExported = annotation != null ? annotation.exported() : true;
this.rel = annotation == null || !StringUtils.hasText(annotation.rel()) ? method.getName() : annotation.rel();
this.path = annotation == null || !StringUtils.hasText(annotation.path()) ? new Path(method.getName()) : new Path(
annotation.path());
this.method = method;
- this.parameterNames = discoverParameterNames(method);
+ this.parameterMetadata = discoverParameterMetadata(method, resourceRel.concat(".").concat(rel));
this.paging = Arrays.asList(method.getParameterTypes()).contains(Pageable.class);
}
- private static final List discoverParameterNames(Method method) {
+ private static final List discoverParameterMetadata(Method method, String baseRel) {
- List result = new ArrayList();
+ List result = new ArrayList();
for (MethodParameter parameter : new MethodParameters(method, PARAM_VALUE).getParameters()) {
- String name = parameter.getParameterName();
- if (name != null) {
- result.add(name);
+ if (StringUtils.hasText(parameter.getParameterName())) {
+ result.add(new ParameterMetadata(parameter, baseRel));
}
}
@@ -123,11 +123,11 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
/*
* (non-Javadoc)
- * @see org.springframework.data.rest.core.mapping.MethodResourceMapping#getParameterNames()
+ * @see org.springframework.data.rest.core.mapping.MethodResourceMapping#getParameterMetadata()
*/
@Override
- public List getParameterNames() {
- return parameterNames;
+ public ParametersMetadata getParametersMetadata() {
+ return new ParametersMetadata(parameterMetadata);
}
/*
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappings.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappings.java
index 9a0cc31ce..44f8de13e 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappings.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappings.java
@@ -210,7 +210,7 @@ public class RepositoryResourceMappings implements ResourceMappings {
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
- ResourceMapping getMappingFor(PersistentProperty> property) {
+ public ResourceMapping getMappingFor(PersistentProperty> property) {
ResourceMapping propertyMapping = propertyCache.get(property);
@@ -322,8 +322,8 @@ public class RepositoryResourceMappings implements ResourceMappings {
@Override
public ResourceDescription getDescription() {
- ResourceDescription fallback = SimpleResourceDescription.defaultFor(property,
- ownerTypeMapping.getItemResourceRel());
+ ResourceDescription fallback = TypedResourceDescription.defaultFor(ownerTypeMapping.getItemResourceRel(),
+ property);
if (description != null) {
return new AnnotationBasedResourceDescription(description, fallback);
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResolvableResourceDescriptionSupport.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResolvableResourceDescriptionSupport.java
index ac6816757..f26d99ef7 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResolvableResourceDescriptionSupport.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResolvableResourceDescriptionSupport.java
@@ -24,15 +24,6 @@ import org.springframework.context.MessageSourceResolvable;
*/
public abstract class ResolvableResourceDescriptionSupport implements ResourceDescription {
- /*
- * (non-Javadoc)
- * @see org.springframework.context.MessageSourceResolvable#getCodes()
- */
- @Override
- public String[] getCodes() {
- return new String[] { getMessage() };
- }
-
/*
* (non-Javadoc)
* @see org.springframework.context.MessageSourceResolvable#getArguments()
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SimpleResourceDescription.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SimpleResourceDescription.java
index 0fef09276..58f69e71b 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SimpleResourceDescription.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SimpleResourceDescription.java
@@ -15,8 +15,8 @@
*/
package org.springframework.data.rest.core.mapping;
-import org.springframework.data.mapping.PersistentProperty;
import org.springframework.http.MediaType;
+import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -24,49 +24,61 @@ import org.springframework.util.StringUtils;
*/
public class SimpleResourceDescription extends ResolvableResourceDescriptionSupport {
- private static final String DEFAULT_KEY_PREFIX = "rest.description";
- private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.TEXT_PLAIN;
+ protected static final String DEFAULT_KEY_PREFIX = "rest.description";
+ protected static final MediaType DEFAULT_MEDIA_TYPE = MediaType.TEXT_PLAIN;
- private String message;
- private MediaType type;
+ private final String message;
+ private final MediaType mediaType;
+
+ /**
+ * Creates a new {@link SimpleResourceDescription} with the given message and {@link MediaType}.
+ *
+ * @param message must not be {@literal null} or empty.
+ * @param mediaType must not be {@literal null} or empty.
+ */
+ protected SimpleResourceDescription(String message, MediaType mediaType) {
+
+ Assert.hasText(message, "Message must not be null or empty!");
+ Assert.notNull(mediaType, "MediaType must not be null!");
- private SimpleResourceDescription(String message, MediaType mediaType) {
this.message = message;
- this.type = mediaType;
- }
-
- public static ResourceDescription defaultFor(PersistentProperty> property, String rel) {
-
- String message = String.format("%s.%s.%s", DEFAULT_KEY_PREFIX, rel, property.getName());
- return new SimpleResourceDescription(message, DEFAULT_MEDIA_TYPE);
+ this.mediaType = mediaType;
}
public static ResourceDescription defaultFor(String rel) {
-
- String message = String.format("%s.%s", DEFAULT_KEY_PREFIX, rel);
- return new SimpleResourceDescription(message, DEFAULT_MEDIA_TYPE);
+ return new SimpleResourceDescription(String.format("%s.%s", DEFAULT_KEY_PREFIX, rel), DEFAULT_MEDIA_TYPE);
}
- public static ResourceDescription defaultForCollection(Class> type) {
- return null;
- }
-
- public static ResourceDescription defaultForMethod(RepositoryMethodResourceMapping mapping) {
- return null;
- }
-
- /**
- * @return the message
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.core.mapping.ResourceDescription#getMessage()
*/
public String getMessage() {
return message;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.core.mapping.ResourceDescription#getType()
+ */
public MediaType getType() {
- return type;
+ return mediaType;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.core.mapping.ResourceDescription#isDefault()
+ */
public boolean isDefault() {
return StringUtils.hasText(message) && message.startsWith(DEFAULT_KEY_PREFIX);
}
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.context.MessageSourceResolvable#getCodes()
+ */
+ @Override
+ public String[] getCodes() {
+ return new String[] { message };
+ }
}
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypeBasedCollectionResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypeBasedCollectionResourceMapping.java
index d3972f686..89f6f02a3 100644
--- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypeBasedCollectionResourceMapping.java
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypeBasedCollectionResourceMapping.java
@@ -127,6 +127,14 @@ class TypeBasedCollectionResourceMapping implements CollectionResourceMapping {
ResourceDescription fallback = SimpleResourceDescription.defaultFor(getRel());
+ if (description != null) {
+ return new AnnotationBasedResourceDescription(description, fallback);
+ }
+
+ if (annotation != null) {
+ return new AnnotationBasedResourceDescription(annotation.description(), fallback);
+ }
+
return fallback;
}
diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypedResourceDescription.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypedResourceDescription.java
new file mode 100644
index 000000000..1e22a3685
--- /dev/null
+++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypedResourceDescription.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2014 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.core.mapping;
+
+import java.util.Arrays;
+
+import org.springframework.data.mapping.PersistentProperty;
+import org.springframework.http.MediaType;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link SimpleResourceDescription} that additionally captures a type to be able to potentially create a reasonable
+ * default message. The implementation will do so for enum types by rendering the available values as default message
+ * and also provide them as arguments for message resolution.
+ *
+ * @author Oliver Gierke
+ */
+public class TypedResourceDescription extends SimpleResourceDescription {
+
+ private final Class> type;
+
+ /**
+ * Creates a new {@link TypedResourceDescription} for the given message, {@link MediaType} and type.
+ *
+ * @param message must not be {@literal null} or empty.
+ * @param mediaType must not be {@literal null} or empty.
+ * @param type can be {@literal null}, defaults to {@link Object}.
+ */
+ private TypedResourceDescription(String message, MediaType mediaType, Class> type) {
+
+ super(message, mediaType);
+
+ this.type = type == null ? Object.class : type;
+ }
+
+ public static ResourceDescription defaultFor(String rel, PersistentProperty> property) {
+
+ String message = String.format("%s.%s.%s", DEFAULT_KEY_PREFIX, rel, property.getName());
+ return new TypedResourceDescription(message, DEFAULT_MEDIA_TYPE, property.getType());
+ }
+
+ public static ResourceDescription defaultFor(String rel, Class> type) {
+
+ String message = String.format("%s.%s", DEFAULT_KEY_PREFIX, rel);
+ return new TypedResourceDescription(message, DEFAULT_MEDIA_TYPE, type);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.core.mapping.ResolvableResourceDescriptionSupport#getArguments()
+ */
+ @Override
+ public Object[] getArguments() {
+ return type.isEnum() ? new Object[] { getEnumValues(type) } : new Object[0];
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.core.mapping.ResolvableResourceDescriptionSupport#getDefaultMessage()
+ */
+ @Override
+ public String getDefaultMessage() {
+ return type.isEnum() ? getEnumValues(type) : null;
+ }
+
+ private String getEnumValues(Class> type) {
+ return StringUtils.collectionToDelimitedString(Arrays.asList(type.getEnumConstants()), ", ");
+ }
+}
diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java
index 9837fdad1..32535b028 100644
--- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java
+++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java
@@ -67,7 +67,7 @@ public class RepositoryMethodResourceMappingUnitTests {
Method method = PersonRepository.class.getMethod("findByLastname", String.class);
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
- assertThat(mapping.getParameterNames(), is(emptyIterable()));
+ assertThat(mapping.getParametersMetadata().getParameterNames(), is(emptyIterable()));
}
/**
@@ -79,8 +79,8 @@ public class RepositoryMethodResourceMappingUnitTests {
Method method = PersonRepository.class.getMethod("findByFirstname", String.class);
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
- assertThat(mapping.getParameterNames(), hasSize(1));
- assertThat(mapping.getParameterNames(), hasItem("firstname"));
+ assertThat(mapping.getParametersMetadata().getParameterNames(), hasSize(1));
+ assertThat(mapping.getParametersMetadata().getParameterNames(), hasItem("firstname"));
}
/**
diff --git a/spring-data-rest-webmvc/pom.xml b/spring-data-rest-webmvc/pom.xml
index 338da2ea9..3b0b58808 100644
--- a/spring-data-rest-webmvc/pom.xml
+++ b/spring-data-rest-webmvc/pom.xml
@@ -39,21 +39,6 @@
provided
-
-
-
- org.springframework
- spring-orm
- true
-
-
-
- org.hibernate.javax.persistence
- hibernate-jpa-2.0-api
- 1.0.1.Final
- true
-
-
@@ -101,6 +86,21 @@
1.7
+
+
+
+ org.springframework
+ spring-orm
+ true
+
+
+
+ org.hibernate.javax.persistence
+ hibernate-jpa-2.0-api
+ 1.0.1.Final
+ true
+
+
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUri.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUri.java
index 0a3205653..145409b00 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUri.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUri.java
@@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UrlPathHelper;
@@ -54,6 +55,15 @@ public class BaseUri {
this.baseUri = URI.create(trimTrailingCharacter(trimTrailingCharacter(uriString, '/'), '/'));
}
+ /**
+ * Creates a new {@link BaseUri} with the given URI as base.
+ *
+ * @param uri must not be {@literal null}.
+ */
+ public BaseUri(String uri) {
+ this(URI.create(uri));
+ }
+
/**
* Returns the base URI.
*
@@ -131,4 +141,19 @@ public class BaseUri {
return null;
}
+
+ /**
+ * Returns a new {@link UriComponentsBuilder} for the base URI. If the base URI is not absolute, it'll lokup the URI
+ * for the current servlet mapping and extend it accordingly.
+ *
+ * @return
+ */
+ public UriComponentsBuilder getUriComponentsBuilder() {
+
+ if (baseUri.isAbsolute()) {
+ return UriComponentsBuilder.fromUri(baseUri);
+ }
+
+ return ServletUriComponentsBuilder.fromCurrentServletMapping().path(baseUri.toString());
+ }
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java
index ccb3dbb52..520242a0d 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java
@@ -19,7 +19,6 @@ import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -28,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.rest.core.mapping.MethodResourceMapping;
+import org.springframework.data.rest.core.mapping.ParameterMetadata;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
import org.springframework.data.web.PagedResourcesAssembler;
@@ -38,6 +38,9 @@ import org.springframework.hateoas.Links;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
+import org.springframework.hateoas.TemplateVariable;
+import org.springframework.hateoas.TemplateVariable.VariableType;
+import org.springframework.hateoas.TemplateVariables;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -45,7 +48,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
-import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -63,7 +65,6 @@ class RepositorySearchController extends AbstractRepositoryRestController {
private static final String SEARCH = "/search";
private static final String BASE_MAPPING = "/{repository}" + SEARCH;
- private static final String PARAMETER_NAME_TEMPALTE_PATTERN = "{?%s}";
private final EntityLinks entityLinks;
private final ResourceMappings mappings;
@@ -302,8 +303,13 @@ class RepositorySearchController extends AbstractRepositoryRestController {
continue;
}
- String parameterTemplateVariable = getParameterTemplateVariable(mapping.getParameterNames());
- String href = builder.slash(mapping.getPath()).toString().concat(parameterTemplateVariable);
+ TemplateVariables variables = new TemplateVariables();
+
+ for (ParameterMetadata metadata : mapping.getParametersMetadata()) {
+ variables = variables.concat(new TemplateVariable(metadata.getName(), VariableType.REQUEST_PARAM));
+ }
+
+ String href = builder.slash(mapping.getPath()).toString().concat(variables.toString());
Link link = new Link(href, mapping.getRel());
@@ -317,11 +323,6 @@ class RepositorySearchController extends AbstractRepositoryRestController {
return new Links(links);
}
- private static String getParameterTemplateVariable(Collection parameters) {
- String parameterString = StringUtils.collectionToCommaDelimitedString(parameters);
- return parameters.isEmpty() ? "" : String.format(PARAMETER_NAME_TEMPALTE_PATTERN, parameterString);
- }
-
/**
* Verifies that the given {@link RootResourceInformation} has searches exposed.
*
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/AlpsController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/AlpsController.java
new file mode 100644
index 000000000..591ea4009
--- /dev/null
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/AlpsController.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.rest.webmvc.alps;
+
+import static org.springframework.web.bind.annotation.RequestMethod.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.repository.support.Repositories;
+import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
+import org.springframework.data.rest.core.mapping.ResourceMappings;
+import org.springframework.data.rest.core.mapping.ResourceMetadata;
+import org.springframework.data.rest.webmvc.BaseUri;
+import org.springframework.data.rest.webmvc.RepositoryRestController;
+import org.springframework.data.rest.webmvc.ResourceNotFoundException;
+import org.springframework.data.rest.webmvc.RootResourceInformation;
+import org.springframework.hateoas.alps.Alps;
+import org.springframework.hateoas.alps.Descriptor;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * Controller exposing semantic documentation for the resources exposed using the Application Level Profile Semantics
+ * format.
+ *
+ * @author Oliver Gierke
+ * @see http://alps.io
+ */
+@RepositoryRestController
+@RequestMapping(AlpsController.ALPS_ROOT_MAPPING)
+public class AlpsController {
+
+ static final String ALPS_ROOT_MAPPING = "/alps";
+
+ private final Repositories repositories;
+ private final ResourceMappings mappings;
+ private final RepositoryRestConfiguration configuration;
+
+ /**
+ * Creates a new {@link AlpsController} for the given {@link Repositories},
+ * {@link RootResourceInformationToAlpsDescriptorConverter} and {@link ResourceMappings}.
+ *
+ * @param repositories must not be {@literal null}.
+ * @param mappings must not be {@literal null}.
+ * @param configuration must not be {@literal null}.
+ */
+ @Autowired
+ public AlpsController(Repositories repositories, ResourceMappings mappings, RepositoryRestConfiguration configuration) {
+
+ Assert.notNull(repositories, "Repositories must not be null!");
+ Assert.notNull(mappings, "ResourceMappings must not be null!");
+ Assert.notNull(configuration, "MetadataConfiguration must not be null!");
+
+ this.repositories = repositories;
+ this.mappings = mappings;
+ this.configuration = configuration;
+ }
+
+ /**
+ * Exposes the allowed HTTP methods for the ALPS resources.
+ *
+ * @return
+ */
+ @RequestMapping(value = { "", "/{repository}" }, method = OPTIONS)
+ HttpEntity> alpsOptions() {
+
+ verifyAlpsEnabled();
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setAllow(Collections.singleton(HttpMethod.GET));
+
+ return new ResponseEntity