diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index 4db922024..41688422a 100644 --- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -18,14 +18,12 @@ package org.springframework.data.mapping.context; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.Lock; @@ -48,6 +46,7 @@ import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.FieldCallback; import org.springframework.util.ReflectionUtils.FieldFilter; +import org.springframework.util.StringUtils; /** * Base class to build mapping metadata and thus create instances of {@link PersistentEntity} and @@ -199,6 +198,9 @@ public abstract class AbstractMappingContext getPersistentPropertyPath(PropertyPath propertyPath) { + + Assert.notNull(propertyPath, "Property path must not be null!"); + return getPersistentPropertyPath(propertyPath.toDotPath(), propertyPath.getOwningType()); } @@ -207,9 +209,22 @@ public abstract class AbstractMappingContext getPersistentPropertyPath(String propertyPath, Class type) { + + Assert.notNull(propertyPath, "Property path must not be null!"); + Assert.notNull(type, "Type must not be null!"); + return getPersistentPropertyPath(propertyPath, ClassTypeInformation.from(type)); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.MappingContext#getPersistentPropertyPath(org.springframework.data.mapping.context.InvalidPersistentPropertyPath) + */ + @Override + public PersistentPropertyPath

getPersistentPropertyPath(InvalidPersistentPropertyPath invalidPath) { + return getPersistentPropertyPath(invalidPath.getResolvedPath(), invalidPath.getType()); + } + private PersistentPropertyPath

getPersistentPropertyPath(String propertyPath, TypeInformation type) { return getPersistentPropertyPath(Arrays.asList(propertyPath.split("\\.")), type); } @@ -221,9 +236,9 @@ public abstract class AbstractMappingContext getPersistentPropertyPath(Iterable parts, TypeInformation type) { + private PersistentPropertyPath

getPersistentPropertyPath(Collection parts, TypeInformation type) { - List

result = new ArrayList

(); + DefaultPersistentPropertyPath

path = DefaultPersistentPropertyPath.empty(); Iterator iterator = parts.iterator(); E current = getPersistentEntity(type); @@ -233,17 +248,22 @@ public abstract class AbstractMappingContext(result); + return path; } /** diff --git a/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java b/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java index 64604f540..3086bf34e 100644 --- a/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 the original author or authors. + * Copyright 2011-2015 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,6 +16,8 @@ package org.springframework.data.mapping.context; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -50,11 +52,45 @@ class DefaultPersistentPropertyPath> implements public DefaultPersistentPropertyPath(List properties) { Assert.notNull(properties); - Assert.isTrue(!properties.isEmpty()); this.properties = properties; } + /** + * Creates an empty {@link DefaultPersistentPropertyPath}. + * + * @return + */ + public static > DefaultPersistentPropertyPath empty() { + return new DefaultPersistentPropertyPath(Collections. emptyList()); + } + + /** + * Appends the given {@link PersistentProperty} to the current {@link PersistentPropertyPath}. + * + * @param property must not be {@literal null}. + * @return a new {@link DefaultPersistentPropertyPath} with the given property appended to the current one. + * @throws IllegalArgumentException in case the property is not a property of the type of the current leaf property. + */ + @SuppressWarnings("unchecked") + public DefaultPersistentPropertyPath append(T property) { + + Assert.notNull(property, "Property must not be null!"); + + if (isEmpty()) { + return new DefaultPersistentPropertyPath(Arrays.asList(property)); + } + + Class leafPropertyType = getLeafProperty().getActualType(); + Assert.isTrue(property.getOwner().getType().equals(leafPropertyType), + String.format("Cannot append property %s to type %s!", property.getName(), leafPropertyType.getName())); + + List properties = new ArrayList(this.properties); + properties.add(property); + + return new DefaultPersistentPropertyPath(properties); + } + /* * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath() @@ -86,8 +122,8 @@ class DefaultPersistentPropertyPath> implements public String toPath(String delimiter, Converter converter) { @SuppressWarnings("unchecked") - Converter converterToUse = (Converter) (converter == null ? PropertyNameConverter.INSTANCE - : converter); + Converter converterToUse = (Converter) (converter == null + ? PropertyNameConverter.INSTANCE : converter); String delimiterToUse = delimiter == null ? "." : delimiter; List result = new ArrayList(); @@ -200,6 +236,14 @@ class DefaultPersistentPropertyPath> implements return properties.iterator(); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.PersistentPropertyPath#isEmpty() + */ + public boolean isEmpty() { + return properties.isEmpty(); + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) diff --git a/src/main/java/org/springframework/data/mapping/context/InvalidPersistentPropertyPath.java b/src/main/java/org/springframework/data/mapping/context/InvalidPersistentPropertyPath.java new file mode 100644 index 000000000..c6d0025c6 --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/context/InvalidPersistentPropertyPath.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015 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.mapping.context; + +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.Assert; + +/** + * Exception to indicate a source path couldn't be resolved into a {@link PersistentPropertyPath} completely. + * + * @author Oliver Gierke + * @soundtrack John Mayer - Slow Dancing In A Burning Room (Acoustic, The Village Sessions) + */ +public class InvalidPersistentPropertyPath extends MappingException { + + private static final long serialVersionUID = 2805815643641094488L; + + private final String source, unresolvableSegment, resolvedPath; + private final TypeInformation type; + + /** + * Creates a new {@link InvalidPersistentPropertyPath} for the given resolved path and message. + * + * @param source must not be {@literal null}. + * @param unresolvableSegment must not be {@literal null} or empty. + * @param resolvedPath + * @param message must not be {@literal null} or empty. + */ + InvalidPersistentPropertyPath(String source, TypeInformation type, String unresolvableSegment, String resolvedPath, + String message) { + + super(message); + + Assert.notNull(source, "Source property path must not be null!"); + Assert.notNull(type, "Type must not be null!"); + Assert.notNull(unresolvableSegment, "Unresolvable segment must not be null!"); + + this.source = source; + this.type = type; + this.unresolvableSegment = unresolvableSegment; + this.resolvedPath = resolvedPath == null ? "" : resolvedPath; + } + + /** + * Returns the source property path. + * + * @return the source will never be {@literal null}. + */ + public String getSource() { + return source; + } + + /** + * Returns the type the source property path was attempted to be resolved on. + * + * @return the type will never be {@literal null}. + */ + public TypeInformation getType() { + return type; + } + + /** + * Returns the segment of the source property path that could not be resolved. + * + * @return the unresolvableSegment + */ + public String getUnresolvableSegment() { + return unresolvableSegment; + } + + /** + * Returns the part of the source path until which the source property path could be resolved. + * + * @return the resolvedPath + */ + public String getResolvedPath() { + return resolvedPath; + } +} diff --git a/src/main/java/org/springframework/data/mapping/context/MappingContext.java b/src/main/java/org/springframework/data/mapping/context/MappingContext.java index b6277cb6f..9b918252c 100644 --- a/src/main/java/org/springframework/data/mapping/context/MappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/MappingContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2015 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. @@ -85,6 +85,7 @@ public interface MappingContext, P extends Pers * * @param propertyPath must not be {@literal null}. * @return the {@link PersistentPropertyPath} representing the given {@link PropertyPath}. + * @throws InvalidPersistentPropertyPath in case not all of the segments of the given {@link PropertyPath} can be resolved. */ PersistentPropertyPath

getPersistentPropertyPath(PropertyPath propertyPath); @@ -94,9 +95,19 @@ public interface MappingContext, P extends Pers * @param propertyPath must not be {@literal null}. * @param type must not be {@literal null}. * @return the {@link PersistentPropertyPath} representing the given property path on the given type. + * @throws InvalidPersistentPropertyPath in case not all of the segments of the given property path can be resolved. */ PersistentPropertyPath

getPersistentPropertyPath(String propertyPath, Class type); + /** + * Returns the {@link PersistentPropertyPath} for the resolvable part of the given {@link InvalidPersistentPropertyPath}. + * + * @param invalidPath must not be {@literal null}. + * @return the {@link PersistentPropertyPath} for the resolvable part of the given {@link InvalidPersistentPropertyPath}. + * @since 1.11 + */ + PersistentPropertyPath

getPersistentPropertyPath(InvalidPersistentPropertyPath invalidPath); + /** * Returns the {@link TypeInformation}s for all {@link PersistentEntity}s in the {@link MappingContext}. * diff --git a/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java b/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java index b97504c6d..c9e75ebcc 100644 --- a/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2015 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. @@ -111,4 +111,12 @@ public interface PersistentPropertyPath> extends * @return */ int getLength(); -} \ No newline at end of file + + /** + * Returns whether the path is empty. + * + * @return + * @since 1.11 + */ + boolean isEmpty(); +} diff --git a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java index 01bc065c1..c7e938c33 100644 --- a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java @@ -18,6 +18,7 @@ package org.springframework.data.mapping.context; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; + import groovy.lang.MetaClass; import java.util.Collections; @@ -59,8 +60,8 @@ public class AbstractMappingContextUnitTests { @Test public void doesNotTryToLookupPersistentEntityForLeafProperty() { - PersistentPropertyPath path = context.getPersistentPropertyPath(PropertyPath.from("name", - Person.class)); + PersistentPropertyPath path = context + .getPersistentPropertyPath(PropertyPath.from("name", Person.class)); assertThat(path, is(notNullValue())); } @@ -255,6 +256,28 @@ public class AbstractMappingContextUnitTests { is(Matchers. iterableWithSize(3))); } + /** + * @see DATACMNS-727 + */ + @Test + public void exposesContextForFailingPropertyPathLookup() { + + try { + + context.getPersistentPropertyPath("persons.firstname", Sample.class); + fail("Expected InvalidPersistentPropertyPath!"); + + } catch (InvalidPersistentPropertyPath o_O) { + + assertThat(o_O.getMessage(), not(isEmptyOrNullString())); + assertThat(o_O.getResolvedPath(), is("persons")); + assertThat(o_O.getUnresolvableSegment(), is("firstname")); + + // Make sure, the resolvable part can be obtained + assertThat(context.getPersistentPropertyPath(o_O), is(notNullValue())); + } + } + private static void assertHasEntityFor(Class type, SampleMappingContext context, boolean expected) { boolean found = false;