IClassFile annotations - java

How do you check for Annotations when using IClassFile in Eclipse?
This doesnt seem to work classFile.getClass().isAnnotationPresent? Any help is appreciated.
The problem with using
for (final IClassFile classFile : classFiles) {
IAnnotation[] annotations = classFile.getType().getAnnotations();
Is that I have to get All the Packages, then get the Class Files in that package then get the Annotations. It will require 3 loops. Is there a way to minimize this?

I would say that the easiest way for you to find annotations is through a triple loop, but it might be slightly faster (assuming you are looking for a specific annotation) to use a 'SearchEngineinstead. Take a look at the source code for theorg.eclipse.jdt.internal.junit.launcher.JUnit4TestFinder` class. It looks for (source) classes annotated with #Test or #RunWith, which is similar to what you want to do, but for binary classes.
You would do something like this:
IJavaElement[] allPackagesToSearch = ...
SearchRequestor requestor = <implement the SearchRequestor abstract class and store all matches>
IJavaSearchScope scope= SearchEngine.createJavaSearchScope(binaryPackages, IJavaSearchScope.APPLICATION_LIBRARIES);
int matchRule= SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE;
SearchPattern runWithPattern= SearchPattern.createPattern("com.foo.MyAnnotation", IJavaSearchConstants.ANNOTATION_TYPE, IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE, matchRule);
SearchParticipant[] searchParticipants= new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() };
new SearchEngine().search(annotationsPattern, searchParticipants, scope, requestor, new SubProgressMonitor(pm, 2));
It's a bit of a mouthful, and to figure out how this works, I'd recommend reading the JavaDoc for SearchEngine, SearchPattern, and SearchRequestor.
If you want to find all annotations, then change the match rule, and instead of "com.foo.MyAnnotation", use "*".

Related

Using Lombok's SuperBuilder with Hibernate Validator (jakarta.validation.x) annotation on a container type leads to "Type mismatch"

I have a class ActivitiesModel which uses Lombok's SuperBuilder.
import jakarta.validation.NotBlank;
// other imports and statements omitted for brevity.
#Data
#SuperBuilder
#NoArgsConstructor
public class ActivitiesModel {
public static final String ACTIVITIES_NOT_NULL_MESSAGE = "Activities cannot be null";
public static final String ACTIVITY_NOT_BLANK_MESSAGE = "Activity cannot be blank";
#NotNull(message = ACTIVITIES_NOT_NULL_MESSAGE)
private List<#NotBlank(message = ACTIVITY_NOT_BLANK_MESSAGE) String> activities;
}
I am using this builder to create an object of ActivitiesModel, and then validating it using Hibernate's Validator interface:
// Somewhere else in the application.
// Create an object using the builder method.
ActivitiesModel activitiesModel = ActivitiesModel.builder()
.activities(List.of("hello", "world")) // <----- Point A
.build();
// Validate the object using Hibernate's validator.
validator.validate(activitiesModel);
However, running this code gives me the following error:
java.lang.Error:
Unresolved compilation problem:
Type mismatch: cannot convert from List<String> to List<E>
The stack trace seems to be pointing at Point A.
I have tried the following approaches:
Replacing the #SuperBuilder with #Builder and #AllArgsConstructor.
Replacing the message attribute with a string literal instead of a static final variable, i.e:
private List<#NotBlank(message = "Activity cannot be blank") String> activities;
1st approach seems to fix this error, however, it's not something I can use as I need to extend the builder functionality to a subclass of ActivitiesModel. Also, this issue is also present in another abstract class, so the super builder functionality for parent classes is definitely required.
2nd approach also works in solving the error. However, going with it is a bit problematic because I then need to have the same message string in the validation test for this model class, which is something I would like to avoid as it duplicates the string.
Another thing to note is that this error only seems to occur in the presence of an annotation on the generic type parameter of the container, which is NotBlank in this case. It is not influenced by any annotations which are present directly on the field itself (NotNull in this case).
So, all in all, these are the questions that I would like to get some answers to:
Somehow, Lombok is able to figure out the types in case of a string literal but not in case of a static final String. Why is that?
Am I going about this totally wrong? The problem occurs because I'm trying to store the message string in a variable, and I'm trying to re-use the same variable at two places: the annotation's message attribute, and in the validation test for the model class. Should I not be checking for the presence of the message in my validation tests, but be checking for something else instead?
For anyone who comes across this later on, the research for this issue has led me to believe that comparing message strings in tests is not the way to go about writing validation test cases. Another downside to this approach is that you might have different validation messages for different locales. In that case, the message string itself might be a template e.g. my.message.key with its values in a ResourceBundle provided to Hibernate, i.e. files such as ValidationMessages.properties and ValidationMessages_de.properties.
In such a scenario, you could compare message for one locale in your validation test case, however, a better approach might be to check the annotation and the field for which the validation has failed. We can get both of these pieces of information via the ConstraintViolation and subsequently the ConstraintDescriptor types, provided by Hibernate. This way we can circumvent checking the message itself, but rely on the actual validation annotation which has failed.
As for the solution to this question, it seems it was a build cache issue. Cleaning maven's build cache results in this code working perfectly fine, but VSCode still seems to have an issue. For now, I will choose to ignore that.

Swagger/Openapi-Annotations: How to produce allOf with $ref?

I'm generating Rest endpoints including adding Openapi/Swagger annotations to the generated code.
While it works quite well with basic types, I have some problems with custom classes.
Right now I have a lot of duplicate schema entries for the custom classes (using #Schema(implementation = MyClass.class)) but at least the needed information is there. However I'd like to find a way to remove the duplicate schema entries while retaining the additional information.
On a github-issue discussing the $ref and lack of sibling properties I found an example how you would write it manually in yaml in order to get the result I'm looking for, however I can't figure out how to set the annotations to produce it.
This is how I think the annotation should look like if I follow the example (just to be on the safe side it is added to both the getter and the setter):
import io.swagger.v3.oas.annotations.media.Schema;
...
public class SepaPaymentRequest {
...
#Schema(name = "w307BetrBeg", description = "BETRAG BEGUENSTIGTER ", allOf = { com.diesoftware.services.utils.Betrag.class }, required = true)
public void setW307BetrBeg(final Betrag w307BetrBeg) {
this.w307BetrBeg = w307BetrBeg;
}
...
}
However what I get when I fetch the openapi.yaml (snippet):
w307BetrBeg:
$ref: '#/components/schemas/Betrag'
What I'd like to have:
w307BetrBeg:
title: 'Betrag'
description: 'BETRAG BEGUENSTIGTER'
allOf:
- $ref: '#/components/schemas/Betrag'
Any hints are more than welcome.
I haven't found a way to do it using annotations, i.e. by annotating the class.
I think it's possible to do, by:
Creating a model
Injecting the model using a ModelConverter
When I say "a model" I mean an instance of io.swagger.v3.oas.models.media.Schema.
In particular I think you'd want to create and inject a io.swagger.v3.oas.models.media.ComposedSchema instance, which supports allOf.
Doing this (i.e. creating model instances) isn't very different from hand-writing the YAML.
Another possibility -- which I haven't tried -- might be to write a slightly different ModelConverter, which you install into the chain of converters. Then, intercept calls to resolve which return a SchemaObject whose name is Betrag, and (sometimes?) replace that with a ComposedSchema instance which uses allOf.

How to customize ModelMapper

I want to use ModelMapper to convert entity to DTO and back. Mostly it works, but how do I customize it. It has has so many options that it's hard to figure out where to start. What's best practice?
I'll answer it myself below, but if another answer is better I'll accept it.
First here are some links
modelmapper getting started
api doc
blog post
random code examples
My impression of mm is that it is very well engineered. The code is solid and a pleasure to read. However, the documentation is very terse, with very few examples. Also the api is confusing because there seems to be 10 ways to do anything, and no indication of why you’d do it one way or another.
There are two alternatives: Dozer is the most popular, and Orika gets good reviews for ease of use.
Assuming you still want to use mm, here’s what I’ve learned about it.
The main class, ModelMapper, should be a singleton in your app. For me, that meant a #Bean using Spring. It works out of the box for simple cases. For example, suppose you have two classes:
class DogData
{
private String name;
private int mass;
}
class DogInfo
{
private String name;
private boolean large;
}
with appropriate getters/setters. You can do this:
ModelMapper mm = new ModelMapper();
DogData dd = new DogData();
dd.setName("fido");
dd.setMass(70);
DogInfo di = mm.map(dd, DogInfo.class);
and the "name" will be copied from dd to di.
There are many ways to customize mm, but first you need to understand how it works.
The mm object contains a TypeMap for each ordered pair of types, such as <DogInfo, DogData> and <DogData, DogInfo> would be two TypeMaps.
Each TypeMap contains a PropertyMap with a list of mappings. So in the example the mm will automatically create a TypeMap<DogData, DogInfo> that contains a PropertyMap that has a single mapping.
We can write this
TypeMap<DogData, DogInfo> tm = mm.getTypeMap(DogData.class, DogInfo.class);
List<Mapping> list = tm.getMappings();
for (Mapping m : list)
{
System.out.println(m);
}
and it will output
PropertyMapping[DogData.name -> DogInfo.name]
When you call mm.map() this is what it does,
see if the TypeMap exists yet, if not create the TypeMap for the <S,
D> source/destination types
call the TypeMap Condition, if it returns FALSE, do nothing and STOP
call the TypeMap Provider to construct a new destination object if necessary
call the TypeMap PreConverter if it has one
do one of the following:
if the TypeMap has a custom Converter, call it
or, generate a PropertyMap (based on Configuration flags plus any custom mappings that were added), and use it
(Note: the TypeMap also has optional custom Pre/PostPropertyConverters that I think will run at this point before and after each mapping.)
call the TypeMap PostConverter if it has one
Caveat: This flowchart is sort of documented but I had to guess a lot, so it might not be all correct!
You can customize every single step of this process. But the two most common are
step 5a. – write custom TypeMap Converter, or
step 5b. – write custom Property Mapping.
Here is a sample of a custom TypeMap Converter:
Converter<DogData, DogInfo> myConverter = new Converter<DogData, DogInfo>()
{
public DogInfo convert(MappingContext<DogData, DogInfo> context)
{
DogData s = context.getSource();
DogInfo d = context.getDestination();
d.setName(s.getName());
d.setLarge(s.getMass() > 25);
return d;
}
};
mm.addConverter(myConverter);
Note the converter is one-way. You have to write another if you want to customize DogInfo to DogData.
Here is a sample of a custom PropertyMap:
Converter<Integer, Boolean> convertMassToLarge = new Converter<Integer, Boolean>()
{
public Boolean convert(MappingContext<Integer, Boolean> context)
{
// If the dog weighs more than 25, then it must be large
return context.getSource() > 25;
}
};
PropertyMap<DogData, DogInfo> mymap = new PropertyMap<DogData, DogInfo>()
{
protected void configure()
{
// Note: this is not normal code. It is "EDSL" so don't get confused
map(source.getName()).setName(null);
using(convertMassToLarge).map(source.getMass()).setLarge(false);
}
};
mm.addMappings(mymap);
The pm.configure function is really funky. It’s not actual code. It is dummy EDSL code that gets interpreted somehow. For instance the parameter to the setter is not relevant, it is just a placeholder. You can do lots of stuff in here, such as
when(condition).map(getter).setter
when(condition).skip().setter – safely ignore field.
using(converter).map(getter).setter – custom field converter
with(provider).map(getter).setter – custom field constructor
Note the custom mappings are added to the default mappings, so you do not need, for example, to specify
map(source.getName()).setName(null);
in your custom PropertyMap.configure().
In this example, I had to write a Converter to map Integer to Boolean. In most cases this will not be necessary because mm will automatically convert Integer to String, etc.
I'm told you can also create mappings using Java 8 lambda expressions. I tried, but I could not figure it out.
Final Recommendations and Best Practice
By default mm uses MatchingStrategies.STANDARD which is dangerous. It can easily choose the wrong mapping and cause strange, hard to find bugs. And what if next year someone else adds a new column to the database? So don't do it. Make sure you use STRICT mode:
mm.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
Always write unit tests and ensure that all mappings are validated.
DogInfo di = mm.map(dd, DogInfo.class);
mm.validate(); // make sure nothing in the destination is accidentally skipped
Fix any validation failures with mm.addMappings() as shown above.
Put all your mappings in a central place, where the mm singleton is created.
I faced a problem while mapping with ModelMapper. Not only properties but also My source and destination type were different. I solved this problem by doing this ->
if the source and destination type are different. For example,
#Entity
class Student {
private Long id;
#OneToOne
#JoinColumn(name = "laptop_id")
private Laptop laptop;
}
And Dto ->
class StudentDto {
private Long id;
private LaptopDto laptopDto;
}
Here, the source and destination types are different. So, if your MatchingStrategies are STRICT, you won't able to map between these two different types.
Now to solve this, Just simply put this below code in the constructor of your controller class or any class where you want to use ModelMapper->
private ModelMapper modelMapper;
public StudentController(ModelMapper modelMapper) {
this.modelMapper = modelMapper;
this.modelMapper.typeMap(Student.class, StudentDto.class).addMapping(Student::getLaptop, StudentDto::setLaptopDto);
}
That's it. Now you can use ModelMapper.map(source, destination) easily. It will map automatically
modelMapper.map(student, studentDto);
I've been using it from last 6 months, I'm going to explain some of my thoughts about that:
First of all, it is recommended to use it as an unique instance (singleton, spring bean,...), that's explained in the manual, and I think all agree with that.
ModelMapper is a great mapping library and wide flexible. Due to its flexibility, there are many ways to get the same result, and that's why it should be in the manual of best practices of when to use one or other way to do the same thing.
Starting with ModelMapper is a little bit difficult, it has a very tight learning curve and sometimes it is not easy to understand the best ways to do something, or how to do some other thing. So, to start it is required to read and understand the manual precisely.
You can configure your mapping as you want using the next settings:
Access level
Field matching
Naming convention
Name transformer
Name tokenizer
Matching strategy
The default configuration is simply the best (http://modelmapper.org/user-manual/configuration/), but if you want to customise it you are able to do it.
Just one thing related to the Matching Strategy configuration, I think this is the most important configuration and is need to be careful with it. I would use the Strict or Standard but never the Loose, why?
Due Loose is the most flexible and intelligent mapper it could be map some properties you can not expect. So, definitively, be careful with it. I think is better to create your own PropertyMap and use Converters if it is needed instead of configuring it as Loose.
Otherwise, it is important to validate all property matches, you verify all it works, and with ModelMapper it's more need due with intelligent mapping it is done via reflection so you will not have the compiler help, it will continue compiling but the mapping will fail without realising it. That's one of the things I least like, but it needs to avoid boilerplate and manual mapping.
Finally, if you are sure to use ModelMapper in your project you should use it using the way it proposes, don't mix it with manual mappings (for example), just use ModelMapper, if you don't know how to do something be sure is possible (investigate,...). Sometimes is hard to do it with model mapper (I also don't like it) as doing by hand but is the price you should pay to avoid boilerplate mappings in other POJOs.
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class EntityDtoConversionUtil {
#Autowired
private ModelMapper modelMapper;
public Object convert(Object object,Class<?> type) {
Object MapperObject=modelMapper.map(object, type);
return MapperObject;
}
}
Here is how you can make a customize Conversion Class and can then autowire it where you would like to convert the object to dto and vice versa.
#Component
public class ConversionUtil {
#Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
public <T,D> D mapItem(T item,Class<D> cl){
return modelMapper().map(item,cl);
}
public <T,D> List<D> map(List<T> list, Class<D> cl){
return list.stream()
.map(item -> modelMapper().map(item, cl))
.collect(Collectors.toList());
}
}

ui binder pattern: initializing variables using generators

I am trying to create a GWT generator that does the following:
public class MyPool {
#InitializeThisVariable
Element1 el1;
#InitializeThisVariable
Element2 el2;
private static final ChildPool childPool = GWT
.create(ChildPool.class);
interface ChildPool extends Pool<MyPool>{}
public MyPool(){
}
}
I want the generator to initialize the annotated fields. After doing some research, I have found out that the only way to do this is to use the pattern used by ui-binder as above (I do not want to use Annotations Processors).
However I get the following error when compiling:
[ERROR] Line 16: Rebind result 'ChildPool' must be a class
Help would be much appreciated.
Your generator needs to return name if the generated class. Either that or you forgot the <generate-with> in your module.
Also, your code doesn't make use of the generated Pool instance.
Note however that generators are being deprecated in GWT 2.8, and you should really use other kind of code generators (be it annotation processors or something else). You shouldn't start writing new generators nowadays.

Problems searching for annotated classes using Reflections API

I'm having a very difficult time using the Reflections API to find classes that are annotated with a custom annotation at runtime. The ultimate goal is to find all classes in the project that are annotated with my custom #Job annotation, collect them, and allow each of them to be run from one location without adding each one to the page manually. However, I'm finding it extremely difficult to get the initial search to work correctly, so I cannot move on with my project.
My current approach is to use:
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("jobs"))
.setScanners(new TypeAnnotationsScanner())
.filterInputsBy(new FilterBuilder().includePackage("jobs")));
Set<Class<?>> jobs = reflections.getTypesAnnotatedWith(Job.class);
where "jobs" is the package containing all of the job classes that I am searching for, which will be annotated with the custom #Job annotation. "jobs" is a base package in my project, but the overall url on my machine looks something like ".../(project)/app/jobs". This setup results in one url being searched, which is ".../(project)/app/" with the additional filter "+jobs.*" in the configuration object. This seems like it is working correctly, but clearly something is wrong because I do not get any classes in the set.
If it matters, the annotation is coded as:
package jobs;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Job {
String description();
}
The annotation class is located within the same "jobs" package as the job classes I am searching for. An example of a job definition with the annotation included is:
package jobs;
#Job(description = "Description of what the job will do")
public class ExampleJob extends MasterJob {...}
I cannot find what I need to change in order to get this search to function as intended. Thanks for the help, and please let me know if I can clarify anything further.
EDIT: I believe the problem is associated with how the Play Framework loads its classes. Fortunately, the framework provides its own annotation search function, which I used instead. According to a comment, the code I have listed here will work, given that you have all the dependencies to run it. Feel free to use it as a template and let me know if it works for you as well.

Categories