Quarkus + Panache + Active Record Pattern + Inheritance problem - quarkus

I can create a Base class that extends PanacheMongoEntity (example below) and the Child class extending Base works as expected. However, if I move the Base class into a separate JAR file (e.g., core.jar), I get an error “java.lang.IllegalStateException: This method is normally automatically overridden in subclasses” when calling Child.listAll().
public class Base extends PanacheMongoEntity {
public String modifiedDate;
}
public class Child extends Base {
public String name;
}
// works
Child.listAll();
As mentioned, if the Base class and Child class are compiled at the same time, it works. But moving Base to a JAR and including as a dependency does not work.

For all external jars to get scanned by Quarkus, you need to add an empty beans.xml in /src/main/resources/META-INF in your external project.
This is described somewhere on Quarkus guides.

Related

Quarkus extension using a repository based on PanacheMongoRepository

I'm currently working on a Quarkus extension which is basically a filter that is using a PanacheMongoRepository. Here is a code snippet (this is in the runtime part of the extension) :
#Provider
#Priority(Priorities.AUTHORIZATION)
#AuthorizationSecured
public class AuthorizationFilter implements ContainerRequestFilter {
// Some injection here
#Inject
UserRepository userRepository;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Some business logic here...
UserEntity userEntity = userRepository.findByName(name);
// Some business logic here...
}
}
The repository :
#ApplicationScoped
public class UserRepository implements PanacheMongoRepository<UserEntity> {
public UserEntity findByName(String name) {
return find("some query...", name).firstResult();
}
}
When the repository is called, I get the following exception:
org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalStateException: This method is normally automatically overridden in subclasses...
java.lang.IllegalStateException: This method is normally automatically overridden in subclasses\n\tat io.quarkus.mongodb.panache.common.runtime.MongoOperations.implementationInjectionMissing(MongoOperations.java:765)\n\tat io.quarkus.mongodb.panache.PanacheMongoRepositoryBase.find(PanacheMongoRepositoryBase.java:119)
The processor
class AuthorizeProcessor {
private static final String FEATURE = "authorize";
#BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
#BuildStep(onlyIf = IsAuthorizeEnabled.class)
void registerAuthorizeFilter(
BuildProducer<AdditionalBeanBuildItem> additionalBeanProducer,
BuildProducer<ResteasyJaxrsProviderBuildItem> resteasyJaxrsProviderProducer
) {
additionalBeanProducer.produce(new AdditionalBeanBuildItem(UserRepository.class));
additionalBeanProducer.produce(new AdditionalBeanBuildItem(AuthorizationFilter.class));
resteasyJaxrsProviderProducer.produce(new ResteasyJaxrsProviderBuildItem(AuthorizationFilter.class.getName()));
}
}
Any idea ?
Thanks for your help :)
MongoDB with Panache (and the same for Hibernate with Panache) uses bytecode enhancement at build time. When this enhancement didn't occurs it leads to the exception you mentionned at runtime: java.lang.IllegalStateException: This method is normally automatically overridden in subclasses
It can occurs only when the repository or entity is not in the Jandex index. Jandex is used to index all the code of your application to avoid using reflection and classpath scanning to discover classes. If your entity / repository is not in the index this means it's not part of your application as we automatically index the classes of your application, so it must be inside an external JAR.
Usually, this is solved by adding the Jandex plugin to index the code of the external JAR (in fact there is multiple way to do this, see How to Generate a Jandex Index).
An extension suffer from the same issue as extensions are not indexed by default. But from an extension you can index the needed classes via a build step wich is more easy and avoid polluting the index with classes that are not needed.
This can be done by generating a new AdditionalIndexedClassesBuildItem(UserRepository.class.getName()) inside a build step.

Spring's couchbase JPA repository with abstract class fails to find entity

We are developing a project in Springboot that uses a Couchbase, I have following classes:
public abstract class Content {
...
}
public class Film extends Content {
...
}
public class Serie extends Content {
...
}
Then I have following JPA repository:
public interface ContentJpaRepository extends ReactiveCouchbaseSortingRepository<Content> {
}
Then, when I save a content (film or serie) the content is successfully saved, however, the _class field gets the simple class name (instead of the full package name).
Then, when doing:
repository.findById(id);
The repository fails as it can't deserialize the json document to the expected entity. How could I achieve that?
Thank you very much
Using a generic repository is currently not supported for Couchbase Spring Data, as the _class attribute will refer to the abstract class instead of its implementations.

Dependency Injection with dynamically instanciated class with Spring Boot

I'm trying to develop a spring-boot application which offer the possibility for the user to create and call some simple workflows.
The steps of the workflows are already written (they all extends the same class), and, when the user create a workflow, he/she just pick which steps he wants to include in his it. The steps and the workflows are saved in a database.
My problem comes when the user call the workflow: I want to instanciate dynamically each step using the class loader but with the dependencies injected by spring!
Here is an example of a plug-in:
public class HelloWorldStepPlugin extends StepPlugin {
private static final Logger LOG = LogManager.getLogger();
#Autowired
private HelloWorldRepository repository;
public HelloWorldStepPlugin() {
super(HelloWorldStepPlugin.class.getSimpleName());
}
#Override
public void process() {
LOG.info("Hello world!");
this.repository.findAll(); // <= throw a NullPointerException because this.repository is null
}
}
Here is how I execute a Workflow (in another class):
ClassLoader cl = getClass().getClassLoader();
for (Step s : workflow.getSteps()) {
StepPlugin sp = (StepPlugin) cl.loadClass(STEP_PLUGIN_PACKAGE + s.getPlugin()).newInstance();
sp.process();
}
How can I do to have my HelloWorldRepository injected by Spring?
Is there a much better approach to do what I intend to?
I suggest you declare your steps as prototype beans. Instead of saving class names in the database, save bean names. Then get the steps and the plugins from the spring context (i.e. using getBean()).

Inject #Parameter in different class in maven plugin

I am writing a maven plugin and based on my previous experience i know that my mojo class will end up with a bunch of #Parameters to configure it. What I would like to do is instead of having those configuration parameters injected in the mojo class, I would like to have them injected in a second, configuration-only class. Is this possible?
The current way I do it is the mojo class just constructs a Configuration object where it passes all the injected parameters. Something like this
#Mojo
public class MyMojo extends AbstractMojo {
private MyConfig myConfig;
#Parameter
private String myArg1;
...
public void execute() {
myConfig = new MyConfig(myArg1, myArg2, ...);
}
}
But this is rather ugly. I want the DI to happen directly in Config
If MyConfig is a pojo, you can use the #Parameter here as well, However, you config will look like:
<configuration>
<myConfig>
<someField>value</someField>
</myConfig>
</configuration>
The second trick is to use setters, because an #Parameter-annotated field will use the matching public setter, if there is one.
private MyConfig myConfig = new MyConfig();
#Parameter
private String someField;
// matching setter for #parameter-annotated field
public void setSomeField( String field )
{
myConfig.setSomeField( field );
}
You can use your MyConfig POJO as #Parameter:
#Mojo
public class MyMojo extends AbstractMojo {
#Parameter
private MyConfig myConfig;
...
}
The rules for mapping complex objects are as follows:
There must be a private field that corresponds to name of the element being mapped. So in our case the person element must map to a person field in the mojo.
The object instantiated must be in the same package as the Mojo itself. So if your mojo is in com.mycompany.mojo.query then the mapping mechanism will look in that package for an object named Person. As you can see the mechanism will capitalize the first letter of the element name and use that to search for the object to instantiate.
Source
However, using the #Parameter for fields of MyConfig has no effect, see this thread.

Good way to define module in Spring mvc

Im using Spring mvc 3.1 version and Apache Tiles 2.2.2 version i'd like to define some common modules in my applications pages.
For example i want to define a menu in the top, a left side and right side,.. all my page will display these block.
Im using Tiles to define the differents blocks, some part of tiles implements ViewPreparer because i need to get information from database, know if the user is logged,... each tile is responsable of its own module(get data, set attribute for the jsp...).
Is it a good way to create some modules ? Or should i define a controller who will define the data, the business...to all page modules ? (left side, right side, menu...)
If your common module only consists of HTML then it doesn't matter how you do it. Tiles template is sufficient.
The problem is if the common module need models to be populated on the controller. You don't want to duplicate the code on every single of your controller which view includes the common module.
One approach you can take is subclass your controller with a class that populates common module model, eg:
public class CommonHandler {
#ModelAttribute("loggedInUser")
public UserInfo getLoggedInUser() {
// check and return logged in user if any here..
}
}
#Controller
public class MyController extends CommonHandler (
#RequestMapping(..)
public String myHandler() {
// ...
}
}
In above example if myHandler is requested, getLoggedInUser from CommonHandler class will automatically be called to populate loggedInUser model. In your view you just obtain it using ${loggedInUser}
When using ViewPreparerSupport which implements ViewPreparer, it works very well :
#Component
public class MyPreparer extends ViewPreparerSupport {
#Autowired
private UtilisateurService utilisateurService;
#Override
public void execute(TilesRequestContext tilesContext,
AttributeContext attributeContext) {
//information to set for the jsp tile
}
}

Resources