information;
@@ -236,11 +240,17 @@ protected P returnPropertyIfBetterIdPropertyCandidateOrNull(P property) {
return property;
}
- /* (non-Javadoc)
+ /*
+ * (non-Javadoc)
* @see org.springframework.data.mapping.MutablePersistentEntity#addAssociation(org.springframework.data.mapping.model.Association)
*/
public void addAssociation(Association association) {
+ if (association == null) {
+ LOGGER.warn(String.format(NULL_ASSOCIATION, this.getClass().getName()));
+ return;
+ }
+
if (!associations.contains(association)) {
associations.add(association);
}
diff --git a/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java b/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java
index abc87f0fbb..a6091e828b 100644
--- a/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java
+++ b/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java
@@ -21,6 +21,7 @@
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.types.PathType;
/**
* Utility class for Querydsl.
@@ -64,6 +65,10 @@ private static String toDotPath(Path> path, String tail) {
return tail;
}
+ if (metadata.getPathType().equals(PathType.DELEGATE)) {
+ return toDotPath(parent, tail);
+ }
+
Object element = metadata.getElement();
if (element == null || !StringUtils.hasText(element.toString())) {
diff --git a/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java
new file mode 100644
index 0000000000..071bf97dd8
--- /dev/null
+++ b/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 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.querydsl.binding;
+
+import java.beans.PropertyDescriptor;
+
+import org.springframework.data.mapping.PropertyPath;
+import org.springframework.data.querydsl.EntityPathResolver;
+
+import com.querydsl.core.types.Path;
+
+/**
+ * Internal abstraction of mapped paths.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+interface PathInformation {
+
+ /**
+ * The type of the leaf property.
+ *
+ * @return
+ */
+ Class> getLeafType();
+
+ /**
+ * The type of the leaf property's parent type.
+ *
+ * @return
+ */
+ Class> getLeafParentType();
+
+ /**
+ * The name of the leaf property.
+ *
+ * @return
+ */
+ String getLeafProperty();
+
+ /**
+ * The {@link PropertyDescriptor} for the leaf property.
+ *
+ * @return
+ */
+ PropertyDescriptor getLeafPropertyDescriptor();
+
+ /**
+ * The dot separated representation of the current path.
+ *
+ * @return
+ */
+ String toDotPath();
+
+ /**
+ * Tries to reify a Querydsl {@link Path} from the given {@link PropertyPath} and base.
+ *
+ * @param path must not be {@literal null}.
+ * @param base can be {@literal null}.
+ * @return
+ */
+ Path> reifyPath(EntityPathResolver resolver);
+}
diff --git a/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java
new file mode 100644
index 0000000000..4634028090
--- /dev/null
+++ b/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2016 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.querydsl.binding;
+
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.mapping.PropertyPath;
+import org.springframework.data.querydsl.EntityPathResolver;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.util.ReflectionUtils;
+
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.CollectionPathBase;
+
+/**
+ * {@link PropertyPath} based implementation of {@link PathInformation}.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+@ToString
+@EqualsAndHashCode
+@RequiredArgsConstructor(staticName = "of", access = AccessLevel.PRIVATE)
+class PropertyPathInformation implements PathInformation {
+
+ private final PropertyPath path;
+
+ /**
+ * Creates a new {@link PropertyPathInformation} for the given path and type.
+ *
+ * @param path must not be {@literal null} or empty.
+ * @param type must not be {@literal null}.
+ * @return
+ */
+ public static PropertyPathInformation of(String path, Class> type) {
+ return PropertyPathInformation.of(PropertyPath.from(path, type));
+ }
+
+ /**
+ * Creates a new {@link PropertyPathInformation} for the given path and {@link TypeInformation}.
+ *
+ * @param path must not be {@literal null} or empty.
+ * @param type must not be {@literal null}.
+ * @return
+ */
+ public static PropertyPathInformation of(String path, TypeInformation> type) {
+ return PropertyPathInformation.of(PropertyPath.from(path, type));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#getLeafType()
+ */
+ @Override
+ public Class> getLeafType() {
+ return path.getLeafProperty().getType();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#getLeafParentType()
+ */
+ @Override
+ public Class> getLeafParentType() {
+ return path.getLeafProperty().getOwningType().getType();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#getLeafProperty()
+ */
+ @Override
+ public String getLeafProperty() {
+ return path.getLeafProperty().getSegment();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#getLeafPropertyDescriptor()
+ */
+ @Override
+ public PropertyDescriptor getLeafPropertyDescriptor() {
+ return BeanUtils.getPropertyDescriptor(getLeafParentType(), getLeafProperty());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#toDotPath()
+ */
+ @Override
+ public String toDotPath() {
+ return path.toDotPath();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.PathInformation#reifyPath(org.springframework.data.querydsl.EntityPathResolver)
+ */
+ @Override
+ public Path> reifyPath(EntityPathResolver resolver) {
+ return reifyPath(resolver, path, null);
+ }
+
+ private static Path> reifyPath(EntityPathResolver resolver, PropertyPath path, Path> base) {
+
+ if (base instanceof CollectionPathBase) {
+ return reifyPath(resolver, path, (Path>) ((CollectionPathBase, ?, ?>) base).any());
+ }
+
+ Path> entityPath = base != null ? base : resolver.createPath(path.getOwningType().getType());
+
+ Field field = ReflectionUtils.findField(entityPath.getClass(), path.getSegment());
+ Object value = ReflectionUtils.getField(field, entityPath);
+
+ if (path.hasNext()) {
+ return reifyPath(resolver, path.next(), (Path>) value);
+ }
+
+ return (Path>) value;
+ }
+}
diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java
index 69a0f45f0b..14c4847e7e 100644
--- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java
+++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java
@@ -17,6 +17,10 @@
import static org.springframework.data.querydsl.QueryDslUtils.*;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.Value;
+
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -197,7 +201,7 @@ boolean isPathAvailable(String path, TypeInformation> type) {
* @return can be {@literal null}.
*/
@SuppressWarnings("unchecked")
- public , T> MultiValueBinding getBindingForPath(PropertyPath path) {
+ public , T> MultiValueBinding getBindingForPath(PathInformation path) {
Assert.notNull(path, "PropertyPath must not be null!");
@@ -212,7 +216,7 @@ public , T> MultiValueBinding getBindingForPat
}
}
- pathAndBinding = (PathAndBinding) typeSpecs.get(path.getLeafProperty().getType());
+ pathAndBinding = (PathAndBinding) typeSpecs.get(path.getLeafType());
return pathAndBinding == null ? null : pathAndBinding.getBinding();
}
@@ -223,7 +227,7 @@ public , T> MultiValueBinding getBindingForPat
* @param path must not be {@literal null}.
* @return
*/
- Path> getExistingPath(PropertyPath path) {
+ Path> getExistingPath(PathInformation path) {
Assert.notNull(path, "PropertyPath must not be null!");
@@ -232,24 +236,27 @@ Path> getExistingPath(PropertyPath path) {
}
/**
- * @param path
- * @param type
+ * Returns the {@link PathInformation} for the given path and {@link TypeInformation}.
+ *
+ * @param path must not be {@literal null}.
+ * @param type must not be {@literal null}.
* @return
*/
- PropertyPath getPropertyPath(String path, TypeInformation> type) {
+ PathInformation getPropertyPath(String path, TypeInformation> type) {
Assert.notNull(path, "Path must not be null!");
+ Assert.notNull(type, "Type information must not be null!");
if (!isPathVisible(path)) {
return null;
}
if (pathSpecs.containsKey(path)) {
- return PropertyPath.from(toDotPath(pathSpecs.get(path).getPath()), type);
+ return QuerydslPathInformation.of(pathSpecs.get(path).getPath());
}
try {
- PropertyPath propertyPath = PropertyPath.from(path, type);
+ PathInformation propertyPath = PropertyPathInformation.of(path, type);
return isPathVisible(propertyPath) ? propertyPath : null;
} catch (PropertyReferenceException o_O) {
return null;
@@ -262,7 +269,7 @@ PropertyPath getPropertyPath(String path, TypeInformation> type) {
* @param path
* @return
*/
- private boolean isPathVisible(PropertyPath path) {
+ private boolean isPathVisible(PathInformation path) {
List segments = Arrays.asList(path.toDotPath().split("\\."));
@@ -436,21 +443,10 @@ protected void registerBinding(PathAndBinding binding) {
*
* @author Oliver Gierke
*/
+ @RequiredArgsConstructor
public final class TypeBinder {
- private final Class type;
-
- /**
- * Creates a new {@link TypeBinder} for the given type.
- *
- * @param type must not be {@literal null}.
- */
- private TypeBinder(Class type) {
-
- Assert.notNull(type, "Type must not be null!");
-
- this.type = type;
- }
+ private final @NonNull Class type;
/**
* Configures the given {@link SingleValueBinding} to be used for the current type.
@@ -479,32 +475,14 @@ public > void all(MultiValueBinding
binding) {
* A pair of a {@link Path} and the registered {@link MultiValueBinding}.
*
* @author Christoph Strobl
+ * @author Oliver Gierke
* @since 1.11
*/
+ @Value
private static class PathAndBinding, T> {
- private final Path> path;
- private final MultiValueBinding binding;
-
- /**
- * Creates a new {@link PathAndBinding} for the given {@link Path} and {@link MultiValueBinding}.
- *
- * @param path must not be {@literal null}.
- * @param binding must not be {@literal null}.
- */
- public PathAndBinding(S path, MultiValueBinding binding) {
-
- this.path = path;
- this.binding = binding;
- }
-
- public Path> getPath() {
- return path;
- }
-
- public MultiValueBinding getBinding() {
- return binding;
- }
+ Path> path;
+ MultiValueBinding binding;
}
/**
@@ -513,26 +491,18 @@ public MultiValueBinding getBinding() {
*
* @author Oliver Gierke
*/
- static class MultiValueBindingAdapter, S> implements MultiValueBinding {
-
- private final SingleValueBinding delegate;
+ @RequiredArgsConstructor
+ static class MultiValueBindingAdapter, T> implements MultiValueBinding
{
- /**
- * Creates a new {@link MultiValueBindingAdapter} for the given {@link SingleValueBinding}.
- *
- * @param delegate must not be {@literal null}.
- */
- public MultiValueBindingAdapter(SingleValueBinding delegate) {
- this.delegate = delegate;
- }
+ private final @NonNull SingleValueBinding delegate;
/*
* (non-Javadoc)
* @see org.springframework.data.web.querydsl.MultiValueBinding#bind(com.mysema.query.types.Path, java.util.Collection)
*/
@Override
- public Predicate bind(T path, Collection extends S> value) {
- Iterator extends S> iterator = value.iterator();
+ public Predicate bind(P path, Collection extends T> value) {
+ Iterator extends T> iterator = value.iterator();
return delegate.bind(path, iterator.hasNext() ? iterator.next() : null);
}
}
diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java
new file mode 100644
index 0000000000..1f78dc3c2c
--- /dev/null
+++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2016 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.querydsl.binding;
+
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
+import java.beans.PropertyDescriptor;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.querydsl.EntityPathResolver;
+import org.springframework.data.querydsl.QueryDslUtils;
+
+import com.querydsl.core.types.Path;
+
+/**
+ * {@link PathInformation} based on a Querydsl {@link Path}.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+@ToString
+@EqualsAndHashCode
+@RequiredArgsConstructor(staticName = "of")
+class QuerydslPathInformation implements PathInformation {
+
+ private final Path> path;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#getLeafType()
+ */
+ @Override
+ public Class> getLeafType() {
+ return path.getType();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#getLeafParentType()
+ */
+ @Override
+ public Class> getLeafParentType() {
+ return path.getMetadata().getParent().getType();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#getLeafProperty()
+ */
+ @Override
+ public String getLeafProperty() {
+ return path.getMetadata().getElement().toString();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#getLeafPropertyDescriptor()
+ */
+ @Override
+ public PropertyDescriptor getLeafPropertyDescriptor() {
+ return BeanUtils.getPropertyDescriptor(getLeafParentType(), getLeafProperty());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.MappedPath#toDotPath()
+ */
+ @Override
+ public String toDotPath() {
+ return QueryDslUtils.toDotPath(path);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.PathInformation#reifyPath(org.springframework.data.querydsl.EntityPathResolver)
+ */
+ public Path> reifyPath(EntityPathResolver resolver) {
+ return path;
+ }
+}
diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java
index bc53abdcd6..bd230fab1e 100644
--- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java
+++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java
@@ -16,7 +16,6 @@
package org.springframework.data.querydsl.binding;
import java.beans.PropertyDescriptor;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -25,7 +24,6 @@
import java.util.Map;
import java.util.Map.Entry;
-import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyValues;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.Property;
@@ -41,7 +39,6 @@
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
-import com.querydsl.core.types.dsl.CollectionPathBase;
/**
* Builder assembling {@link Predicate} out of {@link PropertyValues}.
@@ -54,7 +51,7 @@ public class QuerydslPredicateBuilder {
private final ConversionService conversionService;
private final MultiValueBinding, ?> defaultBinding;
- private final Map> paths;
+ private final Map> paths;
private final EntityPathResolver resolver;
/**
@@ -70,7 +67,7 @@ public QuerydslPredicateBuilder(ConversionService conversionService, EntityPathR
this.defaultBinding = new QuerydslDefaultBinding();
this.conversionService = conversionService;
- this.paths = new HashMap>();
+ this.paths = new HashMap>();
this.resolver = resolver;
}
@@ -106,7 +103,7 @@ public Predicate getPredicate(TypeInformation> type, MultiValueMap type, MultiValueMap values) {
+ private Predicate invokeBinding(PathInformation dotPath, QuerydslBindings bindings, Collection values) {
Path> path = getPath(dotPath, bindings);
@@ -151,7 +148,7 @@ private Predicate invokeBinding(PropertyPath dotPath, QuerydslBindings bindings,
* @param bindings must not be {@literal null}.
* @return
*/
- private Path> getPath(PropertyPath path, QuerydslBindings bindings) {
+ private Path> getPath(PathInformation path, QuerydslBindings bindings) {
Path> resolvedPath = bindings.getExistingPath(path);
@@ -165,37 +162,12 @@ private Path> getPath(PropertyPath path, QuerydslBindings bindings) {
return resolvedPath;
}
- resolvedPath = reifyPath(path, null);
+ resolvedPath = path.reifyPath(resolver);
paths.put(path, resolvedPath);
return resolvedPath;
}
- /**
- * Tries to reify a Querydsl {@link Path} from the given {@link PropertyPath} and base.
- *
- * @param path must not be {@literal null}.
- * @param base can be {@literal null}.
- * @return
- */
- private Path> reifyPath(PropertyPath path, Path> base) {
-
- if (base instanceof CollectionPathBase) {
- return reifyPath(path, (Path>) ((CollectionPathBase, ?, ?>) base).any());
- }
-
- Path> entityPath = base != null ? base : resolver.createPath(path.getOwningType().getType());
-
- Field field = ReflectionUtils.findField(entityPath.getClass(), path.getSegment());
- Object value = ReflectionUtils.getField(field, entityPath);
-
- if (path.hasNext()) {
- return reifyPath(path.next(), (Path>) value);
- }
-
- return (Path>) value;
- }
-
/**
* Converts the given source values into a collection of elements that are of the given {@link PropertyPath}'s type.
* Considers a single element list with an empty {@link String} an empty collection because this basically indicates
@@ -205,10 +177,9 @@ private Path> reifyPath(PropertyPath path, Path> base) {
* @param path must not be {@literal null}.
* @return
*/
- private Collection convertToPropertyPathSpecificType(List source, PropertyPath path) {
+ private Collection convertToPropertyPathSpecificType(List source, PathInformation path) {
- PropertyPath leafProperty = path.getLeafProperty();
- Class> targetType = leafProperty.getOwningType().getProperty(leafProperty.getSegment()).getType();
+ Class> targetType = path.getLeafType();
if (source.isEmpty() || isSingleElementCollectionWithoutText(source)) {
return Collections.emptyList();
@@ -226,26 +197,25 @@ private Collection convertToPropertyPathSpecificType(List source
}
/**
- * Returns the target {@link TypeDescriptor} for the given {@link PropertyPath} by either inspecting the field or
+ * Returns the target {@link TypeDescriptor} for the given {@link PathInformation} by either inspecting the field or
* property (the latter preferred) to pick up annotations potentially defined for formatting purposes.
*
* @param path must not be {@literal null}.
* @return
*/
- private static TypeDescriptor getTargetTypeDescriptor(PropertyPath path) {
+ private static TypeDescriptor getTargetTypeDescriptor(PathInformation path) {
- PropertyPath leafProperty = path.getLeafProperty();
- Class> owningType = leafProperty.getOwningType().getType();
+ PropertyDescriptor descriptor = path.getLeafPropertyDescriptor();
- PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(owningType, leafProperty.getSegment());
+ Class> owningType = path.getLeafParentType();
+ String leafProperty = path.getLeafProperty();
if (descriptor == null) {
- return TypeDescriptor.nested(ReflectionUtils.findField(owningType, leafProperty.getSegment()), 0);
+ return TypeDescriptor.nested(ReflectionUtils.findField(owningType, leafProperty), 0);
}
- return TypeDescriptor.nested(
- new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty.getSegment()),
- 0);
+ return TypeDescriptor
+ .nested(new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty), 0);
}
/**
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
index e28124c3a2..68f0d754c4 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
@@ -82,13 +82,13 @@ public BeanDefinitionBuilder build(RepositoryConfiguration> configuration) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
- factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName : extension
- .getRepositoryFactoryClassName();
+ factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
+ : extension.getRepositoryFactoryClassName();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);
builder.getRawBeanDefinition().setSource(configuration.getSource());
- builder.addPropertyValue("repositoryInterface", configuration.getRepositoryInterface());
+ builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());
@@ -127,8 +127,8 @@ private String registerCustomImplementation(RepositoryConfiguration> configura
return beanName;
}
- AbstractBeanDefinition beanDefinition = implementationDetector.detectCustomImplementation(
- configuration.getImplementationClassName(), configuration.getBasePackages());
+ AbstractBeanDefinition beanDefinition = implementationDetector
+ .detectCustomImplementation(configuration.getImplementationClassName(), configuration.getBasePackages());
if (null == beanDefinition) {
return null;
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanNameGenerator.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanNameGenerator.java
index 667adcc205..1020be8b89 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanNameGenerator.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanNameGenerator.java
@@ -65,7 +65,7 @@ public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry
*/
private Class> getRepositoryInterfaceFrom(BeanDefinition beanDefinition) {
- Object value = beanDefinition.getPropertyValues().getPropertyValue("repositoryInterface").getValue();
+ Object value = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, Class.class).getValue();
if (value instanceof Class>) {
return (Class>) value;
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
index 950e504cb6..6b0718d916 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
@@ -25,12 +25,11 @@
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
-import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
-import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.util.Assert;
@@ -48,7 +47,6 @@ public class RepositoryConfigurationDelegate {
private static final String REPOSITORY_REGISTRATION = "Spring Data {} - Registering repository: {} - Interface: {} - Factory: {}";
private static final String MULTIPLE_MODULES = "Multiple Spring Data modules found, entering strict repository configuration mode!";
- private static final String MODULE_DETECTION_PACKAGE = "org.springframework.data.**.repository.support";
static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
@@ -160,17 +158,13 @@ public List registerRepositoriesIn(BeanDefinitionRegist
*/
private boolean multipleStoresDetected() {
- ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
- scanner.setEnvironment(environment);
- scanner.setResourceLoader(resourceLoader);
- scanner.addIncludeFilter(new AssignableTypeFilter(RepositoryFactorySupport.class));
-
- if (scanner.findCandidateComponents(MODULE_DETECTION_PACKAGE).size() > 1) {
+ boolean multipleModulesFound = SpringFactoriesLoader
+ .loadFactoryNames(RepositoryFactorySupport.class, resourceLoader.getClassLoader()).size() > 1;
+ if (multipleModulesFound) {
LOGGER.info(MULTIPLE_MODULES);
- return true;
}
- return false;
+ return multipleModulesFound;
}
}
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
index 1596b67e45..04f5850a3e 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
@@ -33,7 +33,6 @@
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
-import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -50,8 +49,6 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
private static final String CLASS_LOADING_ERROR = "%s - Could not load type %s using class loader %s.";
private static final String MULTI_STORE_DROPPED = "Spring Data {} - Could not safely identify store assignment for repository candidate {}.";
- private static final String FACTORY_BEAN_TYPE_PREDICTING_POST_PROCESSOR = "org.springframework.data.repository.core.support.FactoryBeanTypePredictingBeanPostProcessor";
-
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getModuleName()
@@ -114,18 +111,8 @@ public String getDefaultNamedQueryLocation() {
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource)
*/
- public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
-
- String typeName = RepositoryFactoryBeanSupport.class.getName();
-
- BeanDefinitionBuilder builder = BeanDefinitionBuilder
- .rootBeanDefinition(FACTORY_BEAN_TYPE_PREDICTING_POST_PROCESSOR);
- builder.addConstructorArgValue(typeName);
- builder.addConstructorArgValue("repositoryInterface");
-
- registerIfNotAlreadyRegistered(builder.getBeanDefinition(), registry, typeName.concat("_Predictor"),
- configurationSource.getSource());
- }
+ public void registerBeansForRoot(BeanDefinitionRegistry registry,
+ RepositoryConfigurationSource configurationSource) {}
/**
* Returns the prefix of the module to be used to create the default location for Spring Data named queries.
diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java
index 93bb399141..76c0fdbbfd 100644
--- a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java
+++ b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java
@@ -22,6 +22,7 @@
import java.io.Serializable;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
@@ -184,6 +185,7 @@ public Set getQueryMethods() {
*/
private boolean isQueryMethodCandidate(Method method) {
return !method.isBridge() && !ReflectionUtils.isDefaultMethod(method) //
+ && !Modifier.isStatic(method.getModifiers()) //
&& (isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method));
}
@@ -243,6 +245,12 @@ Method getTargetClassMethod(Method method, Class> baseClass) {
return method;
}
+ Method result = findMethod(baseClass, method.getName(), method.getParameterTypes());
+
+ if (result != null) {
+ return result;
+ }
+
for (Method baseClassMethod : baseClass.getMethods()) {
// Wrong name
diff --git a/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java b/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java
new file mode 100644
index 0000000000..90200607d5
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2016 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.repository.core.support;
+
+import lombok.RequiredArgsConstructor;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.domain.AfterDomainEventPublication;
+import org.springframework.data.domain.DomainEvents;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.util.AnnotationDetectionMethodCallback;
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * {@link RepositoryProxyPostProcessor} to register a {@link MethodInterceptor} to intercept the
+ * {@link CrudRepository#save(Object)} method and publish events potentially exposed via a method annotated with
+ * {@link DomainEvents}. If no such method can be detected on the aggregate root, no interceptor is added. Additionally,
+ * the aggregate root can expose a method annotated with {@link AfterDomainEventPublication}. If present, the method
+ * will be invoked after all events have been published.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ * @soundtrack Henrik Freischlader Trio - Master Plan (Openness)
+ */
+@RequiredArgsConstructor
+public class EventPublishingRepositoryProxyPostProcessor implements RepositoryProxyPostProcessor {
+
+ private final ApplicationEventPublisher publisher;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.core.support.RepositoryProxyPostProcessor#postProcess(org.springframework.aop.framework.ProxyFactory, org.springframework.data.repository.core.RepositoryInformation)
+ */
+ @Override
+ public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
+
+ EventPublishingMethod method = EventPublishingMethod.of(repositoryInformation.getDomainType());
+
+ if (method == null) {
+ return;
+ }
+
+ factory.addAdvice(new EventPublishingMethodInterceptor(method, publisher));
+ }
+
+ /**
+ * {@link MethodInterceptor} to publish events exposed an aggregate on calls to a save method on the repository.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+ @RequiredArgsConstructor(staticName = "of")
+ static class EventPublishingMethodInterceptor implements MethodInterceptor {
+
+ private final EventPublishingMethod eventMethod;
+ private final ApplicationEventPublisher publisher;
+
+ /*
+ * (non-Javadoc)
+ * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+ */
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+
+ if (!invocation.getMethod().getName().equals("save")) {
+ return invocation.proceed();
+ }
+
+ for (Object argument : invocation.getArguments()) {
+ eventMethod.publishEventsFrom(argument, publisher);
+ }
+
+ return invocation.proceed();
+ }
+ }
+
+ /**
+ * Abstraction of a method on the aggregate root that exposes the events to publish.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+ @RequiredArgsConstructor
+ static class EventPublishingMethod {
+
+ private static Map, EventPublishingMethod> CACHE = new ConcurrentReferenceHashMap, EventPublishingMethod>();
+ private static EventPublishingMethod NONE = new EventPublishingMethod(null, null);
+
+ private final Method publishingMethod;
+ private final Method clearingMethod;
+
+ /**
+ * Creates an {@link EventPublishingMethod} for the given type.
+ *
+ * @param type must not be {@literal null}.
+ * @return an {@link EventPublishingMethod} for the given type or {@literal null} in case the given type does not
+ * expose an event publishing method.
+ */
+ public static EventPublishingMethod of(Class> type) {
+
+ Assert.notNull(type, "Type must not be null!");
+
+ EventPublishingMethod eventPublishingMethod = CACHE.get(type);
+
+ if (eventPublishingMethod != null) {
+ return eventPublishingMethod.orNull();
+ }
+
+ AnnotationDetectionMethodCallback publishing = new AnnotationDetectionMethodCallback(
+ DomainEvents.class);
+ ReflectionUtils.doWithMethods(type, publishing);
+
+ // TODO: Lazify this as the inspection might not be needed if the publishing callback didn't find an annotation in
+ // the first place
+
+ AnnotationDetectionMethodCallback clearing = new AnnotationDetectionMethodCallback(
+ AfterDomainEventPublication.class);
+ ReflectionUtils.doWithMethods(type, clearing);
+
+ EventPublishingMethod result = from(publishing, clearing);
+
+ CACHE.put(type, result);
+
+ return result.orNull();
+ }
+
+ /**
+ * Publishes all events in the given aggregate root using the given {@link ApplicationEventPublisher}.
+ *
+ * @param object can be {@literal null}.
+ * @param publisher must not be {@literal null}.
+ */
+ public void publishEventsFrom(Object object, ApplicationEventPublisher publisher) {
+
+ if (object == null) {
+ return;
+ }
+
+ for (Object aggregateRoot : asCollection(object)) {
+ for (Object event : asCollection(ReflectionUtils.invokeMethod(publishingMethod, aggregateRoot))) {
+ publisher.publishEvent(event);
+ }
+ }
+
+ if (clearingMethod != null) {
+ ReflectionUtils.invokeMethod(clearingMethod, object);
+ }
+ }
+
+ /**
+ * Returns the current {@link EventPublishingMethod} or {@literal null} if it's the default value.
+ *
+ * @return
+ */
+ private EventPublishingMethod orNull() {
+ return this == EventPublishingMethod.NONE ? null : this;
+ }
+
+ /**
+ * Creates a new {@link EventPublishingMethod} using the given pre-populated
+ * {@link AnnotationDetectionMethodCallback} looking up an optional clearing method from the given callback.
+ *
+ * @param publishing must not be {@literal null}.
+ * @param clearing must not be {@literal null}.
+ * @return
+ */
+ private static EventPublishingMethod from(AnnotationDetectionMethodCallback> publishing,
+ AnnotationDetectionMethodCallback> clearing) {
+
+ if (!publishing.hasFoundAnnotation()) {
+ return EventPublishingMethod.NONE;
+ }
+
+ Method eventMethod = publishing.getMethod();
+ ReflectionUtils.makeAccessible(eventMethod);
+
+ return new EventPublishingMethod(eventMethod, getClearingMethod(clearing));
+ }
+
+ /**
+ * Returns the {@link Method} supposed to be invoked for event clearing or {@literal null} if none is found.
+ *
+ * @param clearing must not be {@literal null}.
+ * @return
+ */
+ private static Method getClearingMethod(AnnotationDetectionMethodCallback> clearing) {
+
+ if (!clearing.hasFoundAnnotation()) {
+ return null;
+ }
+
+ Method method = clearing.getMethod();
+ ReflectionUtils.makeAccessible(method);
+
+ return method;
+ }
+
+ /**
+ * Returns the given source object as collection, i.e. collections are returned as is, objects are turned into a
+ * one-element collection, {@literal null} will become an empty collection.
+ *
+ * @param source can be {@literal null}.
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ private static Collection asCollection(Object source) {
+
+ if (source == null) {
+ return Collections.emptyList();
+ }
+
+ if (Collection.class.isInstance(source)) {
+ return (Collection) source;
+ }
+
+ return Arrays.asList(source);
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingBeanPostProcessor.java b/src/main/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingBeanPostProcessor.java
deleted file mode 100644
index b2afe0574c..0000000000
--- a/src/main/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingBeanPostProcessor.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2016 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.repository.core.support;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.PropertyValue;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.BeanFactoryAware;
-import org.springframework.beans.factory.FactoryBean;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
-import org.springframework.beans.factory.config.TypedStringValue;
-import org.springframework.core.Ordered;
-import org.springframework.core.PriorityOrdered;
-import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
-
-/**
- * {@link InstantiationAwareBeanPostProcessorAdapter} to predict the bean type for {@link FactoryBean} implementations
- * by interpreting a configured property of the {@link BeanDefinition} as type to be created eventually.
- *
- * @author Oliver Gierke
- * @since 1.12
- * @soundtrack Ron Spielmann - Lock Me Up (Electric Tales)
- */
-public class FactoryBeanTypePredictingBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
- implements BeanFactoryAware, PriorityOrdered {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(FactoryBeanTypePredictingBeanPostProcessor.class);
-
- private final Map> cache = new ConcurrentHashMap>();
- private final Class> factoryBeanType;
- private final List properties;
- private ConfigurableListableBeanFactory context;
-
- /**
- * Creates a new {@link FactoryBeanTypePredictingBeanPostProcessor} predicting the type created by the
- * {@link FactoryBean} of the given type by inspecting the {@link BeanDefinition} and considering the value for the
- * given property as type to be created eventually.
- *
- * @param factoryBeanType must not be {@literal null}.
- * @param properties must not be {@literal null} or empty.
- */
- public FactoryBeanTypePredictingBeanPostProcessor(Class> factoryBeanType, String... properties) {
-
- Assert.notNull(factoryBeanType, "FactoryBean type must not be null!");
- Assert.isTrue(FactoryBean.class.isAssignableFrom(factoryBeanType), "Given type is not a FactoryBean type!");
- Assert.notEmpty(properties, "Properties must not be empty!");
-
- for (String property : properties) {
- Assert.hasText(property, "Type property must not be null!");
- }
-
- this.factoryBeanType = factoryBeanType;
- this.properties = Arrays.asList(properties);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
- */
- public void setBeanFactory(BeanFactory beanFactory) {
-
- if (beanFactory instanceof ConfigurableListableBeanFactory) {
- this.context = (ConfigurableListableBeanFactory) beanFactory;
- }
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter#predictBeanType(java.lang.Class, java.lang.String)
- */
- @Override
- public Class> predictBeanType(Class> beanClass, String beanName) {
-
- if (null == context || !factoryBeanType.isAssignableFrom(beanClass)) {
- return null;
- }
-
- Class> resolvedBeanClass = cache.get(beanName);
-
- if (resolvedBeanClass != null) {
- return resolvedBeanClass == Void.class ? null : resolvedBeanClass;
- }
-
- BeanDefinition definition = context.getBeanDefinition(beanName);
-
- try {
-
- for (String property : properties) {
-
- PropertyValue value = definition.getPropertyValues().getPropertyValue(property);
- resolvedBeanClass = getClassForPropertyValue(value, beanName);
-
- if (Void.class.equals(resolvedBeanClass)) {
- continue;
- }
-
- return resolvedBeanClass;
- }
-
- return null;
-
- } finally {
- cache.put(beanName, resolvedBeanClass);
- }
- }
-
- /**
- * Returns the class which is configured in the given {@link PropertyValue}. In case it is not a
- * {@link TypedStringValue} or the value contained cannot be interpreted as {@link Class} it will return {@link Void}.
- *
- * @param propertyValue can be {@literal null}.
- * @param beanName must not be {@literal null}.
- * @return
- */
- private Class> getClassForPropertyValue(PropertyValue propertyValue, String beanName) {
-
- if (propertyValue == null) {
- return Void.class;
- }
-
- Object value = propertyValue.getValue();
- String className = null;
-
- if (value instanceof TypedStringValue) {
- className = ((TypedStringValue) value).getValue();
- } else if (value instanceof String) {
- className = (String) value;
- } else if (value instanceof Class>) {
- return (Class>) value;
- } else if (value instanceof String[]) {
-
- String[] values = (String[]) value;
-
- if (values.length == 0) {
- return Void.class;
- } else {
- className = values[0];
- }
-
- } else {
- return Void.class;
- }
-
- try {
- return ClassUtils.resolveClassName(className, context.getBeanClassLoader());
- } catch (IllegalArgumentException ex) {
- LOGGER.warn(
- String.format("Couldn't load class %s referenced as repository interface in bean %s!", className, beanName));
- return Void.class;
- }
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.core.Ordered#getOrder()
- */
- public int getOrder() {
- return Ordered.LOWEST_PRECEDENCE - 1;
- }
-}
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java
index 12ef4656cd..aa17dd4977 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java
+++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2015 the original author or authors.
+ * Copyright 2008-2016 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.
@@ -24,7 +24,8 @@
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Required;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.Repository;
@@ -47,13 +48,14 @@
* @author Oliver Gierke
* @author Thomas Darimont
*/
-public abstract class RepositoryFactoryBeanSupport, S, ID extends Serializable> implements
- InitializingBean, RepositoryFactoryInformation, FactoryBean, BeanClassLoaderAware, BeanFactoryAware {
+public abstract class RepositoryFactoryBeanSupport, S, ID extends Serializable>
+ implements InitializingBean, RepositoryFactoryInformation, FactoryBean, BeanClassLoaderAware,
+ BeanFactoryAware, ApplicationEventPublisherAware {
- private RepositoryFactorySupport factory;
+ private final Class extends T> repositoryInterface;
+ private RepositoryFactorySupport factory;
private Key queryLookupStrategyKey;
- private Class extends T> repositoryInterface;
private Class> repositoryBaseClass;
private Object customImplementation;
private NamedQueries namedQueries;
@@ -62,20 +64,20 @@ public abstract class RepositoryFactoryBeanSupport,
private BeanFactory beanFactory;
private boolean lazyInit = false;
private EvaluationContextProvider evaluationContextProvider = DefaultEvaluationContextProvider.INSTANCE;
+ private ApplicationEventPublisher publisher;
private T repository;
private RepositoryMetadata repositoryMetadata;
/**
- * Setter to inject the repository interface to implement.
+ * Creates a new {@link RepositoryFactoryBeanSupport} for the given repository interface.
*
- * @param repositoryInterface the repository interface to set
+ * @param repositoryInterface must not be {@literal null}.
*/
- @Required
- public void setRepositoryInterface(Class extends T> repositoryInterface) {
+ protected RepositoryFactoryBeanSupport(Class extends T> repositoryInterface) {
- Assert.notNull(repositoryInterface);
+ Assert.notNull(repositoryInterface, "Repository interface must not be null!");
this.repositoryInterface = repositoryInterface;
}
@@ -162,7 +164,15 @@ public void setBeanClassLoader(ClassLoader classLoader) {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
+ }
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
+ */
+ @Override
+ public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
+ this.publisher = publisher;
}
/*
@@ -217,9 +227,8 @@ public T getObject() {
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
- @SuppressWarnings("unchecked")
public Class extends T> getObjectType() {
- return (Class extends T>) (null == repositoryInterface ? Repository.class : repositoryInterface);
+ return repositoryInterface;
}
/*
@@ -236,8 +245,6 @@ public boolean isSingleton() {
*/
public void afterPropertiesSet() {
- Assert.notNull(repositoryInterface, "Repository interface must not be null on initialization!");
-
this.factory = createRepositoryFactory();
this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
this.factory.setNamedQueries(namedQueries);
@@ -246,6 +253,10 @@ public void afterPropertiesSet() {
this.factory.setBeanClassLoader(classLoader);
this.factory.setBeanFactory(beanFactory);
+ if (publisher != null) {
+ this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(publisher));
+ }
+
this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface);
if (!lazyInit) {
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
index b45ec1757f..d2917e0dc8 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
+++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
@@ -203,7 +203,8 @@ public T getRepository(Class repositoryInterface, Object customImplementa
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
- result.addAdvice(ExposeInvocationInterceptor.INSTANCE);
+ result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
+ result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
diff --git a/src/main/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptor.java b/src/main/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptor.java
new file mode 100644
index 0000000000..081ed56007
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptor.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 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.repository.core.support;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * {@link MethodInterceptor} detecting whether a transaction is already running and exposing that fact via
+ * {@link #isSurroundingTransactionActive()}. Useful in case subsequent interceptors might create transactions
+ * themselves but downstream components have to find out whether there was one running before the call entered the
+ * proxy.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ * @soundtrack Hendrik Freischlader Trio - Openness (Openness)
+ */
+public enum SurroundingTransactionDetectorMethodInterceptor implements MethodInterceptor {
+
+ INSTANCE;
+
+ private final ThreadLocal SURROUNDING_TX_ACTIVE = new ThreadLocal();
+
+ /**
+ * Returns whether a transaction was active before the method call entered the repository proxy.
+ *
+ * @return
+ */
+ public boolean isSurroundingTransactionActive() {
+ return Boolean.TRUE == SURROUNDING_TX_ACTIVE.get();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+ */
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+
+ SURROUNDING_TX_ACTIVE.set(TransactionSynchronizationManager.isActualTransactionActive());
+
+ try {
+ return invocation.proceed();
+ } finally {
+ SURROUNDING_TX_ACTIVE.remove();
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryFactoryBeanSupport.java b/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryFactoryBeanSupport.java
index 9d98d6ff40..6120026563 100644
--- a/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryFactoryBeanSupport.java
+++ b/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryFactoryBeanSupport.java
@@ -40,6 +40,15 @@ public abstract class TransactionalRepositoryFactoryBeanSupport repositoryInterface) {
+ super(repositoryInterface);
+ }
+
/**
* Setter to configure which transaction manager to be used. We have to use the bean name explicitly as otherwise the
* qualifier of the {@link org.springframework.transaction.annotation.Transactional} annotation is used. By explicitly
diff --git a/src/main/java/org/springframework/data/repository/history/RevisionRepository.java b/src/main/java/org/springframework/data/repository/history/RevisionRepository.java
index 448b4ebed3..b9aa241e36 100755
--- a/src/main/java/org/springframework/data/repository/history/RevisionRepository.java
+++ b/src/main/java/org/springframework/data/repository/history/RevisionRepository.java
@@ -23,6 +23,7 @@
import org.springframework.data.history.RevisionSort;
import org.springframework.data.history.Revisions;
import org.springframework.data.repository.NoRepositoryBean;
+import org.springframework.data.repository.Repository;
/**
* A repository which can access entities held in a variety of {@link Revisions}.
@@ -31,7 +32,8 @@
* @author Philipp Huegelmeyer
*/
@NoRepositoryBean
-public interface RevisionRepository> {
+public interface RevisionRepository>
+ extends Repository {
/**
* Returns the revision of the entity it was last changed in.
diff --git a/src/main/java/org/springframework/data/repository/query/QueryMethod.java b/src/main/java/org/springframework/data/repository/query/QueryMethod.java
index 3e876764d9..65784283c4 100644
--- a/src/main/java/org/springframework/data/repository/query/QueryMethod.java
+++ b/src/main/java/org/springframework/data/repository/query/QueryMethod.java
@@ -173,8 +173,22 @@ public Class> getReturnedObjectType() {
*/
public boolean isCollectionQuery() {
- return !(isPageQuery() || isSliceQuery())
- && org.springframework.util.ClassUtils.isAssignable(Iterable.class, unwrappedReturnType)
+ if (isPageQuery() || isSliceQuery()) {
+ return false;
+ }
+
+ Class> returnType = method.getReturnType();
+
+ if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) {
+ return true;
+ }
+
+ if (QueryExecutionConverters.supports(unwrappedReturnType)
+ && QueryExecutionConverters.isSingleValue(unwrappedReturnType)) {
+ return false;
+ }
+
+ return org.springframework.util.ClassUtils.isAssignable(Iterable.class, unwrappedReturnType)
|| unwrappedReturnType.isArray();
}
diff --git a/src/main/java/org/springframework/data/repository/query/ReturnedType.java b/src/main/java/org/springframework/data/repository/query/ReturnedType.java
index 8ff15d351e..09dbadb96e 100644
--- a/src/main/java/org/springframework/data/repository/query/ReturnedType.java
+++ b/src/main/java/org/springframework/data/repository/query/ReturnedType.java
@@ -191,7 +191,9 @@ public List getInputProperties() {
List properties = new ArrayList();
for (PropertyDescriptor descriptor : information.getInputProperties()) {
- properties.add(descriptor.getName());
+ if (!properties.contains(descriptor.getName())) {
+ properties.add(descriptor.getName());
+ }
}
return properties;
diff --git a/src/main/java/org/springframework/data/repository/util/JavaslangCollections.java b/src/main/java/org/springframework/data/repository/util/JavaslangCollections.java
new file mode 100644
index 0000000000..6ee4b42210
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/util/JavaslangCollections.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2016 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.repository.util;
+
+import javaslang.collection.Traversable;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.repository.util.QueryExecutionConverters.WrapperType;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Converter implementations to map from and to Javaslang collections.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+class JavaslangCollections {
+
+ public enum ToJavaConverter implements Converter {
+
+ INSTANCE;
+
+ public WrapperType getWrapperType() {
+ return WrapperType.multiValue(javaslang.collection.Traversable.class);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
+ */
+ @Override
+ public Object convert(Object source) {
+
+ if (source instanceof javaslang.collection.Seq) {
+ return ((javaslang.collection.Seq>) source).toJavaList();
+ }
+
+ if (source instanceof javaslang.collection.Map) {
+ return ((javaslang.collection.Map, ?>) source).toJavaMap();
+ }
+
+ if (source instanceof javaslang.collection.Set) {
+ return ((javaslang.collection.Set>) source).toJavaSet();
+ }
+
+ throw new IllegalArgumentException("Unsupported Javaslang collection " + source);
+ }
+ }
+
+ public enum FromJavaConverter implements ConditionalGenericConverter {
+
+ INSTANCE {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
+ */
+ @Override
+ public java.util.Set getConvertibleTypes() {
+ return CONVERTIBLE_PAIRS;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
+ */
+ @Override
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+
+ // Prevent collections to be mapped to maps
+ if (sourceType.isCollection() && javaslang.collection.Map.class.isAssignableFrom(targetType.getType())) {
+ return false;
+ }
+
+ // Prevent maps to be mapped to collections
+ if (sourceType.isMap() && !(javaslang.collection.Map.class.isAssignableFrom(targetType.getType())
+ || targetType.getType().equals(Traversable.class))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
+ */
+ @Override
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+
+ if (source instanceof List) {
+ return ReflectionUtils.invokeMethod(LIST_FACTORY_METHOD, null, source);
+ }
+
+ if (source instanceof java.util.Set) {
+ return ReflectionUtils.invokeMethod(SET_FACTORY_METHOD, null, source);
+ }
+
+ if (source instanceof java.util.Map) {
+ return ReflectionUtils.invokeMethod(MAP_FACTORY_METHOD, null, source);
+ }
+
+ return source;
+ }
+ };
+
+ private static final Set CONVERTIBLE_PAIRS;
+ private static final Method LIST_FACTORY_METHOD;
+ private static final Method SET_FACTORY_METHOD;
+ private static final Method MAP_FACTORY_METHOD;
+
+ static {
+
+ Set pairs = new HashSet();
+ pairs.add(new ConvertiblePair(Collection.class, javaslang.collection.Traversable.class));
+ pairs.add(new ConvertiblePair(Map.class, javaslang.collection.Traversable.class));
+
+ CONVERTIBLE_PAIRS = Collections.unmodifiableSet(pairs);
+
+ MAP_FACTORY_METHOD = ReflectionUtils.findMethod(javaslang.collection.LinkedHashMap.class, "ofAll", Map.class);
+ LIST_FACTORY_METHOD = ReflectionUtils.findMethod(javaslang.collection.List.class, "ofAll", Iterable.class);
+ SET_FACTORY_METHOD = ReflectionUtils.findMethod(javaslang.collection.LinkedHashSet.class, "ofAll",
+ Iterable.class);
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java b/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java
index e8f57d04d9..c9a19d1237 100644
--- a/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java
+++ b/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java
@@ -15,15 +15,22 @@
*/
package org.springframework.data.repository.util;
+import javaslang.collection.Traversable;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import lombok.Value;
import scala.Function0;
import scala.Option;
import scala.runtime.AbstractFunction0;
+import java.lang.reflect.Method;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
+import java.util.function.Supplier;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
@@ -33,6 +40,7 @@
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
import org.springframework.util.concurrent.ListenableFuture;
import com.google.common.base.Optional;
@@ -43,10 +51,11 @@
*
* {@code java.util.Optional}
* {@code com.google.common.base.Optional}
- * {@code scala.Option}
+ * {@code scala.Option} - as of 1.12
* {@code java.util.concurrent.Future}
* {@code java.util.concurrent.CompletableFuture}
* {@code org.springframework.util.concurrent.ListenableFuture<}
+ * {@code javaslang.control.Option} - as of 1.13
*
*
* @author Oliver Gierke
@@ -55,24 +64,22 @@
*/
public abstract class QueryExecutionConverters {
- private static final boolean SPRING_4_2_PRESENT = ClassUtils.isPresent(
- "org.springframework.core.annotation.AnnotationConfigurationException",
- QueryExecutionConverters.class.getClassLoader());
-
private static final boolean GUAVA_PRESENT = ClassUtils.isPresent("com.google.common.base.Optional",
QueryExecutionConverters.class.getClassLoader());
private static final boolean JDK_8_PRESENT = ClassUtils.isPresent("java.util.Optional",
QueryExecutionConverters.class.getClassLoader());
private static final boolean SCALA_PRESENT = ClassUtils.isPresent("scala.Option",
QueryExecutionConverters.class.getClassLoader());
+ private static final boolean JAVASLANG_PRESENT = ClassUtils.isPresent("javaslang.control.Option",
+ QueryExecutionConverters.class.getClassLoader());
- private static final Set> WRAPPER_TYPES = new HashSet>();
+ private static final Set WRAPPER_TYPES = new HashSet();
private static final Set> UNWRAPPERS = new HashSet>();
static {
- WRAPPER_TYPES.add(Future.class);
- WRAPPER_TYPES.add(ListenableFuture.class);
+ WRAPPER_TYPES.add(WrapperType.singleValue(Future.class));
+ WRAPPER_TYPES.add(WrapperType.singleValue(ListenableFuture.class));
if (GUAVA_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
@@ -84,7 +91,7 @@ public abstract class QueryExecutionConverters {
UNWRAPPERS.add(Jdk8OptionalUnwrapper.INSTANCE);
}
- if (JDK_8_PRESENT && SPRING_4_2_PRESENT) {
+ if (JDK_8_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToCompletableFutureConverter.getWrapperType());
}
@@ -92,6 +99,14 @@ public abstract class QueryExecutionConverters {
WRAPPER_TYPES.add(NullableWrapperToScalaOptionConverter.getWrapperType());
UNWRAPPERS.add(ScalOptionUnwrapper.INSTANCE);
}
+
+ if (JAVASLANG_PRESENT) {
+
+ WRAPPER_TYPES.add(NullableWrapperToJavaslangOptionConverter.getWrapperType());
+ WRAPPER_TYPES.add(JavaslangCollections.ToJavaConverter.INSTANCE.getWrapperType());
+
+ UNWRAPPERS.add(JavaslangOptionUnwrapper.INSTANCE);
+ }
}
private QueryExecutionConverters() {}
@@ -106,8 +121,8 @@ public static boolean supports(Class> type) {
Assert.notNull(type, "Type must not be null!");
- for (Class> candidate : WRAPPER_TYPES) {
- if (candidate.isAssignableFrom(type)) {
+ for (WrapperType candidate : WRAPPER_TYPES) {
+ if (candidate.getType().isAssignableFrom(type)) {
return true;
}
}
@@ -115,6 +130,17 @@ public static boolean supports(Class> type) {
return false;
}
+ public static boolean isSingleValue(Class> type) {
+
+ for (WrapperType candidate : WRAPPER_TYPES) {
+ if (candidate.getType().isAssignableFrom(type)) {
+ return candidate.isSingleValue();
+ }
+ }
+
+ return false;
+ }
+
/**
* Registers converters for wrapper types found on the classpath.
*
@@ -124,6 +150,8 @@ public static void registerConvertersIn(ConfigurableConversionService conversion
Assert.notNull(conversionService, "ConversionService must not be null!");
+ conversionService.removeConvertible(Collection.class, Object.class);
+
if (GUAVA_PRESENT) {
conversionService.addConverter(new NullableWrapperToGuavaOptionalConverter(conversionService));
}
@@ -137,6 +165,11 @@ public static void registerConvertersIn(ConfigurableConversionService conversion
conversionService.addConverter(new NullableWrapperToScalaOptionConverter(conversionService));
}
+ if (JAVASLANG_PRESENT) {
+ conversionService.addConverter(new NullableWrapperToJavaslangOptionConverter(conversionService));
+ conversionService.addConverter(JavaslangCollections.FromJavaConverter.INSTANCE);
+ }
+
conversionService.addConverter(new NullableWrapperToFutureConverter(conversionService));
}
@@ -258,8 +291,8 @@ protected Object wrap(Object source) {
return Optional.of(source);
}
- public static Class> getWrapperType() {
- return Optional.class;
+ public static WrapperType getWrapperType() {
+ return WrapperType.singleValue(Optional.class);
}
}
@@ -288,8 +321,8 @@ protected Object wrap(Object source) {
return java.util.Optional.of(source);
}
- public static Class> getWrapperType() {
- return java.util.Optional.class;
+ public static WrapperType getWrapperType() {
+ return WrapperType.singleValue(java.util.Optional.class);
}
}
@@ -344,8 +377,8 @@ protected Object wrap(Object source) {
return source instanceof CompletableFuture ? source : CompletableFuture.completedFuture(source);
}
- public static Class> getWrapperType() {
- return CompletableFuture.class;
+ public static WrapperType getWrapperType() {
+ return WrapperType.singleValue(CompletableFuture.class);
}
}
@@ -370,8 +403,53 @@ protected Object wrap(Object source) {
return Option.apply(source);
}
- public static Class> getWrapperType() {
- return Option.class;
+ public static WrapperType getWrapperType() {
+ return WrapperType.singleValue(Option.class);
+ }
+ }
+
+ /**
+ * Converter to convert from {@link NullableWrapper} into JavaSlang's {@link javaslang.control.Option}.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+ private static class NullableWrapperToJavaslangOptionConverter extends AbstractWrapperTypeConverter {
+
+ private static final Method OF_METHOD;
+ private static final Method NONE_METHOD;
+
+ static {
+ OF_METHOD = ReflectionUtils.findMethod(javaslang.control.Option.class, "of", Object.class);
+ NONE_METHOD = ReflectionUtils.findMethod(javaslang.control.Option.class, "none");
+ }
+
+ /**
+ * Creates a new {@link NullableWrapperToJavaslangOptionConverter} using the given {@link ConversionService}.
+ *
+ * @param conversionService must not be {@literal null}.
+ */
+ public NullableWrapperToJavaslangOptionConverter(ConversionService conversionService) {
+ super(conversionService, createEmptyOption(), javaslang.control.Option.class);
+ }
+
+ public static WrapperType getWrapperType() {
+ return WrapperType.singleValue(javaslang.control.Option.class);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#wrap(java.lang.Object)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ protected Object wrap(Object source) {
+ return (javaslang.control.Option) ReflectionUtils.invokeMethod(OF_METHOD, null, source);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static javaslang.control.Option createEmptyOption() {
+ return (javaslang.control.Option) ReflectionUtils.invokeMethod(NONE_METHOD, null);
}
}
@@ -420,7 +498,7 @@ public Object convert(Object source) {
*
* @author Oliver Gierke
* @author Mark Paluch
- * @author 1.13
+ * @since 1.12
*/
private static enum ScalOptionUnwrapper implements Converter {
@@ -447,4 +525,61 @@ public Object convert(Object source) {
return source instanceof Option ? ((Option>) source).getOrElse(alternative) : source;
}
}
+
+ /**
+ * Converter to unwrap Javaslang {@link javaslang.control.Option} instances.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+ private static enum JavaslangOptionUnwrapper implements Converter {
+
+ INSTANCE;
+
+ private static final Supplier NULL_SUPPLIER = new Supplier() {
+
+ /*
+ * (non-Javadoc)
+ * @see java.util.function.Supplier#get()
+ */
+ public Object get() {
+ return null;
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object convert(Object source) {
+
+ if (source instanceof javaslang.control.Option) {
+ return ((javaslang.control.Option) source).getOrElse(NULL_SUPPLIER);
+ }
+
+ if (source instanceof Traversable) {
+ return JavaslangCollections.ToJavaConverter.INSTANCE.convert(source);
+ }
+
+ return source;
+ }
+ }
+
+ @Value
+ @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+ public static class WrapperType {
+
+ Class> type;
+ boolean singleValue;
+
+ public static WrapperType singleValue(Class> type) {
+ return new WrapperType(type, true);
+ }
+
+ public static WrapperType multiValue(Class> type) {
+ return new WrapperType(type, false);
+ }
+ }
}
diff --git a/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/src/main/java/org/springframework/data/util/ClassTypeInformation.java
index a5b1604554..7ae71d7b8e 100644
--- a/src/main/java/org/springframework/data/util/ClassTypeInformation.java
+++ b/src/main/java/org/springframework/data/util/ClassTypeInformation.java
@@ -92,7 +92,8 @@ public static ClassTypeInformation from(Class type) {
public static TypeInformation fromReturnTypeOf(Method method) {
Assert.notNull(method, "Method must not be null!");
- return new ClassTypeInformation(method.getDeclaringClass()).createInfo(method.getGenericReturnType());
+ return (TypeInformation) ClassTypeInformation.from(method.getDeclaringClass())
+ .createInfo(method.getGenericReturnType());
}
/**
diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java
index a7050ff604..52cdc05967 100644
--- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java
+++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java
@@ -35,13 +35,16 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.BeanUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
@@ -51,6 +54,22 @@
*/
class TypeDiscoverer implements TypeInformation {
+ private static final Iterable> MAP_TYPES;
+
+ static {
+
+ ClassLoader classLoader = TypeDiscoverer.class.getClassLoader();
+
+ Set> mapTypes = new HashSet>();
+ mapTypes.add(Map.class);
+
+ try {
+ mapTypes.add(ClassUtils.forName("javaslang.collection.Map", classLoader));
+ } catch (ClassNotFoundException o_O) {}
+
+ MAP_TYPES = Collections.unmodifiableSet(mapTypes);
+ }
+
private final Type type;
private final Map, Type> typeVariableMap;
private final Map fieldTypes = new ConcurrentHashMap();
@@ -103,7 +122,7 @@ protected TypeInformation> createInfo(Type fieldType) {
}
if (fieldType instanceof Class) {
- return new ClassTypeInformation((Class>) fieldType);
+ return ClassTypeInformation.from((Class>) fieldType);
}
Class resolveType = resolveType(fieldType);
@@ -329,7 +348,14 @@ public TypeInformation> getActualType() {
* @see org.springframework.data.util.TypeInformation#isMap()
*/
public boolean isMap() {
- return Map.class.isAssignableFrom(getType());
+
+ for (Class> mapType : MAP_TYPES) {
+ if (mapType.isAssignableFrom(getType())) {
+ return true;
+ }
+ }
+
+ return false;
}
/*
@@ -349,7 +375,7 @@ public TypeInformation> getMapValueType() {
protected TypeInformation> doGetMapValueType() {
if (isMap()) {
- return getTypeArgument(Map.class, 1);
+ return getTypeArgument(getBaseType(MAP_TYPES), 1);
}
List> arguments = getTypeArguments();
@@ -399,7 +425,7 @@ protected TypeInformation> doGetComponentType() {
}
if (isMap()) {
- return getTypeArgument(Map.class, 0);
+ return getTypeArgument(getBaseType(MAP_TYPES), 0);
}
if (Iterable.class.isAssignableFrom(rawType)) {
@@ -525,6 +551,17 @@ private TypeInformation> getTypeArgument(Class> bound, int index) {
return createInfo(arguments[index]);
}
+ private Class> getBaseType(Iterable> candidates) {
+
+ for (Class> candidate : candidates) {
+ if (candidate.isAssignableFrom(getType())) {
+ return candidate;
+ }
+ }
+
+ throw new IllegalArgumentException(String.format("Type %s not contained in candidates %s!", getType(), candidates));
+ }
+
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
diff --git a/src/main/java/org/springframework/data/web/config/SpringDataWebConfigurationMixin.java b/src/main/java/org/springframework/data/web/AllowedSortProperties.java
similarity index 54%
rename from src/main/java/org/springframework/data/web/config/SpringDataWebConfigurationMixin.java
rename to src/main/java/org/springframework/data/web/AllowedSortProperties.java
index 15b615245d..b0d8939940 100644
--- a/src/main/java/org/springframework/data/web/config/SpringDataWebConfigurationMixin.java
+++ b/src/main/java/org/springframework/data/web/AllowedSortProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2016 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.data.web.config;
+package org.springframework.data.web;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@@ -21,21 +21,23 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.web.config.EnableSpringDataWebSupport.SpringDataWebConfigurationImportSelector;
-
/**
- * Annotation to be able to scan for additional Spring Data configuration classes to contribute to the web integration.
+ * Annotation to define allowed sort properties for {@link org.springframework.data.domain.Sort} and
+ * {@link org.springframework.data.domain.Pageable}.
*
- * @author Oliver Gierke
- * @since 1.10
- * @soundtrack Selah Sue - This World (Selah Sue)
- * @see SpringDataJacksonConfiguration
- * @see SpringDataWebConfigurationImportSelector
+ * @author Kazuki Shimizu
+ * @since 1.13
*/
-@Retention(RetentionPolicy.RUNTIME)
@Documented
-@Configuration
-@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
-public @interface SpringDataWebConfigurationMixin {
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AllowedSortProperties {
+
+ /**
+ * Specify allowed sort properties.
+ *
+ * @return
+ */
+ String[] value();
+
}
diff --git a/src/main/java/org/springframework/data/web/HateoasSortHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/HateoasSortHandlerMethodArgumentResolver.java
index 547a332786..4c3784bd80 100644
--- a/src/main/java/org/springframework/data/web/HateoasSortHandlerMethodArgumentResolver.java
+++ b/src/main/java/org/springframework/data/web/HateoasSortHandlerMethodArgumentResolver.java
@@ -40,7 +40,7 @@ public class HateoasSortHandlerMethodArgumentResolver extends SortHandlerMethodA
UriComponentsContributor {
/**
- * Returns the tempalte variables for the sort parameter.
+ * Returns the template variables for the sort parameter.
*
* @param parameter must not be {@literal null}.
* @return
diff --git a/src/main/java/org/springframework/data/web/PageableArgumentResolver.java b/src/main/java/org/springframework/data/web/PageableArgumentResolver.java
new file mode 100644
index 0000000000..2a57d0f949
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/PageableArgumentResolver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 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.web;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.data.domain.Pageable;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+/**
+ * Argument resolver to extract a {@link Pageable} object from a {@link NativeWebRequest} for a particular
+ * {@link MethodParameter}. A {@link PageableArgumentResolver} can either resolve {@link Pageable} itself or wrap
+ * another {@link PageableArgumentResolver} to post-process {@link Pageable}. {@link Pageable} resolution yields either
+ * in a {@link Pageable} object or {@literal null} if {@link Pageable} cannot be resolved.
+ *
+ * @author Mark Paluch
+ * @since 1.13
+ * @see org.springframework.web.method.support.HandlerMethodArgumentResolver
+ */
+public interface PageableArgumentResolver extends HandlerMethodArgumentResolver {
+
+ /**
+ * Resolves a {@link Pageable} method parameter into an argument value from a given request.
+ *
+ * @param parameter the method parameter to resolve. This parameter must have previously been passed to
+ * {@link #supportsParameter} which must have returned {@code true}.
+ * @param mavContainer the ModelAndViewContainer for the current request
+ * @param webRequest the current request
+ * @param binderFactory a factory for creating {@link WebDataBinder} instances
+ * @return the resolved argument value, or {@code null}
+ */
+ @Override
+ Pageable resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
+ WebDataBinderFactory binderFactory);
+}
diff --git a/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java
index b2bb978800..8d0b8c6c07 100644
--- a/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java
+++ b/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java
@@ -28,7 +28,6 @@
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
-import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
@@ -39,9 +38,11 @@
* @since 1.6
* @author Oliver Gierke
* @author Nick Williams
+ * @author Mark Paluch
*/
-public class PageableHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
+public class PageableHandlerMethodArgumentResolver implements PageableArgumentResolver {
+ private static final SortHandlerMethodArgumentResolver DEFAULT_SORT_RESOLVER = new SortHandlerMethodArgumentResolver();
private static final String INVALID_DEFAULT_PAGE_SIZE = "Invalid default page size configured for method %s! Must not be less than one!";
private static final String DEFAULT_PAGE_PARAMETER = "page";
@@ -52,7 +53,7 @@ public class PageableHandlerMethodArgumentResolver implements HandlerMethodArgum
static final Pageable DEFAULT_PAGE_REQUEST = new PageRequest(0, 20);
private Pageable fallbackPageable = DEFAULT_PAGE_REQUEST;
- private SortHandlerMethodArgumentResolver sortResolver;
+ private SortArgumentResolver sortResolver;
private String pageParameterName = DEFAULT_PAGE_PARAMETER;
private String sizeParameterName = DEFAULT_SIZE_PARAMETER;
private String prefix = DEFAULT_PREFIX;
@@ -64,16 +65,26 @@ public class PageableHandlerMethodArgumentResolver implements HandlerMethodArgum
* Constructs an instance of this resolved with a default {@link SortHandlerMethodArgumentResolver}.
*/
public PageableHandlerMethodArgumentResolver() {
- this(null);
+ this((SortArgumentResolver) null);
}
/**
* Constructs an instance of this resolver with the specified {@link SortHandlerMethodArgumentResolver}.
*
- * @param sortResolver The sort resolver to use
+ * @param sortResolver the sort resolver to use
*/
public PageableHandlerMethodArgumentResolver(SortHandlerMethodArgumentResolver sortResolver) {
- this.sortResolver = sortResolver == null ? new SortHandlerMethodArgumentResolver() : sortResolver;
+ this((SortArgumentResolver) sortResolver);
+ }
+
+ /**
+ * Constructs an instance of this resolver with the specified {@link SortArgumentResolver}.
+ *
+ * @param sortResolver the sort resolver to use
+ * @since 1.13
+ */
+ public PageableHandlerMethodArgumentResolver(SortArgumentResolver sortResolver) {
+ this.sortResolver = sortResolver == null ? DEFAULT_SORT_RESOLVER : sortResolver;
}
/**
diff --git a/src/main/java/org/springframework/data/web/SortArgumentResolver.java b/src/main/java/org/springframework/data/web/SortArgumentResolver.java
new file mode 100644
index 0000000000..9b59971bbe
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/SortArgumentResolver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 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.web;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.data.domain.Sort;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+/**
+ * Argument resolver to extract a {@link Sort} object from a {@link NativeWebRequest} for a particular
+ * {@link MethodParameter}. A {@link SortArgumentResolver} can either resolve {@link Sort} itself or wrap another
+ * {@link SortArgumentResolver} to post-process {@link Sort}. {@link Sort} resolution yields either in a {@link Sort}
+ * object or {@literal null} if {@link Sort} cannot be resolved.
+ *
+ * @author Mark Paluch
+ * @since 1.13
+ * @see org.springframework.web.method.support.HandlerMethodArgumentResolver
+ */
+public interface SortArgumentResolver extends HandlerMethodArgumentResolver {
+
+ /**
+ * Resolves a {@link Sort} method parameter into an argument value from a given request.
+ *
+ * @param parameter the method parameter to resolve. This parameter must have previously been passed to
+ * {@link #supportsParameter} which must have returned {@code true}.
+ * @param mavContainer the ModelAndViewContainer for the current request
+ * @param webRequest the current request
+ * @param binderFactory a factory for creating {@link WebDataBinder} instances
+ * @return the resolved argument value, or {@code null}
+ */
+ @Override
+ Sort resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
+ WebDataBinderFactory binderFactory);
+}
diff --git a/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java
index 870bd4916d..8a1efc7064 100644
--- a/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java
+++ b/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2015 the original author or authors.
+ * Copyright 2013-2016 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.
@@ -16,8 +16,11 @@
package org.springframework.data.web;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
@@ -40,8 +43,10 @@
* @author Oliver Gierke
* @author Thomas Darimont
* @author Nick Williams
+ * @author Mark Paluch
+ * @author Kazuki Shimizu
*/
-public class SortHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
+public class SortHandlerMethodArgumentResolver implements SortArgumentResolver {
private static final String DEFAULT_PARAMETER = "sort";
private static final String DEFAULT_PROPERTY_DELIMITER = ",";
@@ -118,7 +123,10 @@ public Sort resolveArgument(MethodParameter parameter, ModelAndViewContainer mav
return getDefaultFromAnnotationOrFallback(parameter);
}
- return parseParameterIntoSort(directionParameter, propertyDelimiter);
+ AllowedSortProperties annotatedAllowedSortProperties = parameter
+ .getParameterAnnotation(AllowedSortProperties.class);
+
+ return parseParameterIntoSort(directionParameter, propertyDelimiter, annotatedAllowedSortProperties);
}
/**
@@ -197,12 +205,21 @@ protected String getSortParameter(MethodParameter parameter) {
* Parses the given sort expressions into a {@link Sort} instance. The implementation expects the sources to be a
* concatenation of Strings using the given delimiter. If the last element can be parsed into a {@link Direction} it's
* considered a {@link Direction} and a simple property otherwise.
- *
+ *
* @param source will never be {@literal null}.
* @param delimiter the delimiter to be used to split up the source elements, will never be {@literal null}.
+ * @param annotatedAllowedSortProperties annotation that indicate allowed sort properties
* @return
*/
- Sort parseParameterIntoSort(String[] source, String delimiter) {
+ Sort parseParameterIntoSort(String[] source, String delimiter, AllowedSortProperties annotatedAllowedSortProperties) {
+
+ Set allowedSortProperties = null;
+ if (annotatedAllowedSortProperties != null) {
+ if (annotatedAllowedSortProperties.value().length == 0) {
+ return null;
+ }
+ allowedSortProperties = new HashSet(Arrays.asList(annotatedAllowedSortProperties.value()));
+ }
List allOrders = new ArrayList();
@@ -227,6 +244,10 @@ Sort parseParameterIntoSort(String[] source, String delimiter) {
continue;
}
+ if (allowedSortProperties != null && !allowedSortProperties.contains(property)) {
+ continue;
+ }
+
allOrders.add(new Order(direction, property));
}
}
@@ -260,7 +281,7 @@ protected List foldIntoExpressions(Sort sort) {
builder.add(order.getProperty());
}
- return builder == null ? Collections. emptyList() : builder.dumpExpressionIfPresentInto(expressions);
+ return builder == null ? Collections.emptyList() : builder.dumpExpressionIfPresentInto(expressions);
}
/**
@@ -290,7 +311,7 @@ protected List legacyFoldExpressions(Sort sort) {
builder.add(order.getProperty());
}
- return builder == null ? Collections. emptyList() : builder.dumpExpressionIfPresentInto(expressions);
+ return builder == null ? Collections.emptyList() : builder.dumpExpressionIfPresentInto(expressions);
}
/**
diff --git a/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java b/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java
index 3acf2f5650..d895f8d8a9 100644
--- a/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java
+++ b/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java
@@ -23,16 +23,14 @@
import java.util.ArrayList;
import java.util.List;
-import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
-import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
-import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.querydsl.QueryDslUtils;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.util.ClassUtils;
@@ -124,15 +122,8 @@ public String[] selectImports(AnnotationMetadata importingClassMetadata) {
: SpringDataWebConfiguration.class.getName());
if (JACKSON_PRESENT) {
-
- ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
- provider.setEnvironment(environment);
- provider.setResourceLoader(resourceLoader);
- provider.addIncludeFilter(new AnnotationTypeFilter(SpringDataWebConfigurationMixin.class));
-
- for (BeanDefinition definition : provider.findCandidateComponents("org.springframework.data")) {
- imports.add(definition.getBeanClassName());
- }
+ imports.addAll(
+ SpringFactoriesLoader.loadFactoryNames(SpringDataJacksonModules.class, resourceLoader.getClassLoader()));
}
return imports.toArray(new String[imports.size()]);
diff --git a/src/main/java/org/springframework/data/web/config/SpringDataJacksonConfiguration.java b/src/main/java/org/springframework/data/web/config/SpringDataJacksonConfiguration.java
index 3fd412e500..5d9d603343 100644
--- a/src/main/java/org/springframework/data/web/config/SpringDataJacksonConfiguration.java
+++ b/src/main/java/org/springframework/data/web/config/SpringDataJacksonConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2015 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -23,8 +23,7 @@
*
* @author Oliver Gierke
*/
-@SpringDataWebConfigurationMixin
-public class SpringDataJacksonConfiguration {
+public class SpringDataJacksonConfiguration implements SpringDataJacksonModules {
@Bean
public GeoModule jacksonGeoModule() {
diff --git a/src/main/java/org/springframework/data/web/config/SpringDataJacksonModules.java b/src/main/java/org/springframework/data/web/config/SpringDataJacksonModules.java
new file mode 100644
index 0000000000..ef030a84b1
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/config/SpringDataJacksonModules.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 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.web.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Marker interface to describe configuration classes that ship Jackson modules that are supposed to be added to the
+ * Jackson {@link ObjectMapper} configured for {@link EnableSpringDataWebSupport}.
+ *
+ * @author Oliver Gierke
+ * @since 1.13
+ */
+public interface SpringDataJacksonModules {}
diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..5b6d502f70
--- /dev/null
+++ b/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.web.config.SpringDataJacksonConfiguration
diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt
index cd2c034a3a..dd52d99576 100644
--- a/src/main/resources/changelog.txt
+++ b/src/main/resources/changelog.txt
@@ -1,6 +1,73 @@
Spring Data Commons Changelog
=============================
+Changes in version 1.12.6.RELEASE (2016-12-21)
+----------------------------------------------
+* DATACMNS-963 - ReturnedType.getInputProperties() does not guarantee distinct properties.
+* DATACMNS-962 - Doc spelling mistake 'walking thought'.
+* DATACMNS-958 - Use ExposeInvocationInterceptor.ADVISOR over the advice instance.
+* DATACMNS-956 - Ensure consistent usage of ClassTypeInformation.from(…).
+* DATACMNS-953 - GenericPropertyMatchers.exact() should use exact matching.
+* DATACMNS-943 - Redeclared save(Iterable) results in wrong method overload to be invoked eventually.
+* DATACMNS-939 - Static interface methods should not be considered query methods.
+* DATACMNS-934 - BasicPersistentEntity.addAssociations(…) must not add null values to the collection of associations.
+* DATACMNS-932 - Release 1.12.6 (Hopper SR6).
+
+
+Changes in version 1.13.0.RC1 (2016-12-21)
+------------------------------------------
+* DATACMNS-963 - ReturnedType.getInputProperties() does not guarantee distinct properties.
+* DATACMNS-962 - Doc spelling mistake 'walking thought'.
+* DATACMNS-961 - Upgrade to a newer JDK version on TravisCI.
+* DATACMNS-960 - RevisionRepository should extend Repository.
+* DATACMNS-959 - Register repository interceptor to allow detecting a surrounding transaction.
+* DATACMNS-958 - Use ExposeInvocationInterceptor.ADVISOR over the advice instance.
+* DATACMNS-956 - Ensure consistent usage of ClassTypeInformation.from(…).
+* DATACMNS-953 - GenericPropertyMatchers.exact() should use exact matching.
+* DATACMNS-952 - Switch to less expensive lookup of Spring Data web support configuration.
+* DATACMNS-951 - Add Converters for JSR-310 Duration and Period.
+* DATACMNS-943 - Redeclared save(Iterable) results in wrong method overload to be invoked eventually.
+* DATACMNS-941 - QuerydslBindings not working with inheritance.
+* DATACMNS-940 - Support for Javaslang collection types as query method return values.
+* DATACMNS-939 - Static interface methods should not be considered query methods.
+* DATACMNS-937 - Support for Javaslang's Option as return type of query methods.
+* DATACMNS-934 - BasicPersistentEntity.addAssociations(…) must not add null values to the collection of associations.
+* DATACMNS-929 - PageableHandlerMethodArgumentResolver.isFallbackPageable() throws NullPointerException if default is configured to be null.
+* DATACMNS-928 - Support for exposing domain events from aggregate roots as Spring application events.
+* DATACMNS-925 - Improve memory consumption of Parameter and Parameters.
+* DATACMNS-923 - Remove obsolete code in DefaultRepositoryInformation.
+* DATACMNS-921 - ResultProcessor should create approximate collection instead of exact one.
+* DATACMNS-920 - Expose minInclusive and maxInclusive of org.springframework.data.domain.Range.
+* DATACMNS-918 - Provide interfaces for Pageable and Sort method argument resolvers.
+* DATACMNS-917 - Query methods returning Maps shouldn't return null if no result is present.
+* DATACMNS-916 - Generated PropertyAccessor fails lookup of setter accepting a primitive type.
+* DATACMNS-912 - Unable to write custom implementation of CRUD method with generic parameters.
+* DATACMNS-910 - Remove Spring 4.2 build profile for Travis.
+* DATACMNS-909 - Exclude decoratedClass from JSON links, created from Projection and received with Spring @RestController.
+* DATACMNS-908 - Allow creating an Order with a different property name.
+* DATACMNS-903 - Fix typo in reference documentation.
+* DATACMNS-902 - Fix attribute name in example showing how to define a custom repository base class.
+* DATACMNS-900 - ExampleMatcher.PropertySpecifiers does not implement equals/hashCode.
+* DATACMNS-899 - ParameterizedTypeInformation.getMapValueType for non-map types causes StackOverflowError.
+* DATACMNS-896 - ClassTypeInformation computes incorrect TypeVariable mappings for recursive generics.
+* DATACMNS-892 - Expose repository interface via attribute on bean definition for repository factory beans.
+* DATACMNS-891 - Switch constructor injection of the repository interface for repository factory beans.
+* DATACMNS-889 - Release 1.13 RC1 (Ingalls).
+* DATACMNS-888 - Add dedicated RevisionSort to easily capture the desire to sort by revision number.
+* DATACMNS-875 - Add support for exists projection in repository query derivation.
+
+
+Changes in version 2.0.0.M1 (2016-11-23)
+----------------------------------------
+* DATACMNS-939 - Static interface methods should not be considered query methods.
+* DATACMNS-937 - Support JavaSlang and other custom wrappers as return types of query methods.
+* DATACMNS-935 - Avoid hard dependency on Project Reactor in reactive support.
+* DATACMNS-933 - Release 2.0 M1 (Kay).
+* DATACMNS-919 - Remove DomainClassPropertyEditorRegistrar.
+* DATACMNS-836 - Add components to support reactive repositories.
+* DATACMNS-168 - Allow customizing the bean name for repository beans.
+
+
Changes in version 1.12.5.RELEASE (2016-11-03)
----------------------------------------------
* DATACMNS-929 - PageableHandlerMethodArgumentResolver.isFallbackPageable() throws NullPointerException if default is configured to be null.
diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt
index 97e1666cd0..0337ecfe58 100644
--- a/src/main/resources/notice.txt
+++ b/src/main/resources/notice.txt
@@ -1,4 +1,4 @@
-Spring Data Commons 1.13 M1
+Spring Data Commons 1.13 RC1
Copyright (c) [2010-2015] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
diff --git a/src/test/java/org/springframework/data/convert/Jsr310ConvertersUnitTests.java b/src/test/java/org/springframework/data/convert/Jsr310ConvertersUnitTests.java
index 3c6c4aadb6..41aa1f5e23 100644
--- a/src/test/java/org/springframework/data/convert/Jsr310ConvertersUnitTests.java
+++ b/src/test/java/org/springframework/data/convert/Jsr310ConvertersUnitTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -19,26 +19,43 @@
import static org.junit.Assert.*;
import java.text.SimpleDateFormat;
+import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
+import java.time.Period;
import java.time.ZoneId;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.data.convert.Jsr310ConvertersUnitTests.CommonTests;
+import org.springframework.data.convert.Jsr310ConvertersUnitTests.DurationConversionTests;
+import org.springframework.data.convert.Jsr310ConvertersUnitTests.PeriodConversionTests;
/**
* Unit tests for {@link Jsr310Converters}.
*
* @author Oliver Gierke
+ * @author Barak Schoster
*/
+@RunWith(Suite.class)
+@SuiteClasses({ CommonTests.class, DurationConversionTests.class, PeriodConversionTests.class })
public class Jsr310ConvertersUnitTests {
static final Date NOW = new Date();
@@ -55,95 +72,150 @@ public class Jsr310ConvertersUnitTests {
CONVERSION_SERVICE = conversionService;
}
- /**
- * @see DATACMNS-606
- */
- @Test
- public void convertsDateToLocalDateTime() {
- assertThat(CONVERSION_SERVICE.convert(NOW, LocalDateTime.class).toString(),
- is(format(NOW, "yyyy-MM-dd'T'HH:mm:ss.SSS")));
- }
+ public static class CommonTests {
- /**
- * @see DATACMNS-606
- */
- @Test
- public void convertsLocalDateTimeToDate() {
+ /**
+ * @see DATACMNS-606
+ */
+ @Test
+ public void convertsDateToLocalDateTime() {
+ assertThat(CONVERSION_SERVICE.convert(NOW, LocalDateTime.class).toString(),
+ is(format(NOW, "yyyy-MM-dd'T'HH:mm:ss.SSS")));
+ }
- LocalDateTime now = LocalDateTime.now();
- assertThat(format(CONVERSION_SERVICE.convert(now, Date.class), "yyyy-MM-dd'T'HH:mm:ss.SSS"), is(now.toString()));
- }
+ /**
+ * @see DATACMNS-606
+ */
+ @Test
+ public void convertsLocalDateTimeToDate() {
- /**
- * @see DATACMNS-606
- */
- @Test
- public void convertsDateToLocalDate() {
- assertThat(CONVERSION_SERVICE.convert(NOW, LocalDate.class).toString(), is(format(NOW, "yyyy-MM-dd")));
- }
+ LocalDateTime now = LocalDateTime.now();
+ assertThat(format(CONVERSION_SERVICE.convert(now, Date.class), "yyyy-MM-dd'T'HH:mm:ss.SSS"), is(now.toString()));
+ }
- /**
- * @see DATACMNS-606
- */
- @Test
- public void convertsLocalDateToDate() {
+ /**
+ * @see DATACMNS-606
+ */
+ @Test
+ public void convertsDateToLocalDate() {
+ assertThat(CONVERSION_SERVICE.convert(NOW, LocalDate.class).toString(), is(format(NOW, "yyyy-MM-dd")));
+ }
- LocalDate now = LocalDate.now();
- assertThat(format(CONVERSION_SERVICE.convert(now, Date.class), "yyyy-MM-dd"), is(now.toString()));
- }
+ /**
+ * @see DATACMNS-606
+ */
+ @Test
+ public void convertsLocalDateToDate() {
- /**
- * @see DATACMNS-606
- */
- @Test
- public void convertsDateToLocalTime() {
- assertThat(CONVERSION_SERVICE.convert(NOW, LocalTime.class).toString(), is(format(NOW, "HH:mm:ss.SSS")));
- }
+ LocalDate now = LocalDate.now();
+ assertThat(format(CONVERSION_SERVICE.convert(now, Date.class), "yyyy-MM-dd"), is(now.toString()));
+ }
- /**
- * @see DATACMNS-606
- */
- @Test
- public void convertsLocalTimeToDate() {
+ /**
+ * @see DATACMNS-606
+ */
+ @Test
+ public void convertsDateToLocalTime() {
+ assertThat(CONVERSION_SERVICE.convert(NOW, LocalTime.class).toString(), is(format(NOW, "HH:mm:ss.SSS")));
+ }
- LocalTime now = LocalTime.now();
- assertThat(format(CONVERSION_SERVICE.convert(now, Date.class), "HH:mm:ss.SSS"), is(now.toString()));
- }
+ /**
+ * @see DATACMNS-606
+ */
+ @Test
+ public void convertsLocalTimeToDate() {
- /**
- * @see DATACMNS-623
- */
- @Test
- public void convertsDateToInstant() {
+ LocalTime now = LocalTime.now();
+ assertThat(format(CONVERSION_SERVICE.convert(now, Date.class), "HH:mm:ss.SSS"), is(now.toString()));
+ }
- Date now = new Date();
- assertThat(CONVERSION_SERVICE.convert(now, Instant.class), is(now.toInstant()));
- }
+ /**
+ * @see DATACMNS-623
+ */
+ @Test
+ public void convertsDateToInstant() {
+
+ Date now = new Date();
+ assertThat(CONVERSION_SERVICE.convert(now, Instant.class), is(now.toInstant()));
+ }
- /**
- * @see DATACMNS-623
- */
- @Test
- public void convertsInstantToDate() {
+ /**
+ * @see DATACMNS-623
+ */
+ @Test
+ public void convertsInstantToDate() {
- Date now = new Date();
- assertThat(CONVERSION_SERVICE.convert(now.toInstant(), Date.class), is(now));
+ Date now = new Date();
+ assertThat(CONVERSION_SERVICE.convert(now.toInstant(), Date.class), is(now));
+ }
+
+ @Test
+ public void convertsZoneIdToStringAndBack() {
+
+ Map ids = new HashMap();
+ ids.put("Europe/Berlin", ZoneId.of("Europe/Berlin"));
+ ids.put("+06:00", ZoneId.of("+06:00"));
+
+ for (Entry entry : ids.entrySet()) {
+ assertThat(CONVERSION_SERVICE.convert(entry.getValue(), String.class), is(entry.getKey()));
+ assertThat(CONVERSION_SERVICE.convert(entry.getKey(), ZoneId.class), is(entry.getValue()));
+ }
+ }
+
+ private static String format(Date date, String format) {
+ return new SimpleDateFormat(format).format(date);
+ }
}
- @Test
- public void convertsZoneIdToStringAndBack() {
+ @RunWith(Parameterized.class)
+ public static class DurationConversionTests extends ConversionTest {
+
+ /**
+ * @see DATACMNS-951
+ */
+ @Parameters
+ public static Collection data() {
+
+ return Arrays.asList(new Object[][] { //
+ { "PT240H", Duration.ofDays(10) }, //
+ { "PT2H", Duration.ofHours(2) }, //
+ { "PT3M", Duration.ofMinutes(3) }, //
+ { "PT4S", Duration.ofSeconds(4) }, //
+ { "PT0.005S", Duration.ofMillis(5) }, //
+ { "PT0.000000006S", Duration.ofNanos(6) } //
+ });
+ }
+ }
- Map ids = new HashMap();
- ids.put("Europe/Berlin", ZoneId.of("Europe/Berlin"));
- ids.put("+06:00", ZoneId.of("+06:00"));
+ public static class PeriodConversionTests extends ConversionTest {
- for (Entry entry : ids.entrySet()) {
- assertThat(CONVERSION_SERVICE.convert(entry.getValue(), String.class), is(entry.getKey()));
- assertThat(CONVERSION_SERVICE.convert(entry.getKey(), ZoneId.class), is(entry.getValue()));
+ /**
+ * @see DATACMNS-951
+ */
+ @Parameters
+ public static Collection data() {
+
+ return Arrays.asList(new Object[][] { //
+ { "P2D", Period.ofDays(2) }, //
+ { "P21D", Period.ofWeeks(3) }, //
+ { "P4M", Period.ofMonths(4) }, //
+ { "P5Y", Period.ofYears(5) }, //
+ });
}
}
- private static String format(Date date, String format) {
- return new SimpleDateFormat(format).format(date);
+ @RunWith(Parameterized.class)
+ public static class ConversionTest {
+
+ public @Parameter(0) String string;
+ public @Parameter(1) T target;
+
+ @Test
+ public void convertsPeriodToStringAndBack() {
+
+ ResolvableType type = ResolvableType.forClass(ConversionTest.class, this.getClass());
+ assertThat(CONVERSION_SERVICE.convert(target, String.class), is(string));
+ assertThat(CONVERSION_SERVICE.convert(string, type.getGeneric(0).getRawClass()), is((Object) target));
+ }
}
}
diff --git a/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java b/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java
index 5330b73bee..48a52df928 100644
--- a/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java
+++ b/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java
@@ -27,6 +27,7 @@
import java.util.List;
import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -39,11 +40,13 @@
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.TypeAlias;
+import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentEntitySpec;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.Person;
+import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.mapping.context.SamplePersistentProperty;
import org.springframework.data.util.ClassTypeInformation;
@@ -275,6 +278,24 @@ public void invalidBeanAccessCreatesDescriptiveErrorMessage() {
entity.getPropertyAccessor(new Object());
}
+ /**
+ * @see DATACMNS-934
+ */
+ @Test
+ public void doesNotThrowAnExceptionForNullAssociation() {
+
+ BasicPersistentEntity entity = createEntity(Entity.class);
+ entity.addAssociation(null);
+
+ entity.doWithAssociations(new SimpleAssociationHandler() {
+
+ @Override
+ public void doWithAssociation(Association extends PersistentProperty>> association) {
+ Assert.fail("Expected the method to never be called!");
+ }
+ });
+ }
+
private BasicPersistentEntity createEntity(Class type) {
return createEntity(type, null);
}
diff --git a/src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java b/src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java
index da33c453ee..88e57fd93b 100644
--- a/src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java
+++ b/src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java
@@ -17,6 +17,7 @@
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
+import static org.springframework.data.querydsl.QueryDslUtils.*;
import org.junit.Test;
@@ -32,6 +33,17 @@ public class QueryDslUtilsUnitTests {
*/
@Test
public void rendersDotPathForPathTraversalContainingAnyExpression() {
- assertThat(QueryDslUtils.toDotPath(QUser.user.addresses.any().street), is("addresses.street"));
+ assertThat(toDotPath(QUser.user.addresses.any().street), is("addresses.street"));
+ }
+
+ /**
+ * @see DATACMNS-941
+ */
+ @Test
+ public void skipsIntermediateDelegates() {
+
+ assertThat(toDotPath(QUser.user.as(QSpecialUser.class).as(QSpecialUser.class).specialProperty),
+ is("specialProperty"));
+ assertThat(toDotPath(QUser.user.as(QSpecialUser.class).specialProperty), is("specialProperty"));
}
}
diff --git a/src/test/java/org/springframework/data/querydsl/User.java b/src/test/java/org/springframework/data/querydsl/User.java
index 0e523f53d4..3a4d63bcfb 100644
--- a/src/test/java/org/springframework/data/querydsl/User.java
+++ b/src/test/java/org/springframework/data/querydsl/User.java
@@ -45,3 +45,18 @@ public User(String firstname, String lastname, Address address) {
this.address = address;
}
}
+
+@QueryEntity
+class SpecialUser extends User {
+
+ public String specialProperty;
+
+ public SpecialUser(String firstname, String lastname, Address address) {
+ super(firstname, lastname, address);
+ }
+}
+
+@QueryEntity
+class UserWrapper {
+ public User user;
+}
diff --git a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java
index 4e2d3228d9..105ff77e9b 100644
--- a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java
+++ b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java
@@ -26,7 +26,6 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
-import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.querydsl.QUser;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.querydsl.User;
@@ -76,7 +75,7 @@ public void createBindingsShouldHonorQuerydslBinderCustomizerHookWhenPresent() {
QuerydslBindings bindings = factory.createBindingsFor(null, USER_TYPE);
MultiValueBinding, Object> binding = bindings
- .getBindingForPath(PropertyPath.from("firstname", User.class));
+ .getBindingForPath(PropertyPathInformation.of("firstname", User.class));
assertThat(binding.bind((Path) QUser.user.firstname, Collections.singleton("rand")),
is((Predicate) QUser.user.firstname.contains("rand")));
@@ -97,7 +96,7 @@ public void shouldReuseExistingQuerydslBinderCustomizer() {
QuerydslBindings bindings = factory.createBindingsFor(SpecificBinding.class, USER_TYPE);
MultiValueBinding, Object> binding = bindings
- .getBindingForPath(PropertyPath.from("firstname", User.class));
+ .getBindingForPath(PropertyPathInformation.of("firstname", User.class));
assertThat(binding.bind((Path) QUser.user.firstname, Collections.singleton("rand")),
is((Predicate) QUser.user.firstname.eq("RAND")));
diff --git a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsUnitTests.java b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsUnitTests.java
index 6a9bd23c0c..808f2224ba 100644
--- a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsUnitTests.java
+++ b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsUnitTests.java
@@ -21,7 +21,7 @@
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.support.DefaultConversionService;
-import org.springframework.data.mapping.PropertyPath;
+import org.springframework.data.querydsl.QSpecialUser;
import org.springframework.data.querydsl.QUser;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.querydsl.User;
@@ -71,7 +71,10 @@ public void rejectsNullPath() {
*/
@Test
public void returnsNullIfNoBindingRegisteredForPath() {
- assertThat(bindings.getBindingForPath(PropertyPath.from("lastname", User.class)), nullValue());
+
+ PathInformation path = PropertyPathInformation.of("lastname", User.class);
+
+ assertThat(bindings.getBindingForPath(path), nullValue());
}
/**
@@ -82,8 +85,9 @@ public void returnsRegisteredBindingForSimplePath() {
bindings.bind(QUser.user.firstname).first(CONTAINS_BINDING);
- assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("firstname", User.class)),
- CONTAINS_BINDING);
+ PathInformation path = PropertyPathInformation.of("firstname", User.class);
+
+ assertAdapterWithTargetBinding(bindings.getBindingForPath(path), CONTAINS_BINDING);
}
/**
@@ -94,8 +98,9 @@ public void getBindingForPathShouldReturnSpeficicBindingForNestedElementsWhenAva
bindings.bind(QUser.user.address.street).first(CONTAINS_BINDING);
- assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("address.street", User.class)),
- CONTAINS_BINDING);
+ PathInformation path = PropertyPathInformation.of("address.street", User.class);
+
+ assertAdapterWithTargetBinding(bindings.getBindingForPath(path), CONTAINS_BINDING);
}
/**
@@ -106,8 +111,9 @@ public void getBindingForPathShouldReturnSpeficicBindingForTypes() {
bindings.bind(String.class).first(CONTAINS_BINDING);
- assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("address.street", User.class)),
- CONTAINS_BINDING);
+ PathInformation path = PropertyPathInformation.of("address.street", User.class);
+
+ assertAdapterWithTargetBinding(bindings.getBindingForPath(path), CONTAINS_BINDING);
}
/**
@@ -118,7 +124,9 @@ public void propertyNotExplicitlyIncludedAndWithoutTypeBindingIsInvisible() {
bindings.bind(String.class).first(CONTAINS_BINDING);
- assertThat(bindings.getBindingForPath(PropertyPath.from("inceptionYear", User.class)), nullValue());
+ PathInformation path = PropertyPathInformation.of("inceptionYear", User.class);
+
+ assertThat(bindings.getBindingForPath(path), nullValue());
}
/**
@@ -261,7 +269,7 @@ public void aliasesBinding() {
bindings.bind(QUser.user.address.city).as("city").first(CONTAINS_BINDING);
- PropertyPath path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class));
+ PathInformation path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class));
assertThat(path, is(notNullValue()));
assertThat(bindings.isPathAvailable("city", User.class), is(true));
@@ -279,14 +287,14 @@ public void explicitlyIncludesOriginalBindingDespiteAlias() {
bindings.including(QUser.user.address.city);
bindings.bind(QUser.user.address.city).as("city").first(CONTAINS_BINDING);
- PropertyPath path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class));
+ PathInformation path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class));
assertThat(path, is(notNullValue()));
assertThat(bindings.isPathAvailable("city", User.class), is(true));
assertThat(bindings.isPathAvailable("address.city", User.class), is(true));
- PropertyPath propertyPath = bindings.getPropertyPath("address.city", ClassTypeInformation.from(User.class));
+ PathInformation propertyPath = bindings.getPropertyPath("address.city", ClassTypeInformation.from(User.class));
assertThat(propertyPath, is(notNullValue()));
assertAdapterWithTargetBinding(bindings.getBindingForPath(propertyPath), CONTAINS_BINDING);
@@ -300,17 +308,42 @@ public void registedAliasWithNullBinding() {
bindings.bind(QUser.user.address.city).as("city").withDefaultBinding();
- PropertyPath path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class));
+ PathInformation path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class));
assertThat(path, is(notNullValue()));
MultiValueBinding, Object> binding = bindings.getBindingForPath(path);
assertThat(binding, is(nullValue()));
}
+ /**
+ * @see DATACMNS-941
+ */
+ @Test
+ public void registersBindingForPropertyOfSubtype() {
+
+ bindings.bind(QUser.user.as(QSpecialUser.class).specialProperty).first(ContainsBinding.INSTANCE);
+
+ assertThat(bindings.isPathAvailable("specialProperty", User.class), is(true));
+ }
+
private static , S> void assertAdapterWithTargetBinding(MultiValueBinding
binding,
SingleValueBinding extends Path>, ?> expected) {
assertThat(binding, is(instanceOf(QuerydslBindings.MultiValueBindingAdapter.class)));
assertThat(ReflectionTestUtils.getField(binding, "delegate"), is((Object) expected));
}
+
+ enum ContainsBinding implements SingleValueBinding {
+
+ INSTANCE;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.binding.SingleValueBinding#bind(com.querydsl.core.types.Path, java.lang.Object)
+ */
+ @Override
+ public Predicate bind(StringPath path, String value) {
+ return path.contains(value);
+ }
+ }
}
diff --git a/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java b/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java
index 5b75d8b388..46098be98b 100644
--- a/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java
+++ b/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java
@@ -27,7 +27,9 @@
import org.joda.time.format.DateTimeFormatter;
import org.junit.Before;
import org.junit.Test;
+import org.springframework.data.querydsl.QSpecialUser;
import org.springframework.data.querydsl.QUser;
+import org.springframework.data.querydsl.QUserWrapper;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.querydsl.User;
import org.springframework.data.querydsl.Users;
@@ -219,4 +221,40 @@ public void automaticallyInsertsAnyStepInCollectionReference() {
assertThat(predicate, is((Predicate) QUser.user.addresses.any().street.eq("VALUE")));
}
+
+ /**
+ * @see DATACMNS-941
+ */
+ @Test
+ public void buildsPredicateForBindingUsingDowncast() {
+
+ values.add("specialProperty", "VALUE");
+
+ QuerydslBindings bindings = new QuerydslBindings();
+ bindings.bind(QUser.user.as(QSpecialUser.class).specialProperty)//
+ .first(QuerydslBindingsUnitTests.ContainsBinding.INSTANCE);
+
+ Predicate predicate = builder.getPredicate(USER_TYPE, values, bindings);
+
+ assertThat(predicate, is((Predicate) QUser.user.as(QSpecialUser.class).specialProperty.contains("VALUE")));
+ }
+
+ /**
+ * @see DATACMNS-941
+ */
+ @Test
+ public void buildsPredicateForBindingUsingNestedDowncast() {
+
+ values.add("user.specialProperty", "VALUE");
+
+ QUserWrapper $ = QUserWrapper.userWrapper;
+
+ QuerydslBindings bindings = new QuerydslBindings();
+ bindings.bind($.user.as(QSpecialUser.class).specialProperty)//
+ .first(QuerydslBindingsUnitTests.ContainsBinding.INSTANCE);
+
+ Predicate predicate = builder.getPredicate(USER_TYPE, values, bindings);
+
+ assertThat(predicate, is((Predicate) $.user.as(QSpecialUser.class).specialProperty.contains("VALUE")));
+ }
}
diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTests.java
index 10ae062e28..d9d0a00010 100644
--- a/src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTests.java
@@ -62,7 +62,7 @@ public void usesAnnotationValueIfAnnotationPresent() {
private BeanDefinition getBeanDefinitionFor(Class> repositoryInterface) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RepositoryFactoryBeanSupport.class);
- builder.addPropertyValue("repositoryInterface", repositoryInterface.getName());
+ builder.addConstructorArgValue(repositoryInterface.getName());
return builder.getBeanDefinition();
}
diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java
index 5bee74d04e..82603609e3 100644
--- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java
@@ -23,12 +23,7 @@
import java.util.Collections;
import org.junit.Test;
-import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Primary;
-import org.springframework.core.env.StandardEnvironment;
-import org.springframework.core.io.DefaultResourceLoader;
-import org.springframework.core.type.AnnotationMetadata;
-import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
@@ -65,26 +60,6 @@ public void considersRepositoryInterfaceExtendingStoreInterfaceStrictMatch() {
assertThat(extension.isStrictRepositoryCandidate(ExtendingInterface.class), is(true));
}
- /**
- * @see DATACMNS-609
- */
- @Test
- public void registersRepositoryInterfaceAwareBeanPostProcessorOnlyOnceForMultipleConfigurations() {
-
- DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
- AnnotationMetadata annotationMetadata = new StandardAnnotationMetadata(SampleConfiguration.class, true);
-
- DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
- StandardEnvironment environment = new StandardEnvironment();
- AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(
- annotationMetadata, EnableRepositories.class, resourceLoader, environment);
-
- extension.registerBeansForRoot(beanFactory, configurationSource);
- extension.registerBeansForRoot(beanFactory, configurationSource);
-
- assertThat(beanFactory.getBeanDefinitionCount(), is(1));
- }
-
static class SampleRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
@Override
diff --git a/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java
index 875d654751..909cfafc19 100644
--- a/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java
@@ -18,6 +18,8 @@
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
+import lombok.experimental.Delegate;
+
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -254,6 +256,22 @@ public void discoversCustomlyImplementedCrudMethodWithGenericParameters() throws
assertThat(information.isCustomMethod(customBaseRepositoryMethod), is(true));
}
+ /**
+ * @see DATACMNS-943
+ * @throws Exception
+ */
+ @Test
+ public void usesCorrectSaveOverload() throws Exception {
+
+ RepositoryMetadata metadata = new DefaultRepositoryMetadata(DummyRepository.class);
+ RepositoryInformation information = new DefaultRepositoryInformation(metadata, DummyRepositoryImpl.class, null);
+
+ Method method = DummyRepository.class.getMethod("save", Iterable.class);
+
+ assertThat(information.getTargetClassMethod(method),
+ is(DummyRepositoryImpl.class.getMethod("save", Iterable.class)));
+ }
+
private static Method getMethodFrom(Class> type, String name) {
for (Method method : type.getMethods()) {
@@ -303,7 +321,7 @@ static class Boss implements Iterable {
@Override
public Iterator iterator() {
- return Collections.emptySet().iterator();
+ return Collections. emptySet().iterator();
}
}
@@ -366,4 +384,15 @@ public S save(S entity) {
}
static class Sample {}
+
+ interface DummyRepository extends CrudRepository {
+
+ @Override
+ List save(Iterable entites);
+ }
+
+ static class DummyRepositoryImpl implements CrudRepository {
+
+ private @Delegate CrudRepository delegate;
+ }
}
diff --git a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactoryBean.java b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactoryBean.java
index 146cc4f025..10f7fff6c8 100644
--- a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactoryBean.java
+++ b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2013 the original author or authors.
+ * Copyright 2012-2016 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.
@@ -25,22 +25,17 @@
/**
* @author Oliver Gierke
*/
-public class DummyRepositoryFactoryBean, S, ID extends Serializable> extends
- RepositoryFactoryBeanSupport {
+public class DummyRepositoryFactoryBean, S, ID extends Serializable>
+ extends RepositoryFactoryBeanSupport {
private T repository;
- public DummyRepositoryFactoryBean() {
- setMappingContext(new SampleMappingContext());
- }
+ public DummyRepositoryFactoryBean(Class extends T> repositoryInterface) {
+
+ super(repositoryInterface);
- /* (non-Javadoc)
- * @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#setRepositoryInterface(java.lang.Class)
- */
- @Override
- public void setRepositoryInterface(Class extends T> repositoryInterface) {
this.repository = mock(repositoryInterface);
- super.setRepositoryInterface(repositoryInterface);
+ setMappingContext(new SampleMappingContext());
}
/*
diff --git a/src/test/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessorUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessorUnitTests.java
new file mode 100644
index 0000000000..a4953a65bd
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessorUnitTests.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2016 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.repository.core.support;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+import lombok.Getter;
+import lombok.Value;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.UUID;
+
+import org.aopalliance.aop.Advice;
+import org.aopalliance.intercept.MethodInvocation;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.domain.DomainEvents;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.repository.core.support.EventPublishingRepositoryProxyPostProcessor.EventPublishingMethod;
+import org.springframework.data.repository.core.support.EventPublishingRepositoryProxyPostProcessor.EventPublishingMethodInterceptor;
+
+/**
+ * Unit tests for {@link EventPublishingRepositoryProxyPostProcessor} and contained classes.
+ *
+ * @author Oliver Gierke
+ * @soundtrack Henrik Freischlader Trio - Nobody Else To Blame (Openness)
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class EventPublishingRepositoryProxyPostProcessorUnitTests {
+
+ @Mock ApplicationEventPublisher publisher;
+ @Mock MethodInvocation invocation;
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void rejectsNullAggregateTypes() {
+ EventPublishingMethod.of(null);
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void publishingEventsForNullIsNoOp() {
+ EventPublishingMethod.of(OneEvent.class).publishEventsFrom(null, publisher);
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void exposesEventsExposedByEntityToPublisher() {
+
+ SomeEvent first = new SomeEvent();
+ SomeEvent second = new SomeEvent();
+ MultipleEvents entity = MultipleEvents.of(Arrays.asList(first, second));
+
+ EventPublishingMethod.of(MultipleEvents.class).publishEventsFrom(entity, publisher);
+
+ verify(publisher).publishEvent(eq(first));
+ verify(publisher).publishEvent(eq(second));
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void exposesSingleEventByEntityToPublisher() {
+
+ SomeEvent event = new SomeEvent();
+ OneEvent entity = OneEvent.of(event);
+
+ EventPublishingMethod.of(OneEvent.class).publishEventsFrom(entity, publisher);
+
+ verify(publisher, times(1)).publishEvent(event);
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void doesNotExposeNullEvent() {
+
+ OneEvent entity = OneEvent.of(null);
+
+ EventPublishingMethod.of(OneEvent.class).publishEventsFrom(entity, publisher);
+
+ verify(publisher, times(0)).publishEvent(any());
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void doesNotCreatePublishingMethodIfNoAnnotationDetected() {
+ assertThat(EventPublishingMethod.of(Object.class), is(nullValue()));
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void interceptsSaveMethod() throws Throwable {
+
+ doReturn(SampleRepository.class.getMethod("save", Object.class)).when(invocation).getMethod();
+
+ SomeEvent event = new SomeEvent();
+ MultipleEvents sample = MultipleEvents.of(Arrays.asList(event));
+ doReturn(new Object[] { sample }).when(invocation).getArguments();
+
+ EventPublishingMethodInterceptor//
+ .of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
+ .invoke(invocation);
+
+ verify(publisher).publishEvent(event);
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void doesNotInterceptNonSaveMethod() throws Throwable {
+
+ doReturn(SampleRepository.class.getMethod("findOne", Serializable.class)).when(invocation).getMethod();
+
+ EventPublishingMethodInterceptor//
+ .of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
+ .invoke(invocation);
+
+ verify(publisher, never()).publishEvent(any());
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void registersAdviceIfDomainTypeExposesEvents() {
+
+ RepositoryInformation information = new DummyRepositoryInformation(SampleRepository.class);
+ RepositoryProxyPostProcessor processor = new EventPublishingRepositoryProxyPostProcessor(publisher);
+
+ ProxyFactory factory = mock(ProxyFactory.class);
+
+ processor.postProcess(factory, information);
+
+ verify(factory).addAdvice(any(EventPublishingMethodInterceptor.class));
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void doesNotAddAdviceIfDomainTypeDoesNotExposeEvents() {
+
+ RepositoryInformation information = new DummyRepositoryInformation(CrudRepository.class);
+ RepositoryProxyPostProcessor processor = new EventPublishingRepositoryProxyPostProcessor(publisher);
+
+ ProxyFactory factory = mock(ProxyFactory.class);
+
+ processor.postProcess(factory, information);
+
+ verify(factory, never()).addAdvice(any(Advice.class));
+ }
+
+ /**
+ * @see DATACMNS-928
+ */
+ @Test
+ public void publishesEventsForCallToSaveWithIterable() throws Throwable {
+
+ SomeEvent event = new SomeEvent();
+ MultipleEvents sample = MultipleEvents.of(Arrays.asList(event));
+ doReturn(new Object[] { Arrays.asList(sample) }).when(invocation).getArguments();
+
+ doReturn(SampleRepository.class.getMethod("save", Iterable.class)).when(invocation).getMethod();
+
+ EventPublishingMethodInterceptor//
+ .of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
+ .invoke(invocation);
+
+ verify(publisher).publishEvent(any(SomeEvent.class));
+ }
+
+ @Value(staticConstructor = "of")
+ static class MultipleEvents {
+ @Getter(onMethod = @__(@DomainEvents)) Collection extends Object> events;
+ }
+
+ @Value(staticConstructor = "of")
+ static class OneEvent {
+ @Getter(onMethod = @__(@DomainEvents)) Object event;
+ }
+
+ @Value
+ static class SomeEvent {
+ UUID id = UUID.randomUUID();
+ }
+
+ interface SampleRepository extends CrudRepository {}
+}
diff --git a/src/test/java/org/springframework/data/repository/core/support/ExampleSpecificationAccessorUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/ExampleSpecificationAccessorUnitTests.java
index 945fe1f747..44625a83c5 100644
--- a/src/test/java/org/springframework/data/repository/core/support/ExampleSpecificationAccessorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/ExampleSpecificationAccessorUnitTests.java
@@ -38,7 +38,6 @@
* @author Mark Paluch
* @soundtrack Ron Spielman Trio - Fretboard Highway (Electric Tales)
*/
-@SuppressWarnings("unused")
public class ExampleSpecificationAccessorUnitTests {
Person person;
@@ -322,6 +321,19 @@ public void hasPropertySpecifiersReturnsTrueWhenAtLeastOneIsSet() {
assertThat(exampleSpecificationAccessor.hasPropertySpecifiers(), is(true));
}
+ /**
+ * @see DATACMNS-953
+ */
+ @Test
+ public void exactMatcherUsesExactMatching() {
+
+ ExampleMatcher matcher = ExampleMatcher.matching()//
+ .withMatcher("firstname", exact());
+
+ assertThat(new ExampleMatcherAccessor(matcher).getPropertySpecifier("firstname").getStringMatcher(),
+ is(StringMatcher.EXACT));
+ }
+
static class Person {
String firstname;
}
diff --git a/src/test/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingPostProcessorIntegrationTests.java b/src/test/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingPostProcessorIntegrationTests.java
deleted file mode 100644
index 49f852fb86..0000000000
--- a/src/test/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingPostProcessorIntegrationTests.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2013-2016 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.repository.core.support;
-
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.*;
-
-import java.util.Arrays;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.springframework.beans.factory.BeanFactoryUtils;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.DefaultListableBeanFactory;
-import org.springframework.data.querydsl.User;
-import org.springframework.data.repository.Repository;
-
-/**
- * Integration test to make sure Spring Data repository factory beans are found without the factories already
- * instantiated.
- *
- * @see SPR-10517
- * @author Oliver Gierke
- */
-public class FactoryBeanTypePredictingPostProcessorIntegrationTests {
-
- DefaultListableBeanFactory factory;
-
- @Before
- public void setUp() {
-
- factory = new DefaultListableBeanFactory();
-
- // Register factory bean for repository
- BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DummyRepositoryFactoryBean.class);
- builder.addPropertyValue("repositoryInterface", UserRepository.class);
- factory.registerBeanDefinition("repository", builder.getBeanDefinition());
-
- // Register predicting BeanPostProcessor
- FactoryBeanTypePredictingBeanPostProcessor processor = new FactoryBeanTypePredictingBeanPostProcessor(
- RepositoryFactoryBeanSupport.class, "repositoryInterface");
- processor.setBeanFactory(factory);
- factory.addBeanPostProcessor(processor);
- }
-
- @Test
- public void lookupBeforeInstantiation() {
-
- String[] strings = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, RepositoryFactoryInformation.class,
- false, false);
- assertThat(Arrays.asList(strings), hasItem("&repository"));
- }
-
- @Test
- public void lookupAfterInstantiation() {
-
- factory.getBean(UserRepository.class);
-
- String[] strings = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, RepositoryFactoryInformation.class,
- false, false);
- assertThat(Arrays.asList(strings), hasItem("&repository"));
- }
-
- interface UserRepository extends Repository {}
-}
diff --git a/src/test/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingPostProcessorUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingPostProcessorUnitTests.java
deleted file mode 100644
index 2ee1da5655..0000000000
--- a/src/test/java/org/springframework/data/repository/core/support/FactoryBeanTypePredictingPostProcessorUnitTests.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright 2008-2016 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.repository.core.support;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
-
-import java.io.Serializable;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.data.repository.Repository;
-import org.springframework.jndi.JndiObjectFactoryBean;
-
-/**
- * Unit tests for {@link RepositoryInterfaceAwareBeanPostProcessor}.
- *
- * @author Oliver Gierke
- */
-@RunWith(MockitoJUnitRunner.class)
-public class FactoryBeanTypePredictingPostProcessorUnitTests {
-
- private static final Class> FACTORY_CLASS = RepositoryFactoryBeanSupport.class;
- private static final String BEAN_NAME = "foo";
- private static final String DAO_INTERFACE_PROPERTY = "repositoryInterface";
-
- FactoryBeanTypePredictingBeanPostProcessor processor;
- BeanDefinition beanDefinition;
-
- @Mock ConfigurableListableBeanFactory beanFactory;
-
- @Before
- public void setUp() {
-
- BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FACTORY_CLASS)
- .addPropertyValue(DAO_INTERFACE_PROPERTY, UserDao.class);
- this.beanDefinition = builder.getBeanDefinition();
- this.processor = new FactoryBeanTypePredictingBeanPostProcessor(FACTORY_CLASS, "repositoryInterface");
-
- when(beanFactory.getBeanDefinition(BEAN_NAME)).thenReturn(beanDefinition);
- }
-
- @Test
- public void returnsDaoInterfaceClassForFactoryBean() throws Exception {
-
- processor.setBeanFactory(beanFactory);
- assertEquals(UserDao.class, processor.predictBeanType(FACTORY_CLASS, BEAN_NAME));
- }
-
- @Test
- public void doesNotResolveInterfaceForNonFactoryClasses() throws Exception {
-
- processor.setBeanFactory(beanFactory);
- assertNotTypeDetected(BeanFactory.class);
- }
-
- @Test
- public void doesNotResolveInterfaceForUnloadableClass() throws Exception {
-
- BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FACTORY_CLASS)
- .addPropertyValue(DAO_INTERFACE_PROPERTY, "com.acme.Foo");
-
- when(beanFactory.getBeanDefinition(BEAN_NAME)).thenReturn(builder.getBeanDefinition());
-
- assertNotTypeDetected(FACTORY_CLASS);
- }
-
- @Test
- public void doesNotResolveTypeForPlainBeanFactory() throws Exception {
-
- BeanFactory beanFactory = mock(BeanFactory.class);
- processor.setBeanFactory(beanFactory);
-
- assertNotTypeDetected(FACTORY_CLASS);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void rejectsNonFactoryBeanType() {
- new FactoryBeanTypePredictingBeanPostProcessor(Object.class, "property");
- }
-
- /**
- * @see DATACMNS-821
- */
- @Test
- public void usesFirstValueIfPropertyIsOfArrayType() {
-
- BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JndiObjectFactoryBean.class);
- builder.addPropertyValue("proxyInterfaces",
- new String[] { Serializable.class.getName(), Iterable.class.getName() });
-
- when(beanFactory.getBeanDefinition(BEAN_NAME)).thenReturn(builder.getBeanDefinition());
-
- processor = new FactoryBeanTypePredictingBeanPostProcessor(JndiObjectFactoryBean.class, "proxyInterface",
- "proxyInterfaces");
- processor.setBeanFactory(beanFactory);
-
- assertThat(processor.predictBeanType(JndiObjectFactoryBean.class, BEAN_NAME),
- is(typeCompatibleWith(Serializable.class)));
- }
-
- private void assertNotTypeDetected(Class> beanClass) {
- assertThat(processor.predictBeanType(beanClass, BEAN_NAME), is(nullValue()));
- }
-
- private class User {}
-
- private interface UserDao extends Repository {}
-}
diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupportUnitTests.java
index c06a62c5fd..ffbb9af73f 100644
--- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupportUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupportUnitTests.java
@@ -44,10 +44,9 @@ public void setsConfiguredClassLoaderOnRepositoryFactory() {
ClassLoader classLoader = mock(ClassLoader.class);
- RepositoryFactoryBeanSupport factoryBean = new DummyRepositoryFactoryBean();
+ RepositoryFactoryBeanSupport factoryBean = new DummyRepositoryFactoryBean(SampleRepository.class);
factoryBean.setBeanClassLoader(classLoader);
factoryBean.setLazyInit(true);
- factoryBean.setRepositoryInterface(SampleRepository.class);
factoryBean.afterPropertiesSet();
Object factory = ReflectionTestUtils.getField(factoryBean, "factory");
@@ -58,14 +57,13 @@ public void setsConfiguredClassLoaderOnRepositoryFactory() {
* @see DATACMNS-432
*/
@Test
- @SuppressWarnings("rawtypes")
+ @SuppressWarnings({ "rawtypes", "unchecked" })
public void initializationFailsWithMissingRepositoryInterface() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("Repository interface");
- RepositoryFactoryBeanSupport factoryBean = new DummyRepositoryFactoryBean();
- factoryBean.afterPropertiesSet();
+ new DummyRepositoryFactoryBean(null);
}
interface SampleRepository extends Repository {}
diff --git a/src/test/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptorUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptorUnitTests.java
new file mode 100644
index 0000000000..0f9c83ecfe
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/core/support/SurroundingTransactionDetectorMethodInterceptorUnitTests.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 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.repository.core.support;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.*;
+
+import org.junit.Test;
+import org.springframework.aop.framework.ReflectiveMethodInvocation;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * Unit tests for {@link SurroundingTransactionDetectorMethodInterceptor}.
+ *
+ * @author Oliver Gierke
+ * @soundtrack Hendrik Freischlader Trio - Openness (Openness)
+ */
+public class SurroundingTransactionDetectorMethodInterceptorUnitTests {
+
+ /**
+ * @see DATACMNS-959
+ */
+ @Test
+ public void registersActiveSurroundingTransaction() throws Throwable {
+
+ TransactionSynchronizationManager.setActualTransactionActive(true);
+
+ INSTANCE.invoke(new StubMethodInvocation(true));
+ }
+
+ /**
+ * @see DATACMNS-959
+ */
+ @Test
+ public void registersNoSurroundingTransaction() throws Throwable {
+
+ TransactionSynchronizationManager.setActualTransactionActive(false);
+
+ INSTANCE.invoke(new StubMethodInvocation(false));
+ }
+
+ static class StubMethodInvocation extends ReflectiveMethodInvocation {
+
+ boolean transactionActive;
+
+ StubMethodInvocation(boolean expectTransactionActive) throws Exception {
+ super(null, null, Object.class.getMethod("toString"), null, null, null);
+ this.transactionActive = expectTransactionActive;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.aopalliance.intercept.Joinpoint#proceed()
+ */
+ public Object proceed() throws Throwable {
+
+ assertThat(INSTANCE.isSurroundingTransactionActive(), is(transactionActive));
+
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/data/repository/core/support/TransactionRepositoryFactoryBeanSupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/TransactionRepositoryFactoryBeanSupportUnitTests.java
index 13d6599680..9f7e597872 100644
--- a/src/test/java/org/springframework/data/repository/core/support/TransactionRepositoryFactoryBeanSupportUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/TransactionRepositoryFactoryBeanSupportUnitTests.java
@@ -88,7 +88,7 @@ static class SampleTransactionalRepositoryFactoryBean
private final CrudRepository repository = mock(CrudRepository.class);
public SampleTransactionalRepositoryFactoryBean() {
- setRepositoryInterface((Class) CrudRepository.class);
+ super((Class) CrudRepository.class);
}
@Override
diff --git a/src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java b/src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java
index 5a8a0f1fc8..402eca5908 100644
--- a/src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2015 the original author or authors.
+ * Copyright 2008-2016 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.
@@ -19,6 +19,9 @@
import static org.junit.Assert.*;
import static org.junit.Assume.*;
+import javaslang.collection.Seq;
+import javaslang.control.Option;
+
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
@@ -226,6 +229,42 @@ public void doesNotRejectFutureQueryForEntityCollection() throws Exception {
assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(true));
}
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void detectsCustomCollectionReturnType() throws Exception {
+
+ RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
+ Method method = SampleRepository.class.getMethod("returnsSeq");
+
+ assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(true));
+ }
+
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void detectsWrapperWithinWrapper() throws Exception {
+
+ RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
+ Method method = SampleRepository.class.getMethod("returnsFutureOfSeq");
+
+ assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(true));
+ }
+
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void detectsSinglValueWrapperWithinWrapper() throws Exception {
+
+ RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
+ Method method = SampleRepository.class.getMethod("returnsFutureOfOption");
+
+ assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(false));
+ }
+
interface SampleRepository extends Repository {
String pagingMethodWithInvalidReturnType(Pageable pageable);
@@ -269,6 +308,12 @@ interface SampleRepository extends Repository {
* @see DATACMNS-716
*/
Future> returnsFutureForEntityCollection();
+
+ Seq returnsSeq();
+
+ Future> returnsFutureOfSeq();
+
+ Future> returnsFutureOfOption();
}
class User {
diff --git a/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java b/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java
index f3d3cbaa28..384f24e835 100644
--- a/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java
@@ -179,6 +179,20 @@ public void considersInterfaceImplementedByDomainTypeNotProjecting() throws Exce
assertThat(type.isProjecting(), is(false));
}
+ /**
+ * @see DATACMNS-963
+ */
+ @Test
+ public void detectsDistinctInputProperties() {
+
+ ReturnedType type = ReturnedType.of(Child.class, Object.class, new SpelAwareProxyProjectionFactory());
+
+ List properties = type.getInputProperties();
+
+ assertThat(properties, hasSize(1));
+ assertThat(properties, contains("firstname"));
+ }
+
private static ReturnedType getReturnedType(String methodName, Class>... parameters) throws Exception {
return getQueryMethod(methodName, parameters).getResultProcessor().getReturnedType();
}
@@ -262,4 +276,12 @@ interface OpenProjection {
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
}
+
+ static interface Parent {
+ String getFirstname();
+ }
+
+ static interface Child extends Parent {
+ String getFirstname();
+ }
}
diff --git a/src/test/java/org/springframework/data/repository/sample/SampleConfiguration.java b/src/test/java/org/springframework/data/repository/sample/SampleConfiguration.java
index 3293697c39..19617f927e 100644
--- a/src/test/java/org/springframework/data/repository/sample/SampleConfiguration.java
+++ b/src/test/java/org/springframework/data/repository/sample/SampleConfiguration.java
@@ -24,18 +24,15 @@ public Repositories repositories() {
@Bean
public RepositoryFactoryBeanSupport, User, Long> userRepositoryFactory() {
- DummyRepositoryFactoryBean, User, Long> factory = new DummyRepositoryFactoryBean, User, Long>();
- factory.setRepositoryInterface(UserRepository.class);
-
- return factory;
+ return new DummyRepositoryFactoryBean, User, Long>(UserRepository.class);
}
@Bean
public RepositoryFactoryBeanSupport, Product, Long> productRepositoryFactory(
ProductRepository productRepository) {
- DummyRepositoryFactoryBean, Product, Long> factory = new DummyRepositoryFactoryBean, Product, Long>();
- factory.setRepositoryInterface(ProductRepository.class);
+ DummyRepositoryFactoryBean, Product, Long> factory = new DummyRepositoryFactoryBean, Product, Long>(
+ ProductRepository.class);
factory.setCustomImplementation(productRepository);
return factory;
diff --git a/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java b/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java
index f62a189021..b96525b30f 100644
--- a/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java
@@ -214,7 +214,7 @@ public void toIdConverterDoesNotMatchIfTargetTypeIsAssignableFromSource() throws
private ApplicationContext initContextWithRepo() {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DummyRepositoryFactoryBean.class);
- builder.addPropertyValue("repositoryInterface", UserRepository.class);
+ builder.addConstructorArgValue(UserRepository.class);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("provider", builder.getBeanDefinition());
diff --git a/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrarUnitTests.java b/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrarUnitTests.java
index 3522a9035a..71633d70a8 100644
--- a/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrarUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrarUnitTests.java
@@ -51,7 +51,7 @@ public class DomainClassPropertyEditorRegistrarUnitTests {
public void setup() {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DummyRepositoryFactoryBean.class);
- builder.addPropertyValue("repositoryInterface", EntityRepository.class);
+ builder.addConstructorArgValue(EntityRepository.class);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("provider", builder.getBeanDefinition());
diff --git a/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java b/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java
index 7b32bc57d0..3aac0bbd46 100644
--- a/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java
@@ -73,7 +73,7 @@ public void setUp() {
private AbstractBeanDefinition getRepositoryBeanDefinition(Class> repositoryInterface) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DummyRepositoryFactoryBean.class);
- builder.addPropertyValue("repositoryInterface", repositoryInterface);
+ builder.addConstructorArgValue(repositoryInterface);
return builder.getBeanDefinition();
}
diff --git a/src/test/java/org/springframework/data/repository/util/QueryExecutionConvertersUnitTests.java b/src/test/java/org/springframework/data/repository/util/QueryExecutionConvertersUnitTests.java
index 228762ccd4..c5ad8d8f02 100644
--- a/src/test/java/org/springframework/data/repository/util/QueryExecutionConvertersUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/util/QueryExecutionConvertersUnitTests.java
@@ -17,18 +17,26 @@
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
-import static org.junit.Assume.*;
+import static org.springframework.data.repository.util.QueryExecutionConverters.*;
+import javaslang.collection.HashMap;
+import javaslang.collection.HashSet;
+import javaslang.collection.Traversable;
import scala.Option;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import org.junit.Before;
import org.junit.Test;
-import org.springframework.core.SpringVersion;
import org.springframework.core.convert.support.DefaultConversionService;
-import org.springframework.data.util.Version;
+import org.springframework.util.ReflectionUtils;
import org.springframework.util.concurrent.ListenableFuture;
import com.google.common.base.Optional;
@@ -41,9 +49,6 @@
*/
public class QueryExecutionConvertersUnitTests {
- private static final Version SPRING_VERSION = Version.parse(SpringVersion.getVersion());
- private static final Version FOUR_DOT_TWO = new Version(4, 2);
-
DefaultConversionService conversionService;
@Before
@@ -64,6 +69,7 @@ public void registersWrapperTypes() {
assertThat(QueryExecutionConverters.supports(Future.class), is(true));
assertThat(QueryExecutionConverters.supports(ListenableFuture.class), is(true));
assertThat(QueryExecutionConverters.supports(Option.class), is(true));
+ assertThat(QueryExecutionConverters.supports(javaslang.control.Option.class), is(true));
}
/**
@@ -71,9 +77,6 @@ public void registersWrapperTypes() {
*/
@Test
public void registersCompletableFutureAsWrapperTypeOnSpring42OrBetter() {
-
- assumeThat(SPRING_VERSION.isGreaterThanOrEqualTo(FOUR_DOT_TWO), is(true));
-
assertThat(QueryExecutionConverters.supports(CompletableFuture.class), is(true));
}
@@ -172,4 +175,143 @@ public void unwrapsScalaOption() {
public void unwrapsEmptyScalaOption() {
assertThat(QueryExecutionConverters.unwrap(Option.empty()), is((Object) null));
}
+
+ /**
+ * @see DATACMNS-937
+ */
+ @Test
+ public void turnsNullIntoJavaslangOption() {
+ assertThat(conversionService.convert(new NullableWrapper(null), javaslang.control.Option.class),
+ is((Object) optionNone()));
+ }
+
+ /**
+ * @see DATACMNS-937
+ */
+ @Test
+ public void wrapsValueIntoJavaslangOption() {
+
+ javaslang.control.Option> result = conversionService.convert(new NullableWrapper("string"),
+ javaslang.control.Option.class);
+
+ assertThat(result.isEmpty(), is(false));
+ assertThat(result.get(), is((Object) "string"));
+ }
+
+ /**
+ * @see DATACMNS-937
+ */
+ @Test
+ public void unwrapsEmptyJavaslangOption() {
+ assertThat(QueryExecutionConverters.unwrap(optionNone()), is(nullValue()));
+ }
+
+ /**
+ * @see DATACMNS-937
+ */
+ @Test
+ public void unwrapsJavaslangOption() {
+ assertThat(QueryExecutionConverters.unwrap(option("string")), is((Object) "string"));
+ }
+
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void conversListToJavaslang() {
+
+ assertThat(conversionService.canConvert(List.class, javaslang.collection.Traversable.class), is(true));
+ assertThat(conversionService.canConvert(List.class, javaslang.collection.List.class), is(true));
+ assertThat(conversionService.canConvert(List.class, javaslang.collection.Set.class), is(true));
+ assertThat(conversionService.canConvert(List.class, javaslang.collection.Map.class), is(false));
+
+ List integers = Arrays.asList(1, 2, 3);
+
+ Traversable> result = conversionService.convert(integers, Traversable.class);
+
+ assertThat(result, is(instanceOf(javaslang.collection.List.class)));
+ }
+
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void convertsSetToJavaslang() {
+
+ assertThat(conversionService.canConvert(Set.class, javaslang.collection.Traversable.class), is(true));
+ assertThat(conversionService.canConvert(Set.class, javaslang.collection.Set.class), is(true));
+ assertThat(conversionService.canConvert(Set.class, javaslang.collection.List.class), is(true));
+ assertThat(conversionService.canConvert(Set.class, javaslang.collection.Map.class), is(false));
+
+ Set integers = Collections.singleton(1);
+
+ Traversable> result = conversionService.convert(integers, Traversable.class);
+
+ assertThat(result, is(instanceOf(javaslang.collection.Set.class)));
+ }
+
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void convertsMapToJavaslang() {
+
+ assertThat(conversionService.canConvert(Map.class, javaslang.collection.Traversable.class), is(true));
+ assertThat(conversionService.canConvert(Map.class, javaslang.collection.Map.class), is(true));
+ assertThat(conversionService.canConvert(Map.class, javaslang.collection.Set.class), is(false));
+ assertThat(conversionService.canConvert(Map.class, javaslang.collection.List.class), is(false));
+
+ Map map = Collections.singletonMap("key", "value");
+
+ Traversable> result = conversionService.convert(map, Traversable.class);
+
+ assertThat(result, is(instanceOf(javaslang.collection.Map.class)));
+ }
+
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void unwrapsJavaslangCollectionsToJavaOnes() {
+
+ assertThat(unwrap(javaslangList(1, 2, 3)), is(instanceOf(List.class)));
+ assertThat(unwrap(javaslangSet(1, 2, 3)), is(instanceOf(Set.class)));
+ assertThat(unwrap(javaslangMap("key", "value")), is(instanceOf(Map.class)));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static javaslang.control.Option optionNone() {
+
+ Method method = ReflectionUtils.findMethod(javaslang.control.Option.class, "none");
+ return (javaslang.control.Option) ReflectionUtils.invokeMethod(method, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static javaslang.control.Option option(T source) {
+
+ Method method = ReflectionUtils.findMethod(javaslang.control.Option.class, "of", Object.class);
+ return (javaslang.control.Option) ReflectionUtils.invokeMethod(method, null, source);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static javaslang.collection.List javaslangList(T... values) {
+
+ Method method = ReflectionUtils.findMethod(javaslang.collection.List.class, "ofAll", Iterable.class);
+ return (javaslang.collection.List) ReflectionUtils.invokeMethod(method, null, Arrays.asList(values));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static javaslang.collection.Set javaslangSet(T... values) {
+
+ Method method = ReflectionUtils.findMethod(HashSet.class, "ofAll", Iterable.class);
+ return (javaslang.collection.Set) ReflectionUtils.invokeMethod(method, null, Arrays.asList(values));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static javaslang.collection.Map javaslangMap(K key, V value) {
+
+ Method method = ReflectionUtils.findMethod(HashMap.class, "ofAll", Map.class);
+ return (javaslang.collection.Map) ReflectionUtils.invokeMethod(method, null,
+ Collections.singletonMap(key, value));
+ }
}
diff --git a/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java b/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java
index c4a4af0fa2..2d1405efa2 100644
--- a/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java
+++ b/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java
@@ -19,6 +19,8 @@
import static org.junit.Assert.*;
import static org.springframework.data.util.ClassTypeInformation.*;
+import javaslang.collection.Traversable;
+
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Collection;
@@ -106,7 +108,7 @@ public void discoversArraysAndCollections() {
property = information.getProperty("rawSet");
assertEquals(Set.class, property.getType());
- assertThat(property.getComponentType().getType(), is(Matchers.>equalTo(Object.class)));
+ assertThat(property.getComponentType().getType(), is(Matchers.> equalTo(Object.class)));
assertNull(property.getMapValueType());
}
@@ -413,6 +415,29 @@ public void prefersLocalTypeMappingOverNestedWithSameGenericType() {
assertThat(information.getProperty("field").getType(), is(typeCompatibleWith(Nested.class)));
}
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void detectsJavaslangTraversableComponentType() {
+
+ ClassTypeInformation information = ClassTypeInformation.from(SampleTraversable.class);
+
+ assertThat(information.getComponentType().getType(), is(typeCompatibleWith(Integer.class)));
+ }
+
+ /**
+ * @see DATACMNS-940
+ */
+ @Test
+ public void detectsJavaslangMapComponentAndValueType() {
+
+ ClassTypeInformation information = ClassTypeInformation.from(SampleMap.class);
+
+ assertThat(information.getComponentType().getType(), is(typeCompatibleWith(String.class)));
+ assertThat(information.getMapValueType().getType(), is(typeCompatibleWith(Integer.class)));
+ }
+
static class StringMapContainer extends MapContainer {
}
@@ -611,4 +636,8 @@ static class SomeType {
static class Nested extends SomeType {}
static class Concrete extends SomeType {}
+
+ static interface SampleTraversable extends Traversable {}
+
+ static interface SampleMap extends javaslang.collection.Map {}
}
diff --git a/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java
index 94ebea5348..63bf70a4e2 100644
--- a/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java
+++ b/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java
@@ -25,6 +25,7 @@
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.web.SortDefault.SortDefaults;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -36,6 +37,7 @@
*
* @author Oliver Gierke
* @author Nick Williams
+ * @author Kazuki Shimizu
*/
public class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefaultUnitTests {
@@ -283,6 +285,86 @@ public void detectsFallbackPageableIfNullOneIsConfigured() {
assertThat(resolver.isFallbackPageable(new PageRequest(0, 10)), is(false));
}
+ /**
+ * @see DATACMNS-966
+ */
+ @Test
+ public void allAllowSortProperty() throws Exception {
+
+ PageableHandlerMethodArgumentResolver resolver = getResolver();
+ resolver.setOneIndexedParameters(true);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("sort", "id,title");
+
+ MethodParameter parameter = new MethodParameter(
+ Sample.class.getMethod("annotatedAllowedSortProperties", Pageable.class), 0);
+
+ Pageable result = resolver.resolveArgument(parameter, null, new ServletWebRequest(request), null);
+
+ assertThat(result.getSort(), is(new Sort("id", "title")));
+ }
+
+ /**
+ * @see DATACMNS-966
+ */
+ @Test
+ public void containsInvalidSortProperty() throws Exception {
+
+ PageableHandlerMethodArgumentResolver resolver = getResolver();
+ resolver.setOneIndexedParameters(true);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("sort", "id,aaaa");
+
+ MethodParameter parameter = new MethodParameter(
+ Sample.class.getMethod("annotatedAllowedSortProperties", Pageable.class), 0);
+
+ Pageable result = resolver.resolveArgument(parameter, null, new ServletWebRequest(request), null);
+
+ assertThat(result.getSort(), is(new Sort("id")));
+ }
+
+ /**
+ * @see DATACMNS-966
+ */
+ @Test
+ public void allInvalidSortProperty() throws Exception {
+
+ PageableHandlerMethodArgumentResolver resolver = getResolver();
+ resolver.setOneIndexedParameters(true);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("sort", "bbbb,aaaa");
+
+ MethodParameter parameter = new MethodParameter(
+ Sample.class.getMethod("annotatedAllowedSortProperties", Pageable.class), 0);
+
+ Pageable result = resolver.resolveArgument(parameter, null, new ServletWebRequest(request), null);
+
+ assertThat(result.getSort(), nullValue());
+ }
+
+ /**
+ * @see DATACMNS-966
+ */
+ @Test
+ public void ignoreSortProperty() throws Exception {
+
+ PageableHandlerMethodArgumentResolver resolver = getResolver();
+ resolver.setOneIndexedParameters(true);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("sort", "id,title");
+
+ MethodParameter parameter = new MethodParameter(
+ Sample.class.getMethod("annotatedAllowedSortPropertiesIsEmpty", Pageable.class), 0);
+
+ Pageable result = resolver.resolveArgument(parameter, null, new ServletWebRequest(request), null);
+
+ assertThat(result.getSort(), nullValue());
+ }
+
@Override
protected PageableHandlerMethodArgumentResolver getResolver() {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
@@ -322,5 +404,10 @@ void simpleDefaultWithContaineredExternalSort(@PageableDefault(size = PAGE_SIZE,
void validQualifier(@Qualifier("foo") Pageable pageable);
void noQualifiers(Pageable first, Pageable second);
+
+ void annotatedAllowedSortProperties(@AllowedSortProperties({ "id", "title" }) Pageable pageable);
+
+ void annotatedAllowedSortPropertiesIsEmpty(@AllowedSortProperties({}) Pageable pageable);
+
}
}
diff --git a/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java b/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java
index aa373234be..7e44242761 100644
--- a/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java
+++ b/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 the original author or authors.
+ * Copyright 2013-2016 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.
@@ -63,7 +63,7 @@ public void parsesSimpleSortStringCorrectly() {
private static void assertSortStringParsedInto(Sort expected, String... source) {
SortHandlerMethodArgumentResolver resolver = new SortHandlerMethodArgumentResolver();
- Sort sort = resolver.parseParameterIntoSort(source, ",");
+ Sort sort = resolver.parseParameterIntoSort(source, ",", null);
assertThat(sort, is(expected));
}
diff --git a/src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java
index 5a9fe062d5..0815ab212b 100644
--- a/src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java
+++ b/src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2015 the original author or authors.
+ * Copyright 2013-2016 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.
@@ -41,6 +41,7 @@
* @author Oliver Gierke
* @author Thomas Darimont
* @author Nick Williams
+ * @author Kazuki Shimizu
*/
public class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests {
@@ -211,6 +212,55 @@ public void doesNotReturnNullWhenAnnotatedWithSortDefault() throws Exception {
assertThat(resolveSort(request, getParameterOfMethod("containeredDefault")), is(new Sort("foo", "bar")));
}
+ /**
+ * @see DATACMNS-966
+ */
+ @Test
+ public void allAllowSortProperty() throws Exception {
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("sort", "id,title");
+
+ assertThat(resolveSort(request, getParameterOfMethod("annotatedAllowedSortProperties")),
+ is(new Sort("id", "title")));
+ }
+
+ /**
+ * @see DATACMNS-966
+ */
+ @Test
+ public void containsInvalidSortProperty() throws Exception {
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("sort", "id,aaaa");
+
+ assertThat(resolveSort(request, getParameterOfMethod("annotatedAllowedSortProperties")), is(new Sort("id")));
+ }
+
+ /**
+ * @see DATACMNS-966
+ */
+ @Test
+ public void allInvalidSortProperty() throws Exception {
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("sort", "bbbb,aaaa");
+
+ assertThat(resolveSort(request, getParameterOfMethod("annotatedAllowedSortProperties")), nullValue());
+ }
+
+ /**
+ * @see DATACMNS-966
+ */
+ @Test
+ public void ignoreSortProperty() throws Exception {
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("sort", "id,title");
+
+ assertThat(resolveSort(request, getParameterOfMethod("annotatedAllowedSortPropertiesIsEmpty")), nullValue());
+ }
+
private static Sort resolveSort(HttpServletRequest request, MethodParameter parameter) throws Exception {
SortHandlerMethodArgumentResolver resolver = new SortHandlerMethodArgumentResolver();
@@ -271,5 +321,10 @@ void simpleDefaultWithDirection(
void containeredDefault(@SortDefaults(@SortDefault({ "foo", "bar" })) Sort sort);
void invalid(@SortDefaults(@SortDefault({ "foo", "bar" })) @SortDefault({ "bar", "foo" }) Sort sort);
+
+ void annotatedAllowedSortProperties(@AllowedSortProperties({ "id", "title" }) Sort sort);
+
+ void annotatedAllowedSortPropertiesIsEmpty(@AllowedSortProperties({}) Sort sort);
+
}
}
diff --git a/src/test/java/org/springframework/data/web/config/SampleMixin.java b/src/test/java/org/springframework/data/web/config/SampleMixin.java
index 26e8001b35..019e73d74f 100644
--- a/src/test/java/org/springframework/data/web/config/SampleMixin.java
+++ b/src/test/java/org/springframework/data/web/config/SampleMixin.java
@@ -20,8 +20,7 @@
/**
* @author Oliver Gierke
*/
-@SpringDataWebConfigurationMixin
-public class SampleMixin {
+public class SampleMixin implements SpringDataJacksonModules {
@Bean
String sampleBean() {
diff --git a/src/test/resources/META-INF/spring.factories b/src/test/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..81d1c0f829
--- /dev/null
+++ b/src/test/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.web.config.SampleMixin
diff --git a/template.mf b/template.mf
index 2d453512ee..605b08db64 100644
--- a/template.mf
+++ b/template.mf
@@ -13,6 +13,7 @@ Import-Template:
com.google.common.*;version="${guava:[=.=.=,+1.0.0)}";resolution:=optional,
com.jayway.jsonpath.*;version="${jsonpath:[=.=.=,+1.0.0]}";resolution:=optional,
com.querydsl.*;version="${querydsl:[=.=.=,+1.0.0)}";resolution:=optional,
+ javaslang.*;version="${javaslang:[=.=.=,+1.0.0)}";resolution:=optional,
javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional,
javax.inject.*;version="[1.0.0,2.0.0)";resolution:=optional,
javax.servlet.*;version="[2.5.0, 4.0.0)";resolution:=optional,