ClassCastException being thrown from SpringData's QueryDSL SimpleEntityPathResolver - mongodb

I am attempting to use QueryDSL with SpringData plus a Mongo repository.
Here is a simplification of my three levels of a document class hierarchy:
Top-Level Entity class
public abstract class AbstractEntity<T extends Serializable> {
public abstract T getId();
...
}
Base class for Document (Mongo) entities
import org.springframework.data.annotation.Id;
import com.querydsl.core.annotations.QueryEntity;
#QueryEntity
public abstract class DocumentEntity extends AbstractEntity<String> {
#Id
private String id;
public DocumentEntity() {
}
public String getId() {
return id;
}
...
}
Actual entity/document implementation
import org.springframework.data.mongodb.core.index.TextIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
import com.blah.data.entity.DocumentEntity;
import com.querydsl.core.annotations.QueryEntity;
#QueryEntity
#Document(collection="Manufacturers")
public class Manufacturer extends DocumentEntity {
#TextIndexed
private String name;
public Manufacturer() {
}
...
}
The primary version numbers of consequence here, I believe, are
SpringBoot: 1.5.9.RELEASE
QueryDSL: 4.1.4
Here is my plugin configuration:
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor> org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
The "Q" classes are generated successfully in my maven build. However, my QDocumentEntity query class extends BeanPath and my QManufacturer query class extends EntityPathBase. This seems to be the source of the issue.
When I launch my application, I get an exception with the following root cause:
Caused by: java.lang.ClassCastException: com.blah.data.entity.QDocumentEntity cannot be cast to com.querydsl.core.types.EntityPath
at org.springframework.data.querydsl.SimpleEntityPathResolver.createPath(SimpleEntityPathResolver.java:59)
at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.<init>(QueryDslMongoRepository.java:85)
at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.<init>(QueryDslMongoRepository.java:67)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:142)
Based on this error, it seems that the super-class, DocumentEntity, should have been generated as extending EntityPathBase as well.
I'm not sure if it is relevant or not, but the AbstractEntity and DocumentEntity classes are in a separate maven module.
Am I missing something that is required to mark the superclass as an entity to the plugin? How can I force the generator to see my superclasses as entities?

So far, I've discovered 2 things. At this point, these two things are enough to be a workaround.
I ran the maven build from within Eclipse so that I could step through and debug what the annotation processor was doing. This led me to my first realization.
My parent class, DocumentEntity, was not being recognized as an Entity in com.querydsl.apt.ExtendedTypeFactory.createClassType(). The initial determined type was TypeCategory.SIMPLE and it never got flipped to TypeCategory.ENTITY because none of the defined entityAnnotations were present on the parent class. In order for it to be recognized as an entity, there has to be one of the defined annotations: entity, supertype or embeddable. Springframework's MongoAnnotationProcessor defined these as Document.class, QuerySupertype.class and QueryEmbeddable.class.
So, I added #QuerySupertype to my DocumentEntity class and got a different errror.
/target/generated-sources/java/com/blah/entity/QManufacturer.java:22: error: cannot find symbol
public final com.blah.data.entity.QDocumentEntity _super = new com.blah.data.entity.QDocumentEntity(this);
^
symbol: class QDocumentEntity
location: package com.blah.data.entity
1 error
As I mentioned in the OP, the parent class(es) are in another module. If I move the DocumentEntity (with the appropriate annotation) into the same module, it seems to work as expected. The QDocumentEntity class correctly extends EntityPathBase.
I wont' mark the answer as accepted yet, because in my mind it isn't quite answered even though I'm able to make a change and move on.
I don't know if it has to do with the timing of the APT processing for each module or what.
I really would like to have a base "Document" entity class that is in a sharable module and then have specific child modules extend from that and still get the appropriate Q classes generated. My assumption is that I'm still missing something.
When time permits, I may try to step through the APT processing some more to see if I can get to the bottom of it.

Related

Injection causes WELD-001408: Unsatisfied dependencies

I have a java EE web application in which a particular injection causes following error:
WELD-001408: Unsatisfied dependencies for type GenericDAO with qualifiers #Default
MyBean
#Stateless
public class MyBean extends CustomBean<Entity> {
...
}
CustomBean
public class CustomBean<T extends Serializable> implements Serializable {
#Inject
private GenericDAO<T> genericDAO;
}
GenericDAO
public abstract class GenericDAO<T extends Serializable> implements Serializable {
...
}
The issue appears only if a beans.xml is defined in the application. Deleting this, solves also the issue. In my case beans.xml is needed. Also when removing the GenericDAO<T> genericDAO; injection from the CustomBean, the error does not appear anymore. Also the rest of my injection in other classes, seem to work without any issue.
I've tried to solve that by creating an interface of GenericDAO and inject the interface instead. Also tried to with several anotations like #Local , #Dependent etc but i stumble upon different errors every time.

NodeEntity not recognized when SessionFactory is created inside library

I am not sure if my problem is a non-existent feature or I am using the neo4j-ogm framework incorrectly. Since you are only supposed to post bugs or feature requests in the projects GitHub repository, I would like to place my question here first.
Please be aware, that I shortened my real code to just give you an idea what I try to achieve.
My example application consists of two modules:
Module a contains a class to create neo4j-ogm sessions. There I read the configuration from a property file and create the session factory, passing the packages to scan as parameters:
public Neo4jController(final String ... packagesWithNodes) {
ConfigurationSource props = new ClasspathConfigurationSource("neo4jogm.properties");
Configuration configuration = new Configuration.Builder(props).build();
SessionFactory sessionFactory = new SessionFactory(configuration, packagesWithNodes);
...
}
Module b includes module a as a Maven dependency and then tries to persist a NodeEntity via the Session object. The Session is created correctly, but the NodeEntity in the passed package is not recognized.
public MyObject create(final MyObject newObject) {
Neo4jController neo4jController = new Neo4jController("my.example.package");
neo4jController.getNeo4jSession().save(newObject);
...
}
This always results in an IllegalArgumentException:
Class class my.example.package.MyObject is not a valid entity class. Please check the entity mapping.
This is what my NodeEntity in module b looks like
package my.example.package;
import de.bitandgo.workflow.common.model.Neo4jNode;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Property;
#NodeEntity(label = "MyObject")
public class MyObject extends Neo4jNode {
#Property
public String name;
}
The base class contains among others
public abstract class Neo4jNode {
#Id
#GeneratedValue
private Long id;
}
I know that neo4j-ogm uses glassgraph internally for scanning the classpath. The corresponding call in the framework with prepended configuration looks like this:
private static List<String> useClassgraph(String[] packagesOrClasses) {
// .enableExternalClasses() is not needed, as the super classes are loaded anywhere when the class is loaded.
try (ScanResult scanResult = new ClassGraph()
.ignoreClassVisibility()
.acceptPackages(packagesOrClasses)
.acceptClasses(packagesOrClasses)
.scan()) {
return scanResult.getAllClasses().getNames();
}
}
Via debugging I verified that the desired package my.example.package was passed as an argument to the GlassGraph object.
Also the Neo4jController in module a is able to load the class. I have tested this with the following code
try {
Class<?> aClass = Class.forName("my.example.package.MyObject");
System.out.println(aClass); // prints "class my.example.package.MyObject"
} catch (ClassNotFoundException e) {}
My question is whether neo4j-ogm/classgraph may not be able to find the class at all due to the way neo4j-ogm uses classgraph, or if I am doing something wrong in the usage.
Update 1:
The problem seems to be related to my application running inside an application server (TomEE). When I deploy my application ClassGraph is not able to find the configured package.
But executing the exact same scan via a classical main method finds me the expected class.
public static void main(String[] args) {
try (ScanResult scanResult = new ClassGraph()
.ignoreClassVisibility()
.acceptPackages("my.example.package")
.acceptClasses()
.scan()) {
System.out.println(scanResult.getAllClasses().getNames());
}
}
So I assume the problem is related to the compiled classes not being visible in the "normal" classpath when deploying the application into an application server?
I appreciate any input.
Thanks a lot
The problem was indeed related to the glassgraph library, which is used by neo4j-ogm for scanning the class path for model files.
glassgraph supports a wide variety of classloaders, but the one of TomEE wasn't included yet. This problem has been fixed since version 4.8.107, so if you run into a similar problem check whether glassgraph supports the classloader of the application server used by you.
You can easily overwrite neo4j-ogm's classgraph dependency by specifying the version you need in your pom.xml.
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-core</artifactId>
<version>${neo4j-version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-bolt-driver</artifactId>
<version>${neo4j-version}</version>
<scope>runtime</scope>
</dependency>
<!-- overwriting the internal classloader of neo4j-ogm because it's used version does not support ApacheTomEE -->
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.107</version>
</dependency>
The problem was not related to my multi module Maven structure.

Lombok inheritance and Eclipse

Lombok: 1.16.18
Eclipse: Oxygen
I have an object with #Getter and #Setter as a base object:
#Getter
#Setter
#EqualsAndHashCode(of="uuid")
public abstract class BaseEntity implements Serializable,Auditable {
Auditable requires setCreatedBy, which is included in BaseEntity.
However, the following object, which extends BaseEntity, receives an error of "The type Login must implement the inherited method Auditable.setCreatedBy(String)"
#Getter
#Setter
public class Login extends BaseEntity{
Looking on the outline pane, setCreatedBy is properly generated on BaseEntity, but does not exist on Login. What am I configuring wrong?
Not sure what happened, but a full rebuild eventually fixed it. Not sure that was the issue though, as I had done several full builds since upgrading lombok.

AEM Osgi Sling Model #PostConstruct never called

I am having an issue with the javax.annotation.PostConstruct annotation in my Sling model.
My html file that uses my model:
<div data-sly-use="com.company.platform.component.general.textblockvalidator.TextBlockValidatorModel" data-sly-unwrap />
Model:
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
#Model(adaptables = org.apache.sling.api.resource.Resource.class)
public class TextBlockValidatorModel {
#PostConstruct
private void init() {
System.out.println();
}
public String getValidate(){
return "This works";
}
}
I can call the getter from my sightly file but I never seem to enter my #PostConstruct init() method.
IntelliJ does give me a warning on the annotation but I am not sure what I am doing wrong:
Sling-model-packages:
<Sling-Model-Packages>
...
com.asadventure.platform.component
...
</Sling-Model-Packages>
Any ideas? Thanks in advance!
First, check your Sling Model has been registered correctly by looking for your class in this web page:
http://localhost:4502/system/console/status-adapters
If it isn't listed there, you most likely have not specified the <Sling-Model-Packages> property of the maven-bundle-plugin.
I would also try changing the access modifier for the init method to protected or public.
UPDATE:
I've created a sample project for AEM 6.1 demonstrating the use of the #PostConstruct annotation.
The Sling Model class:
#Model(adaptables = Resource.class)
public class SampleModel {
private boolean postContructCalled = false;
#PostConstruct
public void init() {
this.postContructCalled = true;
}
public boolean isPostContructCalled() {
return this.postContructCalled;
}
}
And a simple HTL component:
<sly data-sly-use.model="com.github.mickleroy.models.SampleModel">
<p>#PostConstruct was called: ${model.postContructCalled}</p>
</sly>
Please take note of the use of the data-sly-use directive - you need to provide a model name.
Also, as I mentioned in the comments, you should not be adding javax.annotation-api as a dependency as it is part of the JDK.
Full source available here: https://github.com/mickleroy/sling-models-sample
For anyone still looking for an answer to this that the above did not resolve, the issue for me was that I did not include the javax.annotation-api dependency:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>provided</scope>
</dependency>
Once I added this in the parent pom, and its inclusion in the core pom, #PostConstruct worked just fine.
Update:
The reason I had to do this was because of my inclusion of jersey-client, which requires its own version of javax.annotation-api. Since my first rendition of this answer, I have found I needed to separate jersey-client and its dependencies into a separate bundle project. This allows both Jersey and #PostConstruct to work at the same time.
Just adding the dependency as the answer shows above caused issues with dependency clashes between Jersey's version of javax.annotation-api and AEM's version (Felix's version) of javax.annotation-api.
My guess is that your class is being initialized by the Java Use provider instead of adapting the current resource or request.
In sightly, when you use data-sly-use, it tries several things to obtain an object (I cant recall the order):
get an Osgi service with that name
use the AEM Java USE Api
Adapt the current request / resource into your model class (your desired case)
simply treat the class as a Java Pojo and instantiate it (post construct is not called, injection wont be done).
I've seen several cases where the injection or postconstruct methods of the sling models fails and sightly defaults to the java Use provider. If this happens what you describe happens. You have an object of the right class, but no injection happened and no post construct was called.
My recommendation is to careful check the logs, you should see an error if this is the case. Also, you can install the Scripting HTL Sling Models Use Provider which will propagate any error creating the sling model, making the problem obvious.

Eclipse shows "Target entity "java.lang.Object" is not an Entity "

In my JPA code i use:
#MappedSuperclass
public abstract class BaseEntityVotes<T> extends BaseEntity {
/**
* The entity this vote belongs to.
*/
#ManyToOne
#NotNull
private T entity;
But Eclipse can't validate: "Target entity "java.lang.Object" is not an Entity".
(no error in IntelliJ)
I tried to change the type parameter to <T extends BaseEntity> (BaseEntity is also a MappedSuperclass) but this only changes the error text accordingly. Is this a real problem? Or is this a bug in eclipse?
In this regard i found a fixed bug. But this didn't helped. So is there a fix or work around to get rid of this error or can i fix the code in some way?