Skip to content

Commit

Permalink
Merge pull request #12158 from gytis/rest-data-mongo
Browse files Browse the repository at this point in the history
Panache REST Data with MongoDB
  • Loading branch information
geoand authored Oct 14, 2020
2 parents ecf8d24 + 6b8ed90 commit 815e1fc
Show file tree
Hide file tree
Showing 25 changed files with 1,194 additions and 0 deletions.
10 changes: 10 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,16 @@
<artifactId>quarkus-hibernate-orm-rest-data-panache-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-rest-data-panache</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-rest-data-panache-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-panache</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public enum Feature {
MONGODB_CLIENT,
MONGODB_PANACHE,
MONGODB_PANACHE_KOTLIN,
MONGODB_REST_DATA_PANACHE,
MUTINY,
NARAYANA_JTA,
NARAYANA_STM,
Expand Down
48 changes: 48 additions & 0 deletions extensions/panache/mongodb-rest-data-panache/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-rest-data-panache-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>quarkus-mongodb-rest-data-panache-deployment</artifactId>
<name>Quarkus - MongoDB REST data with Panache - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-rest-data-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-data-panache-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-panache-deployment</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.quarkus.mongodb.rest.data.panache.deployment;

import static io.quarkus.gizmo.MethodDescriptor.ofMethod;

import java.util.List;

import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.mongodb.panache.PanacheMongoEntityBase;
import io.quarkus.mongodb.panache.PanacheQuery;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.rest.data.panache.deployment.DataAccessImplementor;

final class EntityDataAccessImplementor implements DataAccessImplementor {

private final String entityClassName;

EntityDataAccessImplementor(String entityClassName) {
this.entityClassName = entityClassName;
}

@Override
public ResultHandle findById(BytecodeCreator creator, ResultHandle id) {
return creator.invokeStaticMethod(
ofMethod(entityClassName, "findById", PanacheMongoEntityBase.class, Object.class), id);
}

@Override
public ResultHandle listAll(BytecodeCreator creator, ResultHandle sort) {
return creator.invokeStaticMethod(ofMethod(entityClassName, "listAll", List.class, Sort.class), sort);
}

@Override
public ResultHandle findAll(BytecodeCreator creator, ResultHandle page, ResultHandle sort) {
ResultHandle query = creator.invokeStaticMethod(
ofMethod(entityClassName, "findAll", PanacheQuery.class, Sort.class), sort);
creator.invokeInterfaceMethod(ofMethod(PanacheQuery.class, "page", PanacheQuery.class, Page.class), query, page);
return creator.invokeInterfaceMethod(ofMethod(PanacheQuery.class, "list", List.class), query);
}

@Override
public ResultHandle persist(BytecodeCreator creator, ResultHandle entity) {
creator.invokeVirtualMethod(ofMethod(entityClassName, "persist", void.class), entity);
return entity;
}

@Override
public ResultHandle update(BytecodeCreator creator, ResultHandle entity) {
creator.invokeVirtualMethod(ofMethod(entityClassName, "persistOrUpdate", void.class), entity);
return entity;
}

@Override
public ResultHandle deleteById(BytecodeCreator creator, ResultHandle id) {
return creator.invokeStaticMethod(ofMethod(entityClassName, "deleteById", boolean.class, Object.class), id);
}

@Override
public ResultHandle pageCount(BytecodeCreator creator, ResultHandle page) {
ResultHandle query = creator.invokeStaticMethod(ofMethod(entityClassName, "findAll", PanacheQuery.class));
creator.invokeInterfaceMethod(ofMethod(PanacheQuery.class, "page", PanacheQuery.class, Page.class), query, page);
return creator.invokeInterfaceMethod(ofMethod(PanacheQuery.class, "pageCount", int.class), query);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package io.quarkus.mongodb.rest.data.panache.deployment;

import static io.quarkus.deployment.Feature.MONGODB_REST_DATA_PANACHE;

import java.lang.reflect.Modifier;
import java.util.List;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Type;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;

import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.gizmo.Gizmo;
import io.quarkus.mongodb.rest.data.panache.PanacheMongoEntityResource;
import io.quarkus.mongodb.rest.data.panache.PanacheMongoRepositoryResource;
import io.quarkus.rest.data.panache.deployment.DataAccessImplementor;
import io.quarkus.rest.data.panache.deployment.RestDataEntityInfo;
import io.quarkus.rest.data.panache.deployment.RestDataResourceBuildItem;
import io.quarkus.rest.data.panache.deployment.RestDataResourceInfo;

class MongoPanacheRestProcessor {

private static final DotName PANACHE_MONGO_ENTITY_RESOURCE_INTERFACE = DotName
.createSimple(PanacheMongoEntityResource.class.getName());

private static final DotName PANACHE_MONGO_REPOSITORY_RESOURCE_INTERFACE = DotName
.createSimple(PanacheMongoRepositoryResource.class.getName());

@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(MONGODB_REST_DATA_PANACHE);
}

@BuildStep
void findEntityResources(CombinedIndexBuildItem index, BuildProducer<RestDataResourceBuildItem> resourcesProducer) {
RestDataEntityInfoProvider entityInfoProvider = new RestDataEntityInfoProvider(index.getIndex());
for (ClassInfo classInfo : index.getIndex().getKnownDirectImplementors(PANACHE_MONGO_ENTITY_RESOURCE_INTERFACE)) {
validateResource(index.getIndex(), classInfo);
List<Type> generics = getGenericTypes(classInfo);
String entityClassName = generics.get(0).toString();
String idClassName = generics.get(1).toString();
RestDataEntityInfo entityInfo = entityInfoProvider.get(entityClassName, idClassName);
DataAccessImplementor dataAccessImplementor = new EntityDataAccessImplementor(entityClassName);
RestDataResourceInfo resourceInfo = new RestDataResourceInfo(classInfo.toString(), entityInfo,
dataAccessImplementor);
resourcesProducer.produce(new RestDataResourceBuildItem(resourceInfo));
}
}

@BuildStep
void findRepositoryResources(CombinedIndexBuildItem index, BuildProducer<RestDataResourceBuildItem> resourcesProducer,
BuildProducer<UnremovableBeanBuildItem> unremovableBeansProducer,
BuildProducer<BytecodeTransformerBuildItem> bytecodeTransformersProducer) {
RestDataEntityInfoProvider entityInfoProvider = new RestDataEntityInfoProvider(index.getIndex());
for (ClassInfo classInfo : index.getIndex().getKnownDirectImplementors(PANACHE_MONGO_REPOSITORY_RESOURCE_INTERFACE)) {
validateResource(index.getIndex(), classInfo);
List<Type> generics = getGenericTypes(classInfo);
String repositoryClassName = generics.get(0).toString();
String entityClassName = generics.get(1).toString();
String idClassName = generics.get(2).toString();
RestDataEntityInfo entityInfo = entityInfoProvider.get(entityClassName, idClassName);
DataAccessImplementor dataAccessImplementor = new RepositoryDataAccessImplementor(repositoryClassName);
RestDataResourceInfo resourceInfo = new RestDataResourceInfo(classInfo.toString(), entityInfo,
dataAccessImplementor);
resourcesProducer.produce(new RestDataResourceBuildItem(resourceInfo));
unremovableBeansProducer.produce(
new UnremovableBeanBuildItem(new UnremovableBeanBuildItem.BeanClassNameExclusion(repositoryClassName)));
bytecodeTransformersProducer
.produce(getEntityIdAnnotationTransformer(entityClassName, entityInfo.getIdField().name()));
}
}

private void validateResource(IndexView index, ClassInfo classInfo) {
if (!Modifier.isInterface(classInfo.flags())) {
throw new RuntimeException(classInfo.name() + " has to be an interface");
}

if (classInfo.interfaceNames().size() > 1) {
throw new RuntimeException(classInfo.name() + " should only extend REST Data Panache interface");
}

if (!index.getKnownDirectImplementors(classInfo.name()).isEmpty()) {
throw new RuntimeException(classInfo.name() + " should not be extended or implemented");
}
}

private List<Type> getGenericTypes(ClassInfo classInfo) {
return classInfo.interfaceTypes()
.stream()
.findFirst()
.orElseThrow(() -> new RuntimeException(classInfo.toString() + " does not have generic types"))
.asParameterizedType()
.arguments();
}

/**
* Annotate Mongo entity ID fields with a RESTEasy links annotation.
* Otherwise RESTEasy will not be able to generate links that use ID as path parameter.
*/
private BytecodeTransformerBuildItem getEntityIdAnnotationTransformer(String entityClassName, String idFieldName) {
return new BytecodeTransformerBuildItem(entityClassName,
(className, classVisitor) -> new ClassVisitor(Gizmo.ASM_API_VERSION, classVisitor) {
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
FieldVisitor fieldVisitor = super.visitField(access, name, descriptor, signature, value);
if (name.equals(idFieldName)) {
fieldVisitor.visitAnnotation("Lorg/jboss/resteasy/links/ResourceID;", true).visitEnd();
}
return fieldVisitor;
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.quarkus.mongodb.rest.data.panache.deployment;

import static io.quarkus.gizmo.MethodDescriptor.ofMethod;

import java.lang.annotation.Annotation;
import java.util.List;

import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase;
import io.quarkus.mongodb.panache.PanacheQuery;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.rest.data.panache.deployment.DataAccessImplementor;

final class RepositoryDataAccessImplementor implements DataAccessImplementor {

private final String repositoryClassName;

RepositoryDataAccessImplementor(String repositoryClassName) {
this.repositoryClassName = repositoryClassName;
}

@Override
public ResultHandle findById(BytecodeCreator creator, ResultHandle id) {
return creator.invokeInterfaceMethod(ofMethod(PanacheMongoRepositoryBase.class, "findById", Object.class, Object.class),
getRepositoryInstance(creator), id);
}

@Override
public ResultHandle listAll(BytecodeCreator creator, ResultHandle sort) {
return creator.invokeInterfaceMethod(ofMethod(PanacheMongoRepositoryBase.class, "listAll", List.class, Sort.class),
getRepositoryInstance(creator), sort);
}

@Override
public ResultHandle findAll(BytecodeCreator creator, ResultHandle page, ResultHandle sort) {
ResultHandle query = creator.invokeInterfaceMethod(
ofMethod(PanacheMongoRepositoryBase.class, "findAll", PanacheQuery.class, Sort.class),
getRepositoryInstance(creator), sort);
creator.invokeInterfaceMethod(ofMethod(PanacheQuery.class, "page", PanacheQuery.class, Page.class), query, page);
return creator.invokeInterfaceMethod(ofMethod(PanacheQuery.class, "list", List.class), query);
}

@Override
public ResultHandle persist(BytecodeCreator creator, ResultHandle entity) {
creator.invokeInterfaceMethod(ofMethod(PanacheMongoRepositoryBase.class, "persist", void.class, Object.class),
getRepositoryInstance(creator), entity);
return entity;
}

@Override
public ResultHandle update(BytecodeCreator creator, ResultHandle entity) {
creator.invokeInterfaceMethod(ofMethod(PanacheMongoRepositoryBase.class, "persistOrUpdate", void.class, Object.class),
getRepositoryInstance(creator), entity);
return entity;
}

@Override
public ResultHandle deleteById(BytecodeCreator creator, ResultHandle id) {
return creator.invokeInterfaceMethod(
ofMethod(PanacheMongoRepositoryBase.class, "deleteById", boolean.class, Object.class),
getRepositoryInstance(creator), id);
}

@Override
public ResultHandle pageCount(BytecodeCreator creator, ResultHandle page) {
ResultHandle query = creator.invokeInterfaceMethod(
ofMethod(PanacheMongoRepositoryBase.class, "findAll", PanacheQuery.class), getRepositoryInstance(creator));
creator.invokeInterfaceMethod(ofMethod(PanacheQuery.class, "page", PanacheQuery.class, Page.class), query, page);
return creator.invokeInterfaceMethod(ofMethod(PanacheQuery.class, "pageCount", int.class), query);
}

private ResultHandle getRepositoryInstance(BytecodeCreator creator) {
ResultHandle arcContainer = creator.invokeStaticMethod(ofMethod(Arc.class, "container", ArcContainer.class));
ResultHandle instanceHandle = creator.invokeInterfaceMethod(
ofMethod(ArcContainer.class, "instance", InstanceHandle.class, Class.class, Annotation[].class),
arcContainer, creator.loadClass(repositoryClassName), creator.newArray(Annotation.class, 0));
ResultHandle instance = creator.invokeInterfaceMethod(
ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle);

creator.ifNull(instance).trueBranch()
.throwException(RuntimeException.class, repositoryClassName + " instance was not found");

return instance;
}
}
Loading

0 comments on commit 815e1fc

Please sign in to comment.