Skip to content

Commit

Permalink
Automate attribute converter (halo-dev#1325)
Browse files Browse the repository at this point in the history
* Deprecate AbstractConverter

* Remove unused enum and attribute converter

* Add AttributeConverterApplyTest

* Add JpaConfiguration

* Add AttributeConverterAutoGenerator

* Integrate automate-attribute-converter

* Rename JpaConfiguration

* Remove useless attribute converters

* Exclude property enums for auto-generating

* Refine JournalType definition

* Fix an error about existing injected type
  • Loading branch information
JohnNiang authored Mar 27, 2021
1 parent e6b32ac commit 8472a7b
Show file tree
Hide file tree
Showing 22 changed files with 307 additions and 184 deletions.
3 changes: 3 additions & 0 deletions src/main/java/run/halo/app/config/HaloConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
Expand All @@ -19,6 +20,7 @@
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.cache.InMemoryCacheStore;
import run.halo.app.cache.LevelCacheStore;
import run.halo.app.config.attributeconverter.AttributeConverterAutoGenerateConfiguration;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.repository.base.BaseRepositoryImpl;
import run.halo.app.utils.HttpClientUtils;
Expand All @@ -35,6 +37,7 @@
@EnableConfigurationProperties(HaloProperties.class)
@EnableJpaRepositories(basePackages = "run.halo.app.repository", repositoryBaseClass =
BaseRepositoryImpl.class)
@Import(AttributeConverterAutoGenerateConfiguration.class)
public class HaloConfiguration {

private final HaloProperties haloProperties;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package run.halo.app.config.attributeconverter;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
import org.springframework.context.annotation.Bean;

/**
* Jpa configuration.
*
* @author johnniang
*/
public class AttributeConverterAutoGenerateConfiguration {

@Bean
EntityManagerFactoryBuilderCustomizer entityManagerFactoryBuilderCustomizer(
ConfigurableListableBeanFactory factory) {
return builder -> builder.setPersistenceUnitPostProcessors(
new AutoGenerateConverterPersistenceUnitPostProcessor(factory));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package run.halo.app.config.attributeconverter;

import static net.bytebuddy.description.annotation.AnnotationDescription.Builder.ofType;
import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.parameterizedType;
import static net.bytebuddy.implementation.FieldAccessor.ofField;
import static net.bytebuddy.implementation.MethodDelegation.to;
import static net.bytebuddy.matcher.ElementMatchers.isDefaultConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;

import java.lang.reflect.Modifier;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodCall;
import org.apache.commons.lang3.StringUtils;

/**
* Attribute converter auto generator.
*
* @author johnniang
*/
class AttributeConverterAutoGenerator {

/**
* Auto generation suffix.
*/
public static final String AUTO_GENERATION_SUFFIX = "$AttributeConverterGeneratedByByteBuddy";

private final ClassLoader classLoader;

public AttributeConverterAutoGenerator(ClassLoader classLoader) {
this.classLoader = classLoader;
}

public <T> Class<?> generate(Class<T> clazz) {
try {
return new ByteBuddy()
.with(new NamingStrategy.AbstractBase() {
@Override
protected String name(TypeDescription superClass) {
return clazz.getName() + AUTO_GENERATION_SUFFIX;
}
})
.subclass(
parameterizedType(AttributeConverter.class, clazz, Integer.class).build())
.annotateType(ofType(Converter.class).define("autoApply", true).build())
.constructor(isDefaultConstructor())
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
.andThen(ofField("enumType").setsValue(clazz)))
.defineField("enumType", Class.class, Modifier.PRIVATE | Modifier.FINAL)
.method(named("convertToDatabaseColumn"))
.intercept(to(AttributeConverterInterceptor.class))
.method(named("convertToEntityAttribute"))
.intercept(to(AttributeConverterInterceptor.class))
.make()
.load(this.classLoader, ClassLoadingStrategy.Default.INJECTION.allowExistingTypes())
.getLoaded();
} catch (NoSuchMethodException e) {
// should never happen
throw new RuntimeException("Failed to get declared constructor.", e);
}
}

public static boolean isGeneratedByByteBuddy(String className) {
return StringUtils.endsWith(className, AUTO_GENERATION_SUFFIX);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package run.halo.app.config.attributeconverter;

import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import run.halo.app.model.enums.ValueEnum;

/**
* Attribute Converter Interceptor.
*
* @author johnniang
*/
public class AttributeConverterInterceptor {

private AttributeConverterInterceptor() {
}

@RuntimeType
public static <T extends Enum<T> & ValueEnum<V>, V> V convertToDatabaseColumn(T attribute) {
return attribute == null ? null : attribute.getValue();
}

@RuntimeType
public static <T extends Enum<T> & ValueEnum<V>, V> T convertToEntityAttribute(V dbData,
@FieldValue("enumType") Class<T> enumType) {
return dbData == null ? null : ValueEnum.valueToEnum(enumType, dbData);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package run.halo.app.config.attributeconverter;

import static java.util.stream.Collectors.toUnmodifiableSet;

import java.util.Set;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
import org.springframework.util.ClassUtils;
import run.halo.app.model.enums.ValueEnum;
import run.halo.app.model.properties.PropertyEnum;

/**
* Attribute converter persistence unit post processor.
*
* @author johnniang
*/
class AutoGenerateConverterPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {

private static final String PACKAGE_TO_SCAN = "run.halo.app";

private final ConfigurableListableBeanFactory factory;

public AutoGenerateConverterPersistenceUnitPostProcessor(
ConfigurableListableBeanFactory factory) {
this.factory = factory;
}

@Override
public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
var generator = new AttributeConverterAutoGenerator(factory.getBeanClassLoader());

findValueEnumClasses()
.stream()
.map(generator::generate)
.map(Class::getName)
.forEach(pui::addManagedClassName);
}

private Set<Class<?>> findValueEnumClasses() {
var scanner = new ClassPathScanningCandidateComponentProvider(false);
// include ValueEnum class
scanner.addIncludeFilter(new AssignableTypeFilter(ValueEnum.class));
// exclude PropertyEnum class
scanner.addExcludeFilter(new AssignableTypeFilter(PropertyEnum.class));

return scanner.findCandidateComponents(PACKAGE_TO_SCAN)
.stream()
.filter(bd -> bd.getBeanClassName() != null)
.map(bd -> ClassUtils.resolveClassName(bd.getBeanClassName(), null))
.collect(toUnmodifiableSet());
}
}
2 changes: 1 addition & 1 deletion src/main/java/run/halo/app/model/entity/Journal.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class Journal extends BaseEntity {
private Long likes;

@Column(name = "type")
@ColumnDefault("1")
@ColumnDefault("0")
private JournalType type;

@Override
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/run/halo/app/model/enums/JournalType.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ public enum JournalType implements ValueEnum<Integer> {
/**
* Public type.
*/
PUBLIC(1),
PUBLIC(0),

/**
* Intimate type.
*/
INTIMATE(0);
INTIMATE(1);

private final int value;

Expand Down
36 changes: 0 additions & 36 deletions src/main/java/run/halo/app/model/enums/PostType.java

This file was deleted.

17 changes: 8 additions & 9 deletions src/main/java/run/halo/app/model/enums/ValueEnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
*/
public interface ValueEnum<T> {

/**
* Gets enum value.
*
* @return enum value
*/
T getValue();

/**
* Converts value to corresponding enum.
*
Expand All @@ -20,7 +27,7 @@ public interface ValueEnum<T> {
* @param <E> enum generic
* @return corresponding enum
*/
static <V, E extends ValueEnum<V>> E valueToEnum(Class<E> enumType, V value) {
static <V, E extends Enum<E> & ValueEnum<V>> E valueToEnum(Class<E> enumType, V value) {
Assert.notNull(enumType, "enum type must not be null");
Assert.notNull(value, "value must not be null");
Assert.isTrue(enumType.isEnum(), "type must be an enum type");
Expand All @@ -30,12 +37,4 @@ static <V, E extends ValueEnum<V>> E valueToEnum(Class<E> enumType, V value) {
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("unknown database value: " + value));
}

/**
* Gets enum value.
*
* @return enum value
*/
T getValue();

}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit 8472a7b

Please sign in to comment.