I am using Spring boot v1.4 and hibernate v4.3.5.finall in my application
I have writen my own ResourceBundle and MessageInterpolator to save messages in database and have configured them as bean in my project. It seems ResourceBundle works fine and returns my custom message but parameters don't pass,for example for this validation :
#Size(min=5,max = 10)
private String lastName;
I expect : size must be between 5 and 10 bla bla.....
but the result is : size must be between {min} and {max} bla bla.....
any Idea? Thanks..
my ResourceBundle class:
package ir.pt.core.bundles;
import ir.pt.common.bean.ResourceEntity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.io.IOException;
import java.util.*;
public class DatabaseResourceBundle extends ResourceBundle {
#PersistenceContext
protected EntityManager em;
private Map<String, String> cache = new HashMap<String, String>();
protected final static String BUNDLE_NAME = "ir.pt.core.bundles";
protected Control DB_CONTROL = new DBControl();
public DatabaseResourceBundle() {
setParent(ResourceBundle.getBundle(BUNDLE_NAME, DB_CONTROL));
}
public DatabaseResourceBundle(Locale locale) {
setParent(ResourceBundle.getBundle(BUNDLE_NAME, locale, DB_CONTROL));
}
#Override
protected Object handleGetObject(String key) {
return cache != null ? cache.get(key) : parent.getObject(key);
}
#Override
public Enumeration<String> getKeys() {
return parent.getKeys();
}
protected class DBControl extends Control {
#Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
return new CustomizedLocaleResources(locale);
}
protected class CustomizedLocaleResources extends ListResourceBundle {
private Locale locale;
public CustomizedLocaleResources(Locale locale) {
this.locale = locale;
}
#Override
protected Object[][] getContents() {
String sql = "FROM ResourceEntity re WHERE re.locale = '"+locale.getLanguage()+"'";
TypedQuery<ResourceEntity> query =
em.createQuery(sql, ResourceEntity.class);
List<ResourceEntity> resources = query.getResultList();
Object[][] all = new Object[resources.size()][2];
int i = 0;
for (Iterator<ResourceEntity> it = resources.iterator(); it.hasNext();) {
ResourceEntity resource = it.next();
all[i] = new Object[]{resource.getKey(), resource.getMessage()};
cache.put(resource.getKey(), resource.getMessage());
i++;
}
return all;
}
}
}
}
my MessageInterpolator class:
package ir.pt.core.bundles;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.springframework.beans.factory.annotation.Autowired;
import javax.validation.MessageInterpolator;
import java.util.Locale;
import java.util.Map;
public class DatabaseMessageInterpolator extends ResourceBundleMessageInterpolator implements MessageInterpolator{
protected final String BRACE_OPEN = "\\{";
protected final String BRACE_CLOSE = "\\}";
#Autowired
DatabaseResourceBundle databaseResourceBundle;
#Override
public String interpolate(String message, Context context) {
return interpolate(message, context, databaseResourceBundle.getLocale());
}
#Override
public String interpolate(String message, Context context, Locale locale) {
String messageKey = context.getConstraintDescriptor().getAttributes().get("message").toString();
message = databaseResourceBundle.getString(messageKey.replaceAll(BRACE_OPEN, "").replaceAll(BRACE_CLOSE, ""));
Map<String, Object> attributes = context.getConstraintDescriptor().getAttributes();
for (String key : attributes.keySet()) {
String value = attributes.get(key).toString();
key = BRACE_OPEN + key + BRACE_CLOSE;
message = message.replaceAll(key, value);
}
return message;
}
}
my bean configuration:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurationSupport {
#Override
public Validator getValidator() {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setMessageInterpolator(messageInterpolator());
return factory;
}
#Bean
public MessageInterpolator messageInterpolator() {
return new DatabaseMessageInterpolator();
}
#Bean
ResourceBundle resourceBundle() {
return new DatabaseResourceBundle(new Locale("fa"));
}
}
Related
I have multiple merchants and a WSDL with different configurations is needed for each merchant. Right now, I'm copying and pasting the following method and changing the configs. But it creates some difficulties (requires code change & deployment). I want to initialize it programmatically at the run time. I've tried these methods but it din't work.
Is it possible?
#Bean(name = "marchant-1")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema commonSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("Marchant1WSPort");
wsdl11Definition.setLocationUri("/Marchant1WebService");
wsdl11Definition.setTargetNamespace("http://.../");
wsdl11Definition.setSchema(commonSchema);
return wsdl11Definition;
}
I solved similar kind of problem by specifying scope of my bean as prototype. Below example explains my implementation:
Create your DefaultWsdl11Definition class as shown below:
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component
#Scope("prototype")
#Getter
#Setter
public class DefaultWsdl11Definition {
private String portTypeName;
private String locationUri;
private String targetNamespace;
private XsdSchema schema;
}
Implement ApplicationContextAware to generate beans programmatically:
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
#Service
public class ApplicationContextAwareImpl implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextAwareImpl.initApplicationContext(applicationContext);
}
private static void initApplicationContext(ApplicationContext applicationContext) {
ApplicationContextAwareImpl.context = applicationContext;
}
/**
* #param requiredType Bean class
*
* #return Bean of required type
*/
public static <T> T getBean(Class<T> requiredType) {
return context.getBean(requiredType);
}
}
Use ApplicationContextAwareImpl.getBean() method to generate beans programmatically:
DefaultWsdl11Definition wsdl11Definition = ApplicationContextAwareImpl.getBean(DefaultWsdl11Definition.class);
wsdl11Definition.setPortTypeName("Marchant1WSPort");
wsdl11Definition.setLocationUri("/Marchant1WebService");
wsdl11Definition.setTargetNamespace("http://.../");
wsdl11Definition.setSchema(commonSchema);
Also you can make use of Qualifier and Bean annotations to generate multiple beans of same type:
#Configuration
public class BeanConfigurations {
#Qualifier("marchant-1")
#Bean(name = "marchant-1")
public DefaultWsdl11Definition defaultWsdl11Definition1(XsdSchema commonSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("Marchant1WSPort");
wsdl11Definition.setLocationUri("/Marchant1WebService");
wsdl11Definition.setTargetNamespace("http://.../");
wsdl11Definition.setSchema(commonSchema);
return wsdl11Definition;
}
#Qualifier("marchant-2")
#Bean(name = "marchant-2")
public DefaultWsdl11Definition defaultWsdl11Definition2(XsdSchema commonSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("Marchant2WSPort");
wsdl11Definition.setLocationUri("/Marchant2WebService");
wsdl11Definition.setTargetNamespace("http://.../");
wsdl11Definition.setSchema(commonSchema);
return wsdl11Definition;
}
}
This solution worked for me:
#Configuration
public class WsBeanLoader implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
SimpleXsdSchema xsd = new SimpleXsdSchema(new ClassPathResource("xsd/sample.xsd"));
xsd.afterPropertiesSet();
BeanDefinitionBuilder builder = builder(name, xsd);
registry.registerBeanDefinition(name, builder.getBeanDefinition());
}
private BeanDefinitionBuilder builder(String name, SimpleXsdSchema xsd) {
name = StringUtils.capitalize(name);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DefaultWsdl11Definition.class);
builder.addPropertyValue("serviceName", name + "WebService");
builder.addPropertyValue("portTypeName", name + "WSPortBinding");
builder.addPropertyValue("locationUri", "/WS/" + name + "WS");
builder.addPropertyValue("targetNamespace", "url..");
builder.addPropertyValue("schema", xsd);
return builder;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
I wrote a DateTimeFormatAnnotationFormatterFactory to use for global conversion of LocalDateTime, imitating LocalDateTimeFormatAnnotationFormatterFactory
package com.taimi.localdatetimedemo.format.date;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
/**
* LocalDateTimeFormatAnnotationFormatterFactory
*/
public class LocalDateTimeFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<LocalDateTimeFormat> {
private static final Set<Class<?>> FIELD_TYPES;
static {
Set<Class<?>> fieldTypes = new HashSet<>(1);
fieldTypes.add(LocalDateTime.class);
FIELD_TYPES = Collections.unmodifiableSet(fieldTypes);
}
#Override
public Set<Class<?>> getFieldTypes() {
return FIELD_TYPES;
}
#Override
public Printer<?> getPrinter(LocalDateTimeFormat annotation, Class<?> fieldType) {
return getFormatter(annotation, fieldType);
}
#Override
public Parser<?> getParser(LocalDateTimeFormat annotation, Class<?> fieldType) {
return getFormatter(annotation, fieldType);
}
protected Formatter<LocalDateTime> getFormatter(LocalDateTimeFormat annotation, Class<?> fieldType) {
LocalDateTimeFormater localDateTimeFormater=new LocalDateTimeFormater(annotation.pattern());
return localDateTimeFormater;
}
}
public #interface LocalDateTimeFormat {
String pattern() default "";
}
package com.taimi.localdatetimedemo.format.date;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.springframework.format.Formatter;
import org.springframework.util.StringUtils;
/**
* LocalDateFormater
*/
public class LocalDateTimeFormater implements Formatter<LocalDateTime> {
private String pattern;
public LocalDateTimeFormater(String pattern) {
if (StringUtils.isEmpty(pattern)) {
throw new IllegalArgumentException("Pattern can't be empty.", null);
}
this.pattern = pattern;
}
#Override
public String print(LocalDateTime localDateTime, Locale locale) {
return createDateTimeFormatter(locale).format(localDateTime);
}
#Override
public LocalDateTime parse(String text, Locale locale) throws ParseException {
return LocalDateTime.parse(text, createDateTimeFormatter(locale));
}
private DateTimeFormatter createDateTimeFormatter(Locale locale) {
return DateTimeFormatter.ofPattern(pattern, locale);
}
}
The configuration is
#Configuration
public class TimeConfig extends WebMvcConfigurationSupport {
#Override
public FormattingConversionService mvcConversionService() {
// DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
FormattingConversionService conversionService=new FormattingConversionService();
conversionService.addFormatterForFieldAnnotation(new LocalDateTimeFormatAnnotationFormatterFactory());
return conversionService;
}
}
But it always failed in conversion because converterCache can not find a suitable converter or is empty.
The source code of finding converter is showing down below from GenericConversionService
#Nullable
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
GenericConverter converter = this.converterCache.get(key);
if (converter != null) {
return (converter != NO_MATCH ? converter : null);
}
converter = this.converters.find(sourceType, targetType);
if (converter == null) {
converter = getDefaultConverter(sourceType, targetType);
}
if (converter != null) {
this.converterCache.put(key, converter);
return converter;
}
this.converterCache.put(key, NO_MATCH);
return null;
}
I've found out where I was wrong, the #Interface should be
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface LocalDateTimeFormat {
String pattern() default "";
}
I've been reading a lot about spring request validation. I read a lot articles on how to appropriately implement that, but I have some problem. This is my code:
RestController:
#Autowired
EmployeeManager employeeManager;
#Autowired
EmployeeValidator employeeValidator;
#InitBinder("employee")
public void setupBinder(WebDataBinder binder) {
binder.addValidators(employeeValidator);
}
// -------------- CREATE EMPLOYEES --------------
#PostMapping(value = "add")
public ResponseEntity<EmployeeDTO> addEmployee(#Valid #RequestBody EmployeeDTO employee) {
boolean isCreated = employeeManager.addEmployee(employee);
if(isCreated) {
return new ResponseEntity<>(employee, HttpStatus.CREATED);
}
return new ResponseEntity(new CustomError("Unable to create, employee with email " +
employee.getEmail() + " already exist."), HttpStatus.CONFLICT);
}
Validator:
package com.employee.api.EmployeeAPI.validator;
import com.employee.api.EmployeeAPI.model.dto.EmployeeDTO;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
#Component
public class EmployeeValidator implements Validator {
private Pattern pattern;
private Matcher matcher;
private static final String STRING_PATTERN = "[a-zA-Z]+";
private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*#"
+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
#Override
public boolean supports(Class<?> clazz) {
return EmployeeDTO.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
EmployeeDTO employee = (EmployeeDTO) target;
if (validateInputString(employee.getFirstName(), STRING_PATTERN)) {
errors.rejectValue("firstName", "firstName.invalid");
}
if (validateInputString(employee.getLastName(), STRING_PATTERN)) {
errors.rejectValue("lastName", "lastName.invalid");
}
if (validateInputString(employee.getJob(), STRING_PATTERN)) {
errors.rejectValue("job", "job.invalid");
}
if (validateInputString(employee.getEmail(), EMAIL_PATTERN)) {
errors.rejectValue("email", "email.invalid");
}
}
private boolean validateInputString(String input, String regexPattern) {
pattern = Pattern.compile(regexPattern);
matcher = pattern.matcher(input);
return (!matcher.matches() || input == null || input.trim().length() == 0);
}
}
and in config I added bean:
#Bean
public EmployeeValidator beforeAddOrUpdateEmployeeValidator() {
return new EmployeeValidator();
}
I am not really sure of how it should be invoked right now when adding employees, because it surely does not work for now. Could you help me with the right implementation or point in the right direction?
I'm not familiar with org.springframework.validation.Validator, but will suggest you how to do the same validation as you need with javax.validation.ConstraintValidator (JSR-303). Your controller class is fine and no changes needed there.
you need to create a custom annotation #ValidEmployee and annotate your dto with it:
#ValidEmployee
public class EmployeeDto {
...
}
ValidEmployee annotation:
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = EmployeeValidator.class)
#Documented
public #interface ValidEmployee {
String message() default "{ValidEmployee.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and implement your validation logic in isValid method:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class EmployeeValidator implements ConstraintValidator<ValidEmployee, EmployeeDto> {
#Override
public void initialize(ValidEmployee constraintAnnotation) {
}
#Override
public boolean isValid(EmployeeDto employee, ConstraintValidatorContext context) {
// do your validation logic
}
}
I'm looking for the proper way—in a Jersey application—to read a header from an incoming request and automatically install it in any outgoing requests that might be made by a JAX-RS client that my application is using.
Ideally I'd like to do this without polluting any of my classes' inner logic at all, so via various filters and interceptors.
For simple use cases, I can do this: I have a ClientRequestFilter implementation that I register on my ClientBuilder, and that filter implementation has:
#Context
private HttpHeaders headers;
...which is a context-sensitive proxy (by definition), so in its filter method it can refer to headers that were present on the inbound request that's driving all this, and install them on the outgoing request. For straightforward cases, this appears to work OK.
However, this fails in the case of asynchronicity: if I use the JAX-RS asynchronous client APIs to spawn a bunch of GETs, the filter is still invoked, but can no longer invoke methods on that headers instance variable; Jersey complains that as far as it knows we're no longer in request scope. This makes sense if request scope is defined to be per-thread: the spawned GETs are running in some Jersey-managed thread pool somewhere, not on the same thread as the one with which the headers proxy is associated, so that proxy throws IllegalStateExceptions all over the place when my filter tries to talk to it.
I feel like there's some combination of ContainerRequestFilter and ClientRequestFilter that should be able to get the job done even in asynchronous cases, but I'm not seeing it.
What I would do is make a WebTarget injectable that is preconfigured with a ClientRequestFilter to add the headers. It's better to configure the WebTarget this way, as opposed to the Client, since the Client is an expensive object to create.
We can make the WebTarget injectable using a custom annotation and an InjectionResolver. In the InjectionResolver, we can get the ContainerRequest and get the headers from that, which we will pass to the ClientRequestFilter.
Here it is in action
Create the custom annotation
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface WithHeadersTarget {
String baseUri();
String[] headerNames() default {};
}
Make the InjectionResolver with the custom ClientRequestFilter
private static class WithHeadersTargetInjectionResolver
implements InjectionResolver<WithHeadersTarget> {
private final Provider<ContainerRequest> requestProvider;
private final Client client;
#Inject
public WithHeadersTargetInjectionResolver(Provider<ContainerRequest> requestProvider) {
this.requestProvider = requestProvider;
this.client = ClientBuilder.newClient();
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (injectee.getRequiredType() == WebTarget.class
&& injectee.getParent().isAnnotationPresent(WithHeadersTarget.class)) {
WithHeadersTarget anno = injectee.getParent().getAnnotation(WithHeadersTarget.class);
String uri = anno.baseUri();
String[] headersNames = anno.headerNames();
MultivaluedMap<String, String> requestHeaders = requestProvider.get().getRequestHeaders();
return client.target(uri)
.register(new HeadersFilter(requestHeaders, headersNames));
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return false;
}
private class HeadersFilter implements ClientRequestFilter {
private final MultivaluedMap<String, String> headers;
private final String[] headerNames;
private HeadersFilter(MultivaluedMap<String, String> headers, String[] headerNames) {
this.headers = headers;
this.headerNames = headerNames;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
// if headers names is empty, add all headers
if (this.headerNames.length == 0) {
for (Map.Entry<String, List<String>> entry: this.headers.entrySet()) {
requestContext.getHeaders().put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
// else just add the headers from the annotation
} else {
for (String header: this.headerNames) {
requestContext.getHeaders().put(header, new ArrayList<>(this.headers.get(header)));
}
}
}
}
}
One thing about this implementation is that it checks for an empty headerNames in the #WithHeadersTarget annotation. If it is empty, then we just forward all headers. If the user specifies some header names, then it will only forward those
Register the InjectionResolver
new ResourceConfig()
.register(new AbstractBinder() {
#Override
protected void configure() {
bind(WithHeadersTargetInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<WithHeadersTarget>>() {
}).in(Singleton.class);
}
})
Use it
#Path("test")
public static class TestResource {
#WithHeadersTarget(
baseUri = BASE_URI
headerNames = {TEST_HEADER_NAME})
private WebTarget target;
#GET
public String get() {
return target.path("client").request().get(String.class);
}
}
In this example if, the headerNames is left out, then it will default to an empty array, which will cause all the request headers to be forwarded.
Complete test using Jersey Test Framework
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
public class ForwardHeadersTest extends JerseyTest {
private static final String BASE_URI = "http://localhost:8000";
private static final String TEST_HEADER_NAME = "X-Test-Header";
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface WithHeadersTarget {
String baseUri();
String[] headerNames() default {};
}
#Path("test")
public static class TestResource {
#WithHeadersTarget(
baseUri = BASE_URI
)
private WebTarget target;
#GET
public String get() {
return target.path("client").request().get(String.class);
}
}
#Path("client")
public static class ClientResource {
#GET
public String getReversedHeader(#HeaderParam(TEST_HEADER_NAME) String header) {
System.out.println(header);
return new StringBuilder(header).reverse().toString();
}
}
private static class WithHeadersTargetInjectionResolver
implements InjectionResolver<WithHeadersTarget> {
private final Provider<ContainerRequest> requestProvider;
private final Client client;
#Inject
public WithHeadersTargetInjectionResolver(Provider<ContainerRequest> requestProvider) {
this.requestProvider = requestProvider;
this.client = ClientBuilder.newClient();
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (injectee.getRequiredType() == WebTarget.class
&& injectee.getParent().isAnnotationPresent(WithHeadersTarget.class)) {
WithHeadersTarget anno = injectee.getParent().getAnnotation(WithHeadersTarget.class);
String uri = anno.baseUri();
String[] headersNames = anno.headerNames();
MultivaluedMap<String, String> requestHeaders = requestProvider.get().getRequestHeaders();
return client.target(uri)
.register(new HeadersFilter(requestHeaders, headersNames));
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return false;
}
private class HeadersFilter implements ClientRequestFilter {
private final MultivaluedMap<String, String> headers;
private final String[] headerNames;
private HeadersFilter(MultivaluedMap<String, String> headers, String[] headerNames) {
this.headers = headers;
this.headerNames = headerNames;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
// if headers names is empty, add all headers
if (this.headerNames.length == 0) {
for (Map.Entry<String, List<String>> entry: this.headers.entrySet()) {
requestContext.getHeaders().put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
// else just add the headers from the annotation
} else {
for (String header: this.headerNames) {
requestContext.getHeaders().put(header, new ArrayList<>(this.headers.get(header)));
}
}
}
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(ClientResource.class)
.register(new AbstractBinder() {
#Override
protected void configure() {
bind(WithHeadersTargetInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<WithHeadersTarget>>() {
}).in(Singleton.class);
}
})
.register(new LoggingFilter(Logger.getAnonymousLogger(), true))
.register(new ExceptionMapper<Throwable>() {
#Override
public Response toResponse(Throwable t) {
t.printStackTrace();
return Response.serverError().entity(t.getMessage()).build();
}
});
}
#Override
public URI getBaseUri() {
return URI.create(BASE_URI);
}
#Test
public void testIt() {
final String response = target("test")
.request()
.header(TEST_HEADER_NAME, "HelloWorld")
.get(String.class);
assertThat(response).isEqualTo("dlroWolleH");
}
}
I am new to Spring and i need to convert a CDI class to Spring.
I have the below code in CDI
#Inject
private Instance<HealthCheck> healthChecks;
I then iterate over healthChecks.
I found a similar question What is the Spring equivalent for CDI's Instance, or Guices Provider , where it was advised to use
#Inject
Provider<MyObject> myObjectInstance;
//...
MyObject myObjectInstance.get();
However, since provider does not implements iterable, I am not able to iterate.
Can someone please help me on how can I convert the CDI code block to spring.
create a new file spring.factories inside META-INF with following content:
org.springframework.context.ApplicationContextInitializer=package_name.CustomApplicationContextInitializer
or you can use it in your junit test like:
#SpringApplicationConfiguration(initializers = CustomApplicationContextInitializer.class)
And now you can use like:
#Autowired
private Instance<HealthCheck> healthChecks;
CustomApplicationContextInitializer.class
public class CustomApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// Configure custom CustomAutowireCandidateResolver to handle CDI
// Instance<T> dependency requests
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
beanFactory.setAutowireCandidateResolver(new CustomAutowireCandidateResolver());
}
}
CustomAutowireCandidateResolver.class
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Priority;
import javax.enterprise.inject.Instance;
import javax.enterprise.util.TypeLiteral;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.core.annotation.Order;
import org.springframework.util.ClassUtils;
public class CustomAutowireCandidateResolver extends ContextAnnotationAutowireCandidateResolver {
static final boolean IS_CDI_INSTANCE_CLASS_PRESENT = ClassUtils.isPresent("javax.enterprise.inject.Instance", null);
#Override
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) {
if (IS_CDI_INSTANCE_CLASS_PRESENT && Instance.class.equals(descriptor.getDependencyType())) {
// TODO refactor getLazyResolutionProxyIfNecessary to allow to
// customize lazy dependency resolution for Instance<T>
return getInstanceAdapterFor(descriptor);
}
return super.getLazyResolutionProxyIfNecessary(descriptor, beanName);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
private Object getInstanceAdapterFor(DependencyDescriptor descriptor) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) getBeanFactory();
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) listableBeanFactory;
// Instance<TargetType>
Class targetType = descriptor.getResolvableType().getGeneric(0).getRawClass();
Map<String, Object> beansOfType = listableBeanFactory.getBeansOfType(targetType);
List<Bean> beansInstances = beansOfType.entrySet().stream() //
.map(e -> new Bean(e.getValue(), registry.getBeanDefinition(e.getKey()).isPrimary()))//
.collect(Collectors.toList());
Annotation[] qualifiers = retainQualifierAnnotations(descriptor.getAnnotations());
Beans beans = new Beans(targetType, beansInstances);
return qualifiers.length == 0 ? beans : beans.select(qualifiers);
}
private Annotation[] retainQualifierAnnotations(Annotation[] annotations) {
return Arrays.stream(annotations) //
.filter(a -> a.annotationType().isAnnotationPresent(Qualifier.class)) //
.toArray(Annotation[]::new);
}
static class Beans<T> implements Instance<T> {
private final List<Bean> beans;
private final Class<?> type;
public Beans(Class<?> type, List<Bean> beans) {
this.type = type;
this.beans = beans;
}
protected List<Bean> getBeans() {
return beans;
}
#Override
public T get() {
return (T) findDefaultInstance();
}
protected Object findDefaultInstance() {
List<Bean> beans = getBeans();
if (beans.size() == 1) {
return beans.get(0).getInstance();
}
Object highestPrioBean = returnPrimaryOrHighestPriorityBean(beans);
if (highestPrioBean != null) {
return highestPrioBean;
}
// TODO figure out a sane default to use here - maybe throw an
// exception?
return beans.get(0).getInstance();
}
private Object returnPrimaryOrHighestPriorityBean(List<Bean> beans) {
long highestPriority = Integer.MIN_VALUE;
Object highestPrioBean = null;
for (Bean bean : beans) {
if (bean.isPrimary()) {
return bean.getInstance();
}
// TODO figure out to retrieve order from BeanDefinition /
// BeanDeclaration
Object instance = bean.getInstance();
Order order = instance.getClass().getAnnotation(Order.class);
if (order != null) {
if (order.value() > highestPriority) {
highestPriority = order.value();
highestPrioBean = instance;
}
}
Priority priority = instance.getClass().getAnnotation(Priority.class);
if (priority != null) {
if (priority.value() > highestPriority) {
highestPriority = priority.value();
highestPrioBean = instance;
}
}
}
return highestPrioBean;
}
#Override
#SuppressWarnings("unchecked")
public Instance<T> select(Annotation... qualifiers) {
return select((Class<T>) type, qualifiers);
}
#Override
public <U extends T> Instance<U> select(Class<U> subtype, Annotation... qualifiers) {
return new Beans<U>(subtype, filterBeans(subtype, qualifiers));
}
protected List<Bean> filterBeans(Class<?> subtype, Annotation... qualifiers) {
List<Annotation> requiredQualifiers = Arrays.asList(qualifiers);
return getBeans().stream() //
.filter(bean -> subtype.isInstance(bean.getInstance())) //
.filter(bean -> bean.getAnnotations().containsAll(requiredQualifiers)) //
.collect(Collectors.toList());
}
#Override
public <U extends T> Instance<U> select(TypeLiteral<U> subtype, Annotation... qualifiers) {
// TODO implement (Class<U> subtype, Annotation... qualifiers) via
// select(TypeLiteral<U> subtype, Annotation... qualifiers)
return select(subtype.getRawType(), qualifiers);
}
#Override
public Iterator<T> iterator() {
return getBeans().stream().map(bean -> (T) bean.getInstance()).iterator();
}
#Override
public boolean isUnsatisfied() {
return getBeans().isEmpty();
}
#Override
public boolean isAmbiguous() {
return getBeans().size() > 1;
}
#Override
public void destroy(Object bean) {
if (bean instanceof DisposableBean) {
try {
((DisposableBean) bean).destroy();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
static class Bean {
private final boolean primary;
private final Object instance;
private final List<Annotation> annotations;
public Bean(Object instance, boolean primary) {
this.primary = primary;
this.instance = instance;
this.annotations = Arrays.asList(instance.getClass().getAnnotations());
}
public Object getInstance() {
return instance;
}
public boolean isPrimary() {
return primary;
}
public List<Annotation> getAnnotations() {
return annotations;
}
}
}
here is full source link: https://github.com/thomasdarimont/spring-boot-cdi-instance-example