/* * Copyright 2012-2017 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.auditing; import lombok.NonNull; import lombok.RequiredArgsConstructor; import java.lang.reflect.Field; import java.time.temporal.TemporalAccessor; import java.util.Calendar; import java.util.Date; import java.util.Optional; import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.data.convert.JodaTimeConverters; import org.springframework.data.convert.Jsr310Converters; import org.springframework.data.convert.ThreeTenBackPortConverters; import org.springframework.data.domain.Auditable; import org.springframework.data.util.ReflectionUtils; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.util.Assert; /** * A factory class to {@link AuditableBeanWrapper} instances. * * @author Oliver Gierke * @author Christoph Strobl * @since 1.5 */ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory { /** * Returns an {@link AuditableBeanWrapper} if the given object is capable of being equipped with auditing information. * * @param source the auditing candidate. * @return */ @SuppressWarnings("unchecked") public Optional<AuditableBeanWrapper> getBeanWrapperFor(Object source) { Assert.notNull(source, "Source must not be null!"); return Optional.of(source).map(it -> { if (it instanceof Auditable) { return new AuditableInterfaceBeanWrapper((Auditable<Object, ?, TemporalAccessor>) it); } AnnotationAuditingMetadata metadata = AnnotationAuditingMetadata.getMetadata(it.getClass()); if (metadata.isAuditable()) { return new ReflectionAuditingBeanWrapper(it); } return null; }); } /** * An {@link AuditableBeanWrapper} that works with objects implementing * * @author Oliver Gierke */ @RequiredArgsConstructor static class AuditableInterfaceBeanWrapper extends DateConvertingAuditableBeanWrapper { private final @NonNull Auditable<Object, ?, TemporalAccessor> auditable; private final Class<? extends TemporalAccessor> type; @SuppressWarnings("unchecked") public AuditableInterfaceBeanWrapper(Auditable<Object, ?, TemporalAccessor> auditable) { this.auditable = auditable; this.type = (Class<? extends TemporalAccessor>) ResolvableType.forClass(Auditable.class, auditable.getClass()) .getGeneric(2).getRawClass(); } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedBy(java.util.Optional) */ @Override public Object setCreatedBy(Object value) { auditable.setCreatedBy(value); return value; } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(java.util.Optional) */ @Override public TemporalAccessor setCreatedDate(TemporalAccessor value) { auditable.setCreatedDate( getAsTemporalAccessor(Optional.of(value), type).orElseThrow(() -> new IllegalStateException())); return value; } /* * (non-Javadoc) * @see org.springframework.data.auditing.DefaultAuditableBeanWrapperFactory.AuditableInterfaceBeanWrapper#setLastModifiedBy(java.util.Optional) */ @Override public Object setLastModifiedBy(Object value) { auditable.setLastModifiedBy(value); return value; } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#getLastModifiedDate() */ @Override public Optional<TemporalAccessor> getLastModifiedDate() { return getAsTemporalAccessor(auditable.getLastModifiedDate(), TemporalAccessor.class); } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(java.util.Optional) */ @Override public TemporalAccessor setLastModifiedDate(TemporalAccessor value) { auditable.setLastModifiedDate( getAsTemporalAccessor(Optional.of(value), type).orElseThrow(() -> new IllegalStateException())); return value; } } /** * Base class for {@link AuditableBeanWrapper} implementations that might need to convert {@link Calendar} values into * compatible types when setting date/time information. * * @author Oliver Gierke * @since 1.8 */ abstract static class DateConvertingAuditableBeanWrapper implements AuditableBeanWrapper { private final ConversionService conversionService; /** * Creates a new {@link DateConvertingAuditableBeanWrapper}. */ public DateConvertingAuditableBeanWrapper() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); JodaTimeConverters.getConvertersToRegister().forEach(conversionService::addConverter); Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter); ThreeTenBackPortConverters.getConvertersToRegister().forEach(conversionService::addConverter); this.conversionService = conversionService; } /** * Returns the {@link Calendar} in a type, compatible to the given field. * * @param value can be {@literal null}. * @param targetType must not be {@literal null}. * @param source must not be {@literal null}. * @return */ protected Object getDateValueToSet(TemporalAccessor value, Class<?> targetType, Object source) { if (TemporalAccessor.class.equals(targetType)) { return value; } if (conversionService.canConvert(value.getClass(), targetType)) { return conversionService.convert(value, targetType); } if (conversionService.canConvert(Date.class, targetType)) { if (!conversionService.canConvert(value.getClass(), Date.class)) { throw new IllegalArgumentException( String.format("Cannot convert date type for member %s! From %s to java.util.Date to %s.", source, value.getClass(), targetType)); } Date date = conversionService.convert(value, Date.class); return conversionService.convert(date, targetType); } throw new IllegalArgumentException(String.format("Invalid date type for member %s! Supported types are %s.", source, AnnotationAuditingMetadata.SUPPORTED_DATE_TYPES)); } /** * Returns the given object as {@link Calendar}. * * @param source can be {@literal null}. * @return */ @SuppressWarnings("unchecked") protected <T extends TemporalAccessor> Optional<T> getAsTemporalAccessor(Optional<? extends Object> source, Class<? extends T> target) { return source.map(it -> target.isInstance(it) ? (T) it : conversionService.convert(it, target)); } } /** * An {@link AuditableBeanWrapper} implementation that sets values on the target object using reflection. * * @author Oliver Gierke */ static class ReflectionAuditingBeanWrapper extends DateConvertingAuditableBeanWrapper { private final AnnotationAuditingMetadata metadata; private final Object target; /** * Creates a new {@link ReflectionAuditingBeanWrapper} to set auditing data on the given target object. * * @param target must not be {@literal null}. */ public ReflectionAuditingBeanWrapper(Object target) { Assert.notNull(target, "Target object must not be null!"); this.metadata = AnnotationAuditingMetadata.getMetadata(target.getClass()); this.target = target; } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedBy(java.util.Optional) */ @Override public Object setCreatedBy(Object value) { return setField(metadata.getCreatedByField(), value); } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(java.util.Optional) */ @Override public TemporalAccessor setCreatedDate(TemporalAccessor value) { return setDateField(metadata.getCreatedDateField(), value); } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedBy(java.util.Optional) */ @Override public Object setLastModifiedBy(Object value) { return setField(metadata.getLastModifiedByField(), value); } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#getLastModifiedDate() */ @Override public Optional<TemporalAccessor> getLastModifiedDate() { return getAsTemporalAccessor(metadata.getLastModifiedDateField().map(field -> { Object value = org.springframework.util.ReflectionUtils.getField(field, target); return Optional.class.isInstance(value) ? ((Optional<?>) value).orElse(null) : value; }), TemporalAccessor.class); } /* * (non-Javadoc) * @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(java.util.Optional) */ @Override public TemporalAccessor setLastModifiedDate(TemporalAccessor value) { return setDateField(metadata.getLastModifiedDateField(), value); } /** * Sets the given field to the given value if present. * * @param field * @param value */ private <T> T setField(Optional<Field> field, T value) { field.ifPresent(it -> ReflectionUtils.setField(it, target, value)); return value; } /** * Sets the given field to the given value if the field is not {@literal null}. * * @param field * @param value */ private TemporalAccessor setDateField(Optional<Field> field, TemporalAccessor value) { field.ifPresent(it -> ReflectionUtils.setField(it, target, getDateValueToSet(value, it.getType(), it))); return value; } } }