Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.springframework.data.mapping.AccessOptions Maven / Gradle / Ivy
/*
* Copyright 2020-2023 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
*
* https://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;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.springframework.data.mapping.AccessOptions.SetOptions.SetNulls;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Access options when using {@link PersistentPropertyPathAccessor} to get and set properties. Allows defining how to
* handle {@literal null} values, register custom transforming handlers when accessing collections and maps and
* propagation settings for how to handle intermediate collection and map values when setting values.
*
* @author Oliver Drotbohm
* @since 2.3
*/
public class AccessOptions {
/**
* Returns the default {@link SetOptions} rejecting setting values when finding an intermediate property value to be
* {@literal null}.
*
* @return
*/
public static SetOptions defaultSetOptions() {
return SetOptions.DEFAULT;
}
/**
* Returns the default {@link GetOptions} rejecting intermediate {@literal null} values when accessing property paths.
*
* @return
*/
public static GetOptions defaultGetOptions() {
return GetOptions.DEFAULT;
}
/**
* Access options for getting values for property paths.
*
* @author Oliver Drotbohm
*/
public static class GetOptions {
private static final GetOptions DEFAULT = new GetOptions(new HashMap<>(), GetNulls.REJECT);
private final Map, Function> handlers;
private final GetNulls nullValues;
public GetOptions(Map, Function> handlers, GetNulls nullValues) {
this.handlers = handlers;
this.nullValues = nullValues;
}
public GetNulls getNullValues() {
return this.nullValues;
}
public GetOptions withNullValues(GetNulls nullValues) {
return this.nullValues == nullValues ? this : new GetOptions(this.handlers, nullValues);
}
/**
* How to handle null values during a {@link PersistentPropertyPath} traversal.
*
* @author Oliver Drotbohm
*/
public enum GetNulls {
/**
* Reject the path lookup as a {@literal null} value cannot be traversed any further.
*/
REJECT,
/**
* Returns {@literal null} as the entire path's traversal result.
*/
EARLY_RETURN;
public SetOptions.SetNulls toNullHandling() {
return REJECT == this ? SetNulls.REJECT : SetNulls.SKIP;
}
}
/**
* Registers a {@link Function} to post-process values for the given property.
*
* @param property must not be {@literal null}.
* @param handler must not be {@literal null}.
* @return
*/
public GetOptions registerHandler(PersistentProperty> property, Function handler) {
Assert.notNull(property, "Property must not be null");
Assert.notNull(handler, "Handler must not be null");
Map, Function> newHandlers = new HashMap<>(handlers);
newHandlers.put(property, handler);
return new GetOptions(newHandlers, nullValues);
}
/**
* Registers a {@link Function} to handle {@link Collection} values for the given property.
*
* @param property must not be {@literal null}.
* @param handler must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
public GetOptions registerCollectionHandler(PersistentProperty> property,
Function super Collection>, Object> handler) {
return registerHandler(property, Collection.class, (Function) handler);
}
/**
* Registers a {@link Function} to handle {@link List} values for the given property.
*
* @param property must not be {@literal null}.
* @param handler must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
public GetOptions registerListHandler(PersistentProperty> property, Function super List>, Object> handler) {
return registerHandler(property, List.class, (Function) handler);
}
/**
* Registers a {@link Function} to handle {@link Set} values for the given property.
*
* @param property must not be {@literal null}.
* @param handler must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
public GetOptions registerSetHandler(PersistentProperty> property, Function super Set>, Object> handler) {
return registerHandler(property, Set.class, (Function) handler);
}
/**
* Registers a {@link Function} to handle {@link Map} values for the given property.
*
* @param property must not be {@literal null}.
* @param handler must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
public GetOptions registerMapHandler(PersistentProperty> property, Function super Map, ?>, Object> handler) {
return registerHandler(property, Map.class, (Function) handler);
}
/**
* Registers the given {@link Function} to post-process values obtained for the given {@link PersistentProperty} for
* the given type.
*
* @param the type of the value to handle.
* @param property must not be {@literal null}.
* @param type must not be {@literal null}.
* @param handler must not be {@literal null}.
* @return
*/
public GetOptions registerHandler(PersistentProperty> property, Class type,
Function super T, Object> handler) {
Assert.isTrue(type.isAssignableFrom(property.getType()), () -> String
.format("Cannot register a property handler for %s on a property of type %s", type, property.getType()));
Function caster = (Function) it -> type.cast(it);
return registerHandler(property, caster.andThen(handler));
}
/**
* Post-processes the value obtained for the given {@link PersistentProperty} using the registered handler.
*
* @param property must not be {@literal null}.
* @param value can be {@literal null}.
* @return the post-processed value or the value itself if no handlers registered.
*/
@Nullable
Object postProcess(PersistentProperty> property, @Nullable Object value) {
Function handler = handlers.get(property);
return handler == null ? value : handler.apply(value);
}
}
/**
* Access options for setting values for property paths.
*
* @author Oliver Drotbohm
*/
public static class SetOptions {
public SetOptions(SetNulls nullHandling, Propagation collectionPropagation, Propagation mapPropagation) {
this.nullHandling = nullHandling;
this.collectionPropagation = collectionPropagation;
this.mapPropagation = mapPropagation;
}
public SetOptions withNullHandling(SetNulls nullHandling) {
return this.nullHandling == nullHandling ? this
: new SetOptions(nullHandling, this.collectionPropagation, this.mapPropagation);
}
public SetOptions withCollectionPropagation(Propagation collectionPropagation) {
return this.collectionPropagation == collectionPropagation ? this
: new SetOptions(this.nullHandling, collectionPropagation, this.mapPropagation);
}
public SetOptions withMapPropagation(Propagation mapPropagation) {
return this.mapPropagation == mapPropagation ? this
: new SetOptions(this.nullHandling, this.collectionPropagation, mapPropagation);
}
public SetNulls getNullHandling() {
return this.nullHandling;
}
/**
* How to handle intermediate {@literal null} values when setting
*
* @author Oliver Drotbohm
*/
public enum SetNulls {
/**
* Reject {@literal null} values detected when traversing a path to eventually set the leaf property. This will
* cause a {@link MappingException} being thrown in that case.
*/
REJECT,
/**
* Skip setting the value but log an info message to leave a trace why the value wasn't actually set.
*/
SKIP_AND_LOG,
/**
* Silently skip the attempt to set the value.
*/
SKIP;
}
/**
* How to propagate setting values that cross collection and map properties.
*
* @author Oliver Drotbohm
*/
public enum Propagation {
/**
* Skip the setting of values when encountering a collection or map value within the path to traverse.
*/
SKIP,
/**
* Propagate the setting of values when encountering a collection or map value and set it on all collection or map
* members.
*/
PROPAGATE;
}
private static final SetOptions DEFAULT = new SetOptions();
private final SetNulls nullHandling;
private final Propagation collectionPropagation;
private final Propagation mapPropagation;
private SetOptions() {
this.nullHandling = SetNulls.REJECT;
this.collectionPropagation = Propagation.PROPAGATE;
this.mapPropagation = Propagation.PROPAGATE;
}
/**
* Returns a new {@link AccessOptions} that will cause paths that contain {@literal null} values to be skipped when
* setting a property.
*
* @return
*/
public SetOptions skipNulls() {
return withNullHandling(SetNulls.SKIP);
}
/**
* Returns a new {@link AccessOptions} that will cause paths that contain {@literal null} values to be skipped when
* setting a property but a log message produced in TRACE level.
*
* @return
*/
public SetOptions skipAndLogNulls() {
return withNullHandling(SetNulls.SKIP_AND_LOG);
}
/**
* Returns a new {@link AccessOptions} that will cause paths that contain {@literal null} values to be skipped when
* setting a property.
*
* @return
*/
public SetOptions rejectNulls() {
return withNullHandling(SetNulls.REJECT);
}
/**
* Shortcut to configure the same {@link Propagation} for both collection and map property path segments.
*
* @param propagation must not be {@literal null}.
* @return
*/
public SetOptions withCollectionAndMapPropagation(Propagation propagation) {
Assert.notNull(propagation, "Propagation must not be null");
return withCollectionPropagation(propagation) //
.withMapPropagation(propagation);
}
/**
* Returns whether the given property is supposed to be propagated, i.e. if values for it are supposed to be set at
* all.
*
* @param property can be {@literal null}.
* @return
*/
public boolean propagate(@Nullable PersistentProperty> property) {
if (property == null) {
return true;
}
if (property.isCollectionLike() && collectionPropagation.equals(Propagation.SKIP)) {
return false;
}
if (property.isMap() && mapPropagation.equals(Propagation.SKIP)) {
return false;
}
return true;
}
}
}