Java bean cascade validation customization using JSR 303/380 - java

Is it possible to replace, augment or intercept java bean cascade validation using the JSR 303/380 specification for #Valid. Basically, I want to perform some additional processing for every cascaded validation using #Valid annotation.

Make sure you want it.
import org.springframework.beans.BeanUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
public class JSR303CollectionListValidator implements Validator {
private final Validator validator;
public JSR303CollectionListValidator(LocalValidatorFactoryBean localValidatorFactoryBean) {
this.validator = localValidatorFactoryBean;
}
#Override
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.invokeValidator(validator, target, errors);
if(!target.getClass().isPrimitive()){
PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(target.getClass());
for(PropertyDescriptor descriptor : propertyDescriptors){
if(List.class.isAssignableFrom(descriptor.getPropertyType())){
Method method = descriptor.getReadMethod();
try {
Object object = method.invoke(target);
if(!Objects.isNull(object)){
List typeOfObject = (List) object;
for(Object resource : typeOfObject){
validate(resource, errors);
}
}
} catch (IllegalAccessException | InvocationTargetException e) {
FieldError error = new FieldError(target.getClass().getSimpleName(), descriptor.getName()
, "bean validation fail");
errors.getFieldErrors().add(error);
}
}
}
}
}
}
register bean
#ControllerAdvice
public class WebDataBindHandler {
#Inject
private LocalValidatorFactoryBean localValidatorFactoryBean;
#InitBinder
void initBinder(WebDataBinder binder) {
binder.addValidators(new JSR303CollectionListValidator(localValidatorFactoryBean));
}
}

Related

Spring inject XML unmarshalled entity

I'm working on a Spring application and I'd like to know if there's any way I could specify in my configuration the path of an XML file, having it automatically unmarshalled into a Java object through JAXB (I may consider other libraries though) and then inject it into a bean.
A Google search yields different results but they seem more about injecting a marshaller/unmarshaller in your bean and then doing the work yourself (like this one https://www.intertech.com/Blog/jaxb-tutorial-how-to-marshal-and-unmarshal-xml/) and I'm more interested in delegating this boilerplate to Spring.
Thanks
You can implement your custom resource loader based on this article: Spicy Spring: Create your own ResourceLoader. It requires some assumptions:
Classes you want to load have all required annotation used by JAXB which allow deserialisation.
You can build JaxbContext using given list of classes.
You need to check yourself whether loaded class is what you expect.
Step 0 - create POJO
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "User")
#XmlAccessorType(XmlAccessType.FIELD)
public class User {
#XmlElement(name = "firstName")
private String firstName;
#XmlElement(name = "lastName")
private String lastName;
// getters, setters, toString
}
You need to predefine POJO model which will be loaded from XML files. Above example just present one class but it should be similar for all other POJO classes.
Step 1 - create unmarshaller
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
#Component
public class JaxbResourceUnmarshaller {
private JAXBContext context;
public JaxbResourceUnmarshaller() {
try {
context = JAXBContext.newInstance(User.class);
} catch (JAXBException e) {
throw new IllegalArgumentException(e);
}
}
public Object read(Resource resource) {
try {
Unmarshaller unmarshaller = context.createUnmarshaller();
return unmarshaller.unmarshal(resource.getInputStream());
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
Simple unmarshaller implementation where you need to create JAXBContext. You need to provide all root classes.
Step 2 - create class resource
import org.springframework.core.io.AbstractResource;
import java.io.IOException;
import java.io.InputStream;
public class ClassResource extends AbstractResource {
private final Object instance;
public ClassResource(Object instance) {
this.instance = instance;
}
public Object getInstance() {
return instance;
}
#Override
public String getDescription() {
return "Resource for " + instance;
}
#Override
public InputStream getInputStream() throws IOException {
return null;
}
}
I could not find any specific class which could allow to return POJO instance. Above class has simple job to transfer class from deserialiser to Spring bean. You can try to find better implementation or improve this one if needed.
Step 3 - create JAXB resource loader
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
public class JaxbResourceLoader implements ResourceLoader {
private static final String DB_URL_PREFIX = "jaxb:";
private final ApplicationContext applicationContext;
private final ResourceLoader delegate;
public JaxbResourceLoader(ApplicationContext applicationContext, ResourceLoader delegate) {
this.applicationContext = applicationContext;
this.delegate = delegate;
}
#Override
public Resource getResource(String location) {
if (location.startsWith(DB_URL_PREFIX)) {
JaxbResourceUnmarshaller unmarshaller = this.applicationContext.getBean(JaxbResourceUnmarshaller.class);
String resourceName = location.replaceFirst(DB_URL_PREFIX, "");
Resource resource = applicationContext.getResource("classpath:" + resourceName);
Object instance = unmarshaller.read(resource);
return new ClassResource(instance);
}
return this.delegate.getResource(location);
}
#Override
public ClassLoader getClassLoader() {
return this.delegate.getClassLoader();
}
}
In case resource definition starts from jaxb: let's try to handle it. In other case postpone to default implementation. Only classpath resources are supported.
Step 4 - register JAXB resource loader
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Ordered;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
#Component
public class ResourceLoaderBeanPostProcessor implements BeanPostProcessor, BeanFactoryPostProcessor, Ordered,
ResourceLoaderAware, ApplicationContextAware {
private ResourceLoader resourceLoader;
private ApplicationContext applicationContext;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.resourceLoader);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.resourceLoader = new JaxbResourceLoader(this.applicationContext, this.resourceLoader);
beanFactory.registerResolvableDependency(ResourceLoader.class, this.resourceLoader);
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
#Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
This is just a copy of register class from article with only some changes. Probably could be much improved with latest Spring version.
Step 5 - simple usage
Assume you have pojos/user.xml file in resource folder which looks like below:
<User>
<firstName>Rick</firstName>
<lastName>Bartez</lastName>
</User>
You can inject it into Spring context like below:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
#Configuration
public class JaxbAwareConfiguration {
#Bean
public AppOwner appOwner(ResourceLoader resourceLoader) {
ClassResource resource = (ClassResource) resourceLoader.getResource("jaxb:pojos/user.xml");
User user = (User) resource.getInstance();
return new AppOwner(user);
}
}
A little bit unpleasant is casting resource to ClassResource and instance to User class but it is a downside of this solution.

DAO and Spring Autowired

I tried to create an abstract Dao. I use Spring + Hibernate.
Here's my code.
Main class with configuration:
package ru.makaek.growbox.api;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
#ComponentScan(value = "ru.makaek.growbox")
#EnableAutoConfiguration(exclude = HibernateJpaAutoConfiguration.class)
#EnableTransactionManagement
#SpringBootApplication
public class Application {
#Autowired
private Environment env;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty("datasource.driver"));
dataSource.setUrl(env.getRequiredProperty("datasource.url"));
dataSource.setUsername(env.getRequiredProperty("datasource.username"));
dataSource.setPassword(env.getRequiredProperty("datasource.password"));
return dataSource;
}
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(getDataSource());
sessionFactory.setPackagesToScan(new String[]{"ru.makaek.growbox"});
return sessionFactory;
}
#Bean
public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
}
Rest controller
package ru.makaek.growbox.api.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import ru.makaek.growbox.api.model.data.entities.Device;
import ru.makaek.growbox.api.service.IStructureService;
#RestController
public class DeviceController extends AbstractController {
#Autowired
IStructureService structureService;
#RequestMapping(value = "/devices", method = RequestMethod.POST)
public Answer addDevice(#RequestBody Device device) {
structureService.addDevice(device);
return ok("Device has been added");
}
#RequestMapping(value = "/devices", method = RequestMethod.GET)
public Answer getDevices() {
return ok(structureService.getDevices());
}
#RequestMapping(value = "/devices/{deviceId}", method = RequestMethod.GET)
public Answer getDevice(#PathVariable Long deviceId) {
return ok(structureService.getDevice(deviceId));
}
}
Service layer. Interface
package ru.makaek.growbox.api.service;
import ru.makaek.growbox.api.model.data.entities.Device;
import java.util.List;
public interface IStructureService {
void addDevice(Device device);
List<Device> getDevices();
Device getDevice(Long deviceId);
}
Service layer. Implementation
package ru.makaek.growbox.api.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.makaek.growbox.api.model.data.dao.base.IDao;
import ru.makaek.growbox.api.model.data.entities.Device;
import java.util.List;
#Service
#Transactional
public class StructureService implements IStructureService {
IDao<Device> deviceDao;
#Autowired
public void setDao(IDao<Device> dao) {
deviceDao = dao;
dao.setClazz(Device.class);
}
#Override
public void addDevice(Device device) {
deviceDao.create(device);
}
#Override
public List<Device> getDevices() {
return deviceDao.findAll();
}
#Override
public Device getDevice(Long deviceId) {
return deviceDao.findOne(deviceId);
}
}
Entity
package ru.makaek.growbox.api.model.data.entities;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity(name = "devices")
#Data public class Device extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
DAO. Interface
package ru.makaek.growbox.api.model.data.dao.base;
import ru.makaek.growbox.api.model.data.entities.BaseEntity;
import java.util.List;
public interface IDao<T extends BaseEntity> {
T findOne(final long id);
void setClazz(Class<T> clazz);
List<T> findAll();
void create(final T entity);
T update(final T entity);
void delete(final T entity);
void deleteById(final long entityId);
}
Abstract DAO
package ru.makaek.growbox.api.model.data.dao.base;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import ru.makaek.growbox.api.model.data.entities.BaseEntity;
import ru.makaek.growbox.api.util.GBException;
import java.util.List;
public abstract class AbstractDao<T extends BaseEntity> implements IDao<T> {
private Class<T> clazz;
#Autowired
private SessionFactory sessionFactory;
public final void setClazz(Class<T> clazz) {
this.clazz = clazz;
}
public T findOne(long id) {
try {
return (T) getCurrentSession().get(clazz, id);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public List<T> findAll() {
try {
return getCurrentSession().createQuery("from " + clazz.getName()).list();
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public void create(T entity) {
try {
getCurrentSession().persist(entity);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public T update(T entity) {
try {
return (T) getCurrentSession().merge(entity);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public void delete(T entity) {
try {
getCurrentSession().delete(entity);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public void deleteById(long entityId) {
try {
T entity = findOne(entityId);
delete(entity);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
protected final Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
DAO. Implementation
package ru.makaek.growbox.api.model.data.dao;
import org.springframework.stereotype.Repository;
import ru.makaek.growbox.api.model.data.dao.base.AbstractDao;
import ru.makaek.growbox.api.model.data.entities.Device;
#Repository
public class DeviceDao extends AbstractDao<Device> {
}
I have one trouble. When I call GET http://host:port/devices API method I have null in the clazz variable in the AbstractDao.findAll() method. When I was debugging the code i found one interesting thing: in the service layer method deviceDao.getClazz() returned needed clazz (not null). But in method AbstractDao.findAll() I have null in clazz variable. Why? Please help.
Sorry for my English and formulation. I'm new in this site, Spring and English
You are overcomplicating things. Because you are using Spring Boot it is possible to just create generic interface that extends CrudRepository and add the methods you need and are not already present in there.
Take a look here https://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html

Specifying which validation group to use for a bean

Specs : hibernate-validator[5.2.4.Final], spring-context[4.2.2.RELEASE]
I am trying to make the solution described here work as below. But there are no constraint violations encountered & things just pass by fine. Why?
I have two beans, one parent , other child. The child definition is as below
package code;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
#Service("SampleBean")
#Validated
public class SampleBean {
#NotNull(message= "value can not be null" , groups = Group1.class)
// #NotNull(message= "value can not be null")
private Integer value;
#NotNull(message= "value1 can not be null" , groups = Group2.class)
// #NotNull(message= "value can not be null" )
private Integer value1;
public Integer getValue() {
return value;
}
public void setValue(#NotNull
Integer value) {
this.value = value;
}
public Integer getValue1() {
return value1;
}
public void setValue1(Integer value1) {
this.value1 = value1;
}
}
The Parent bean definition is as below :
package code;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
#Service("SampleBeanParent")
#Validated
public class SampleBeanParent {
public void acceptChildBean(#NotNull(message = "child cannot be null")
// #Valid
#Validated(Group1.class)
SampleBean childBean) throws NoSuchMethodException, SecurityException{
System.out.println("successfully finished");
}
}
The test class is as
package code;
import java.util.ArrayList;
import java.util.List;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, SecurityException{
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
SampleBean sampleBean = (SampleBean) context.getBean("SampleBean");
try{
SampleBeanParent parent = (SampleBeanParent) context.getBean("SampleBeanParent");
parent.acceptChildBean(sampleBean);
}
catch(ConstraintViolationException e){
System.out.println("there were validation errors");
}
}
}
By The way, i have setup appropriate spring level beans as below & it works fine without groups(i have commented the validation lines in above code without groups for the working case). So this is not the problem :)
package code;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#Configuration
#ComponentScan(basePackageClasses = {SampleBean.class})
public class SpringConfiguration {
#Bean(name = "validator")
public LocalValidatorFactoryBean initValidatorFactory(){
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor initValidationPostProcessor(){
return new MethodValidationPostProcessor();
}
}
I could do that the following way. The changed classes are as follows (I removed everything from the code that seemed to be redundant/unnecessary from the particular problem point of view)
#Service("SampleBeanParent")
#Validated(Group1.class)
public class SampleBeanParent {
public void acceptChildBean(
#Valid SampleBean childBean) throws NoSuchMethodException, SecurityException {
System.out.println("successfully finished");
}
}
#Service("SampleBean")
public class SampleBean {
#NotNull(message = "value can not be null", groups = Group1.class)
private Integer value;
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
Please also refer to this thread: Spring #Validated in service layer, it contains several useful points, among others quoted from one of the answers:
"The #Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations, like #Null or #Valid." - indicating that #Validated(Group1.class) SampleBean childBean in your example does not seem to be correct
The last answer is discussing the specific case when there is another annotation on the method parameter besides #Valid like in your case there was also #NotNull(message = "child cannot be null")

Injecting into a Jersey Resource class

I did try going through the following links
How to wire in a collaborator into a Jersey resource?
and
Access external objects in Jersey Resource class
But still i am unable to find a working sample which shows how to inject into a Resource class.
I am not using Spring or a web container.
My Resource is
package resource;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/something")
public class Resource
{
#MyResource
Integer foo = null;
private static String response = "SampleData from Resource";
public Resource()
{
System.out.println("...constructor called :" + foo);
}
#Path("/that")
#GET
#Produces("text/plain")
public String sendResponse()
{
return response + "\n";
}
}
My Provider is
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
#Provider
public class MyResourceProvider implements InjectableProvider<MyResource, Integer>
{
#Override
public ComponentScope getScope()
{
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(final ComponentContext arg0, final MyResource arg1, final Integer arg2)
{
return new Injectable<Object>()
{
#Override
public Object getValue()
{
return new Integer(99);
}
};
}
}
My EndpointPublisher is
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory;
class EndpointPublisher
{
public static void main(final String[] args)
{
final String address = "http://localhost:8080/";
final Map<String, String> config = new HashMap<String, String>();
config.put("com.sun.jersey.config.property.packages", "resource");
try
{
GrizzlyWebContainerFactory.create(address, config);
System.out.println("server started ....." + address);
callGet();
}
catch (final Exception e)
{
e.printStackTrace();
}
}
public static void callGet()
{
Client client = null;
ClientResponse response = null;
client = Client.create();
final WebResource resource =
client.resource("http://localhost:8080/something");
response = resource.path("that")
.accept(MediaType.TEXT_XML_TYPE, MediaType.APPLICATION_XML_TYPE)
.type(MediaType.TEXT_XML)
.get(ClientResponse.class);
System.out.println(">>>> " + response.getResponseDate());
}
}
My annotation being
#Retention(RetentionPolicy.RUNTIME)
public #interface MyResource
{}
But when i execute my EndpointPublisher i am unable to inject foo!!
Your InjectableProvider is not implemented correctly. The second type parameter should not be the type of the field you are trying to inject - instead it should be the context - either java.lang.reflect.Type class or com.sun.jersey.api.model.Parameter class. In your case, you would use Type. So, your InjectableProvider implementation should look as follows:
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import java.lang.reflect.Type;
#Provider
public class MyResourceProvider implements InjectableProvider<MyResource, Type> {
#Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(final ComponentContext arg0, final MyResource arg1, final Type arg2) {
if (Integer.class.equals(arg2)) {
return new Injectable<Integer>() {
#Override
public Integer getValue() {
return new Integer(99);
}
};
} else {
return null;
}
}
}
There is a helper class for per-request injectable providers (PerRequestTypeInjectableProvider) as well as singleton injectable providers (SingletonTypeInjectableProvider), so you can further simplify it by inheriting from that:
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
#Provider
public class MyResourceProvider extends PerRequestTypeInjectableProvider<MyResource, Integer> {
public MyResourceProvider() {
super(Integer.class);
}
#Override
public Injectable<Integer> getInjectable(ComponentContext ic, MyResource a) {
return new Injectable<Integer>() {
#Override
public Integer getValue() {
return new Integer(99);
}
};
}
}
Note that for these helper classes the second type parameter is the type of the field.
And one more thing - the injection happens after the constructor is called, so the constructor of your resource will still print out ...constructor called :null, but if you change your resource method to return foo, you'll see the response you'll get will be 99.
This solution works well and I wanted to share what I found to enable CDI on jersey resources.
Here is the simplest bean ever :
package fr.test;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
#RequestScoped
public class Test {
private int i;
#PostConstruct
public void create() {
i = 6;
}
public int getI() {
return i;
}
}
In your resource class, we just inject this bean, as we would do in a any normal context :
package fr.test;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/login")
public class LoginApi {
#Inject
private Test test;
#GET
#Produces("text/plain")
public String getIt() {
return "Hi there!" + test;
}
}
And here is the key. We define a Jersey "InjectionProvider" which will be responsible of beans' resolution :
package fr.test;
import javax.inject.Inject;
import java.lang.reflect.Type;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import fr.xxxxxxxxxx.ApplicationBeans;
#Provider
public class InjectionProvider implements InjectableProvider<Inject, Type> {
public ComponentScope getScope() {
// CDI will handle scopes for us
return ComponentScope.Singleton;
}
#Override
public Injectable<?> getInjectable(ComponentContext context,
Inject injectAnno, Type t) {
if (!(t instanceof Class))
throw new RuntimeException("not injecting a class type ?");
Class<?> clazz = (Class<?>) t;
final Object instance = ApplicationBeans.get(clazz);
return new Injectable<Object>() {
public Object getValue() {
return instance;
}
};
}
}
InjectableProvider is typed with the kind of annotation we are handling, and the context type (here, normal java type)
ApplicationBeans is just a simple helper for bean resolution. Here is its content :
package fr.xxxxxxxxxx;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import fr.xxxxxxxxxxxxx.UnexpectedException;
/**
* Gives direct access to managed beans - Designed to be used from unmanaged code
*
* #author lgrignon
*
*/
#ApplicationScoped
public class ApplicationBeans
{
protected static ApplicationBeans instance;
#Inject
private BeanManager beanManager;
/**
* Gets instance
*
* #return Instance from managed environment
*/
public static ApplicationBeans instance()
{
if (instance == null)
{
BeanManager beanManager;
InitialContext ctx = null;
try
{
ctx = new InitialContext();
beanManager = (BeanManager)ctx.lookup("java:comp/BeanManager");
}catch(NamingException e)
{
try
{
beanManager = (BeanManager)ctx.lookup("java:app/BeanManager");
}catch(NamingException ne)
{
throw new UnexpectedException("Unable to obtain BeanManager.", ne);
}
}
instance = getBeanFromManager(beanManager, ApplicationBeans.class);
}
return instance;
}
/**
* Gets bean instance from context
*
* #param <T>
* Bean's type
* #param beanType
* Bean's type
* #param annotations
* Bean's annotations
* #return Bean instance or null if no
*/
public static <T> T get(final Class<T> beanType, Annotation... annotations)
{
return instance().getBean(beanType, annotations);
}
/**
* Gets bean instance from context
*
* #param <T>
* Bean's type
* #param beanType
* Bean's type
* #param annotations
* Bean's annotations
* #return Bean instance or null if no
*/
public <T> T getBean(final Class<T> beanType, Annotation... annotations)
{
return getBeanFromManager(beanManager, beanType, annotations);
}
#SuppressWarnings("unchecked")
private static <T> T getBeanFromManager(BeanManager beanManager, final Class<T> beanType, Annotation... annotations)
{
Set<Bean<?>> beans = beanManager.getBeans(beanType, annotations);
if (beans.size() > 1)
{
throw new UnexpectedException("Many bean declarations found for type %s (%s)", beanType.getSimpleName(), beansToString(beans));
}
if (beans.isEmpty())
{
throw new UnexpectedException("No bean declaration found for type %s", beanType.getSimpleName());
}
final Bean<T> bean = (Bean<T>)beans.iterator().next();
final CreationalContext<T> context = beanManager.createCreationalContext(bean);
return (T)beanManager.getReference(bean, beanType, context);
}
private static String beansToString(Collection<Bean<?>> beans)
{
String[] beansLabels = new String[beans.size()];
int i = 0;
for (final Bean<?> bean : beans)
{
beansLabels[i++] = bean.getName();
}
return Arrays.toString(beansLabels);
}
}
Hope this will help those who want to enable CDI injection in their Jersey resources.
Bye !

Spring validator: having both annotation and validator implementation

Is it possible to have both a validator for a form and annotation constraints?
For example to have in a form object this field:
#NotEmpty
private String date;
but then validate the date's pattern in a validator.
I know there is the pattern annotation but I just want to see if I can use both types of validating.
Here is the link to a very good site where it's explained how you can combine the JSR-303 validator with the spring validator.
I'll present next my solution that works. Hope it helps.
My abstract Validator:
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.validation.Errors;
public abstract class AbstractValidator implements org.springframework.validation.Validator, ApplicationContextAware,
ConstraintValidatorFactory {
#Autowired
private Validator validator;
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
Map<String, T> beansByNames = applicationContext.getBeansOfType(key);
if (beansByNames.isEmpty()) {
try {
return key.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Could not instantiate constraint validator class '" + key.getName() + "'", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not instantiate constraint validator class '" + key.getName() + "'", e);
}
}
if (beansByNames.size() > 1) {
throw new RuntimeException("Only one bean of type '" + key.getName() + "' is allowed in the application context");
}
return (T) beansByNames.values().iterator().next();
}
public boolean supports(Class<?> c) {
return true;
}
public void validate(Object objectForm, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(objectForm);
for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
String propertyPath = constraintViolation.getPropertyPath().toString();
String message = constraintViolation.getMessage();
errors.rejectValue(propertyPath, "", message);
}
addExtraValidation(objectForm, errors);
}
protected abstract void addExtraValidation(Object objectForm, Errors errors);
}
An actual Validator:
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import ro.scorpionsoftware.demo3.dao.AbstractValidator;
#Component(value="doctorValidator")
public class DoctorValidator extends AbstractValidator {
#Override
protected void addExtraValidation(Object objectForm, Errors errors) {
//perform typical validation
//can autowire to context
}
}
A controller: (At the end it's the binding of the #Valid with the validator)
#Controller
public class DoctorEditController {
#Autowired
private DoctorValidator doctorValidator;
#RequestMapping(value = "/doctorEdit", method = RequestMethod.POST)
public String processSubmit(
#ModelAttribute("doctorForm") #Valid DoctorForm df,
BindingResult result,
ModelMap model) {
...
}
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(doctorValidator);
}
}
In context declare the JSR-303 validator:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
With this approach you can get the context in both the actual validator and any other custom annotation you'd like to implement.
You can compose together annotations to use multiple validators, so it would be something like this.
#NotEmpty
#Pattern("") // not sure of syntax here
#Target(ElementType.METHOD)
#Retention( RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {})
#Documented
public #interface DatePattern {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

Categories

Resources