I am trying to create custom mapper in external class to map String to Integer. the map method is called successfully when it is defined in the mapper that uses it. but when I put the method in an external class, I get error that MapStruct cannot find the method:
error: Qualifier error. No method found annotated with #Named#value: [ MapperUtils and mapEnum ]. See https://mapstruct.org/faq/#qualifier for more info. #Mapping(target = "invoiceLanguage", source = "invoiceLanguage", qualifiedByName = {"MapperUtils", "mapEnum"})
This is the mapper abstract class. the commented code contains the custom mapping method that is called successfully. this method I want to externalize in order to be able to call it from multiple mappers
#Mapper(componentModel = "spring", uses = MapperUtils.class, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class CustomerAccountMapper {
public abstract ExistingCustomerAccountDto map(CustomerAccountDao dao);
public abstract CustomerAccountDao map(NewCustomerAccountRequest request);
#Mapping(target = "invoiceLanguage", source = "invoiceLanguage",
qualifiedByName = {"MapperUtils", "mapEnum"})
public abstract CustomerAccountDao map(UpdateCustomerAccountRequest request);
// works fine when method is in mapper class
// #Named("mapEnum")
// Integer mapEnum(String input) {
// if ("null".equalsIgnoreCase(input)) {
// return null;
// }
// return Integer.valueOf(input);
// }
}
I have followed the instructions in the user guide:
Created external class with custom map method. added #Named to class and method
added uses to #Mapper annotation of mapper class
added qualifiedByName to #Mapping annotation on method of mapper
This is my external mapper class
#Component
#Named("MapperUtils")
#SuppressWarnings("unused")
public class MapperUtils {
#Named("mapEnum")
Integer mapEnum(String input) {
if ("null".equalsIgnoreCase(input)) {
return null;
}
return Integer.valueOf(input);
}
}
what am I missing?
Looks like when implementing it with #Named and qualifiedByName will work only if MapperUtils will be in the exact same package as CustomerAccountMapper
Mapstruct documentation mentions that working with qualifiedByName is not the best way as its not very predictable
Although the used mechanism is the same, the user has to be a bit more
careful. Refactoring the name of a defined qualifier in an IDE will
neatly refactor all other occurrences as well. This is obviously not
the case for changing a name.
Also from qualifiedByName Java docs
Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large number of qualifiers as no custom annotation types are needed.
Related
I am currently using mapstruct to map data between entities and DTOs, inside a mapper I need to instantiate a class using #Autowired, inside the class I need to instantiate I have a method that loads data into the cache, when I try to do the following: # Autowired RepositoryImpl repository; IntelliJ tells me: The variable 'repository' may not have been initialized. How could I use instantiate the class correctly or use the method I need?
mapper
#Service
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DataMapper {
**#Autowired
RepositoryImpl repository;**
}
default DetailTemp mapDetail(String itemType, counter){
**ItemType itemType = repository.getType(itemType);**
DetailTemp detailTemp = new DetailTemp();
detailTemp.setPosition(counter);
detailTemp.setItemType(itemType);
return DetailTemp;
}
}
According to this you need to be using an abstract class if you are using Spring components (i.e. #Autowired RepositoryImpl repository):
5.2. Inject Spring Components into the Mapper Sometimes, we'll need to utilize other Spring components inside our mapping logic. In this
case, we have to use an abstract class instead of an interface:
#Mapper(componentModel = "spring") public abstract class
SimpleDestinationMapperUsingInjectedService
Then, we can easily inject
the desired component using a well-known #Autowired annotation and use
it in our code:
#Mapper(componentModel = "spring") public abstract class
SimpleDestinationMapperUsingInjectedService {
#Autowired
protected SimpleService simpleService;
#Mapping(target = "name", expression = "java(simpleService.enrichName(source.getName()))")
public abstract SimpleDestination sourceToDestination(SimpleSource source); }
We must remember not to make the injected bean private!
This is because MapStruct has to access the object in the generated
implementation class.
I'm trying to generate an implementation in MapStruct that will create a constructor for me that I can use for constructor-based dependency injection. I have learned that I can't use constructor-injection in the mapper definition(seen below), but how do I make it so that my generated class has one?
I have tried below:
#Mapper(componentModel = "spring", uses = Dependency.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public abstract class MapStructTest {
private Dependency dependency;
#Mapping(source = "field", target "target")
#Mapping(target = "target2", ignore = true)
#AfterMapping
public final void runAfter() {
//dostuff for target2
}
}
With no success. My class is generated, looks ok, except there is no constructor. How do I define my mapper so that I get a constructor I can work with in the implementation?
Best regards,
Prince of Sweden
MapStruct does not support calling constructors for abstract classes. You have to have a default empty constructor.
In order to inject Dependency you can use #Autowired on your field or add a setter with #Autowired
I would like to use JavaPoet to generate an interface and a class implementing this interface.
TypeSpec if = TypeSpec.interfaceBuilder("MyInterface")
.build();
TypeSpec cl = TypeSpec.classBuilder("MyClass")
.build();
But I am strugelling to tell JavaPoet that MyClass should implement MyInterface. The method addSuperinterface(TypeName) requires a type name and I didn't findout how to turn a TypeSpec into an TypeName. The only way I found is calling ClassName#get(String, String).
Is there an better way to achive this and to use the type specification for the interface directly?
It is not as complicated as it may seem.
The TypeSpec.Builder has two versions of the addSuperInterface method:
TypeSpec.Builder addSuperinterface(Type superinterface)
TypeSpec.Builder addSuperinterface(TypeName superinterface)
We could use the second version for example and obtain the super interface as an instance of the TypeName class using ClassName.get
One of the signatures of the get method of the ClassName class is:
public static ClassName get(String packageName, String simpleName, String... simpleNames)
So we could use it with empty string for the package name since you did not specify any package name in your interface spec. It will work because ClassName extends TypeName.
On the other hand we can obtain the interface's simple name using the type spec's name property.
Here a complete sample implementation. I modified the name of the variables (the variable name if that you used for the interface spec will not work as it is a java keyword).
#Data
public class SimpleClassSpecs {
public final TypeSpec interfaceSpec;
public final TypeSpec classSpec;
public SimpleClassSpecs() {
interfaceSpec = TypeSpec.interfaceBuilder("MyInterface")
.build();
TypeName interfaceTypeName = ClassName.get("", interfaceSpec.name);
classSpec = TypeSpec.classBuilder("MyClass")
.addSuperinterface(interfaceTypeName)
.build();
}
}
I used Lombok's #Data for the boilerplate code (getters and setters...)
Here is a corresponding test (assertion written with assertj):
#Test
public void should_generate_spec_with_superInterface() {
SimpleClassSpecs ps = new SimpleClassSpecs();
assertThat(ps.classSpec.toString()).contains("class MyClass implements MyInterface");
}
Or by simply doing doing a System.out.println(ps.classSpec), one can obtain the following result:
class MyClass implements MyInterface {
}
I would like to use ByteBuddy to generate simple interfaces like this:
public interface MyInterface {
void myMethod(#Deprecated String myDeprecatedParameter);
}
This is just an example, but the point is that the parameters of the methods need a number of custom annotations.
Does anyone have a simple example that would demonstrate how to achieve this in ByteBuddy?
You can create an interface with an annotated parameter like the following. First define the interface name and modifiers, then define the method with it's name, return type and modifiers and finally the parameters and annotations if have any.
Class<?> myInterface = new ByteBuddy()
.makeInterface()
.name("MyInterface")
.modifiers(Visibility.PUBLIC, TypeManifestation.ABSTRACT)
.defineMethod("myMethod", void.class, Visibility.PUBLIC)
.withParameter(String.class, "myDeprecatedParameter")
.annotateParameter(AnnotationDescription.Builder.ofType(Deprecated.class)
.build())
.withoutCode()
.make()
.load(this.getClass().getClassLoader())
.getLoaded();
You can call annotateParameter(...) many times if you need multiple annotations.
After the make() method you get the unloaded class, just load the class and use it.
Here are some prints with the reflection api of the interface class.
System.out.println(Modifier.toString(myInterface.getModifiers())); // public abstract interface
System.out.println(myInterface.getSimpleName()); // MyInterface
System.out.println(Arrays.toString(myInterface.getDeclaredMethods())); // [public abstract void MyInterface.myMethod(java.lang.String)]
Method method = myInterface.getDeclaredMethod("myMethod", String.class);
System.out.println(method.getName()); // myMethod
System.out.println(Arrays.toString(method.getParameters())); // [java.lang.String myDeprecatedParameter]
Parameter parameter = method.getParameters()[0];
System.out.println(parameter); // java.lang.String myDeprecatedParameter
System.out.println(parameter.getName()); // myDeprecatedParameter
System.out.println(Arrays.toString(parameter.getAnnotations())); // [#java.lang.Deprecated()]
Annotation annotation = parameter.getAnnotations()[0];
System.out.println(annotation); // #java.lang.Deprecated()
I've got a fairly standard Spring webapp, and I have a number of custom annotations that I would like to use to denote the requirements and constraints applied to a given web-service method. For instance, I might apply an #RequiresLogin annotation to any method that requires a valid user session, and #RequiresParameters(paramNames = {"name", "email"}) on a method that requires that "name" and "email" be set, and so on.
In support of this I implemented an ad-hoc utility for validating a method's annotated constraints at runtime, which basically followed a pattern of:
Map<Class<? extends Annotation>, Annotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
if (annotations.containsKey(AnnotationType1.class)) {
AnnotationType1 annotation = (AnnotationType1)annotations.get(AnnotationType1.class);
//do validation appropriate to 'AnnotationType1'
}
if (annotations.containsKey(AnnotationType2.class)) {
AnnotationType2 annotation = (AnnotationType2)annotations.get(AnnotationType2.class);
//do validation appropriate to 'AnnotationType2'
}
//...
This works fine, but has become a bit unwieldy as I have added additional annotations. I'd like to replace it with something a bit more maintainable. Ideally I'd like to be able to do:
List<ValidatableAnnotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
for (ValidatableAnnotation annotation : annotations) {
annotation.validate(request);
}
But I'm pretty sure that is not possible since annotations themselves cannot contain executable code and since the compiler will not let me extend java.lang.annotation.Annotation (not that I'd know how to go about allowing executable code to be contained in an annotation even if the compiler let me try).
What annotations can contain, however, is a nested inner class, and that inner class can do anything that a normal Java class can do. So what I've come up with based upon that and in the interest of keeping my validation code as closely associated with the annotation being validated as possible is:
public interface AnnotationProcessor {
public boolean processRequest(Annotation theAnnotation, HttpServletRequest request);
}
And then the annotations can be implemented like:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface RequiresLogin {
public static class Processor implements AnnotationProcessor {
#Override
public boolean processRequest(Annotation theAnnotation, HttpServletRequest request) {
if (! (theAnnotation instanceof RequiresLogin)) {
//someone made an invalid call, just return true
return true;
}
return request.getSession().getAttribute(Constants.SESSION_USER_KEY) != null;
}
}
}
Which keeps the validation logic nice and tightly coupled with the annotation that is being validated. Then all my ad-hoc validation code can be replaced with:
List<Annotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
for (Annotation annotation : annotations) {
processAnnotation(annotation, request);
}
private static boolean processAnnotation(Annotation annotation, HttpServletRequest request) {
AnnotationProcessor processor = null;
for (Class<?> processorClass : annotation.annotationType().getDeclaredClasses()) {
if (AnnotationProcessor.class.isAssignableFrom(processorClass)) {
try {
processor = (AnnotationProcessor)processorClass.newInstance();
break;
}
catch (Exception ignored) {
//couldn't create it, but maybe there is another inner
//class that also implements the required interface that
//we can construct, so keep going
}
}
}
if (processor != null) {
return processor.processRequest(annotation, request);
}
//couldn't get a a processor and thus can't process the
//annotation, perhaps this annotation does not support
//validation, return true
return true;
}
Which leaves no more ad-hoc code that needs to be revised every time I add a new annotation type. I just implement the validator as part of the annotation, and I'm done.
Does this seem like a reasonable pattern to use? If not then what might work better?
You may want to investigate AOP. You can advise methods that expose certain annotations and perform pre/post processing accordingly.
I would just like to add that while AOP would be a good solution, the Spring framework already provides this functionality by way of the #Secured annotation.
#Secured("ROLE_USER")
public void foo() {
}
Spring also supports JSR-303 validation with the #Valid annotation. So for these use cases at least, it seems you are re-inventing the wheel.
IMHO one could think about the Visitor pattern in combination with a factory. The factory will return a wrapper object that knows the exact annotation type and which the visitor will be able...
class MyVisitor {
public void visit(VisitableAnnotationType1 at) {
//something AnnotationType1 specific
}
public void visit(VisitableAnnotationType2 at) {
//something AnnotationType2 specific
}
... // put methods for further annotation types here
}
class VisitableFactory {
public abstract class VisitableAnnotation {
public abstract void accept(MyVisitor visitor);
}
class VisitableAnnotationType1 implements VisitableAnnotation {
public void accept(MyVisitor visitor) {
visitor.visit(this);
}
}
public static VisitableAnnotation getVisitable(Annotation a) {
if(AnnotationType1.class.isAssignableFrom(a.getClass()) {
//explicitely cast to the respective AnnotationType
return new VisitableAnnotationType1((AnnotationType1)a);
} else if (AnnotationType2.class.isAssignableFrom(a.getClass()) {
//explicitely cast to the respective AnnotationType
return new VisitableAnnotationType1((AnnotationType1)a);
}
}
}
As we cannot extend Annotation, we need those wrapper classes in the factory. You could also pass the original annotation which is then contained in that wrapper class.
What you have to do: For each new AnnotationType add a new "wrapper" class to the factory, extend the factory's
getVisitable()
method accordingly and also add an according method to the Visitor:
public void doSomething(VisitableAnnotationTypeXYZ at) {
//something AnnotationTypeXYZ specific
}
now the generic validation (or whatever) code looks like:
List<ValidatableAnnotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
MyVisitor visitor = new MyVisitor();
for (ValidatableAnnotation annotation : annotations) {
VisitableFactory.getVisitable(annotation).accept(visitor);
}
The visiting works by the indirection that the visited object calls the visitor with itself as the argument and thus the correct visit method will be invoked.
Hope that helps ;-)
Code is not tested, though...