Wiring Repository interfaces in service layer dynamically - spring

The Service class and my repository classes in my spring MVC set up are something like this -
public class ObjectServiceImpl implements ObjectService {
#Autowired
Temp1Repo temp1Repo;
#Autowired
Temp2Repo temp2Repo;
...
}
public interface Temp1Repo extends CrudRepository<Temp1, Integer> {
}
public interface Temp2Repo extends CrudRepository<Temp2, Integer> {
}
Now, in my service class, i am getting a object of a type Temp1, I have to call temp1Repo.save(). If I get an object of Temp2, I have to call temp2Repo.save() and so on...
How do i achieve this?

Seems fairly simple to just have an if statement:
if(object instanceof Temp1) {
temp1Repo.save((Temp1) object);
} else if(object instanceof Temp2) {
temp2Repo.save((Temp2) object);
}
Or perhaps you are looking for a more generic way?

I suppose that you want to regroup all repositories in one. Something like
#SuppressWarnings("rawtypes")
public class ObjectServiceImpl {
#Autowired
private CrudRepository[] repositories;
private Map<Class<?>, CrudRepository> repositoryMap = new HashMap<Class<?>, CrudRepository>();
#PostConstruct
public void init() {
for (CrudRepository r : repositories)
repositoryMap.put(getType(r), r);
}
private Class<?> getType(CrudRepository repository) {
Type[] types = repository.getClass().getGenericInterfaces();
for (Type t : types) {
if (t instanceof ParameterizedType)
return (Class<?>) ((ParameterizedType) t).getActualTypeArguments()[0];
}
throw new IllegalStateException("Check repositories...");
}
public void save(Object entity) {
repositoryMap.get(entity.getClass()).save(entity);
}
public <T> T get(Object id, Class<T> clazz) {
return repositoryMap.get(clazz).findOne(id);
}
....
}
Consider to use EntityManager directly, but could be useful anyway...

Following the code you wrote, Spring will rise an exception at startup time if any injection is missing.
What you want to do is a dynamic Module load, depending on a condition you omitted within your question.
You probably have to use XML configuration style and create a by condition spring context and load the correct one to be used.
Cheers

Related

try Open - Close principle in my code but got strange behavior in java

public interface MainInterface {
}
#Transactional
#Service
#Qualifier("A")
#Primary
public class A implements MainInterface {
}
#Transactional
#Service
#Qualifier("B")
public interface B implements MainInterface {
}
public interface C implements MainInterface {
}
#Service
public class InterfaceFactory {
public MainInterface getInstance(String type ) {
if (type.equalsIgnoreCase("A") ) {
return new A();
} else if (type.equalsIgnoreCase("B") ) {
return new B();
} else if (type.equalsIgnoreCase("C") ) {
return new C();
}else {
return null;
}
}
}
#Service
public class EventInterceptor {
public ADTO callEvent(A a, Entity entity) throws URISyntaxException {
System.out.print("A");
}
public ADTO callEvent(B b, Entity entity) throws URISyntaxException {
System.out.print("B");
}
public ADTO callEvent(C c, Entity entity) throws URISyntaxException {
System.out.print("C");
}
public ADTO callEvent(MainInterface m, Entity entity) throws
URISyntaxException {
System.out.print("MainInterface );
}
}
//------------ i'm calling it like so
MainInterface obj= interfaceFactory.getInstanceInterface("A");
gateWayInterceptor.callEvent( obj,entity)));
The problem that it always prints MainInterface
and always enter callEvent(MainInterface m, Entity entity) even if the return type is A OR B OR C this is open close Principle implementation as I understand
I tried to make it generic type the factory but I couldn't
I tried to remove callEvent(MainInterface m, Entity entity) but I got exception
I did the same as the code here in that blog
https://blog.jetbrains.com/upsource/2015/08/31/what-to-look-for-in-a-code-review-solid-principles-2/ about sold - open close Principle
should enter callEvent(A a, Entity entity) not callEvent(MainInterface m, Entity entity)
Note i'm using Java 8
I'm not primarily a Java developer but would assume that it's because the obj variable you are passing in to CallEvent is declared as type MainInterface... try casting it as you pass it in, i.e. use "(A)obj" rather than just "obj" and see what you get.
Also, I know you mention you are playing with the Open/Closed principle here but it's worth mentioning this would technically violate Liskov's Substitution Principle which essentially declares that if you do something with a base class you should get the same result if you do that thing with a sub class.

inject spring repository into static method

I generate a list of instances which implement interface CheckInterface
using reflection, but some of those instances need to use JPA repository
,as they are new by reflection so Spring will not inject repository for them.
So how can I inject repository into these instance
Or how can I delegate these class to spring as #Bean and iterate them?
#Component
#Log4j
public class ValidationRegister {
public static HashMap<String, CheckItem> itemHashMap = new HashMap<>();
static {
if (itemHashMap.size() == 0) {
Reflections reflections = new Reflections(CheckItem.class.getPackage().getName());
Set<Class<? extends CheckItem>> itemClasses =
reflections.getSubTypesOf(CheckItem.class);
for (Class<? extends CheckItem> checkItemClass : itemClasses) {
try {
itemHashMap.put(checkItemClass.getName(), checkItemClass.newInstance());
} catch (Exception e) {
log.error("ValidationRegister fail : ", e);
}
}
}
}
}
public class LPHYCZ extends CheckItem {
#Autowired
ClaimDataEntityRepository claimDataEntityRepository;
#Override
public boolean check(ClaimRequest request, List<String> errorList) {
ClaimDataEntity claimDataEntity = claimDataEntityRepository.findByClaimId(request.getClaimId());
if (claimDataEntity != null) {
return true;
}
return false;
}
}
When I get instance of LPHYCZ from ValidationRegister.itemHashMap its
claimDataEntityRepository will be null
Just make your Reflections reflections a Spring Bean and get it injected into 'ValidationRegister' then use it to initialize your HashMap your hashmap still can be static, if you want it to be shared by multiple ValidationRegister instances, but the injection won't happen on static fields.

Spring Boot YML Config Class Inheritance

Is it possible to use inheritance in Spring Boot YML configuration classes? If so, how would that be accomplished?
For example:
#ConfigurationProperties(prefix="my-config")
public class Config {
List<Vehicle> vehicles;
}
And the class (or interface) "Vehicle" has two implementations: Truck and Car. So the YAML might look like:
my.config.vehicles:
-
type: car
seats: 3
-
type: truck
axles: 3
I do not think it is possible (at least not that I know of). You could however design your code as follow:
Inject the properties into a Builder object
Define an object with all properties, which we'll call the VehicleBuilder (or factory, you choose its name).
The VehicleBuilders are injected from the Yaml.
You can then retrieve each builder's vehicle in a #PostConstruct block. The code:
#ConfigurationProperties(prefix="my-config")
#Component
public class Config {
private List<VehicleBuilder> vehicles = new ArrayList<VehicleBuilder>();
private List<Vehicle> concreteVehicles;
public List<VehicleBuilder> getVehicles() {
return vehicles;
}
public List<Vehicle> getConcreteVehicles() {
return concreteVehicles;
}
#PostConstruct
protected void postConstruct(){
concreteVehicles = vehicles.stream().map(f -> f.get())
.collect(Collectors.<Vehicle>toList());
}
}
The builder:
public class VehicleBuilder {
private String type;
private int seats;
private int axles;
public Vehicle get() {
if ("car".equals(type)) {
return new Car(seats);
} else if ("truck".equals(type)) {
return new Trunk(axles);
}
throw new AssertionError();
}
public void setType(String type) {
this.type = type;
}
public void setSeats(int seats) {
this.seats = seats;
}
public void setAxles(int axles) {
this.axles = axles;
}
}

conditional #Autowired?

I have a HsqldbReconciler (for "work" with a HSQLDB database) which I autowired, like:
#Autowired
HsqldbReconciler hsqldbReconciler;
In Future there will be a OracleReconciler, MssqlReconciler, etc. I will need to use them accordingly to the type of connection a user has chosen.
How should I implement this? Usually I would have a kind of factory, which returns only the needed Reconciler. The only way in spring, I can currently imagine, is to Autowire an instance of each Reconciler, then use one of them in the code. Is there a better way?
make a Factory Class that will contain all your beans, e.g
#Component
class Factory{
#Autowired HsqldbReconciler hsqldb;
#Autowired OracleReconciler oracle;
#Autowired MssqlReconciler mssql;
public Object getInstance(String type){
switch(type){
case "mssql" : return mssql;
case "oracle" : return oracle;
// and so on
default : return null;
}
}
}
Now use this Factory as follows
class SomeClass{
#Autowired private Factory factory;
public Object someMethod(){
Object reconciler = factory.getInstance("mssql");
((MssqlReconciler)reconciler).someMethod();
}
}
Define them in your Config with the same name, but different conditions:
#Bean(name = "dbReconciler")
#Conditional(HsqldbReconcilerEnabled.class)
public ReconcilerBase getHsqldbReconciler() {
return new HsqldbReconciler();
}
#Bean(name = "dbReconciler")
#Conditional(OracleReconcilerEnabled.class)
public ReconcilerBase getOracleReconciler() {
return new OracleReconciler();
}
#Bean(name = "dbReconciler")
#Conditional(MssqlReconcilerEnabled.class)
public ReconcilerBase getMssqlReconciler() {
return new MssqlReconciler();
}
create conditions reading from app.properties:
HsqldbReconciler.enabled=true
OracleReconciler.enabled=false
MssqlReconciler.enabled=false
like this:
public class HsqldbReconcilerEnabled implements Condition {
private static final String PROP_ENABLED = "HsqldbReconciler.enabled";
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String property = context.getEnvironment().getProperty(PROP_ENABLED);
return Boolean.parseBoolean(property);
}
}
// etc...
use like:
#Autowired
#Qualifier("dbReconciler")
ReconcilerBase dbReconsiler;
ensure you're not enabling multiple beans at the same time.

Spring -Mongodb storing/retrieving enums as int not string

My enums are stored as int in mongodb (from C# app). Now in Java, when I try to retrieve them, it throws an exception (it seems enum can be converted from string value only). Is there any way I can do it?
Also when I save some collections into mongodb (from Java), it converts enum values to string (not their value/cardinal). Is there any override available?
This can be achieved by writing mongodb-converter on class level but I don't want to write mondodb-converter for each class as these enums are in many different classes.
So do we have something on the field level?
After a long digging in the spring-mongodb converter code,
Ok i finished and now it's working :) here it is (if there is simpler solution i will be happy see as well, this is what i've done ) :
first define :
public interface IntEnumConvertable {
public int getValue();
}
and a simple enum that implements it :
public enum tester implements IntEnumConvertable{
vali(0),secondvali(1),thirdvali(5);
private final int val;
private tester(int num)
{
val = num;
}
public int getValue(){
return val;
}
}
Ok, now you will now need 2 converters , one is simple ,
the other is more complex. the simple one (this simple baby is also handling the simple convert and returns a string when cast is not possible, that is great if you want to have enum stored as strings and for enum that are numbers to be stored as integers) :
public class IntegerEnumConverters {
#WritingConverter
public static class EnumToIntegerConverter implements Converter<Enum<?>, Object> {
#Override
public Object convert(Enum<?> source) {
if(source instanceof IntEnumConvertable)
{
return ((IntEnumConvertable)(source)).getValue();
}
else
{
return source.name();
}
}
}
}
the more complex one , is actually a converter factory :
public class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
#Override
public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
if (enumType == null) {
throw new IllegalArgumentException(
"The target type " + targetType.getName() + " does not refer to an enum");
}
return new IntegerToEnum(enumType);
}
#ReadingConverter
public static class IntegerToEnum<T extends Enum> implements Converter<Integer, Enum> {
private final Class<T> enumType;
public IntegerToEnum(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public Enum convert(Integer source) {
for(T t : enumType.getEnumConstants()) {
if(t instanceof IntEnumConvertable)
{
if(((IntEnumConvertable)t).getValue() == source.intValue()) {
return t;
}
}
}
return null;
}
}
}
and now for the hack part , i personnaly didnt find any "programmitacly" way to register a converter factory within a mongoConverter , so i digged in the code and with a little casting , here it is (put this 2 babies functions in your #Configuration class)
#Bean
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
converters.add(new IntegerEnumConverters.EnumToIntegerConverter());
// this is a dummy registration , actually it's a work-around because
// spring-mongodb doesnt has the option to reg converter factory.
// so we reg the converter that our factory uses.
converters.add(new IntegerToEnumConverterFactory.IntegerToEnum(null));
return new CustomConversions(converters);
}
#Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(appContext);
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mappingContext);
mongoConverter.setCustomConversions(customConversions());
ConversionService convService = mongoConverter.getConversionService();
((GenericConversionService)convService).addConverterFactory(new IntegerToEnumConverterFactory());
mongoConverter.afterPropertiesSet();
return mongoConverter;
}
You will need to implement your custom converters and register it with spring.
http://static.springsource.org/spring-data/data-mongo/docs/current/reference/html/#mongo.custom-converters
Isn't it easier to use plain constants rather than an enum...
int SOMETHING = 33;
int OTHER_THING = 55;
or
public class Role {
public static final Stirng ROLE_USER = "ROLE_USER",
ROLE_LOOSER = "ROLE_LOOSER";
}
String yourRole = Role.ROLE_LOOSER

Resources