From b57a78497e2a81a35789f256c7f1075d2f5d7064 Mon Sep 17 00:00:00 2001 From: Stanislav-Zabramnyi Date: Thu, 29 Sep 2022 12:06:19 +0200 Subject: [PATCH 1/3] GP - 160: create a completed solution for Enable-string-trimming exercise --- .../bobocode/StringTrimmingConfiguration.java | 11 +++ .../TrimmedAnnotationBeanPostProcessor.java | 91 ++++++++++++++++++- .../annotation/EnableStringTrimming.java | 11 ++- 3 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/StringTrimmingConfiguration.java diff --git a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/StringTrimmingConfiguration.java b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/StringTrimmingConfiguration.java new file mode 100644 index 0000000..87495de --- /dev/null +++ b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/StringTrimmingConfiguration.java @@ -0,0 +1,11 @@ +package com.bobocode; + +import org.springframework.context.annotation.Bean; + +public class StringTrimmingConfiguration { + + @Bean + public TrimmedAnnotationBeanPostProcessor trimmedAnnotationBeanPostProcessor() { + return new TrimmedAnnotationBeanPostProcessor(); + } +} diff --git a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java index ee1d441..580e2d2 100644 --- a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java +++ b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java @@ -1,9 +1,21 @@ package com.bobocode; import com.bobocode.annotation.Trimmed; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import lombok.SneakyThrows; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cglib.proxy.Callback; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; /** * This is processor class implements {@link BeanPostProcessor}, looks for a beans where method parameters are marked with @@ -11,11 +23,84 @@ * {@link Trimmed}. For example if there is a string " Java " as an input parameter it has to be automatically trimmed to "Java" * if parameter is marked with {@link Trimmed} annotation. *

- * + *

* Note! This bean is not marked as a {@link Component} to avoid automatic scanning, instead it should be created in * {@link StringTrimmingConfiguration} class which can be imported to a {@link Configuration} class by annotation * {@link EnableStringTrimming} */ -public class TrimmedAnnotationBeanPostProcessor { -//todo: Implement TrimmedAnnotationBeanPostProcessor according to javadoc +public class TrimmedAnnotationBeanPostProcessor implements BeanPostProcessor { + + private final Map beansToBeProxied = new HashMap<>(); + private final Map beanInterceptors = new HashMap<>(); + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (containsParametersAnnotatedWithTrimmed(bean)) { + beansToBeProxied.put(beanName, bean); + beanInterceptors.put(beanName, new MethodInterceptor[]{provideTrimmingInterceptor()}); + } + return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + Object toProxy = beansToBeProxied.get(beanName); + if (toProxy != null) { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(toProxy.getClass()); + setInterceptorsIfNeeded(enhancer, bean, beanName); + return enhancer.create(); + } + return bean; + } + + private MethodInterceptor provideTrimmingInterceptor() { + return (beanInstance, method, args, methodProxy) -> + methodProxy.invokeSuper(beanInstance, processParameters(method, args)); + } + + private Object[] processParameters(Method method, Object[] args) { + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].isAnnotationPresent(Trimmed.class)) { + args[i] = trimMarkedString(args[i]); + } + } + return args; + } + + private Object trimMarkedString(Object arg) { + if (arg instanceof String string) { + return StringUtils.hasLength(string) ? string.trim() : arg; + } + return arg; + } + + private boolean containsParametersAnnotatedWithTrimmed(Object bean) { + return Arrays.stream(bean.getClass().getDeclaredMethods()).flatMap(m -> Stream.of(m.getParameters())) + .anyMatch(p -> p.isAnnotationPresent(Trimmed.class)); + } + + private void setInterceptorsIfNeeded(Enhancer enhancer, Object bean, String beanName) { + MethodInterceptor[] methodInterceptors = beanInterceptors.get(beanName); + if (methodInterceptors.length != 0) { + Callback[] interceptors = findInterceptors(bean); + if (interceptors.length != 0) { + MethodInterceptor[] newInterceptors = Arrays.copyOf(methodInterceptors, methodInterceptors.length + 1); + newInterceptors[newInterceptors.length - 1] = provideTrimmingInterceptor(); + enhancer.setCallbacks(newInterceptors); + } + enhancer.setCallbacks(methodInterceptors); + } + } + + @SneakyThrows + private MethodInterceptor[] findInterceptors(Object bean) { + var method = Arrays.stream(bean.getClass().getDeclaredMethods()).filter(m -> m.getName().equals("getCallbacks")) + .findFirst(); + if (method.isPresent()) { + return (MethodInterceptor[]) method.get().invoke(bean); + } + return new MethodInterceptor[0]; + } } diff --git a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/annotation/EnableStringTrimming.java b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/annotation/EnableStringTrimming.java index 8c06d65..e40525a 100644 --- a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/annotation/EnableStringTrimming.java +++ b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/annotation/EnableStringTrimming.java @@ -1,8 +1,17 @@ package com.bobocode.annotation; +import com.bobocode.StringTrimmingConfiguration; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.context.annotation.Import; + /** * Annotation that can be placed on configuration class to import {@link StringTrimmingConfiguration} */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(StringTrimmingConfiguration.class) public @interface EnableStringTrimming { -//todo: Implement EnableStringTrimming annotation according to javadoc } From 60dec7dd707cbfe43983284f94e39dd2691109a1 Mon Sep 17 00:00:00 2001 From: Stanislav-Zabramnyi Date: Thu, 29 Sep 2022 18:12:03 +0200 Subject: [PATCH 2/3] GP - 160: refactoring --- .../com/bobocode/StringTrimmingConfiguration.java | 8 ++++++++ .../TrimmedAnnotationBeanPostProcessor.java | 13 ++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/StringTrimmingConfiguration.java b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/StringTrimmingConfiguration.java index 87495de..1e14972 100644 --- a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/StringTrimmingConfiguration.java +++ b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/StringTrimmingConfiguration.java @@ -1,7 +1,15 @@ package com.bobocode; +import com.bobocode.annotation.EnableStringTrimming; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +/** + * Class to provide a bean of {@link TrimmedAnnotationBeanPostProcessor}. + *

+ * Note! This class is not a {@link Configuration} class itself, but can be imported to a config class by the + * {@link EnableStringTrimming} annotation + */ public class StringTrimmingConfiguration { @Bean diff --git a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java index 580e2d2..69e6408 100644 --- a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java +++ b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java @@ -23,31 +23,30 @@ * {@link Trimmed}. For example if there is a string " Java " as an input parameter it has to be automatically trimmed to "Java" * if parameter is marked with {@link Trimmed} annotation. *

- *

* Note! This bean is not marked as a {@link Component} to avoid automatic scanning, instead it should be created in * {@link StringTrimmingConfiguration} class which can be imported to a {@link Configuration} class by annotation * {@link EnableStringTrimming} */ public class TrimmedAnnotationBeanPostProcessor implements BeanPostProcessor { - private final Map beansToBeProxied = new HashMap<>(); + private final Map> beansToBeProxied = new HashMap<>(); private final Map beanInterceptors = new HashMap<>(); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (containsParametersAnnotatedWithTrimmed(bean)) { - beansToBeProxied.put(beanName, bean); + beansToBeProxied.put(beanName, bean.getClass()); beanInterceptors.put(beanName, new MethodInterceptor[]{provideTrimmingInterceptor()}); } - return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); + return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - Object toProxy = beansToBeProxied.get(beanName); + Class toProxy = beansToBeProxied.get(beanName); if (toProxy != null) { Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(toProxy.getClass()); + enhancer.setSuperclass(toProxy); setInterceptorsIfNeeded(enhancer, bean, beanName); return enhancer.create(); } @@ -71,7 +70,7 @@ private Object[] processParameters(Method method, Object[] args) { private Object trimMarkedString(Object arg) { if (arg instanceof String string) { - return StringUtils.hasLength(string) ? string.trim() : arg; + return string.trim(); } return arg; } From 115395fd9a72ec01bbe224a8342ff1e138dd4c33 Mon Sep 17 00:00:00 2001 From: Stanislav-Zabramnyi Date: Fri, 30 Sep 2022 15:46:12 +0200 Subject: [PATCH 3/3] GP - 160: refactoring --- .../TrimmedAnnotationBeanPostProcessor.java | 34 +++---------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java index 69e6408..4e1c57a 100644 --- a/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java +++ b/3-0-spring-framework/3-3-0-enable-string-trimming/src/main/java/com/bobocode/TrimmedAnnotationBeanPostProcessor.java @@ -7,10 +7,8 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; -import lombok.SneakyThrows; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.context.annotation.Configuration; @@ -23,6 +21,7 @@ * {@link Trimmed}. For example if there is a string " Java " as an input parameter it has to be automatically trimmed to "Java" * if parameter is marked with {@link Trimmed} annotation. *

+ * * Note! This bean is not marked as a {@link Component} to avoid automatic scanning, instead it should be created in * {@link StringTrimmingConfiguration} class which can be imported to a {@link Configuration} class by annotation * {@link EnableStringTrimming} @@ -30,15 +29,13 @@ public class TrimmedAnnotationBeanPostProcessor implements BeanPostProcessor { private final Map> beansToBeProxied = new HashMap<>(); - private final Map beanInterceptors = new HashMap<>(); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (containsParametersAnnotatedWithTrimmed(bean)) { beansToBeProxied.put(beanName, bean.getClass()); - beanInterceptors.put(beanName, new MethodInterceptor[]{provideTrimmingInterceptor()}); } - return bean; + return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); } @Override @@ -47,7 +44,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw if (toProxy != null) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(toProxy); - setInterceptorsIfNeeded(enhancer, bean, beanName); + enhancer.setCallback(provideTrimmingInterceptor()); return enhancer.create(); } return bean; @@ -70,7 +67,7 @@ private Object[] processParameters(Method method, Object[] args) { private Object trimMarkedString(Object arg) { if (arg instanceof String string) { - return string.trim(); + return StringUtils.hasLength(string) ? string.trim() : arg; } return arg; } @@ -79,27 +76,4 @@ private boolean containsParametersAnnotatedWithTrimmed(Object bean) { return Arrays.stream(bean.getClass().getDeclaredMethods()).flatMap(m -> Stream.of(m.getParameters())) .anyMatch(p -> p.isAnnotationPresent(Trimmed.class)); } - - private void setInterceptorsIfNeeded(Enhancer enhancer, Object bean, String beanName) { - MethodInterceptor[] methodInterceptors = beanInterceptors.get(beanName); - if (methodInterceptors.length != 0) { - Callback[] interceptors = findInterceptors(bean); - if (interceptors.length != 0) { - MethodInterceptor[] newInterceptors = Arrays.copyOf(methodInterceptors, methodInterceptors.length + 1); - newInterceptors[newInterceptors.length - 1] = provideTrimmingInterceptor(); - enhancer.setCallbacks(newInterceptors); - } - enhancer.setCallbacks(methodInterceptors); - } - } - - @SneakyThrows - private MethodInterceptor[] findInterceptors(Object bean) { - var method = Arrays.stream(bean.getClass().getDeclaredMethods()).filter(m -> m.getName().equals("getCallbacks")) - .findFirst(); - if (method.isPresent()) { - return (MethodInterceptor[]) method.get().invoke(bean); - } - return new MethodInterceptor[0]; - } }