blob: 0b4c40d76804e3fb3cfbf252162cc8697bf651ed [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openejb.assembler.classic;
import org.apache.openejb.bval.ValidatorUtil;
import org.apache.openejb.jee.bval.DefaultValidatedExecutableTypesType;
import org.apache.openejb.jee.bval.ExecutableValidationType;
import org.apache.openejb.jee.bval.PropertyType;
import org.apache.openejb.jee.bval.ValidationConfigType;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.util.AppFinder;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.container.BeanManagerImpl;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.naming.NamingException;
import javax.validation.BootstrapConfiguration;
import javax.validation.ClockProvider;
import javax.validation.Configuration;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.MessageInterpolator;
import javax.validation.ParameterNameProvider;
import javax.validation.TraversableResolver;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.ValidatorContext;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableType;
import javax.validation.valueextraction.ValueExtractor;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public final class ValidatorBuilder {
public static final Logger logger = Logger.getInstance(LogCategory.OPENEJB_STARTUP, ValidatorBuilder.class);
public static final String VALIDATION_PROVIDER_KEY = "openejb.bean-validation.provider";
private ValidatorBuilder() {
// no-op
}
public static ValidatorFactory buildFactory(final ClassLoader classLoader, final ValidationInfo info) {
// now we will not be polluted by log build
return buildFactory(info, classLoader);
}
public static ValidationInfo getInfo(final ValidationConfigType config) {
final ValidationInfo info = new ValidationInfo();
if (config != null) {
info.version = config.getVersion();
info.providerClassName = config.getDefaultProvider();
info.constraintFactoryClass = config.getConstraintValidatorFactory();
info.traversableResolverClass = config.getTraversableResolver();
info.messageInterpolatorClass = config.getMessageInterpolator();
info.parameterNameProviderClass = config.getParameterNameProvider();
info.valueExtractorClassNames = config.getValueExtractor();
info.clockProviderClassName = config.getClockProvider();
final ExecutableValidationType executableValidation = config.getExecutableValidation();
if (executableValidation != null) {
info.executableValidationEnabled = executableValidation.getEnabled();
final DefaultValidatedExecutableTypesType executableTypes = executableValidation.getDefaultValidatedExecutableTypes();
if (executableTypes != null) {
for (final ExecutableType type : executableTypes.getExecutableType()) {
info.validatedTypes.add(type.name());
}
}
}
for (final PropertyType p : config.getProperty()) {
info.propertyTypes.put(p.getName(), p.getValue());
}
for (final String element : config.getConstraintMapping()) {
info.constraintMappings.add(element);
}
}
return info;
}
public static ValidatorFactory buildFactory(final ValidationInfo config, final ClassLoader classLoader) {
ValidatorFactory factory = null;
final Thread thread = Thread.currentThread();
final ClassLoader oldContextLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(classLoader);
if (config == null) {
factory = Validation.buildDefaultValidatorFactory();
} else {
final Configuration<?> configuration = getConfig(config);
try {
factory = configuration.buildValidatorFactory();
} catch (final ValidationException ve) {
thread.setContextClassLoader(ValidatorBuilder.class.getClassLoader());
factory = Validation.buildDefaultValidatorFactory();
thread.setContextClassLoader(classLoader);
logger.warning("Unable create validator factory with config " + config
+ " (" + ve.getMessage() + ")."
+ " Default factory will be used.");
}
}
} finally {
thread.setContextClassLoader(oldContextLoader);
}
return factory;
}
@SuppressWarnings("unchecked")
private static Configuration<?> getConfig(final ValidationInfo info) {
Configuration<?> target = null;
final Thread thread = Thread.currentThread();
final ClassLoader classLoader = thread.getContextClassLoader();
String providerClassName = info.providerClassName;
if (providerClassName == null) {
providerClassName = SystemInstance.get().getOptions().get(VALIDATION_PROVIDER_KEY, (String) null);
}
if (providerClassName != null) {
try {
@SuppressWarnings({"unchecked", "rawtypes"}) final
Class clazz = classLoader.loadClass(providerClassName);
target = Validation.byProvider(clazz).configure();
logger.info("Using " + providerClassName + " as validation provider.");
} catch (final ClassNotFoundException e) {
logger.warning("Unable to load provider class " + providerClassName, e);
} catch (final ValidationException ve) {
logger.warning("Unable create validator factory with provider " + providerClassName
+ " (" + ve.getMessage() + ")."
+ " Default one will be used.");
}
}
if (target == null) {
// force to use container provider to ignore any conflicting configuration
thread.setContextClassLoader(ValidatorBuilder.class.getClassLoader());
target = Validation.byDefaultProvider().configure();
thread.setContextClassLoader(classLoader);
}
final Set<ExecutableType> types = new HashSet<>();
for (final String type : info.validatedTypes) {
types.add(ExecutableType.valueOf(type));
}
final Map<String, String> props = new HashMap<>();
for (final Map.Entry<Object, Object> entry : info.propertyTypes.entrySet()) {
final PropertyType property = new PropertyType();
property.setName((String) entry.getKey());
property.setValue((String) entry.getValue());
props.put(property.getName(), property.getValue());
if (logger.isDebugEnabled()) {
logger.debug("Found property '" + property.getName() + "' with value '" + property.getValue());
}
target.addProperty(property.getName(), property.getValue());
}
final OpenEjbBootstrapConfig bootstrapConfig = new OpenEjbBootstrapConfig(
providerClassName, info.constraintFactoryClass, info.messageInterpolatorClass, info.traversableResolverClass,
info.parameterNameProviderClass, new LinkedHashSet<>(info.constraintMappings), info.executableValidationEnabled,
types, props, info.clockProviderClassName,
info.valueExtractorClassNames == null ? null : new LinkedHashSet<>(info.valueExtractorClassNames));
final OpenEjbConfig config = new OpenEjbConfig(bootstrapConfig, target);
target.ignoreXmlConfiguration();
final String messageInterpolatorClass = info.messageInterpolatorClass;
if (messageInterpolatorClass != null) {
try {
@SuppressWarnings("unchecked") final
Class<MessageInterpolator> clazz = (Class<MessageInterpolator>) classLoader.loadClass(messageInterpolatorClass);
target.messageInterpolator(newInstance(config, clazz));
logger.info("Using " + messageInterpolatorClass + " as message interpolator.");
} catch (final Exception e) {
logger.warning("Unable to set " + messageInterpolatorClass + " as message interpolator.", e);
}
}
final String traversableResolverClass = info.traversableResolverClass;
if (traversableResolverClass != null) {
try {
@SuppressWarnings("unchecked") final
Class<TraversableResolver> clazz = (Class<TraversableResolver>) classLoader.loadClass(traversableResolverClass);
target.traversableResolver(newInstance(config, clazz));
logger.info("Using " + traversableResolverClass + " as traversable resolver.");
} catch (final Exception e) {
logger.warning("Unable to set " + traversableResolverClass + " as traversable resolver.", e);
}
}
final String constraintFactoryClass = info.constraintFactoryClass;
if (constraintFactoryClass != null) {
try {
@SuppressWarnings("unchecked") final
Class<ConstraintValidatorFactory> clazz = (Class<ConstraintValidatorFactory>) classLoader.loadClass(constraintFactoryClass);
target.constraintValidatorFactory(newInstance(config, clazz));
} catch (final Exception e) {
logger.warning("Unable to set " + constraintFactoryClass + " as constraint factory.", e);
}
logger.info("Using " + constraintFactoryClass + " as constraint factory.");
}
for (final String mappingFileName : info.constraintMappings) {
if (logger.isDebugEnabled()) {
logger.debug("Opening input stream for " + mappingFileName);
}
final InputStream in = classLoader.getResourceAsStream(mappingFileName);
if (in == null) {
logger.warning("Unable to open input stream for mapping file " + mappingFileName + ". It will be ignored");
} else {
target.addMapping(in);
}
}
if (info.parameterNameProviderClass != null) {
try {
final Class<ParameterNameProvider> clazz = (Class<ParameterNameProvider>) classLoader.loadClass(info.parameterNameProviderClass);
target.parameterNameProvider(newInstance(config, clazz));
logger.info("Using " + info.parameterNameProviderClass + " as parameter name provider.");
} catch (final Exception e) {
logger.warning("Unable to set " + info.parameterNameProviderClass + " as parameter name provider.", e);
}
}
if (info.valueExtractorClassNames != null) {
try {
info.valueExtractorClassNames.stream()
.map(it -> {
try {
return (ValueExtractor<?>) newInstance(config, classLoader.loadClass(it));
} catch (final Exception e) {
logger.warning("Unable to load " + it, e);
return null;
}
})
.filter(Objects::nonNull)
.forEach(target::addValueExtractor);
logger.info("Using " + info.valueExtractorClassNames + " value extractors.");
} catch (final Exception e) {
logger.warning("Unable to set " + info.valueExtractorClassNames + " as parameter name provider.", e);
}
}
if (info.clockProviderClassName != null) {
try {
final Class<ClockProvider> clazz = (Class<ClockProvider>) classLoader.loadClass(info.clockProviderClassName);
target.clockProvider(newInstance(config, clazz));
logger.info("Using " + info.clockProviderClassName + " as clock provider.");
} catch (final Exception e) {
logger.warning("Unable to set " + info.clockProviderClassName + " as clock provider.", e);
}
}
return config;
}
private static <T> T newInstance(final OpenEjbConfig config, final Class<T> clazz) throws Exception {
final WebBeansContext webBeansContext = AppFinder.findAppContextOrWeb(
Thread.currentThread().getContextClassLoader(), AppFinder.WebBeansContextTransformer.INSTANCE);
if (webBeansContext == null) {
return clazz.newInstance();
}
final BeanManagerImpl beanManager = webBeansContext.getBeanManagerImpl();
if (!beanManager.isInUse()) {
return clazz.newInstance();
}
final AnnotatedType<T> annotatedType = beanManager.createAnnotatedType(clazz);
final InjectionTarget<T> it = beanManager.createInjectionTarget(annotatedType);
final CreationalContext<T> context = beanManager.createCreationalContext(null);
final T instance = it.produce(context);
it.inject(instance, context);
it.postConstruct(instance);
config.releasables.add(new Releasable<T>(context, it, instance));
return instance;
}
private static final class OpenEjbBootstrapConfig implements BootstrapConfiguration, Serializable {
private final String providerClassName;
private final String constraintFactoryClass;
private final String messageInterpolatorClass;
private final String traversableResolverClass;
private final String parameterNameProviderClass;
private final Set<String> constraintMappings;
private final boolean executableValidationEnabled;
private final Set<ExecutableType> validatedTypes;
private final Map<String, String> props;
private final String clockProviderClassName;
private final Set<String> valueExtractorClassNames;
public OpenEjbBootstrapConfig(final String providerClassName,
final String constraintFactoryClass,
final String messageInterpolatorClass,
final String traversableResolverClass,
final String parameterNameProviderClass,
final Set<String> constraintMappings,
final boolean executableValidationEnabled,
final Set<ExecutableType> validatedTypes,
final Map<String, String> props,
final String clockProviderClassName,
final Set<String> valueExtractorClassNames) {
this.providerClassName = providerClassName;
this.constraintFactoryClass = constraintFactoryClass;
this.messageInterpolatorClass = messageInterpolatorClass;
this.traversableResolverClass = traversableResolverClass;
this.parameterNameProviderClass = parameterNameProviderClass;
this.constraintMappings = constraintMappings;
this.executableValidationEnabled = executableValidationEnabled;
this.validatedTypes = validatedTypes;
this.props = props;
this.clockProviderClassName = clockProviderClassName;
this.valueExtractorClassNames = valueExtractorClassNames;
}
@Override
public String getDefaultProviderClassName() {
return providerClassName;
}
@Override
public String getConstraintValidatorFactoryClassName() {
return constraintFactoryClass;
}
@Override
public String getMessageInterpolatorClassName() {
return messageInterpolatorClass;
}
@Override
public String getTraversableResolverClassName() {
return traversableResolverClass;
}
@Override
public String getParameterNameProviderClassName() {
return parameterNameProviderClass;
}
@Override
public Set<String> getConstraintMappingResourcePaths() {
return constraintMappings;
}
@Override
public boolean isExecutableValidationEnabled() {
return executableValidationEnabled;
}
@Override
public Set<ExecutableType> getDefaultValidatedExecutableTypes() {
return validatedTypes;
}
@Override
public Map<String, String> getProperties() {
return props;
}
@Override
public String getClockProviderClassName()
{
return clockProviderClassName;
}
@Override
public Set<String> getValueExtractorClassNames()
{
return valueExtractorClassNames;
}
}
private static final class OpenEjbConfig<T extends Configuration<T>> implements Configuration<T> {
private final Collection<Releasable<?>> releasables = new LinkedList<>();
private final Configuration<T> delegate;
private final BootstrapConfiguration bootstrap;
public OpenEjbConfig(final BootstrapConfiguration bootstrapConfig, final Configuration<T> target) {
bootstrap = bootstrapConfig;
delegate = target;
}
@Override
public T ignoreXmlConfiguration() {
return delegate.ignoreXmlConfiguration();
}
@Override
public T messageInterpolator(final MessageInterpolator interpolator) {
return delegate.messageInterpolator(interpolator);
}
@Override
public T traversableResolver(final TraversableResolver resolver) {
return delegate.traversableResolver(resolver);
}
@Override
public T constraintValidatorFactory(final ConstraintValidatorFactory constraintValidatorFactory) {
return delegate.constraintValidatorFactory(constraintValidatorFactory);
}
@Override
public T addMapping(final InputStream stream) {
return delegate.addMapping(stream);
}
@Override
public T addProperty(final String name, final String value) {
return delegate.addProperty(name, value);
}
@Override
public MessageInterpolator getDefaultMessageInterpolator() {
return delegate.getDefaultMessageInterpolator();
}
@Override
public TraversableResolver getDefaultTraversableResolver() {
return delegate.getDefaultTraversableResolver();
}
@Override
public ConstraintValidatorFactory getDefaultConstraintValidatorFactory() {
return delegate.getDefaultConstraintValidatorFactory();
}
@Override
public ValidatorFactory buildValidatorFactory() {
return new OpenEJBValidatorFactory(delegate.buildValidatorFactory(), releasables);
}
@Override
public T parameterNameProvider(ParameterNameProvider parameterNameProvider) {
return delegate.parameterNameProvider(parameterNameProvider);
}
@Override
public ParameterNameProvider getDefaultParameterNameProvider() {
return delegate.getDefaultParameterNameProvider();
}
@Override
public BootstrapConfiguration getBootstrapConfiguration() {
return bootstrap;
}
@Override
public T clockProvider(ClockProvider clockProvider)
{
return delegate.clockProvider(clockProvider);
}
@Override
public T addValueExtractor(ValueExtractor<?> valueExtractor)
{
return delegate.addValueExtractor(valueExtractor);
}
@Override
public ClockProvider getDefaultClockProvider()
{
return delegate.getDefaultClockProvider();
}
}
private static final class OpenEJBValidatorFactory implements ValidatorFactory, Serializable {
private transient ValidatorFactory delegate;
private transient Collection<Releasable<?>> toRelease;
public OpenEJBValidatorFactory(final ValidatorFactory validatorFactory, final Collection<Releasable<?>> releasables) {
delegate = validatorFactory;
toRelease = releasables;
}
private ValidatorFactory delegate() {
if (delegate != null) {
return delegate;
}
try {
return ValidatorUtil.lookupFactory();
} catch (final NamingException e) {
return Validation.buildDefaultValidatorFactory();
}
}
@Override
public Validator getValidator() {
return delegate().getValidator();
}
@Override
public ValidatorContext usingContext() {
return delegate().usingContext();
}
@Override
public MessageInterpolator getMessageInterpolator() {
return delegate().getMessageInterpolator();
}
@Override
public TraversableResolver getTraversableResolver() {
return delegate().getTraversableResolver();
}
@Override
public ConstraintValidatorFactory getConstraintValidatorFactory() {
return delegate().getConstraintValidatorFactory();
}
@Override
public <T> T unwrap(final Class<T> type) {
return delegate().unwrap(type);
}
@Override
public ParameterNameProvider getParameterNameProvider() {
return delegate().getParameterNameProvider();
}
@Override
public ClockProvider getClockProvider()
{
return delegate().getClockProvider();
}
@Override
public void close() {
if (delegate != null) {
delegate.close();
}
if (toRelease != null) {
for (final Releasable<?> r : toRelease) {
r.release();
}
}
}
}
private static final class Releasable<T> {
private final CreationalContext<T> context;
private final InjectionTarget<T> injectionTarget;
private final T instance;
private Releasable(final CreationalContext<T> context, final InjectionTarget<T> injectionTarget, final T instance) {
this.context = context;
this.injectionTarget = injectionTarget;
this.instance = instance;
}
private void release() {
try {
injectionTarget.preDestroy(instance);
injectionTarget.dispose(instance);
context.release();
} catch (final Exception | NoClassDefFoundError e) {
// no-op
}
}
}
}