Turning one annotation into many annotations with AspectJ - java

I have discovered a pattern in my JPA mappings that I would like to codify. A simple example follows:
#OneToMany(fetch=FetchType.EAGER)
#Sort(type=SortType.NATURAL)
private SortedSet<Item> items;
I would like to create a single annotation called SortedOneToMany that I can apply to the above set:
public #interface SortedOneToMany {
FetchType fetch() default EAGER;
SortType sort() default NATURAL;
Class comparator() default void.class;
}
I have written the following aspect to "attach" the JPA annotations whenever it sees my annotation:
public aspect SortedOneToManyAspect {
declare #field: #SortedOneToMany * * : #OneToMany(fetch=FetchType.EAGER);
declare #field: #SortedOneToMany * * : #Sort(type=SortType.NATURAL);
}
But I don't know how can I access the values of the SortedOneToMany annotation parameters and use them when defining the OneToMany and Sort annotations. There may be cases where I want to change one of the default values like so:
#SortedOneToMany(sort=SortType.COMPARATOR,comparator=ItemComparator.class)
private SortedSet<Item> items;
So how can I pass the annotation values from SortedOneToMany to the Sort annotation?

I received this answer from Andy Clement on the aspectj-users mailing list:
Hi,
I'm afraid you can't do that with AspectJ right now, you can't pass a
piece of the matched information to the new annotation. I can perhaps
imagine some hypothetical syntax:
declare #field:
#SortedOneToMany(sort=SortType.COMPARATOR,comparator={1}) * * :
#Sort(type=SortType.COMPARATOR,comparator={1});
which would seem to achieve what you want.
Maybe raise an enhancement request for it:
https://bugs.eclipse.org/bugs/enter_bug.cgi?product=AspectJ
sorry I don't have better news.
cheers
Andy
I created a ticket for the issue in case anyone wants to follow the progress: https://bugs.eclipse.org/bugs/show_bug.cgi?id=345515

Related

How to extend non-modifiable model to use with JPA?

What's the best practice to create persistence (say via Spring Boot or just JPA or Hibernate itself) for a data model coming from a non-modifiable dependency? Typical limitations like not being able to override a field or what patterns like Decorator allow and what not slowed my progress down. I tried some things, but I always end up with the result that it would be necessary to either modify the source model (like adding annotations to make it natively compatible -> the fork I don't want) OR write a ton of wrapper code which would replicate the original model too much - but even this isn't working right now:
I tried
Creating a JpaRepository for the original class. Doesn't work, because casting the extended class to its parent class is not working.
Extend the original class with a custom class that gets necessary annotations like #Entity can be used in such a repository. But problems here were
that the original class is missing an #Id annotation, which could be fixed by using a new ID in the extended class, but
the given model also has a non-simple architecture, including lists of other classes that are part of the model itself. So other annotations like #ElementCollection might be necessary, which can't be added because overriding of fields is not possible.
Hiding it with creating a new field with the same name in the new class is not working:
An error like Could not determine type for: java.util.List, at table: yeah_this_one, for columns:[org.hibernate.mapping.Column(objects)] indicates that the original field can't be hidden completely (changed table and column name in new class to verify that).
So of course adding #ElementCollection (which is said to solve that) isn't helping here, too.
#AttributeOverride is also not working to override annotations to set the ID or other settings, only the name and column can be changed.
I'm stuck at this state and am wondering if this is even the right approach at all.
The setup or what I would expect to work from my understanding:
The general idea is based on this Spring Boot REST tutorial, which I tried to expand with a model from a dependency.
Let's assume there is the original model class Model from a dependency that can not be modified. The ModelEntity would be the extended class to act as way to pull the model into Spring persistence.
In the scope of the dependency the original class would be like:
// Given dependency, not modifiable
#Some existing annotation
public class Model extends AnotherClassFromDep {
#more annotations
private IdLikeClassFromDep modelId;
//more complex attribute
#Nullable
private List<RefClassFromDep> objects = new ArrayList<>();
// more attributes, getter, setter etc.
}
In the scope of my program:
In combination with this little additional orm.xml it is possible to annotate the original Model as MappedSuperclass without modifying it (according to https://stackoverflow.com/a/2516951/1844976).
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0">
<mapped-superclass class="package.name.of.original.Model">
</mapped-superclass>
</entity-mappings>
This allows to create a class like this, which extends the original POJO model to add JPA annotations:
#Entity
public class ModelEntity extends Model {
// some #Id attribute is necessary, which should correspond to
// the already existing ID attribute from the original `Model`
// in the best case, but an additional one would work too
private #Id #GeneratedValue Long id;
// Different approaches to solve the List error from above, for
// instance hiding the original attribute
#ElementCollection
private List<RefClassFromDep> objects;
public ModelEntity(){
super();
}
}
At the current state the issues are blocking me from going further. But, altogether I would expect this to work with a JpaRepository:
// of course, creating a JpaRepository with original `Model` wouldn't
// work, because it has no `#Entity`
public interface ModelRepository extends JpaRepository<ModelEntity, IdLikeClassFromDep> {
}
In a way that actually accessing it like that is possible:
#Configuration
public class LoadDatabase {
#Bean
CommandLineRunner initDatabase(ModelRepository modelRepository) {
return args -> {
// depending on the implementation above, either create a
// Model and cast it or directly create a ModelEntity, set
// attriubtes and save it through the JpaRepository
modelRepository.save(model);
};
}
}
Both more abstract and specific code-related ideas and comments would help me. Thanks!
In the old days, Jpa/Hibernate were configured via XML.
You needed to provide persistence.xml for general configuration. In this file, you added <mapping-file> tag pointing to another file orm.xml In this file you configured mapping for your entities (which is done via JPA annotations these days).
See https://vladmihalcea.com/how-to-use-external-xml-mappings-files-outside-of-jar-with-jpa-and-hibernate/
While the methods described above are considered legacy, they are still supported. LocalContainerEntityManagerFactoryBean has method setMappingResources allowing you to point to the orm.xml file. There is some funkiness about search paths and default locations, but it is well documented:
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.html#setMappingResources-java.lang.String...-
Note that the third-party class you are configuring this way needs to conform to Java Beans conventions (no-args constructor, getters and setters)

Extend spring data's default syntax

In my current project almost every entity has a field recordStatus which can have 2 values:
A for Active
D for Deleted
In spring data one can normally use:
repository.findByLastName(lastName)
but with the current data model we have to remember about the active part in every repository call, eg.
repository.findByLastNameAndRecordStatus(lastName, A)
The question is: is there any way to extend spring data in such a way it would be able to recognize the following method:
repository.findActiveByLastName(lastName)
and append the
recordStatus = 'A'
automatically?
Spring Data JPA provides 2 additional options for you dealing with circumstances that their DSL can't handle by default.
The first solution is custom queries with an #Query annotation
#Query("select s from MyTable s where s.recordStatus like 'A%'")
public MyObect findActiveByLastName(String lastName);
The second solution is to add a completely custom method the "Old Fashion Way" You can create a new class setup like: MyRepositoryImpl The Impl is important as it is How spring knows to find your new method (Note: you can avoid this, but you will have to manually link things the docs can help you with that)
//Implementation
public class MyRepositoryImpl implements MyCustomMethodInterface {
#PersistenceContext
EntityManager em;
public Object myCustomJPAMethod() {
//TODO custom JPA work similar to this
String myQuery = "TODO";
return em.createQuery(myQuery).execute();
}
}
//Interface
public interface MyCustomMethodInterface {
public Object myCustomJPAMethod();
}
//For clarity update your JPA repository as well so people see your custom work
public interface MySuperEpicRepository extends JPARepository<Object, String>, MyCustomMethodInterface {
}
These are just some quick samples so feel free to go read their Spring Data JPA docs if you would like to get a bit more custom with it.
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/
Finally just a quick note. Technically this isn't a built in feature from Spring Data JPA, but you can also use Predicates. I will link you to a blog on this one since I am not overly familiar on this approach.
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
You can use Spring Data's Specifications. Take a look at this article.
If you create a 'Base'-specification with the recordStatus filter, and deriving all other specifications form this one.
Of course, everybody in your team should use the specifactions api, and not the default spring data api.
I am not sure you can extend the syntax unless you override the base class (SimpleReactiveMongoRepository; this is for reactive mongo but you can find the class for your DB type), what I can suggest you is to extend the base methods and then make your method be aware of what condition you want to execute. If you check this post you get the idea that I did for the patch operation for all entities.
https://medium.com/#ghahremani/extending-default-spring-data-repository-methods-patch-example-a23c07c35bf9

Java 8 Repeatable annotation

Trying to figure out how to get along with Java 8 Repeatable annotations support.
Following :
https://blog.idrsolutions.com/2015/03/java-8-repeating-annotation-explained-in-5-minutes/
it works flawlessly.
But if I'm modifying the example and add just a #Manufacturer on the Car class I'm unable to read that single annotation. So if there's only 1 occurrence of the repeated annotation it can't be read.
So having:
#Manufacturer("Range Rover")
public class Car {
}
Manufacturer[] a = Car.class.getAnnotationsByType(Manufacturer.class );
the size here will be 0
and
Cars cars = Car.class.getAnnotation(Cars.class);
for(Manufacturer car: cars.value())
System.out.println(car.value());
here there will be a NPE on the cars..
Why's that ?
You try to get an annotation of type Cars and not of type Manufacturer.
The following solution worked:
Manufacturer[] annotations = Car.class.getAnnotationsByType(Manufacturer.class);
for (Manufacturer annotation : annotations) {
System.out.println(annotation.name());
}
You should always pass the type of the annotation to getAnnotation or getAnnotationsByType and not the type of the class itself.
Hope it helpt you
So the problem is that the Manufacturer annotation doesn't have a RetentionPolicy declared only the container annotation (Cars).
So adding
#Retention(RetentionPolicy.RUNTIME)
also on the Manufacturer annotation will read the annotation in a container based /single based manner (in a manner of speaking)
Thanks Sean, at least you answer gave me a clue...

How to build custom #Projections for spring-rest?

spring-data-rest makes it possible to expose #Entity domain objects directly and even provide a DTO projection as follows:
#Projection(name = "personDTO", types = { Person.class })
public interface PersonDTO {
#Value("#{target.firstName} #{target.lastName}") //SPeL
String getFullName();
}
Question: what if I want to construct only some of the dto fields myself? Eg having some kind of condition on the firstname field, and fill it based on this either the one or other way. Is that possible?
Spring mentions a example, but unfortunately it's not complete:
https://spring.io/blog/2014/05/21/what-s-new-in-spring-data-dijkstra
#Projection(name = "summary", types = Order.class)
interface OrderSummary {
#Value("#{#shop.calculateTotal(target)}")
Money getTotal();
}
Here the logic is exported to #shop.calulcateTotal(), BUT they don't tell in the example how this #shop bean is injected here. I assume this is a #Service, but don't know how to get it in.
Says right below the example you posted.
https://spring.io/blog/2014/05/21/what-s-new-in-spring-data-dijkstra
For advanced use cases you can even equip the projection methods with #Value to return the result of a SpEL expression to the marshaller. In our sample here, we invoke a method on a Spring bean named shop and hand the proxy target instance to it to calculate the order total, which could consider rebates, taxes etc.
Since your projections are already managed by spring, you don't really need to inject it. Spring magic takes care of it for you.

Make Java annotation act differently depending on field annotated

In Java, is there a way to change the behaviour of an annotation depending on the type of the annotated field?
I know that annotation presence is supposed to be tested by code. Not the opposite. But the case is rather particular: this is a Jackson 2.0 « inside » annotation which gather a list of annotations. We use it to define the field name (#JsonProperty) and the field serializing policies (#JsonSerialize).
The serialisation policies must be adapted to the annotated field. And, because we are talking of a framework, one unique annotation is far better than two separate ones.
#Retention(RUNTIME)
#JacksonAnnotationsInside.
#JsonProperty("_id")
#JsonSerialize(using=IdSerializer.class)
#JsonDeserialize(using=IdDeserializer.class)
public #interface Id {}
Some cases need to turn the serializers down, that's the point. In the following example, the String must be processed by the de/serializers, ObjectId don't. Both need to be renamed _id by the #JsonProperty.
public class Car {
#Id String id
}
public class Bus {
#Id ObjectId id
}
Any clues?

Categories

Resources