I have a springboot application with two Converters that can convert from class A to class B.
B is extending A.
When I call the Conversion Service, A is always invoked.
I would like have the Conversion Service to call B over A.
I tried #Order and #Primary but none of that worked.
Is there a way to define a priority?
-- EDIT
I am using Springboot + Mapstruct + https://github.com/mapstruct/mapstruct-spring-extensions
If I take for a example: https://github.com/mapstruct/mapstruct-spring-extensions/tree/main/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean
which generates CarMapperImpl (which implements Converter) and I extend it like this:
#Component
public class CarMapperImplExtended extends CarMapperImpl {
public CarMapperImplExtended(ConversionServiceAdapter conversionServiceAdapter) {
super(conversionServiceAdapter);
}
#Override
public CarDto convert(Car car) {
CarDto carDto = super.convert(car);
// do something special with it
return carDto;
}
}
The code is generated, that's why I am gonna end up with two beans implementing Converter<Car, CarDto>.
Ideally, I would not have CarMapperImpl - true - but I could not find another way to use mapstruct-extension and override convert or remove #Component from the generated code.
Finally, I found a workaround: if there are two converters implementing Converter<A,B> the ConversionService is going to use the one added last.
If CarMapperImplA and CarMapperImplB exist, CarMapperImplB will be used.
Solved using #DecoratedWith with spring component model as explained here:
https://mapstruct.org/documentation/stable/reference/html/#customizing-mappers-using-decorators
Related
I have a requirement that based on profile I need to inject 2 different classes into DAO layer to perform CRUD operation. Let's say we have class A and Class B for profiles a and b respectively. Now in the DAO layer without using if else condition (As I am using that currently based on the profile, I am using service layer to call 2 different methods 1.saveA(), 2.saveB().) But is there any way to make it more generic and based on profile or either by the class reference I can instantiate different entity as well as JPA Classes? I tried to use
<T extends Parent> T factoryMethod(Class<T> clazz) throws Exception {
return (T) clazz.newInstance();
}
but this also will force me to cast the returned object to a class. I tried creating a parent P for both class A and B. and used them instead but got confused when injecting the entity types to JPARepository.
I tried creating a SimpleJPARepository but didnt worked as there are overridden methods in ARepository and BRepository.
Or,
is there a way I can use the same entity class for 2 different tables? that way it can be solved. for 1 profile I have different sets of columns whereas for 2nd profile I have different columns.
this is how I am expecting: Would it be possible? or, how I am doing now is correct?
public void doStuff(Class<T> class){
GenericRepository repo;
if(class instanceof A){
//use ARepository;
repo = applicationContext.getBean(ARepository);
}else{
//use BRepository;
repo = applicationContext.getBean(BRepository);
}
repo.save(class);
repo.flush();
}
You can create a method utility like following: The key is the class type of the entity and the value is the repository.
Map<Class<? extends Parent>, JpaRepository> repoMapping = new HashMap<>();
#PostConstruct
public void init(){
repoMapping.put(A.class, applicationContext.getBean(ARepository));
repoMapping.put(B.class, applicationContext.getBean(BRepository));
}
public JpaRepository getRepo(Class<? extends Parent> classs){
return repoMapping.get(classs);
}
I'm using Spring frame work with jersey to implement REST.
I have a 2 sets of paths in the following patterns
Set 1:
/top/{top_id}/<some string>
ex:
/top/{top_id}/book
/top/{top_id}/pen
/top/{top_id}/dog
Set 2 :
/top/{top_id}/middle/{middle_id}/<some string>
ex:
/top/{top_id}/middle/{middle_id}/book
/top/{top_id}/middle/{middle_id}/pen
/top/{top_id}/middle/{middle_id}/dog
Since these work on different levels ( set 1 on top level and set 2 on middle level) , I want to create different controllers classes for them.
#Component
public class Top{
}
#Component
public class Middle{
}
The problem I'm having is both sets have /top/{top_id} common.
I don't know what to use as the value for #Path annotation written above the class. I tried removing it, but Jersey is not recognizing the class. Please suggest a method to implement this. I'm trying to do this because there are around 100 paths in each layer. I don't want to keep them all together in a single file. Thank you.
After going through orcale docs , I found a way to make it work.
#Path("/top/{top_id}")
#Component
public class Top{
#GET
#Path("/book")
#Produces({"application/json"})
public getBooks(){
}
}
#Path("/top/{top_id}/middle/{middle_id}")
#Component
public class Middle{
#GET
#Path("/book")
#Produces({"application/json"})
public getBooks(){
}
}
It works. Using like this they can be split into different controller files.
I've been looking around the web for a while.
I'm trying to create an instance of a subclass dynamically, let me explain:
I have the following class:
Public abstract class Property
And a lot of sub class created from this class, for example the following two:
public class PropertyDns extends Property
Public class PropretyNetBios extends Property
I want the client to choose one of the subclass name, and then I need to create an instance of that class.
I'm going to have a lot of subclass that extends Property so switch-case statements will be exhausting:
So:
switch (user_input){
case "PropertyDns ": return new PropertyDns();
case "PropretyNetBios": return new PropretyNetBios();
.
.
.
}
will be terrible...
any ideas?
You can use one of the following two ways to achieve the result:
Create a factory method, which takes a String parameter, and based on the parameter, write up a switch to serve the required object.
The second option (and the recommended approach here) would be to dynamically create an instance from the class name using Class.newInstance
Using the second approach would make your solution easily extendable, and the addition of new classes won't need any structural changes.
A sample implementation of the second approach would be like:
public Property getProperty(String name) {
//Make sure the name contains full cannonical name of the class
return (Property) Class.forName(name).newInstance();
}
As I commented before, this problem can be solved with the Factory Pattern, if the number of classes is too large you can mix the Factory Pattern with an Annotation Processor to generate the factory. You need to create an annotation and a corresponding annotation processor.
Here is an example of the annotation you should create:
#Target(ElementType.TYPE) #Retention(RetentionPolicy.CLASS)
public #interface Property {
String name();
Class type();
}
Your property classes will look like this:
#Property(name="DnsProperty", class=Property.class)
public class DnsProperty extends Property{
...
}
You need to implement your own processor extending the class
javax.annotation.processing.AbstractProcessor
and register it at
/META-INF/services/javax.annotation.processing.Processor
The idea is to annotate each class to provide it's name, and generate the factory statements with the annotation processor, saving you from writing the exhausting switch-case statements. Explaining the annotation processors, as switch-case statements can be exhausting, so, you can read about it in the Java documentation, here is a blog post explaining annotation processors and here is the source code.
You need a Factory and a proper naming system for you classes(for example an enumeration), read about Factory pattern. This should work for you.
public class PropertyFactory {
public enum PropertyName {
DNS,
NET_BIOS
}
public Property createProperty(PropertyName name) {
switch (name) {
case DNS:
return new PropertyDns();
case NET_BIOS:
return new PropretyNetBios();
}
return null; //Or throw an exception
}
}
I am working on three different tables. I am using Hibernate to query these tables. I implemented successfully the DAO and the service layers, but i have few problems with the controller package. Here is my code, my controller package contains 3 classes , each should handle a table (i have 3 tables as i said before).
#Controller
public class Ods_Gis_Actel_Controller {
Param_Gis_Actel_Controller Param = new Param_Gis_Actel_Controller();
Tbl_Dim_Actel_Controller Dim = new Tbl_Dim_Actel_Controller();
#Autowired
Ods_Gis_Actel_metier service;
#RequestMapping(value="/index")
public String pageIndex(Model model)
{
addOdsTable(model);
Param.addParamTable(model);
Dim.addDimTable(model);
return "Affichage";
}
public void addOdsTable(Model model)
{
model.addAttribute("listeOds",service.getAll());
}
}
#Controller
public class Param_Gis_Actel_Controller {
#Autowired
Param_Gis_Actel_metier service;
public void addParamTable(Model model)
{
model.addAttribute("listeParam",service.getAll());
}
}
#Controller
public class Tbl_Dim_Actel_Controller {
#Autowired
Tbl_Dim_Actel_metier service;
public void addDimTable(Model model)
{
model.addAttribute("listeDim",service.getAll());
}
}
The request mapping is done in the 1st class, whose method calls 2 other methods from the other classes. But it seems, that the autowiring works only in the class, where the RequestMapping is performed.
Is this true?
how can i use the other methods from the classes which don't contain the RequestMapping if the autowiring doesn't work for them?
I gone through your problem , I think you are not so much aware the objective of #Controller , #RequestMapping . So First of all you need to know , why we use #Controller?, this is used to give business logic to your request. When request is hited from user , then your DispatcherServlet match the url from your request to value of RequestMapping annotation of all defined controller. And according to that , the matched mapping method is called and further procees done by framework. Now come to #Autowire, this is used to load the bean class definition from the xml configuration. So the #Autowire and #RequestMapping having different objective . So it's wrong to say here that the
**autowiring** works only in the class where the RequestMapping is performed.
Now your second question , How you can use simple class? there are two ways to achieve that as far as I know,
1) To create the Object of that class inside your class as you done in your code
2) To create the instance of that class using factory-method.
for the second point , you have to first define your class inside the configuration file by following the below format
<bean id="paramGis" class="<whatever_package_detail>.Param_Gis_Actel_Controller" factory-method="createInstance"/>
here one things you have to care that this method should be static .
and your class would look like that
#Service
public class Param_Gis_Actel_Controller {
private static Param_Gis_Actel_Controller paramGis;
public static Param_Gis_Actel_Controller createInstance(){
if(paramGis==null){
return new Param_Gis_Actel_Controller();
}
return paramGis;
}
public void addParamTable(Model model)
{
model.addAttribute("listeParam",service.getAll());
}
}
If you are still getting problem let me know.
I think you are having difficulties with the Java/Spring way. We don't use #Controller/#Autowired like that.
It's kind of hard to explain shortly (I strongly recommend you read the official document for that), but in short, you shouldn't create a Controller object inside another controller. The objects with annotation marks (#Controller, #Service...) should be generated and managed by Spring. At initiation time they will be injected with the #Autowired services by "Spring" way. Of courses you can intervene into that process, but by other special methods.
P/s: your naming convention is not for Java ;). If you create a program for personal use it maybe ok, but you will have difficult times collaborating with other Java developers.
Through method name as default one for access that particular method or use #Qualifier annotations.
The Adobe AEM software provides several classes which can take an apache Sling Resource and adapt it to another class like so:
Page page = resource.adaptTo(Page.class);
To use this syntax with classes that you author and control this boils down to simply implementing the Adaptable interface.
However, if you want to enable a Resource to adaptTo your new custom class, is seems that you have to implement the AdapterFactory interface and register it in OSGI.
This is how the Adobe website describes it:
By an AdapterFactory, which can map arbitrary objects.
The objects must still implement the Adaptable interface and must extend SlingAdaptable (which passes the adaptTo call to a central adapter manager).
This allows hooks into the adaptTo mechanism for existing classes, such as Resource.
I have walked through the SlingScriptAdapterFactory code, but ultimately I am not connecting the dots here. Basically I want to do this:
MyClass myClass = Resource.adaptTo(MyClass.class);
Do I create a class that implements AdapterFactory and simply deploy it with the package expecting that Sling will just find it by type or is there more to it?
Here is a little bit better documentation https://sling.apache.org/documentation/the-sling-engine/adapters.html
So you should implement the Adaptable interface, as you already described. Then create a properly annotated AdapterFactory:
#Component
#Service(value=org.apache.sling.api.adapter.AdapterFactory.class)
#Properties({
#Property(name = "adaptables", value = { "org.apache.sling.api.resource.Resource" }),
#Property(name = "adapters", value = { "org.sling.MyClass" })
})
public class MyAdapterFactory implements AdapterFactory{
public <AdapterType> AdapterType getAdapter(final Object adaptable, Class<AdapterType> type){
return new MyClassAdapter(adaptable);
}
}
Note that I've been working on a simpler way to create Sling adapters, by annotating methods with a new #Adapter annotation, as in
#Component
#Service
public class C implements AdapterMethodsProvider {
#Adapter
public CustomerRecord convert(Resource r) { ... }
#Adapter
public Person adaptToPerson(Resource r) { ... }
}
See https://issues.apache.org/jira/browse/SLING-2938 for details, but note that this is not even in the Sling trunk yet, so it will take some time before it's released and available in AEM/CQ.