diff --git a/CHANGELOG.md b/CHANGELOG.md index 78b2c2cca..0dc17064c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,8 @@ * [new] Java 9 compatibility. * [new] Refactored and improved integration testing API. -* [brk] Removed Arquillian support (to its own add-on) in favor of Undertow-based Web integration testing. * [fix] Defer JNDI lookup through `@Resource` annotation until the instance containing the injection is created. - +* [fix] Fix lack of injection in custom ConstraintValidators. # Version 3.4.2 (2018-01-12) diff --git a/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyClassProxy.java b/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyClassProxy.java deleted file mode 100644 index e5d257b4b..000000000 --- a/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyClassProxy.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright © 2013-2018, The SeedStack authors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.seedstack.seed.core.internal.dependency; - -import static org.seedstack.shed.reflect.ReflectUtils.makeAccessible; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import javassist.util.proxy.MethodHandler; -import javassist.util.proxy.ProxyFactory; -import org.seedstack.seed.SeedException; -import org.seedstack.seed.core.internal.CoreErrorCode; - -/** - * Proxy to implement abstract class. Override methods to proxy.
- * For example: to create a proxy for a RatioGauge abstract class - * - *
- * DependencyClassProxy<RatioGauge> ratio = new DependencyClassProxy<RatioGauge>(RatioGauge.class, new
- * ProxyMethodReplacer() {
- *    public Ratio getRatio() {
- *        return Ratio.of(2, 1);
- *    }
- * });
- *
- * 
- * - * @param class to proxy - */ -public class DependencyClassProxy implements MethodHandler { - private final T proxy; - private final Object substitute; - - /** - * Create a proxy replacing methods of a class. - * - * @param clazz the class to be proxied. - * @param substitute the substitute implementation. - */ - @SuppressWarnings("unchecked") - public DependencyClassProxy(Class clazz, final Object substitute) { - this.substitute = substitute; - try { - ProxyFactory factory = new ProxyFactory(); - factory.setSuperclass(clazz); - factory.setFilter(method -> { - for (Method m : substitute.getClass().getDeclaredMethods()) { - if (m.getName().equals(method.getName())) - return true; - } - return false; - }); - this.proxy = (T) factory.create(new Class[0], new Object[0], this); - } catch (Exception e) { - throw SeedException.wrap(e, CoreErrorCode.UNABLE_TO_CREATE_PROXY).put("class", clazz.getName()); - } - } - - public T getProxy() { - return proxy; - } - - @Override - public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { - try { - return makeAccessible( - substitute.getClass().getMethod(thisMethod.getName(), thisMethod.getParameterTypes())).invoke( - substitute, args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } - } -} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyModule.java b/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyModule.java deleted file mode 100644 index 508df7761..000000000 --- a/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyModule.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright © 2013-2018, The SeedStack authors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.seedstack.seed.core.internal.dependency; - -import com.google.inject.AbstractModule; -import com.google.inject.TypeLiteral; -import com.google.inject.util.Types; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import org.seedstack.seed.spi.DependencyProvider; - -class DependencyModule extends AbstractModule { - private final Map, Optional> dependencies; - - DependencyModule(Map, Optional> dependencies) { - this.dependencies = dependencies; - } - - @Override - protected void configure() { - for (final Entry, Optional> dependency : this.dependencies.entrySet()) { - @SuppressWarnings("unchecked") - TypeLiteral> typeLiteral = (TypeLiteral>) TypeLiteral.get( - Types.newParameterizedType(Optional.class, dependency.getKey())); - bind(typeLiteral).toInstance(dependency.getValue()); - } - } -} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyPlugin.java b/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyPlugin.java deleted file mode 100644 index c95601e4f..000000000 --- a/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyPlugin.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright © 2013-2018, The SeedStack authors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.seedstack.seed.core.internal.dependency; - -import io.nuun.kernel.api.plugin.InitState; -import io.nuun.kernel.api.plugin.context.InitContext; -import io.nuun.kernel.api.plugin.request.ClasspathScanRequest; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import org.seedstack.seed.SeedException; -import org.seedstack.seed.core.internal.AbstractSeedPlugin; -import org.seedstack.seed.core.internal.CoreErrorCode; -import org.seedstack.seed.spi.DependencyProvider; -import org.seedstack.shed.reflect.Classes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DependencyPlugin extends AbstractSeedPlugin { - private static final Logger LOGGER = LoggerFactory.getLogger(DependencyPlugin.class); - private final Map, Optional> dependencies = new HashMap<>(); - - @Override - public String name() { - return "dependency"; - } - - @Override - public Collection classpathScanRequests() { - return classpathScanRequestBuilder() - .subtypeOf(DependencyProvider.class) - .build(); - } - - @Override - @SuppressWarnings("unchecked") - protected InitState initialize(InitContext initContext) { - initContext.scannedSubTypesByParentClass().get(DependencyProvider.class) - .stream() - .filter(DependencyProvider.class::isAssignableFrom) - .forEach(candidate -> getDependency((Class) candidate)); - - return InitState.INITIALIZED; - } - - @Override - public Object nativeUnitModule() { - return new DependencyModule(dependencies); - } - - /** - * Return {@link Optional} which contains the provider if dependency is present. - * Always return a {@link Optional} instance. - * - * @param providerClass provider to use an optional dependency - * @return {@link Optional} which contains the provider if dependency is present - */ - @SuppressWarnings("unchecked") - public Optional getDependency(Class providerClass) { - if (!dependencies.containsKey(providerClass)) { - Optional optionalDependency = Optional.empty(); - try { - T provider = providerClass.newInstance(); - if (Classes.optional(provider.getClassToCheck()).isPresent()) { - LOGGER.debug("Found a new optional provider [{}] for [{}]", providerClass.getName(), - provider.getClassToCheck()); - optionalDependency = Optional.of(provider); - } - } catch (InstantiationException | IllegalAccessException e) { - throw SeedException.wrap(e, CoreErrorCode.UNABLE_TO_INSTANTIATE_CLASS).put("class", - providerClass.getCanonicalName()); - } - dependencies.put(providerClass, optionalDependency); - } - return (Optional) dependencies.get(providerClass); - } -} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyProxy.java b/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyProxy.java deleted file mode 100644 index 35e947a6b..000000000 --- a/core/src/main/java/org/seedstack/seed/core/internal/dependency/DependencyProxy.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright © 2013-2018, The SeedStack authors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.seedstack.seed.core.internal.dependency; - -import static org.seedstack.shed.reflect.ReflectUtils.makeAccessible; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import org.seedstack.seed.SeedException; -import org.seedstack.seed.core.internal.CoreErrorCode; - -/** - * Proxy to implement interfaces. Override each method to proxy (method from the interface).
- * For example: to create a proxy for a Gauge interface - * - *
- * DependencyProxy<Gauge<Long>> pgauge = new DependencyProxy<Gauge<Long>>(new Class[]{Gauge
- * .class}, new ProxyMethodReplacer() {
- *   public Long getValue(){
- *      return RandomUtils.nextLong();
- *   }
- * });
- *
- * 
- * - * @param class to proxy - */ -public class DependencyProxy implements InvocationHandler { - - private final T proxy; - private final Object substitute; - - /** - * Create a proxy with all specified interfaces and override methods with the substitute object. - * - * @param interfaces interfaces for the proxy - * @param substitute the method replacer to override method. - */ - @SuppressWarnings("unchecked") - public DependencyProxy(Class[] interfaces, Object substitute) { - try { - this.proxy = (T) Proxy.newProxyInstance(DependencyProxy.class.getClassLoader(), interfaces, this); - } catch (IllegalArgumentException e) { - throw SeedException.wrap(e, CoreErrorCode.UNABLE_TO_CREATE_PROXY).put("class", interfaces[0].getName()); - } - this.substitute = substitute; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Method m; - try { - m = substitute.getClass().getMethod(method.getName(), method.getParameterTypes()); - makeAccessible(m); - } catch (Exception e) { - throw new UnsupportedOperationException(method.toString(), e); - } - try { - return m.invoke(substitute, args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } - } - - public T getProxy() { - return proxy; - } -} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/el/ELContextBuilderImpl.java b/core/src/main/java/org/seedstack/seed/core/internal/el/ELContextBuilderImpl.java index 3fce7b31b..d8aa07697 100644 --- a/core/src/main/java/org/seedstack/seed/core/internal/el/ELContextBuilderImpl.java +++ b/core/src/main/java/org/seedstack/seed/core/internal/el/ELContextBuilderImpl.java @@ -29,17 +29,17 @@ class ELContextBuilderImpl implements ELContextBuilder { private ExpressionFactory expressionFactory; static ELContext createDefaultELContext(ExpressionFactory expressionFactory) { - if (ELPlugin.EL3_OPTIONAL.isPresent()) { + if (ELPlugin.EL_3_CONTEXT_CLASS != null) { try { - return ELPlugin.EL3_OPTIONAL.get().getConstructor(ExpressionFactory.class).newInstance( + return ELPlugin.EL_3_CONTEXT_CLASS.getConstructor(ExpressionFactory.class).newInstance( expressionFactory); } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException | InstantiationException e) { throw new RuntimeException("Unable to instantiate StandardELContext", e); } - } else if (ELPlugin.JUEL_OPTIONAL.isPresent()) { + } else if (ELPlugin.JUEL_CONTEXT_CLASS != null) { try { - return ELPlugin.JUEL_OPTIONAL.get().newInstance(); + return ELPlugin.JUEL_CONTEXT_CLASS.newInstance(); } catch (IllegalAccessException | InstantiationException e) { throw new RuntimeException("Unable to instantiate JUEL SimpleContext", e); } @@ -76,12 +76,12 @@ public ELPropertyProvider withProperty(String name, Object object) { @Override public ELPropertyProvider withFunction(String prefix, String localName, Method method) { checkArgument(!Strings.isNullOrEmpty(localName), "A function local name is required"); - if (ELPlugin.EL3_OPTIONAL.isPresent()) { + if (ELPlugin.isLevel3()) { elContext.getFunctionMapper().mapFunction(prefix, localName, method); - } else if (ELPlugin.JUEL_OPTIONAL.isPresent()) { - if (ELPlugin.JUEL_OPTIONAL.get().isAssignableFrom(elContext.getClass())) { + } else if (ELPlugin.JUEL_CONTEXT_CLASS != null) { + if (ELPlugin.JUEL_CONTEXT_CLASS.isAssignableFrom(elContext.getClass())) { try { - ELPlugin.JUEL_OPTIONAL.get().getMethod("setFunction", String.class, String.class, + ELPlugin.JUEL_CONTEXT_CLASS.getMethod("setFunction", String.class, String.class, Method.class).invoke(elContext, prefix, localName, method); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw SeedException.wrap(e, ExpressionLanguageErrorCode.UNEXPECTED_EXCEPTION); @@ -92,7 +92,7 @@ public ELPropertyProvider withFunction(String prefix, String localName, Method m } } else { throw new UnsupportedOperationException( - "Function mapping is not supported in this environment (EL level 3+ required)"); + "Function mapping is not supported in this environment (EL level 3+ or JUEL required)"); } return this; } diff --git a/core/src/main/java/org/seedstack/seed/core/internal/el/ELErrorCode.java b/core/src/main/java/org/seedstack/seed/core/internal/el/ELErrorCode.java new file mode 100644 index 000000000..0492e9f09 --- /dev/null +++ b/core/src/main/java/org/seedstack/seed/core/internal/el/ELErrorCode.java @@ -0,0 +1,14 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.internal.el; + +import org.seedstack.shed.exception.ErrorCode; + +public enum ELErrorCode implements ErrorCode { + UNABLE_TO_INSTANTIATE_EXPRESSION_FACTORY +} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/el/ELModule.java b/core/src/main/java/org/seedstack/seed/core/internal/el/ELModule.java index 5a3534d4c..8e341a34b 100644 --- a/core/src/main/java/org/seedstack/seed/core/internal/el/ELModule.java +++ b/core/src/main/java/org/seedstack/seed/core/internal/el/ELModule.java @@ -19,18 +19,17 @@ import org.seedstack.seed.el.spi.ELHandler; class ELModule extends AbstractModule { - private static final TypeLiteral, Class>> MAP_TYPE_LITERAL = new - TypeLiteral, Class>>() { - }; + private final ExpressionFactory expressionFactory; private final Map, Class> elMap; - ELModule(Map, Class> elMap) { + ELModule(ExpressionFactory expressionFactory, Map, Class> elMap) { + this.expressionFactory = expressionFactory; this.elMap = elMap; } @Override protected void configure() { - bind(ExpressionFactory.class).toInstance(ExpressionFactory.newInstance()); + bind(ExpressionFactory.class).toInstance(expressionFactory); bind(ELService.class).to(ELServiceInternal.class); bind(ELContextBuilder.class).to(ELContextBuilderImpl.class); @@ -39,6 +38,10 @@ protected void configure() { } // bind the map of annotation -> ELHandler - bind(MAP_TYPE_LITERAL).toInstance(ImmutableMap.copyOf(elMap)); + bind(new AnnotationHandlersTypeLiteral()).toInstance(ImmutableMap.copyOf(elMap)); + } + + private static class AnnotationHandlersTypeLiteral + extends TypeLiteral, Class>> { } } diff --git a/core/src/main/java/org/seedstack/seed/core/internal/el/ELPlugin.java b/core/src/main/java/org/seedstack/seed/core/internal/el/ELPlugin.java index de5b513a8..e61c34f68 100644 --- a/core/src/main/java/org/seedstack/seed/core/internal/el/ELPlugin.java +++ b/core/src/main/java/org/seedstack/seed/core/internal/el/ELPlugin.java @@ -12,11 +12,13 @@ import io.nuun.kernel.api.plugin.context.InitContext; import io.nuun.kernel.api.plugin.request.ClasspathScanRequest; import java.lang.annotation.Annotation; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import javax.el.ELContext; +import javax.el.ExpressionFactory; import net.jodah.typetools.TypeResolver; import org.kametic.specifications.Specification; import org.seedstack.seed.SeedException; @@ -27,13 +29,77 @@ import org.slf4j.LoggerFactory; public class ELPlugin extends AbstractSeedPlugin { - static final Optional> EL3_OPTIONAL = Classes.optional("javax.el.StandardELContext"); - static final Optional> JUEL_OPTIONAL = Classes.optional("de.odysseus.el.util.SimpleContext"); + static final Class EL_3_CONTEXT_CLASS; + static final Class JUEL_CONTEXT_CLASS; + private static final Object EXPRESSION_FACTORY; private static final Logger LOGGER = LoggerFactory.getLogger(ELPlugin.class); - private static final Optional> EL_OPTIONAL = Classes.optional("javax.el.Expression"); private final Specification> specificationELHandlers = classImplements(ELHandler.class); private ELModule elModule; + static { + if (Classes.optional("javax.el.Expression").isPresent()) { + EXPRESSION_FACTORY = buildExpressionFactory(); + EL_3_CONTEXT_CLASS = Classes.optional("javax.el.StandardELContext") + .>map(objectClass -> objectClass.asSubclass(ELContext.class)) + .orElse(null); + JUEL_CONTEXT_CLASS = Classes.optional("de.odysseus.el.util.SimpleContext") + .>map(objectClass -> objectClass.asSubclass(ELContext.class)) + .orElse(null); + } else { + EXPRESSION_FACTORY = null; + EL_3_CONTEXT_CLASS = null; + JUEL_CONTEXT_CLASS = null; + } + } + + public static boolean isEnabled() { + return EXPRESSION_FACTORY != null; + } + + public static boolean isLevel3() { + return isEnabled() && EL_3_CONTEXT_CLASS != null; + } + + public static boolean isFunctionMappingAvailable() { + return isEnabled() && (EL_3_CONTEXT_CLASS != null || JUEL_CONTEXT_CLASS != null); + } + + public static Object getExpressionFactory() { + if (!isEnabled()) { + throw new IllegalStateException("EL expression factory is not available"); + } + return EXPRESSION_FACTORY; + } + + private static Object buildExpressionFactory() { + try { + // EL will use the TCCL to find the implementation + return ExpressionFactory.newInstance(); + } catch (Throwable t1) { + // If TCCL failed, we use the ClassLoader that loaded the ELPlugin + final ClassLoader originalTCCL = run(Thread.currentThread()::getContextClassLoader); + try { + run((PrivilegedAction) () -> { + Thread.currentThread().setContextClassLoader(ELPlugin.class.getClassLoader()); + return null; + }); + return ExpressionFactory.newInstance(); + } catch (Throwable t2) { + throw SeedException.wrap(t2, ELErrorCode.UNABLE_TO_INSTANTIATE_EXPRESSION_FACTORY); + } finally { + // restore original TCCL + run((PrivilegedAction) () -> { + Thread.currentThread().setContextClassLoader(originalTCCL); + return null; + }); + } + } + } + + private static T run(PrivilegedAction action) { + return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run(); + } + @Override public String name() { return "el"; @@ -47,7 +113,7 @@ public Collection classpathScanRequests() { @SuppressWarnings("unchecked") @Override public InitState initialize(InitContext initContext) { - if (EL_OPTIONAL.isPresent()) { + if (isEnabled()) { Map, Class> elMap = new HashMap<>(); // Scan all the ExpressionLanguageHandler @@ -65,11 +131,10 @@ public InitState initialize(InitContext initContext) { .put("annotation", typeParameterClass.getSimpleName()) .put("handler", elHandlerClass); } - elMap.put(typeParameterClass, (Class) elHandlerClass); } - elModule = new ELModule(elMap); + elModule = new ELModule((ExpressionFactory) EXPRESSION_FACTORY, elMap); } else { LOGGER.debug("Java EL is not present in the classpath, EL support disabled"); } @@ -81,16 +146,4 @@ public InitState initialize(InitContext initContext) { public Object nativeUnitModule() { return elModule; } - - public boolean isEnabled() { - return EL_OPTIONAL.isPresent(); - } - - public boolean isLevel3() { - return isEnabled() && EL3_OPTIONAL.isPresent(); - } - - public boolean isFunctionMappingAvailable() { - return isEnabled() && (EL3_OPTIONAL.isPresent() || JUEL_OPTIONAL.isPresent()); - } } diff --git a/core/src/main/java/org/seedstack/seed/core/internal/init/GlobalValidatorFactory.java b/core/src/main/java/org/seedstack/seed/core/internal/init/GlobalValidatorFactory.java index c453f8f51..29c1d679d 100644 --- a/core/src/main/java/org/seedstack/seed/core/internal/init/GlobalValidatorFactory.java +++ b/core/src/main/java/org/seedstack/seed/core/internal/init/GlobalValidatorFactory.java @@ -10,8 +10,8 @@ import javax.validation.Validation; import javax.validation.ValidatorFactory; -import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; import org.hibernate.validator.parameternameprovider.ReflectionParameterNameProvider; +import org.seedstack.seed.core.internal.validation.SeedMessageInterpolator; public class GlobalValidatorFactory { private final ValidatorFactory validatorFactory; @@ -20,7 +20,7 @@ private GlobalValidatorFactory() { validatorFactory = Validation.byDefaultProvider() .configure() .parameterNameProvider(new ReflectionParameterNameProvider()) - .messageInterpolator(new ParameterMessageInterpolator()) + .messageInterpolator(new SeedMessageInterpolator()) .buildValidatorFactory(); } diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/ConstraintValidatorSpecification.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/ConstraintValidatorSpecification.java new file mode 100644 index 000000000..c79aa0973 --- /dev/null +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/ConstraintValidatorSpecification.java @@ -0,0 +1,30 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.seedstack.seed.core.internal.validation; + +import java.lang.reflect.Modifier; +import javax.validation.ConstraintValidator; +import org.kametic.specifications.AbstractSpecification; +import org.seedstack.shed.reflect.ClassPredicates; + +class ConstraintValidatorSpecification extends AbstractSpecification> { + static ConstraintValidatorSpecification INSTANCE = new ConstraintValidatorSpecification(); + + private ConstraintValidatorSpecification() { + // no instantiation allowed + } + + @Override + public boolean isSatisfiedBy(Class candidate) { + return ClassPredicates.classIsAssignableFrom(ConstraintValidator.class) + .and(ClassPredicates.classIsInterface().negate()) + .and(ClassPredicates.classModifierIs(Modifier.ABSTRACT).negate()) + .test(candidate); + } +} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/MethodValidationInterceptor.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/MethodValidationInterceptor.java index 4ab06cce9..14908662f 100644 --- a/core/src/main/java/org/seedstack/seed/core/internal/validation/MethodValidationInterceptor.java +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/MethodValidationInterceptor.java @@ -9,6 +9,7 @@ package org.seedstack.seed.core.internal.validation; import java.util.Set; +import javax.inject.Inject; import javax.validation.ConstraintViolation; import javax.validation.ValidatorFactory; import javax.validation.executable.ExecutableValidator; @@ -17,11 +18,8 @@ import org.seedstack.seed.SeedException; class MethodValidationInterceptor implements MethodInterceptor { - private final ValidatorFactory validatorFactory; - - MethodValidationInterceptor(ValidatorFactory validatorFactory) { - this.validatorFactory = validatorFactory; - } + @Inject + private ValidatorFactory validatorFactory; @Override public Object invoke(MethodInvocation invocation) throws Throwable { diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/SeedConstraintValidatorFactory.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/SeedConstraintValidatorFactory.java new file mode 100644 index 000000000..31856229c --- /dev/null +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/SeedConstraintValidatorFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.seedstack.seed.core.internal.validation; + +import com.google.inject.Injector; +import com.google.inject.ProvisionException; +import javax.inject.Inject; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorFactory; +import org.seedstack.shed.reflect.Classes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class SeedConstraintValidatorFactory implements ConstraintValidatorFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(SeedConstraintValidatorFactory.class); + private final Injector injector; + + @Inject + SeedConstraintValidatorFactory(Injector injector) { + this.injector = injector; + } + + @Override + public > T getInstance(Class key) { + if (key.getName().startsWith("org.hibernate.validator")) { + // Hibernate constraint validators are instantiated directly (no injection possible nor needed) + return Classes.instantiateDefault(key); + } else { + try { + return injector.getInstance(key); + } catch (ProvisionException e) { + LOGGER.warn("Constraint validator {} was not detected by SeedStack and is not injectable", + key.getName()); + return Classes.instantiateDefault(key); + } + } + } + + @Override + public void releaseInstance(ConstraintValidator instance) { + // nothing to do + } +} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/SeedMessageInterpolator.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/SeedMessageInterpolator.java new file mode 100644 index 000000000..1501eb2bf --- /dev/null +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/SeedMessageInterpolator.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.internal.validation; + +import java.util.Locale; +import javax.el.ExpressionFactory; +import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm; +import org.hibernate.validator.internal.engine.messageinterpolation.ParameterTermResolver; +import org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator; +import org.seedstack.seed.core.internal.el.ELPlugin; +import org.slf4j.Logger; + +public class SeedMessageInterpolator extends AbstractMessageInterpolator { + private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(SeedMessageInterpolator.class); + + @Override + public String interpolate(Context context, Locale locale, String term) { + if (InterpolationTerm.isElExpression(term)) { + if (ELPlugin.isEnabled()) { + InterpolationTerm expression = new InterpolationTerm( + term, + locale, + (ExpressionFactory) ELPlugin.getExpressionFactory()); + return expression.interpolate(context); + } else { + LOGGER.warn("Message contains EL expression: {}, which is not available in the runtime environment", + term); + return term; + } + } else { + ParameterTermResolver parameterTermResolver = new ParameterTermResolver(); + return parameterTermResolver.interpolate(context, term); + } + } +} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/StaticValidationProvisionListener.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/StaticValidationProvisionListener.java index 14bfca982..a0fe50c08 100644 --- a/core/src/main/java/org/seedstack/seed/core/internal/validation/StaticValidationProvisionListener.java +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/StaticValidationProvisionListener.java @@ -10,16 +10,13 @@ import com.google.inject.spi.ProvisionListener; import java.util.Set; +import javax.inject.Inject; import javax.validation.ConstraintViolation; import javax.validation.Validator; -import javax.validation.ValidatorFactory; class StaticValidationProvisionListener implements ProvisionListener { - private final Validator validator; - - StaticValidationProvisionListener(ValidatorFactory validatorFactory) { - this.validator = validatorFactory.getValidator(); - } + @Inject + private Validator validator; @Override public void onProvision(ProvisionInvocation provision) { diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidationModule.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidationModule.java index 99afc2263..a74f37ef4 100644 --- a/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidationModule.java +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidationModule.java @@ -10,14 +10,17 @@ import com.google.inject.AbstractModule; import com.google.inject.Binding; -import com.google.inject.Provides; +import com.google.inject.PrivateModule; +import com.google.inject.Scopes; import com.google.inject.matcher.AbstractMatcher; import com.google.inject.matcher.Matcher; import com.google.inject.matcher.Matchers; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Set; import javax.validation.Constraint; +import javax.validation.ConstraintValidator; import javax.validation.Valid; import javax.validation.Validator; import javax.validation.ValidatorFactory; @@ -29,15 +32,34 @@ @ValidationConcern class ValidationModule extends AbstractModule { private static final Logger LOGGER = LoggerFactory.getLogger(ValidationModule.class); - private final ValidatorFactory validatorFactory; + private final ValidatorFactory globalValidatorFactory; + private final Set> constraintValidators; - ValidationModule(ValidatorFactory validatorFactory) { - this.validatorFactory = validatorFactory; + ValidationModule(ValidatorFactory globalValidatorFactory, + Set> constraintValidators) { + this.globalValidatorFactory = globalValidatorFactory; + this.constraintValidators = constraintValidators; } @Override protected void configure() { - bind(ValidatorFactory.class).toInstance(validatorFactory); + install(new PrivateModule() { + @Override + protected void configure() { + // Validator factory + bind(ValidatorFactory.class).toProvider(ValidatorFactoryProvider.class).in(Scopes.SINGLETON); + expose(ValidatorFactory.class); + + // Validator + bind(Validator.class).toProvider(ValidatorProvider.class).in(Scopes.SINGLETON); + expose(Validator.class); + + // Detected constraint validators + constraintValidators.forEach(this::bind); + } + }); + + // Validation on injection / method call enableValidationOnInjectionPoints(); if (isDynamicValidationSupported()) { configureDynamicValidation(); @@ -45,28 +67,27 @@ protected void configure() { } private void enableValidationOnInjectionPoints() { - bindListener(staticValidationMatcher(), new StaticValidationProvisionListener(validatorFactory)); + StaticValidationProvisionListener staticValidationProvisionListener = new StaticValidationProvisionListener(); + requestInjection(staticValidationProvisionListener); + bindListener(staticValidationMatcher(), staticValidationProvisionListener); } private void configureDynamicValidation() { - bindInterceptor(Matchers.any(), dynamicValidationMatcher(), new MethodValidationInterceptor(validatorFactory)); + MethodValidationInterceptor methodValidationInterceptor = new MethodValidationInterceptor(); + requestInjection(methodValidationInterceptor); + bindInterceptor(Matchers.any(), dynamicValidationMatcher(), methodValidationInterceptor); } private boolean isDynamicValidationSupported() { ExecutableValidator executableValidator = null; try { - executableValidator = validatorFactory.getValidator().forExecutables(); + executableValidator = globalValidatorFactory.getValidator().forExecutables(); } catch (Throwable t) { LOGGER.warn("Unable to create the dynamic validator, support for dynamic validation disabled", t); } return executableValidator != null; } - @Provides - Validator provideValidator() { - return validatorFactory.getValidator(); - } - private Matcher> staticValidationMatcher() { return new AbstractMatcher>() { @Override diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidationPlugin.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidationPlugin.java index 57fd682ff..63c5d0693 100644 --- a/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidationPlugin.java +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidationPlugin.java @@ -8,15 +8,26 @@ package org.seedstack.seed.core.internal.validation; +import io.nuun.kernel.api.plugin.InitState; +import io.nuun.kernel.api.plugin.context.InitContext; +import io.nuun.kernel.api.plugin.request.ClasspathScanRequest; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.validation.ConstraintValidator; import javax.validation.ValidatorFactory; import org.seedstack.seed.core.SeedRuntime; import org.seedstack.seed.core.internal.AbstractSeedPlugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This plugin handles validation through jsr303 and jsr349. */ public class ValidationPlugin extends AbstractSeedPlugin { - private ValidatorFactory validatorFactory = null; + private static final Logger LOGGER = LoggerFactory.getLogger(ValidationPlugin.class); + private final Set> constraintValidators = new HashSet<>(); + private ValidatorFactory globalValidatorFactory = null; @Override public String name() { @@ -25,15 +36,32 @@ public String name() { @Override protected void setup(SeedRuntime seedRuntime) { - validatorFactory = seedRuntime.getValidatorFactory(); + globalValidatorFactory = seedRuntime.getValidatorFactory(); } @Override - public Object nativeUnitModule() { - if (validatorFactory != null) { - return new ValidationModule(validatorFactory); - } else { - return null; + public Collection classpathScanRequests() { + return classpathScanRequestBuilder() + .specification(ConstraintValidatorSpecification.INSTANCE) + .build(); + } + + @Override + protected InitState initialize(InitContext initContext) { + Collection> constraintValidatorCandidates = initContext.scannedTypesBySpecification() + .get(ConstraintValidatorSpecification.INSTANCE); + for (Class candidate : constraintValidatorCandidates) { + if (ConstraintValidator.class.isAssignableFrom(candidate)) { + LOGGER.debug("Detected constraint validator {}", candidate.getCanonicalName()); + constraintValidators.add(candidate.asSubclass(ConstraintValidator.class)); + } } + LOGGER.debug("Detected {} constraint validator(s)", constraintValidators.size()); + return InitState.INITIALIZED; + } + + @Override + public Object nativeUnitModule() { + return new ValidationModule(globalValidatorFactory, constraintValidators); } } diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidatorFactoryProvider.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidatorFactoryProvider.java new file mode 100644 index 000000000..99356b28b --- /dev/null +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidatorFactoryProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.seedstack.seed.core.internal.validation; + +import com.google.inject.Injector; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.validation.Validation; +import javax.validation.ValidatorFactory; +import org.hibernate.validator.parameternameprovider.ReflectionParameterNameProvider; + +class ValidatorFactoryProvider implements Provider { + private final Injector injector; + + @Inject + ValidatorFactoryProvider(Injector injector) { + this.injector = injector; + } + + @Override + public ValidatorFactory get() { + return Validation.byDefaultProvider() + .configure() + .parameterNameProvider(new ReflectionParameterNameProvider()) + .messageInterpolator(new SeedMessageInterpolator()) + .constraintValidatorFactory(new SeedConstraintValidatorFactory(injector)) + .buildValidatorFactory(); + } +} diff --git a/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidatorProvider.java b/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidatorProvider.java new file mode 100644 index 000000000..ed197a218 --- /dev/null +++ b/core/src/main/java/org/seedstack/seed/core/internal/validation/ValidatorProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.seedstack.seed.core.internal.validation; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +class ValidatorProvider implements Provider { + private final ValidatorFactory validatorFactory; + + @Inject + ValidatorProvider(ValidatorFactory validatorFactory) { + this.validatorFactory = validatorFactory; + } + + @Override + public Validator get() { + return validatorFactory.getValidator(); + } +} diff --git a/core/src/main/resources/META-INF/services/io.nuun.kernel.api.Plugin b/core/src/main/resources/META-INF/services/io.nuun.kernel.api.Plugin index dabef80b5..e88dd2487 100644 --- a/core/src/main/resources/META-INF/services/io.nuun.kernel.api.Plugin +++ b/core/src/main/resources/META-INF/services/io.nuun.kernel.api.Plugin @@ -10,4 +10,3 @@ org.seedstack.seed.core.internal.crypto.CryptoPlugin org.seedstack.seed.core.internal.cli.CliPlugin org.seedstack.seed.core.internal.el.ELPlugin org.seedstack.seed.core.internal.transaction.TransactionPlugin -org.seedstack.seed.core.internal.dependency.DependencyPlugin diff --git a/core/src/test/java/org/seedstack/seed/core/ValidationIT.java b/core/src/test/java/org/seedstack/seed/core/ValidationIT.java new file mode 100755 index 000000000..b448d236f --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/ValidationIT.java @@ -0,0 +1,113 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.seedstack.seed.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.inject.Injector; +import com.google.inject.ProvisionException; +import javax.inject.Inject; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.seedstack.seed.core.fixtures.validation.CustomPojo; +import org.seedstack.seed.core.fixtures.validation.FieldValidationKO; +import org.seedstack.seed.core.fixtures.validation.FieldValidationOK; +import org.seedstack.seed.core.fixtures.validation.ParamReturnType; +import org.seedstack.seed.core.fixtures.validation.ParamValidation; +import org.seedstack.seed.core.fixtures.validation.Pojo; +import org.seedstack.seed.core.fixtures.validation.WithoutValidation; +import org.seedstack.seed.testing.junit4.SeedITRunner; + +@RunWith(SeedITRunner.class) +public class ValidationIT { + @Inject + private ValidatorFactory validatorFactory; + @Inject + private Validator validator; + @Inject + private ParamValidation paramValidation; + @Inject + private ParamReturnType paramReturnType; + @Inject + private WithoutValidation withoutValidation; + @Inject + private FieldValidationOK fieldValidationOK; + @Inject + private Injector injector; + + @Test + public void injections() { + assertThat(validatorFactory).isNotNull(); + assertThat(validator).isNotNull(); + + assertThat(paramValidation).isNotNull(); + assertThat(fieldValidationOK).isNotNull(); + assertThat(paramReturnType).isNotNull(); + assertThat(withoutValidation).isNotNull(); + } + + @Test + public void fieldValidationsOk() { + assertThat(fieldValidationOK.getParam()).isNotNull(); + } + + @Test(expected = ProvisionException.class) + public void fieldNotNullValidationsAreWellIntercepted() { + injector.getInstance(FieldValidationKO.class); + } + + @Test + public void paramNotNullValidationsOk() { + paramValidation.validateNotNullParam(""); + } + + @Test(expected = ConstraintViolationException.class) + public void paramNotNullValidationsAreWellIntercepted() { + paramValidation.validateNotNullParam(null); + } + + @Test + public void paramValidValidationsOk() { + paramValidation.validateValidParam(new Pojo(Pojo.State.VALID)); + } + + @Test(expected = ConstraintViolationException.class) + public void paramValidValidationsAreWellIntercepted() { + paramValidation.validateValidParam(new Pojo(Pojo.State.INVALID)); + } + + @Test + public void notNullReturnValidationsOk() { + paramReturnType.validateNotNullReturn(""); + } + + @Test(expected = ConstraintViolationException.class) + public void notNullReturnValidationsAreWellIntercepted() { + paramReturnType.validateNotNullReturn(null); + } + + @Test + public void validReturnValidationsOk() { + paramReturnType.validateValidReturn(Pojo.State.VALID); + } + + @Test(expected = ConstraintViolationException.class) + public void validReturnValidationsAreWellIntercepted() { + paramReturnType.validateValidReturn(Pojo.State.INVALID); + } + + @Test + public void custom_validator() { + assertThat(validator.validate(new CustomPojo("abc")).size()).isEqualTo(1); + assertThat(validator.validate(new CustomPojo("ABC")).size()).isEqualTo(0); + } +} diff --git a/specs/src/main/java/org/seedstack/seed/spi/DependencyProvider.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/Bean.java old mode 100644 new mode 100755 similarity index 50% rename from specs/src/main/java/org/seedstack/seed/spi/DependencyProvider.java rename to core/src/test/java/org/seedstack/seed/core/fixtures/validation/Bean.java index d99a4e24f..744df37c5 --- a/specs/src/main/java/org/seedstack/seed/spi/DependencyProvider.java +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/Bean.java @@ -5,18 +5,19 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +package org.seedstack.seed.core.fixtures.validation; -package org.seedstack.seed.spi; +import javax.validation.constraints.Max; -/** - * Interface to use to discover optional dependencies. - */ -public interface DependencyProvider { +public class Bean { + private int hour; + + @Max(24) + public int getHour() { + return hour; + } - /** - * Return the class to check in the classpath. - * - * @return the class to check in the classpath. - */ - String getClassToCheck(); + public void setHour(int hour) { + this.hour = hour; + } } diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CaseMode.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CaseMode.java new file mode 100644 index 000000000..3638878be --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CaseMode.java @@ -0,0 +1,13 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +public enum CaseMode { + UPPER, + LOWER; +} \ No newline at end of file diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CheckCase.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CheckCase.java new file mode 100644 index 000000000..b7354fcbc --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CheckCase.java @@ -0,0 +1,43 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) +@Retention(RUNTIME) +@Constraint(validatedBy = CheckCaseValidator.class) +@Documented +public @interface CheckCase { + + String message() default "{org.hibernate.validator.referenceguide.chapter06.CheckCase." + + "message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + CaseMode value(); + + @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) + @Retention(RUNTIME) + @Documented + @interface List { + CheckCase[] value(); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CheckCaseValidator.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CheckCaseValidator.java new file mode 100644 index 000000000..877a9920d --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CheckCaseValidator.java @@ -0,0 +1,47 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import javax.inject.Inject; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import org.seedstack.seed.Application; + +public class CheckCaseValidator implements ConstraintValidator { + private final Application application; + private CaseMode caseMode; + + @Inject + public CheckCaseValidator(Application application) { + this.application = application; + } + + @Override + public void initialize(CheckCase constraintAnnotation) { + this.caseMode = constraintAnnotation.value(); + if (application == null) { + throw new IllegalStateException("Custom constraint validator is not injectable"); + } + } + + @Override + public boolean isValid(String object, ConstraintValidatorContext constraintContext) { + if (application == null) { + throw new IllegalStateException("Custom constraint validator is not injectable"); + } + if (object == null) { + return true; + } + + if (caseMode == CaseMode.UPPER) { + return object.equals(object.toUpperCase()); + } else { + return object.equals(object.toLowerCase()); + } + } +} \ No newline at end of file diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CustomPojo.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CustomPojo.java new file mode 100755 index 000000000..f22d0ab8e --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/CustomPojo.java @@ -0,0 +1,17 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +public class CustomPojo { + @CheckCase(CaseMode.UPPER) + private final String item; + + public CustomPojo(String item) { + this.item = item; + } +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/FieldValidationKO.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/FieldValidationKO.java new file mode 100755 index 000000000..5def53c79 --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/FieldValidationKO.java @@ -0,0 +1,21 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import javax.validation.constraints.NotNull; +import org.seedstack.seed.Bind; + +@Bind +public class FieldValidationKO { + @NotNull + private Object param; + + public void doSomethingAwesome(Object param) { + this.param = param; + } +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/FieldValidationOK.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/FieldValidationOK.java new file mode 100755 index 000000000..9a53fb2e0 --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/FieldValidationOK.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.seedstack.seed.core.fixtures.validation; + +import javax.validation.constraints.NotNull; +import org.seedstack.seed.Bind; + +@Bind +public class FieldValidationOK { + @NotNull + private Object param = new Object(); + + public Object getParam() { + return param; + } +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/MyImpl.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/MyImpl.java new file mode 100755 index 000000000..45ca4f845 --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/MyImpl.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +public class MyImpl implements MyInterface { + private int color; + + @Override + public int getColor() { + return color; + } + + @Override + public void setColor(int color) { + this.color = color; + } +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/MyInterface.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/MyInterface.java new file mode 100755 index 000000000..3daff7d50 --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/MyInterface.java @@ -0,0 +1,17 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import javax.validation.constraints.Min; + +public interface MyInterface { + @Min(1) + int getColor(); + + void setColor(int color); +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/ParamReturnType.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/ParamReturnType.java new file mode 100755 index 000000000..4eecc8718 --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/ParamReturnType.java @@ -0,0 +1,25 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import org.seedstack.seed.Bind; + +@Bind +public class ParamReturnType { + @NotNull + public Object validateNotNullReturn(Object param) { + return param; + } + + @Valid + public Pojo validateValidReturn(Pojo.State state) { + return new Pojo(state); + } +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/ParamValidation.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/ParamValidation.java new file mode 100755 index 000000000..875642ef6 --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/ParamValidation.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import org.seedstack.seed.Bind; + +@Bind +public class ParamValidation { + + public void validateNotNullParam(@NotNull Object param) { + } + + public void validateValidParam(@Valid Pojo param) { + } +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/Pojo.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/Pojo.java new file mode 100755 index 000000000..7fa8e137c --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/Pojo.java @@ -0,0 +1,52 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import javax.validation.constraints.AssertFalse; +import javax.validation.constraints.Max; +import javax.validation.constraints.Size; + +public class Pojo { + @Size(min = 5) + private String name = "yoda"; + + @Max(100) + private int age = 10000; + + @AssertFalse + private boolean dead = true; + + public Pojo(State state) { + if (State.INVALID.equals(state)) { + createInvalidPojo(); + } else { + createValidPojo(); + } + } + + private void createValidPojo() { + name = "yodaa"; + age = 99; + dead = false; + } + + private void createInvalidPojo() { + name = "yoda"; + age = 10000; + dead = true; + } + + public String getName() { + return this.name; + } + + public enum State { + VALID, + INVALID + } +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/PojoWithDeepValidation.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/PojoWithDeepValidation.java new file mode 100755 index 000000000..e6a2c1fa0 --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/PojoWithDeepValidation.java @@ -0,0 +1,19 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import javax.validation.Valid; +import javax.validation.constraints.Null; + +public class PojoWithDeepValidation { + @Null + String str = "should be null"; + + @Valid + private Pojo pojo = new Pojo(Pojo.State.INVALID); +} diff --git a/core/src/test/java/org/seedstack/seed/core/fixtures/validation/WithoutValidation.java b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/WithoutValidation.java new file mode 100755 index 000000000..deb39cb42 --- /dev/null +++ b/core/src/test/java/org/seedstack/seed/core/fixtures/validation/WithoutValidation.java @@ -0,0 +1,16 @@ +/* + * Copyright © 2013-2018, The SeedStack authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.seedstack.seed.core.fixtures.validation; + +import org.seedstack.seed.Bind; + +@Bind +public class WithoutValidation { + public void doSomethingAweswome(Object param) { + } +} diff --git a/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyClassProxyTest.java b/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyClassProxyTest.java deleted file mode 100644 index cb6ca2294..000000000 --- a/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyClassProxyTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright © 2013-2018, The SeedStack authors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.seedstack.seed.core.internal.dependency; - -import org.assertj.core.api.Assertions; -import org.junit.Test; -import org.seedstack.seed.SeedException; - -public class DependencyClassProxyTest { - @Test - public void testInvoke() { - final int result = 10; - AbstractDummyProxy proxy = new DependencyClassProxy<>(AbstractDummyProxy.class, new Object() { - public int getResult() { - return result; - } - }).getProxy(); - Assertions.assertThat(proxy.getResult()).isEqualTo(result); - } - - @Test - public void testInvokeWithException() { - final String errorMessage = "dummy exception"; - AbstractDummyProxy proxy = new DependencyClassProxy<>(AbstractDummyProxy.class, new Object() { - public int getResult() { - throw new RuntimeException(errorMessage); - } - }).getProxy(); - try { - proxy.getResult(); - } catch (Exception e) { - Assertions.assertThat(e).isInstanceOf(RuntimeException.class); - Assertions.assertThat(e.getMessage()).isEqualTo(errorMessage); - } - } - - @Test(expected = SeedException.class) - public void testCreationError() { - new DependencyClassProxy<>(AbstractDummyProxyError.class, new Object() { - }); - } - - static abstract class AbstractDummyProxy { - protected abstract int getResult(); - } - - abstract class AbstractDummyProxyError { - } -} diff --git a/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyModuleTest.java b/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyModuleTest.java deleted file mode 100644 index c747ec824..000000000 --- a/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyModuleTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright © 2013-2018, The SeedStack authors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.seedstack.seed.core.internal.dependency; - -import com.google.inject.Binder; -import com.google.inject.TypeLiteral; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import mockit.Mocked; -import mockit.Verifications; -import mockit.integration.junit4.JMockit; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.seedstack.seed.spi.DependencyProvider; - -@RunWith(JMockit.class) -public class DependencyModuleTest { - @Test - public void testConfigure(@Mocked final Binder binder, @Mocked final DependencyProvider myProvider) { - Map, Optional> optionalDependencies = new HashMap<>(); - final Optional maybe = Optional.of(myProvider); - optionalDependencies.put(DependencyProvider.class, maybe); - - DependencyModule module = new DependencyModule(optionalDependencies); - module.configure(binder); - - new Verifications() { - { - binder.bind(new TypeLiteral>() { - }).toInstance(maybe); - } - }; - } -} diff --git a/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyPluginTest.java b/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyPluginTest.java deleted file mode 100644 index 76c211746..000000000 --- a/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyPluginTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright © 2013-2018, The SeedStack authors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.seedstack.seed.core.internal.dependency; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import io.nuun.kernel.api.plugin.context.InitContext; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import mockit.Expectations; -import mockit.Mocked; -import mockit.integration.junit4.JMockit; -import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.seedstack.coffig.Coffig; -import org.seedstack.seed.Application; -import org.seedstack.seed.SeedException; -import org.seedstack.seed.spi.ApplicationProvider; -import org.seedstack.seed.spi.DependencyProvider; - -@RunWith(JMockit.class) -public class DependencyPluginTest { - private DependencyPlugin dependencyPlugin; - - @Before - public void before() { - dependencyPlugin = new DependencyPlugin(); - } - - @Test - public void verify_nativeUnitModule_instance() { - Object object = dependencyPlugin.nativeUnitModule(); - Assertions.assertThat(object).isInstanceOf(DependencyModule.class); - } - - @Test - public void nameTest() { - Assertions.assertThat(dependencyPlugin.name()).isNotNull(); - } - - private InitContext mockInitContextForCore(Class dependencyClass) { - InitContext initContext = mock(InitContext.class); - Application application = mock(Application.class); - - Map, Collection>> scannedSubTypesByParentClass = new HashMap<>(); - Collection> providerClasses = new ArrayList<>(); - if (dependencyClass != null) { - providerClasses.add(dependencyClass); - } - scannedSubTypesByParentClass.put(DependencyProvider.class, providerClasses); - - when(application.getConfiguration()).thenReturn(Coffig.builder().build()); - when(initContext.dependency(ApplicationProvider.class)).thenReturn(() -> application); - when(initContext.scannedSubTypesByParentClass()).thenReturn(scannedSubTypesByParentClass); - - return initContext; - } - - @Test - public void checkOptionalDependency(@Mocked final DependencyProvider myProvider) { - new Expectations() { - { - myProvider.getClassToCheck(); - result = "java.lang.String"; - } - }; - InitContext initContext = mockInitContextForCore(myProvider.getClass()); - dependencyPlugin.init(initContext); - Optional optionalDependency = dependencyPlugin.getDependency(myProvider.getClass()); - Assertions.assertThat(optionalDependency).isNotNull(); - Assertions.assertThat(optionalDependency.isPresent()).isTrue(); - } - - @Test - public void checkOptionalDependencyNOK(@Mocked final DependencyProvider myProvider) { - new Expectations() { - { - myProvider.getClassToCheck(); - result = "xxxxx"; - } - }; - InitContext initContext = mockInitContextForCore(myProvider.getClass()); - dependencyPlugin.init(initContext); - Optional optionalDependency = dependencyPlugin.getDependency(myProvider.getClass()); - Assertions.assertThat(optionalDependency).isNotNull(); - Assertions.assertThat(optionalDependency.isPresent()).isFalse(); - } - - @Test(expected = SeedException.class) - public void checkOptionalDependencyWithInstantiationError() { - InitContext initContext = mockInitContextForCore(DependencyProvider.class); - dependencyPlugin.init(initContext); - } - -} diff --git a/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyProxyTest.java b/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyProxyTest.java deleted file mode 100644 index 61ec296a0..000000000 --- a/core/src/test/java/org/seedstack/seed/core/internal/dependency/DependencyProxyTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright © 2013-2018, The SeedStack authors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.seedstack.seed.core.internal.dependency; - -import org.assertj.core.api.Assertions; -import org.junit.Test; -import org.seedstack.seed.SeedException; -import org.seedstack.seed.spi.DependencyProvider; - -public class DependencyProxyTest { - private static final String PROXY_METHOD = "proxy method"; - - @Test - public void testInvoke() { - DependencyProvider provider = new DependencyProxy(new Class[]{DependencyProvider.class}, - new Object() { - @SuppressWarnings("unused") - public String getClassToCheck() { - return PROXY_METHOD; - } - }).getProxy(); - Assertions.assertThat(provider.getClassToCheck()).isEqualTo(PROXY_METHOD); - } - - @Test(expected = SeedException.class) - public void testCreationError() { - new DependencyProxy(new Class[]{String.class}, new Object() { - }); - } - - @Test(expected = UnsupportedOperationException.class) - public void testInvokeWithoutMethod() { - DependencyProvider provider = new DependencyProxy(new Class[]{DependencyProvider.class}, - new Object() { - }).getProxy(); - provider.getClassToCheck(); - } - - @Test - public void testInvokeWithoutInvocationError() { - final String errorMessage = "Dummy exception"; - DependencyProvider provider = new DependencyProxy(new Class[]{DependencyProvider.class}, - new Object() { - @SuppressWarnings("unused") - public String getClassToCheck() { - throw new RuntimeException(errorMessage); - } - }).getProxy(); - try { - provider.getClassToCheck(); - } catch (Exception e) { - Assertions.assertThat(e).isInstanceOf(RuntimeException.class); - Assertions.assertThat(e.getMessage()).isEqualTo(errorMessage); - } - } - -} diff --git a/pom.xml b/pom.xml index 26187b3bd..f115830c0 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,8 @@ org.seedstack.seed.rest.hal.Link org.seedstack.seed.undertow.UndertowConfig org.seedstack.seed.transaction.spi.TransactionMetadata + org.seedstack.seed.transaction.spi.TransactionMetadata + org.seedstack.seed.spi.DependencyProvider diff --git a/security/core/src/test/java/org/seedstack/seed/security/internal/SecurityProviderTest.java b/security/core/src/test/java/org/seedstack/seed/security/internal/SecurityProviderTest.java index ee2950a48..fc78ce0ba 100644 --- a/security/core/src/test/java/org/seedstack/seed/security/internal/SecurityProviderTest.java +++ b/security/core/src/test/java/org/seedstack/seed/security/internal/SecurityProviderTest.java @@ -28,8 +28,7 @@ import org.seedstack.seed.spi.ApplicationProvider; public class SecurityProviderTest { - - SecurityPlugin underTest; + private SecurityPlugin underTest; @Before public void before() { @@ -61,14 +60,13 @@ InitContext buildCoherentInitContext() { when(initContext.scannedSubTypesByAncestorClass()).thenReturn(types); ApplicationProvider applicationProvider = mock(ApplicationProvider.class); - ELPlugin elPlugin = mock(ELPlugin.class); - when(elPlugin.isEnabled()).thenReturn(true); SecurityConfig securityConfig = mock(SecurityConfig.class); when(initContext.dependency(ApplicationProvider.class)).thenReturn(applicationProvider); Coffig coffig = mock(Coffig.class); when(coffig.get(SecurityConfig.class)).thenReturn(securityConfig); when(initContext.dependency(ApplicationProvider.class)).thenReturn(() -> application); - when(initContext.dependency(ELPlugin.class)).thenReturn(elPlugin); + // Not pretty because we use real ELPlugin to avoid mocking static methods + when(initContext.dependency(ELPlugin.class)).thenReturn(new ELPlugin()); return initContext; }