Skip to content
Permalink
Browse files
refactor 'participant' object creation logic to support CDI-injectabl…
…e service loader based ValueExtractors (abstracted for future implementation-specific use)
  • Loading branch information
mbenson committed Oct 16, 2018
1 parent 77175f4 commit d9b528988a0b36204fbd561d050803da4840632a
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 61 deletions.
@@ -35,12 +35,14 @@
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.spi.ConfigurationState;
import javax.validation.valueextraction.ValueExtractor;

import org.apache.bval.jsr.descriptor.DescriptorManager;
import org.apache.bval.jsr.metadata.MetadataBuilders;
import org.apache.bval.jsr.util.AnnotationsManager;
import org.apache.bval.jsr.valueextraction.ValueExtractors;
import org.apache.bval.jsr.xml.ValidationMappingParser;
import org.apache.bval.util.CloseableAble;
import org.apache.bval.util.reflection.Reflection;
import org.apache.commons.weaver.privilizer.Privilizing;
import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
@@ -55,19 +57,6 @@ public class ApacheValidatorFactory implements ValidatorFactory, Cloneable {

private static volatile ApacheValidatorFactory DEFAULT_FACTORY;

private MessageInterpolator messageResolver;
private TraversableResolver traversableResolver;
private ConstraintValidatorFactory constraintValidatorFactory;
private ParameterNameProvider parameterNameProvider;
private ClockProvider clockProvider;
private final Map<String, String> properties;
private final AnnotationsManager annotationsManager;
private final DescriptorManager descriptorManager = new DescriptorManager(this);
private final MetadataBuilders metadataBuilders = new MetadataBuilders();
private final ValueExtractors valueExtractors = new ValueExtractors();
private final ConstraintCached constraintsCache = new ConstraintCached();
private final Collection<Closeable> toClose = new ArrayList<>();

/**
* Convenience method to retrieve a default global ApacheValidatorFactory
*
@@ -94,6 +83,26 @@ public static void setDefault(ApacheValidatorFactory aDefaultFactory) {
DEFAULT_FACTORY = aDefaultFactory;
}

private static ValueExtractors createBaseValueExtractors(ParticipantFactory participantFactory) {
final ValueExtractors result = new ValueExtractors();
participantFactory.loadServices(ValueExtractor.class).forEach(result::add);
return result;
}

private MessageInterpolator messageResolver;
private TraversableResolver traversableResolver;
private ConstraintValidatorFactory constraintValidatorFactory;
private ParameterNameProvider parameterNameProvider;
private ClockProvider clockProvider;
private final Map<String, String> properties;
private final AnnotationsManager annotationsManager;
private final DescriptorManager descriptorManager = new DescriptorManager(this);
private final MetadataBuilders metadataBuilders = new MetadataBuilders();
private final ConstraintCached constraintsCache = new ConstraintCached();
private final Collection<Closeable> toClose = new ArrayList<>();
private final ParticipantFactory participantFactory;
private final ValueExtractors valueExtractors;

/**
* Create a new ApacheValidatorFactory instance.
*/
@@ -105,9 +114,13 @@ public ApacheValidatorFactory(ConfigurationState configuration) {
constraintValidatorFactory = configuration.getConstraintValidatorFactory();
clockProvider = configuration.getClockProvider();

if (ConfigurationImpl.class.isInstance(configuration)) {
toClose.add(ConfigurationImpl.class.cast(configuration).getClosable());
if (configuration instanceof CloseableAble) {
toClose.add(((CloseableAble) configuration).getCloseable());
}
participantFactory = new ParticipantFactory(ApacheValidatorFactory.class.getClassLoader());
toClose.add(participantFactory);

valueExtractors = createBaseValueExtractors(participantFactory).createChild();
configuration.getValueExtractors().forEach(valueExtractors::add);

annotationsManager = new AnnotationsManager(this);
@@ -21,14 +21,13 @@
import java.io.Closeable;
import java.io.InputStream;
import java.time.Clock;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;

import javax.validation.BootstrapConfiguration;
@@ -45,11 +44,12 @@
import javax.validation.spi.ValidationProvider;
import javax.validation.valueextraction.ValueExtractor;

import org.apache.bval.cdi.BValExtension;
import org.apache.bval.jsr.parameter.DefaultParameterNameProvider;
import org.apache.bval.jsr.resolver.DefaultTraversableResolver;
import org.apache.bval.jsr.util.IOs;
import org.apache.bval.jsr.valueextraction.ValueExtractors;
import org.apache.bval.jsr.xml.ValidationParser;
import org.apache.bval.util.CloseableAble;
import org.apache.bval.util.Exceptions;
import org.apache.bval.util.Lazy;
import org.apache.commons.weaver.privilizer.Privileged;
@@ -60,7 +60,7 @@
* hence this can be passed to buildValidatorFactory(ConfigurationState).
* <br/>
*/
public class ConfigurationImpl implements ApacheValidatorConfiguration, ConfigurationState {
public class ConfigurationImpl implements ApacheValidatorConfiguration, ConfigurationState, CloseableAble {

private class LazyParticipant<T> extends Lazy<T> {
private boolean locked;
@@ -135,11 +135,13 @@ synchronized ConfigurationImpl externalOverride(T value) {

private final LazyParticipant<ClockProvider> clockProvider = new LazyParticipant<>(this::getDefaultClockProvider);

private Lazy<BootstrapConfiguration> bootstrapConfiguration = new Lazy<>(this::createBootstrapConfiguration);
private final ValueExtractors bootstrapValueExtractors = new ValueExtractors();
private final ValueExtractors valueExtractors = bootstrapValueExtractors.createChild();

private Collection<BValExtension.Releasable<?>> releasables = new CopyOnWriteArrayList<>();
private final Lazy<BootstrapConfiguration> bootstrapConfiguration = new Lazy<>(this::createBootstrapConfiguration);

private Set<ValueExtractor<?>> valueExtractors = new HashSet<>();
private final Set<InputStream> mappingStreams = new HashSet<>();
private final Map<String, String> properties = new HashMap<>();

private boolean beforeCdi = false;
private ClassLoader loader;
@@ -151,10 +153,10 @@ synchronized ConfigurationImpl externalOverride(T value) {
private boolean prepared = false;
// END DEFAULTS

private final Set<InputStream> mappingStreams = new HashSet<>();
private final Map<String, String> properties = new HashMap<>();
private boolean ignoreXmlConfiguration = false;

private ParticipantFactory participantFactory;

/**
* Create a new ConfigurationImpl instance.
* @param aState bootstrap state
@@ -369,7 +371,7 @@ public ApacheValidatorConfiguration addValueExtractor(ValueExtractor<?> extracto

@Override
public Set<ValueExtractor<?>> getValueExtractors() {
return Collections.unmodifiableSet(valueExtractors);
return Collections.unmodifiableSet(new LinkedHashSet<>(valueExtractors.getValueExtractors().values()));
}

public void deferBootstrapOverrides() {
@@ -383,13 +385,13 @@ public void releaseDeferredBootstrapOverrides() {
}
}

public Closeable getClosable() {
return () -> {
for (final BValExtension.Releasable<?> releasable : releasables) {
releasable.release();
}
releasables.clear();
};
@Override
public Closeable getCloseable() {
if (participantFactory == null) {
return () -> {
};
}
return participantFactory;
}

@Privileged
@@ -407,16 +409,20 @@ private void prepare() {
}

private BootstrapConfiguration createBootstrapConfiguration() {
if (!ignoreXmlConfiguration) {
loader = ValidationParser.class.getClassLoader();
final BootstrapConfiguration xmlBootstrap =
ValidationParser.processValidationConfig(getProperties().get(Properties.VALIDATION_XML_PATH), this);
if (xmlBootstrap != null) {
return xmlBootstrap;
try {
if (!ignoreXmlConfiguration) {
loader = ValidationParser.class.getClassLoader();
final BootstrapConfiguration xmlBootstrap =
ValidationParser.processValidationConfig(getProperties().get(Properties.VALIDATION_XML_PATH), this);
if (xmlBootstrap != null) {
return xmlBootstrap;
}
}
loader = ApacheValidatorFactory.class.getClassLoader();
return BootstrapConfigurationImpl.DEFAULT;
} finally {
participantFactory = new ParticipantFactory(loader);
}
loader = ApacheValidatorFactory.class.getClassLoader();
return BootstrapConfigurationImpl.DEFAULT;
}

private void applyBootstrapConfiguration() {
@@ -425,7 +431,6 @@ private void applyBootstrapConfiguration() {
if (bootstrapConfig.getDefaultProviderClassName() != null) {
this.providerClass = loadClass(bootstrapConfig.getDefaultProviderClassName());
}

bootstrapConfig.getProperties().forEach(this::addProperty);
bootstrapConfig.getConstraintMappingResourcePaths().stream().map(ValidationParser::open)
.forEach(this::addMapping);
@@ -442,6 +447,9 @@ private void performBootstrapOverrides() {
override(constraintValidatorFactory, bootstrapConfig::getConstraintValidatorFactoryClassName);
override(parameterNameProvider, bootstrapConfig::getParameterNameProviderClassName);
override(clockProvider, bootstrapConfig::getClockProviderClassName);

bootstrapConfig.getValueExtractorClassNames().stream().<ValueExtractor<?>> map(participantFactory::create)
.forEach(bootstrapValueExtractors::add);
}

@SuppressWarnings("unchecked")
@@ -475,25 +483,6 @@ private ValidationProvider<?> findProvider() {
}

private <T> void override(LazyParticipant<T> participant, Supplier<String> getClassName) {
final String className = getClassName.get();
if (className != null) {
final Class<T> t = loadClass(className);
participant.override(newInstance(t));
}
}

@Privileged
private <T> T newInstance(final Class<T> cls) {
try {
final BValExtension.Releasable<T> releasable = BValExtension.inject(cls);
releasables.add(releasable);
return releasable.getInstance();
} catch (Exception | NoClassDefFoundError e) {
}
try {
return cls.getConstructor().newInstance();
} catch (final Exception e) {
throw new ValidationException(e.getMessage(), e);
}
Optional.ofNullable(getClassName.get()).<T> map(participantFactory::create).ifPresent(participant::override);
}
}
@@ -0,0 +1,107 @@
/*
* 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.bval.jsr;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

import javax.validation.ValidationException;

import org.apache.bval.cdi.BValExtension;
import org.apache.bval.jsr.util.ToUnmodifiable;
import org.apache.bval.util.Validate;
import org.apache.commons.weaver.privilizer.Privileged;

/**
* Factory object for helper/participant classes. The typical pattern is that this factory loads an instance of a class
* by name, taking into account whether Apache BVal is operating in a CDI environment.
*/
class ParticipantFactory implements Closeable {
private static final String META_INF_SERVICES = "/META_INF/services/";

private final Collection<BValExtension.Releasable<?>> releasables = new CopyOnWriteArrayList<>();
private final ClassLoader loader;

ParticipantFactory(ClassLoader loader) {
super();
this.loader = Validate.notNull(loader);
}

@Override
public void close() throws IOException {
for (final BValExtension.Releasable<?> releasable : releasables) {
releasable.release();
}
releasables.clear();
}

<T> T create(String classname) {
return newInstance(loadClass(classname));
}

<T> Set<T> loadServices(Class<T> type) {
Validate.notNull(type);
try {
return Collections.list(loader.getResources(META_INF_SERVICES + type.getName())).stream().map(this::read)
.flatMap(Collection::stream).<T> map(this::create).collect(ToUnmodifiable.set());
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

private Set<String> read(URL url) {
try (BufferedReader r = new BufferedReader(new InputStreamReader(url.openStream()))) {
return r.lines().map(String::trim).filter(line -> line.charAt(0) != '#').collect(Collectors.toSet());
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

@SuppressWarnings("unchecked")
private <T> Class<T> loadClass(final String className) {
try {
return (Class<T>) Class.forName(className, true, loader);
} catch (final ClassNotFoundException ex) {
throw new ValidationException(ex);
}
}

@Privileged
private <T> T newInstance(final Class<T> cls) {
try {
final BValExtension.Releasable<T> releasable = BValExtension.inject(cls);
releasables.add(releasable);
return releasable.getInstance();
} catch (Exception | NoClassDefFoundError e) {
}
try {
return cls.getConstructor().newInstance();
} catch (final Exception e) {
throw new ValidationException(e.getMessage(), e);
}
}
}
@@ -0,0 +1,27 @@
/*
* 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.bval.util;

import java.io.Closeable;

@FunctionalInterface
public interface CloseableAble {

Closeable getCloseable();
}

0 comments on commit d9b5289

Please sign in to comment.