/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.server.validation.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.Configuration;
import javax.validation.TraversableResolver;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.ValidationProviderResolver;
import javax.validation.Validator;
import javax.validation.ValidatorContext;
import javax.validation.ValidatorFactory;
import javax.validation.spi.ValidationProvider;
import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.model.internal.RankedComparator;
import org.glassfish.jersey.model.internal.RankedProvider;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.internal.inject.ConfiguredValidator;
import org.glassfish.jersey.server.spi.ValidationInterceptor;
import org.glassfish.jersey.server.validation.ValidationConfig;
/**
* Bean Validation provider injection binder.
*
* @author Michal Gajdos
*/
public final class ValidationBinder extends AbstractBinder {
private static final Logger LOGGER = Logger.getLogger(ValidationBinder.class.getName());
@Override
protected void configure() {
bindFactory(DefaultConfigurationProvider.class, Singleton.class).to(Configuration.class).in(Singleton.class);
bindFactory(DefaultValidatorFactoryProvider.class, Singleton.class).to(ValidatorFactory.class).in(Singleton.class);
bindFactory(DefaultValidatorProvider.class, Singleton.class).to(Validator.class).in(Singleton.class);
bindFactory(ConfiguredValidatorProvider.class, Singleton.class).to(ConfiguredValidator.class);
// Custom Exception Mapper and Writer - registering in binder to make possible for users register their own providers.
bind(ValidationExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class);
bind(ValidationErrorMessageBodyWriter.class).to(MessageBodyWriter.class).in(Singleton.class);
}
/**
* Factory providing default {@link javax.validation.Configuration} instance.
*/
private static class DefaultConfigurationProvider implements Supplier<Configuration> {
private final boolean inOsgi;
public DefaultConfigurationProvider() {
this.inOsgi = ReflectionHelper.getOsgiRegistryInstance() != null;
}
@Override
public Configuration get() {
try {
if (!inOsgi) {
return Validation.byDefaultProvider().configure();
} else {
return Validation
.byDefaultProvider()
.providerResolver(new ValidationProviderResolver() {
@Override
public List<ValidationProvider<?>> getValidationProviders() {
final List<ValidationProvider<?>> validationProviders = new ArrayList<>();
for (final ValidationProvider validationProvider : ServiceFinder
.find(ValidationProvider.class)) {
validationProviders.add(validationProvider);
}
return validationProviders;
}
})
.configure();
}
} catch (final ValidationException e) {
// log and re-trow
LOGGER.log(Level.FINE, LocalizationMessages.VALIDATION_EXCEPTION_PROVIDER(), e);
throw e;
}
}
}
/**
* Factory providing default (un-configured) {@link ValidatorFactory} instance.
*/
private static class DefaultValidatorFactoryProvider implements Supplier<ValidatorFactory> {
@Inject
private Configuration config;
@Override
public ValidatorFactory get() {
return config.buildValidatorFactory();
}
}
/**
* Factory providing default (un-configured) {@link Validator} instance.
*/
private static class DefaultValidatorProvider implements Supplier<Validator> {
@Inject
private ValidatorFactory factory;
@Override
public Validator get() {
return factory.getValidator();
}
}
/**
* Factory providing configured {@link Validator} instance.
*/
private static class ConfiguredValidatorProvider implements Supplier<ConfiguredValidator> {
@Inject
private InjectionManager injectionManager;
@Inject
private Configuration validationConfig;
@Inject
private ValidatorFactory factory;
@Context
private javax.ws.rs.core.Configuration jaxRsConfig;
@Context
private Providers providers;
@Context
private ResourceContext resourceContext;
private ConfiguredValidator defaultValidator;
private final WeakHashMap<ContextResolver<ValidationConfig>, ConfiguredValidator> validatorCache =
new WeakHashMap<>();
@Override
public ConfiguredValidator get() {
// Custom Configuration.
final ContextResolver<ValidationConfig> contextResolver =
providers.getContextResolver(ValidationConfig.class, MediaType.WILDCARD_TYPE);
if (contextResolver == null) {
return getDefaultValidator();
} else {
if (!validatorCache.containsKey(contextResolver)) {
final ValidateOnExecutionHandler validateOnExecutionHandler =
new ValidateOnExecutionHandler(validationConfig, !isValidateOnExecutableOverrideCheckDisabled());
final ValidatorContext context = getDefaultValidatorContext(validateOnExecutionHandler);
final ValidationConfig config = contextResolver.getContext(ValidationConfig.class);
if (config != null) {
// MessageInterpolator
if (config.getMessageInterpolator() != null) {
context.messageInterpolator(config.getMessageInterpolator());
}
// TraversableResolver
if (config.getTraversableResolver() != null) {
context.traversableResolver(
getTraversableResolver(config.getTraversableResolver(), validateOnExecutionHandler));
}
// ConstraintValidatorFactory
if (config.getConstraintValidatorFactory() != null) {
context.constraintValidatorFactory(config.getConstraintValidatorFactory());
}
// ParameterNameProvider
if (config.getParameterNameProvider() != null) {
context.parameterNameProvider(config.getParameterNameProvider());
}
}
validatorCache.put(contextResolver,
new DefaultConfiguredValidator(context.getValidator(), this.validationConfig,
validateOnExecutionHandler, getValidationInterceptors()));
}
return validatorCache.get(contextResolver);
}
}
private Iterable<ValidationInterceptor> getValidationInterceptors() {
final Iterable<RankedProvider<ValidationInterceptor>> validationInterceptorIterable =
org.glassfish.jersey.internal.inject.Providers
.getAllRankedProviders(injectionManager, ValidationInterceptor.class);
return org.glassfish.jersey.internal.inject.Providers.sortRankedProviders(
new RankedComparator<ValidationInterceptor>(), validationInterceptorIterable);
}
/**
* Return default validator.
*
* @return default validator.
*/
private ConfiguredValidator getDefaultValidator() {
if (defaultValidator == null) {
final ValidateOnExecutionHandler validateOnExecutionHandler =
new ValidateOnExecutionHandler(validationConfig, !isValidateOnExecutableOverrideCheckDisabled());
final Validator validator = getDefaultValidatorContext(validateOnExecutionHandler).getValidator();
defaultValidator = new DefaultConfiguredValidator(validator, validationConfig,
validateOnExecutionHandler, getValidationInterceptors());
}
return defaultValidator;
}
/**
* Return default {@link ValidatorContext validator context} able to inject JAX-RS resources/providers.
*
* @param handler handler to create traversable resolver for.
* @return default validator context.
*/
private ValidatorContext getDefaultValidatorContext(final ValidateOnExecutionHandler handler) {
final ValidatorContext context = factory.usingContext();
// Default Configuration.
context.constraintValidatorFactory(resourceContext.getResource(InjectingConstraintValidatorFactory.class));
// Traversable Resolver.
context.traversableResolver(getTraversableResolver(factory.getTraversableResolver(), handler));
return context;
}
/**
* Create traversable resolver able to process {@link javax.validation.executable.ValidateOnExecution} annotation on
* beans.
*
* @param delegate resolver to be wrapped into the custom traversable resolver.
* @param handler handler to create traversable resolver for.
* @return custom traversable resolver.
*/
private ValidateOnExecutionTraversableResolver getTraversableResolver(TraversableResolver delegate,
final ValidateOnExecutionHandler handler) {
if (delegate == null) {
delegate = validationConfig.getDefaultTraversableResolver();
}
final boolean validationEnabled = validationConfig.getBootstrapConfiguration().isExecutableValidationEnabled();
final ValidateOnExecutionTraversableResolver traversableResolver = new
ValidateOnExecutionTraversableResolver(delegate, handler, validationEnabled);
return resourceContext.initResource(traversableResolver);
}
private boolean isValidateOnExecutableOverrideCheckDisabled() {
return PropertiesHelper.isProperty(
jaxRsConfig.getProperty(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK));
}
}
}