Add BeanUtils, abstract input converter and output converter

pull/137/head
johnniang 2019-03-04 13:30:20 +08:00
parent 1c17fe5a33
commit 7a1c3234d9
6 changed files with 281 additions and 0 deletions

View File

@ -0,0 +1,24 @@
package cc.ryanc.halo.exception;
import org.springframework.http.HttpStatus;
/**
* BeanUtils exception.
*
* @author johnniang
*/
public class BeanUtilsException extends HaloException {
public BeanUtilsException(String message) {
super(message);
}
public BeanUtilsException(String message, Throwable cause) {
super(message, cause);
}
@Override
public HttpStatus getStatus() {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}

View File

@ -0,0 +1,39 @@
package cc.ryanc.halo.model.dto.base;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import static cc.ryanc.halo.utils.BeanUtils.transformFrom;
import static cc.ryanc.halo.utils.BeanUtils.updateProperties;
/**
* Convenience for input dto.Abstract input dto converter.
*
* @author johnniang
*/
public abstract class AbstractInputConverter<DOMAIN> implements InputConverter<DOMAIN> {
@SuppressWarnings("unchecked")
private final Class<DOMAIN> domainType = (Class<DOMAIN>) fetchType(0);
@Override
public DOMAIN convertTo() {
return transformFrom(this, domainType);
}
@Override
public void update(DOMAIN domain) {
updateProperties(this, domain);
}
/**
* Get actual generic type.
*
* @param index generic type index
* @return real type will be returned
*/
private Type fetchType(int index) {
return ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[index];
}
}

View File

@ -0,0 +1,41 @@
package cc.ryanc.halo.model.dto.base;
import org.springframework.util.Assert;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import static cc.ryanc.halo.utils.BeanUtils.updateProperties;
/**
* Abstract output dto converter. (it must be extended by DTO)
*
* @author johnniang
*/
public abstract class AbstractOutputConverter<DTO, DOMAIN> implements OutputConverter<DTO, DOMAIN> {
@SuppressWarnings("unchecked")
private final Class<DTO> dtoType = (Class<DTO>) fetchType(0);
public AbstractOutputConverter() {
Assert.isTrue(dtoType.equals(getClass()), "this converter must be extended by DTO type");
}
@Override
@SuppressWarnings("unchecked")
public DTO convertFrom(DOMAIN domain) {
updateProperties(domain, this);
return (DTO) this;
}
/**
* Get actual generic type.
*
* @param index generic type index
* @return real type will be returned
*/
private Type fetchType(int index) {
return ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[index];
}
}

View File

@ -0,0 +1,24 @@
package cc.ryanc.halo.model.dto.base;
/**
* Converter interface for input DTO.
*
* @author johnniang
*/
public interface InputConverter<DOMAIN> {
/**
* Convert to domain.(shallow)
*
* @return new domain with same value(not null)
*/
DOMAIN convertTo();
/**
* Update a domain by dto.(shallow)
*
* @param domain updated domain
*/
void update(DOMAIN domain);
}

View File

@ -0,0 +1,17 @@
package cc.ryanc.halo.model.dto.base;
/**
* Converter interface for output DTO.
*
* @author johnniang
*/
public interface OutputConverter<DTO, DOMAIN> {
/**
* Convert from domain.(shallow)
*
* @param domain domain data
* @return converted dto data
*/
DTO convertFrom(DOMAIN domain);
}

View File

@ -0,0 +1,136 @@
package cc.ryanc.halo.utils;
import cc.ryanc.halo.exception.BeanUtilsException;
import cc.ryanc.halo.logging.Logger;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.beans.PropertyDescriptor;
import java.util.*;
import java.util.stream.Collectors;
/**
* Bean utilities.
*
* @author johnniang
*/
public class BeanUtils {
private final static Logger LOG = Logger.getLogger(BeanUtils.class);
private BeanUtils() {
}
/**
* Transforms from the source object. (copy same properties only)
*
* @param source source data
* @param targetClass target class must not be null
* @param <T> target class type
* @return instance with specified type copying from source data; or null if source data is null
* @throws BeanUtilsException if newing target instance failed or copying failed
*/
@Nullable
public static <T> T transformFrom(@Nullable Object source, @NonNull Class<T> targetClass) {
Assert.notNull(targetClass, "Target class must not be null");
if (source == null) {
return null;
}
// Init the instance
try {
// New instance for the target class
T targetInstance = targetClass.getDeclaredConstructor().newInstance();
// Copy properties
org.springframework.beans.BeanUtils.copyProperties(source, targetInstance, getNullPropertyNames(source));
// Return the target instance
return targetInstance;
} catch (Exception e) {
throw new BeanUtilsException("Failed to new " + targetClass.getName() + "instance or copy properties", e);
}
}
/**
* Transforms from source data collection in batch.
*
* @param sources source data collection
* @param targetClass target class must not be null
* @param <T> target class type
* @return target collection transforming from source data collection.
* @throws BeanUtilsException if newing target instance failed or copying failed
*/
@NonNull
public static <T> List<T> transformFromInBatch(Collection<?> sources, @NonNull Class<T> targetClass) {
if (CollectionUtils.isEmpty(sources)) {
return Collections.emptyList();
}
// Transform in batch
return sources.stream()
.map(source -> transformFrom(source, targetClass))
.collect(Collectors.toList());
}
/**
* Update properties (non null).
*
* @param source source data must not be null
* @param target target data must not be null
* @throws BeanUtilsException if copying failed
*/
public static void updateProperties(@NonNull Object source, @NonNull Object target) {
Assert.notNull(source, "source object must not be null");
Assert.notNull(target, "target object must not be null");
// Set non null properties from source properties to target properties
try {
org.springframework.beans.BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
} catch (BeansException e) {
throw new BeanUtilsException("Failed to copy properties", e);
}
}
/**
* Gets null names array of property.
*
* @param source object data must not be null
* @return null name array of property
*/
@NonNull
private static String[] getNullPropertyNames(@NonNull Object source) {
return getNullPropertyNameSet(source).toArray(new String[0]);
}
/**
* Gets null names set of property.
*
* @param source object data must not be null
* @return null name set of property
*/
@NonNull
private static Set<String> getNullPropertyNameSet(@NonNull Object source) {
Assert.notNull(source, "source object must not be null");
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(source);
PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String propertyName = propertyDescriptor.getName();
Object propertyValue = beanWrapper.getPropertyValue(propertyName);
// if propertye value is equal to null, add it to empty name set
if (propertyValue == null) {
emptyNames.add(propertyName);
}
}
return emptyNames;
}
}