generic attribute in java with multiple fields - java

i have a question. do you know how can i use the same field with différent generic attribut implementation.
I have an interface for modelMapper that i am using to generalise the Type of objects that will be mapped
public interface IMapper<S, D> {
D map(S src, Class destination);
}
I also have this implementation of this interface :
#Component
public class ModelMapperImpl<S,D> implements IMapper<S,D> {
#Autowired
private ModelMapper mapper;
#Override
public D map(S src, Class destination) {
return (D) mapper.map(src, destination);
}
}
The problem is this, i need to have for each mapping a field in my class which is not a good practice i think and i am searching if there is a way to have only one generic field for all my type of mappings
#Service
public class UserService {
private IMapper<AddressDTO, Address> mapperAddress;
private IMapper<UsersDTO, Users> mapperUser; // i want to have only one IMapper field
is there is a way to do That ? thank you guys for your help.

I'll assume you are trying to make it easy to change the mapping library(moving from ModelMapper to something else, if it's needed). Then you can make the method generic, not the class.
public interface IMapper {
<S, D> D map(S src, Class<D> destination);
}
Impl:
#Component
public class ModelMapperImpl implements IMapper {
#Autowired
private ModelMapper mapper;
#Override
public <S, D> D map(S src, Class<D> destinationClass) {
return mapper.map(src, destinationClass);
}
}
Now you need only one IMapper in your service.
#Service
public class UserService {
#Autowired
private IMapper mapper;
}

Related

Autowire Java Bean created with NewInstance from an external library

I am doing a Spring Boot Project and using the OpenCSV library to parse some csvs into POJOs to be persisted to db.
OpenCSV uses the annotation #CsvCustomBindByName to map a CSV field to a Java object.
The converter = DepartmentConverter.class is a custom converter that is instantiated with:
Class<? extends AbstractBeanField<T,K>>.newInstance()
by the library, at runtime.
The problem is that because the custom field converter is instantiated reflectively by the OpenCSV library, it cant autowire beans because it is not registered in the Spring Context.
How can i make that dynamically instantiated converter be aware of the Spring context or the other way around. Some kind of interceptor? Thanks!
//Spring Managed class
public class Specialization {
#CsvCustomBindByName(required = true, converter = DepartmentConverter.class)
private Department department;
....
}
In my DepartmentConverter i need to use a Spring JPARepository to retrieve some data. DepartmentRepository can not be autowired.
#Component
public class DepartmentConverter extends AbstractBeanField<Department, String> {
#Autowired
private DepartmentRepository departmentRepository;
public DepartmentConverter() {
}
#Override protected Object convert(String val) throws CsvConstraintViolationException, ResourceNotFoundException {
//use departmentRepository
...
}
}
The newInstance() call you're referring to is in the HeaderColumnNameMappingStrategy class, which calls the instantiateCustomConverter() method to do the newInstance() call.
Create a subclass and override the method:
#Override
protected BeanField<T, K> instantiateCustomConverter(Class<? extends AbstractBeanField<T, K>> converter) throws CsvBadConverterException {
BeanField<T, K> c = super.instantiateCustomConverter(converter);
// TODO autowire here
return c;
}
As can be seen in this answer to Spring #Autowired on a class new instance, you can do the autowiring as follows:
autowireCapableBeanFactory.autowireBean(c);
So the subclass would be something like:
public class AutowiredConverterMappingStrategy extends HeaderColumnNameMappingStrategy {
private final AutowireCapableBeanFactory beanFactory;
public AutowiredConverterMappingStrategy(AutowireCapableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
#Override
protected BeanField<T, K> instantiateCustomConverter(Class<? extends AbstractBeanField<T, K>> converter) throws CsvBadConverterException {
BeanField<T, K> c = super.instantiateCustomConverter(converter);
this.beanFactory.autowireBean(c);
return c;
}
}
To use it, you'd need something like this:
#Component
class MyComponent {
#Autowired
private AutowireCapableBeanFactory beanFactory;
public <T> List<T> parseCsvToBean(Reader reader, Class<? extends T> type) {
return new CsvToBeanBuilder(reader)
.withType(type)
.withMappingStrategy(new AutowiredConverterMappingStrategy(this.beanFactory))
.build()
.parse();
}
}
That is of course just an example. Your CsvToBean setup may be more complex, but the key part is the withMappingStrategy() call, and that the code is itself in a Spring Bean, so it has access to the bean factory.

SpringBoot: create object from generic type in generic mapper

I have a lot of entity extend Catalog entity and as well as have a lot of dto that extent CatalogDto
And I have a generic repository, service, and mapper as follows
My repository:
#Repository
public interface CatalogRepository<T extends Catalog> extends JpaRepository<T, Integer>{
}
My service:
#Service
#Transactional
public class CatalogServiceImpl<T extends Catalog,Dto extends CatalogDto>{
private final Logger log = LoggerFactory.getLogger(CatalogServiceImpl.class);
private final CatalogRepository<T> repository;
private CatalogMapper<T,Dto> catalogMapper=new CatalogMapper<T,Dto>() {};
public CatalogServiceImpl(CatalogRepository<T> repository) {
this.repository = repository;
}
}
My Mapper:
public abstract class CatalogMapper<T extends Catalog,Dto extends CatalogDto> implements Rapper<T,Dto> {
#Override
public Dto entityToDto(T entity) {
return null;
}
#Override
public T dtoToEntity(Dto dto) {
return null;
}
}
I want to create an object from T in dtoToEntity method and an object from Dto in entityToDto method in CatalogMapper class
I think that these two methods should be abstract because every mapper probably works in different ways. Anyway you can provide a base implementation like this
public T dtoToEntity(Dto dto) throws InstantiationException, IllegalAccessException {
T entity = (T) ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
BeanUtils.copyProperties(dto, entity);
return entity;
}
public Dto entityToDto(T entity) throws InstantiationException, IllegalAccessException {
Dto dto = (Dto) ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
BeanUtils.copyProperties(entity, dto);
return dto;
}
Using ParameterizedType of the generic class you can create a new instance and then execute a simple copyProperties

Spring Data JPA method + REST: Enum to Integer conversion

I've got an endpoint:
/api/offers/search/findByType?type=X
where X should be an Integer value (an ordinal value of my OfferType instance), whereas Spring considers X a String and will be applying its StringToEnumConverterFactory with the StringToEnum convertor.
public interface OfferRepository extends PagingAndSortingRepository<Offer, Long> {
List<Offer> findByType(#Param("type") OfferType type);
}
So I wrote a custom Converter<Integer, OfferType> which simply get a instance by the given ordinal number:
public class IntegerToOfferTypeConverter implements Converter<Integer, OfferType> {
#Override
public OfferType convert(Integer source) {
return OfferType.class.getEnumConstants()[source];
}
}
Then I registered it properly with a Configuration:
#EnableWebMvc
#Configuration
#RequiredArgsConstructor
public class GlobalMVCConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new IntegerToOfferTypeConverter());
}
}
And I was expected that all requests to findByType?type=X will pass through my converter, but they do not.
Is any way to say that all enums defined as a request parameters have to be provided as an Integer? Furthermore, is any way to say it globally, not just for a specific enum?
EDIT: I've found IntegerToEnumConverterFactory in my classpath that does all I need. And it is registered with DefaultConversionService which is a default service for conversion. How can that be applied?
EDIT2: It's such a trivial thing, I was wondering if there is a property to turn enum conversion on.
EDIT3: I tried to write a Converter<String, OfferType> after I had got String from TypeDescriptor.forObject(value), it didn't help.
EDIT4: My problem was that I had placed custom converter registration into a MVC configuration (WebMvcConfigurerAdapter with addFormatters) instead of a REST Repositories one (RepositoryRestConfigurerAdapter with configureConversionService).
Spring parses the query parameters as Strings. I believe it always uses Converter<String, ?> converters to convert from the query parameters to your repository methods parameters. It uses an enhanced converter service, since it registers its own converters such as Converter<Entity, Resource>.
Therefore you have to create a Converter<String, OfferType>, e.g.:
#Component
public class StringToOfferTypeConverter implements Converter<String, OfferType> {
#Override
public OfferType convert(String source) {
return OfferType.class.getEnumConstants()[Integer.valueOf(source)];
}
}
And then configure this converter to be used by the Spring Data REST, in a class extending RepositoryRestConfigurerAdapter:
#Configuration
public class ConverterConfiguration extends RepositoryRestConfigurerAdapter {
#Autowired
StringToOfferTypeConverter converter;
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
}
I tried to add this to the basic tutorial, added a simple enum to the Person class:
public enum OfferType {
ONE, TWO;
}
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private OfferType type;
public OfferType getType() {
return type;
}
public void setType(OfferType type) {
this.type = type;
}
}
And when I call:
http://localhost:8080/people/search/findByType?type=1
I get the result without errors:
{
"_embedded" : {
"people" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/search/findByType?type=1"
}
}
}
To implement a global Enum converter, you have to create a factory and register it in the configuration using the method: conversionService.addConverterFactory(). The code below is a modified example from the documentation:
public class StringToEnumFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnum(targetType);
}
private final class StringToEnum<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnum(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
Integer index = Integer.valueOf(source);
return enumType.getEnumConstants()[index];
}
}
}

Inject Spring repositories and their provided class

Is there a way to extract all Repositories as well as the Class<T> they are providing?
I have some Repositories which are annotated with a Qualifier:
#NetworkDataProvider
#Repository
public interface SwitchRepository extends CrudRepository<Switch, SwitchPK>
The beans they provide are annotated with metadata which defines the way they will be displayed in the GUI:
#Entity
#Table(...)
public class Switch implements Serializable {
#Column(name = "switch_name")
#NotNull
#UIName(value = "name of switch")
#UIPrio(value = 2)
private String name;
Now I have to extract all Repositories and their corresponding classes:
#Autowired
#NetworkDataProvider
List<Repository<?>> repositories;
public List<RepositoryClassTuple> getAllNetworkDataProvider() {
return repositories.map(r ->
new RepositoryClassTuple(r, /* how to do this */ r.getProidedClass())).asList();
}
Is there any Way to do this? I really need the Annotations of the repository provided data beans.
Spring Data has a type called Repositories that takes a ListableBeanFactory which can then be used to inspect the repositories:
Repositories repositories = new Repositories(beanFactory);
for (Class<?> domainType : repositories) {
RepositoryInformation info = repositories.getRepositoryInformationFor(domainType);
…
}
I still wonder why you need to mess with this low-level stuff. Really nothing that normal application code should do 🙃.
You can define an interface :
public interface NetworkRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
Class<T> getType();
}
Then you can define your implementions like this :
public interface PersonRepository extends NetworkRepository<Person, Long> {
#Override
default Class<Person> getType() {
return Person.class;
}
}
public interface AnimalRepository extends NetworkRepository<Animal, Long> {
#Override
default Class<Animal> getType() {
return Animal.class;
}
}
Then to get them all :
#Autowired
Collection<NetworkRepository> networkRepositories;
Finally you can use the getType() method to get your class information.
IMPORTANT : you have to declare NetworkRepository in a package not scannable by Spring Data.
You can create an interface named MyRepository
public interface MyRepository{
}
Then, all your repository classes must implement your interface:
#Repository("foo")
public class FooExample implements MyRepository{
}
#Repository("bar")
public class BarExample implements MyRepository{
}
Finally you can have a map of MyRepository beans injected:
#Component
public class ExampleConsumer {
private final Map<String, MyRepository> repositories;
#Autowired
public ExampleConsumer(Map<String, MyRepository> repositories) {
this.examples = examples;
}
}
In this case the map will contain two entries:
"foo" -> FooExample instance
"bar" -> BarExample instance
Another way is to use java Reflection to read the annotation
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
Here there is a tutorial
http://tutorials.jenkov.com/java-reflection/annotations.html

Spring 3.2 Autowire generic types

So I have a number of generics in Spring 3.2 and ideally my architecture would look something like this.
class GenericDao<T>{}
class GenericService<T, T_DAO extends GenericDao<T>>
{
// FAILS
#Autowired
T_DAO;
}
#Component
class Foo{}
#Repository
class FooDao extends GenericDao<Foo>{}
#Service
FooService extends GenericService<Foo, FooDao>{}
Unfortunately with multiple implementations of the generics the autowiring throws an error about multiple matching bean definitions. I assume this is because #Autowired processes before type erasure. Every solution I've found or come up with looks ugly to me or just inexplicably refuses to work. What is the best way around this problem?
How about adding a constructor to the GenericService and move the autowiring to the extending class, e.g.
class GenericService<T, T_DAO extends GenericDao<T>> {
private final T_DAO tDao;
GenericService(T_DAO tDao) {
this.tDao = tDao;
}
}
#Service
FooService extends GenericService<Foo, FooDao> {
#Autowired
FooService(FooDao fooDao) {
super(fooDao);
}
}
Update:
As of Spring 4.0 RC1, it is possible to autowire based on generic type, which means that you can write a generic service like
class GenericService<T, T_DAO extends GenericDao<T>> {
#Autowired
private T_DAO tDao;
}
and create multiple different Spring beans of it like:
#Service
class FooService extends GenericService<Foo, FooDao> {
}
Here is a closest solution. The specialized DAOs are annotated at the business layer. As in the question from OP, the best effort would be having an annotated DAO in the EntityDAO generic template itself. Type erasure seems to be not allowing the specialized type information to get passed onto the spring factories [resulting in reporting matching beans from all the specialized DAOs]
The Generic Entity DAO template
public class EntityDAO<T>
{
#Autowired
SessionFactory factory;
public Session getCurrentSession()
{
return factory.getCurrentSession();
}
public void create(T record)
{
getCurrentSession().save(record);
}
public void update(T record)
{
getCurrentSession().update(record);
}
public void delete(T record)
{
getCurrentSession().delete(record);
}
public void persist(T record)
{
getCurrentSession().saveOrUpdate(record);
}
public T get(Class<T> clazz, Integer id)
{
return (T) getCurrentSession().get(clazz, id);
}
}
The Generic Entity Based Business Layer Template
public abstract class EntityBusinessService<T>
implements Serializable
{
public abstract EntityDAO<T> getDAO();
//Rest of code.
}
An Example Specialized Entity DAO
#Transactional
#Repository
public class UserDAO
extends EntityDAO<User>
{
}
An Example Specialized Entity Business Class
#Transactional
#Service
#Scope("prototype")
public class UserBusinessService
extends EntityBusinessService<User>
{
#Autowired
UserDAO dao;
#Override
public EntityDAO<User> getDAO()
{
return dao;
}
//Rest of code
}
You can remove the #autowire annotation and perform delayed “autowire” using #PostConstruct and ServiceLocatorFactoryBean.
Your GenericService will look similar to this
public class GenericService<T, T_DAO extends GenericDao<T>>{
#Autowired
private DaoLocator daoLocatorFactoryBean;
//No need to autowried, autowireDao() will do this for you
T_DAO dao;
#SuppressWarnings("unchecked")
#PostConstruct
protected void autowireDao(){
//Read the actual class at run time
final Type type;
type = ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[1];
//figure out the class of the fully qualified class name
//this way you can know the bean name to look for
final String typeClass = type.toString();
String daoName = typeClass.substring(typeClass.lastIndexOf('.')+1
,typeClass.length());
daoName = Character.toLowerCase(daoName.charAt(0)) + daoName.substring(1);
this.dao = (T_DAO) daoLocatorFactoryBean.lookup(daoName);
}
daoLocatorFactoryBean does the magic for you.
In order to use it you need to add an interface similar to the one below:
public interface DaoLocator {
public GenericDao<?> lookup(String serviceName);
}
You need to add the following snippet to your applicationContext.xml
<bean id="daoLocatorFactoryBean"
class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="serviceLocatorInterface"
value="org.haim.springframwork.stackoverflow.DaoLocator" />
</bean>
This is a nice trick and it will save you little boilerplate classes.
B.T.W I do not see this boilerplate code as a big issue and the project I working for uses matsev approach.
Why do you want a generic service ? Service classes are meant for specific units of work involving multple entities. You can just inject a repository straight into a controller.
Here is an example of generic repository with constructor argument, you could also make each method Generic instead and have no constructor argument. But each method call would require class as parameter:
public class DomainRepository<T> {
#Resource(name = "sessionFactory")
protected SessionFactory sessionFactory;
public DomainRepository(Class genericType) {
this.genericType = genericType;
}
#Transactional(readOnly = true)
public T get(final long id) {
return (T) sessionFactory.getCurrentSession().get(genericType, id);
}
Example of bean definition for the generic repository - you could have multple different beans, using different contstructor args.
<bean id="tagRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="com.yourcompnay.domain.Tag"/>
</bean>
Depdncy injection of bean using resource annotation
#Resource(name = "tagRepository")
private DomainRepository<Tag> tagRepository;
And this allows the Domainreposiroty to be subclassed for specific entities/methods, which woul dallow autowiring :
public class PersonRepository extends DomainRepository<Person> {
public PersonRepository(){
super(Person.class);
}
...
You should use autowiring in classes which extends these generics
For this question one needs to understand about what autowire is. In common terms we can say that through autowire we create a object instance/bean at the time of deployment of the web app. So now going with the question if you are declaring autowiring in multiple places with the same name. Then this error comes. Autowiring can be done in multiple ways so if you are using multiple type of autowiring technique, then also one could get this error.
Complete Generic Solution using Spring 4:
Domain Class
#Component
class Foo{
}
#Component
class Bar{
}
DAO Layer
interface GenericDao<T>{
//list of methods
}
class GenericDaoImpl<T> implements GenericDao<T>{
#Autowired
SessionFactory factory;
private Class<T> domainClass; // Get Class Type of <T>
public Session getCurrentSession(){
return factory.getCurrentSession();
}
public DaoImpl() {
this.domainClass = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), DaoImpl.class);
}
//implementation of methods
}
interface FooDao extends GenericDao<Foo>{
//Define extra methods if required
}
interface BarDao extends GenericDao<Bar>{
//Define extra methods if required
}
#Repository
class FooDao extends GenericDaoImpl<Foo> implements FooDao{
//implementation of extra methods
}
#Repository
class BarDao extends GenericDaoImpl<Bar> implements BarDao{
//implementation of extra methods
}
Service Layer
interface GenericService<T>{
//List of methods
}
class GenericServiceImpl<T> implements GenericService<T>{
#Autowire
protected GenericDao<T> dao; //used to access DAO layer
}
class FooService extends GenericService<Foo>{
//Add extra methods of required
}
class BarService extends GenericService<Bar>{
//Add extra methods of required
}
#Service
class FooServiceImpl extends GenericServiceImpl<Foo> implements GenericService<Foo>{
//implementation of extra methods
}
#Service
class BarServiceImpl extends GenericServiceImpl<Bar> implements GenericService<Bar>{
//implementation of extra methods
}

Categories

Resources