Skip to content

Commit

Permalink
Merge pull request #6 from Koboo/async
Browse files Browse the repository at this point in the history
Async
  • Loading branch information
Koboo authored Jan 29, 2023
2 parents 12d3c74 + d5fbbff commit d52ca2b
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 31 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Project properties ###
projectGroup=eu.koboo
projectVersion=2.1.0
projectVersion=2.2.0
#
### Dependency versions ###
lombokVersion=1.18.24
Expand Down
52 changes: 47 additions & 5 deletions src/main/java/eu/koboo/en2do/MongoManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import eu.koboo.en2do.repository.entity.compound.CompoundIndex;
import eu.koboo.en2do.repository.entity.compound.Index;
import eu.koboo.en2do.repository.entity.ttl.TTLIndex;
import eu.koboo.en2do.repository.methods.async.Async;
import eu.koboo.en2do.repository.methods.pagination.Pagination;
import eu.koboo.en2do.repository.methods.sort.*;
import eu.koboo.en2do.repository.methods.transform.Transform;
Expand All @@ -50,7 +51,9 @@
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.regex.Pattern;

import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
Expand All @@ -63,6 +66,7 @@
* See documentation: <a href="https://koboo.gitbook.io/en2do/get-started/create-the-mongomanager">...</a>
*/
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@SuppressWarnings("unused")
public class MongoManager {

// Predefined methods by Java objects
Expand All @@ -74,15 +78,19 @@ public class MongoManager {
Map<Class<?>, Repository<?, ?>> repositoryRegistry;
Map<Class<?>, RepositoryMeta<?, ?, ?>> repositoryMetaRegistry;

ExecutorService executorService;

@Getter
CodecRegistry codecRegistry;
MongoClient client;
MongoDatabase database;

public MongoManager(Credentials credentials) {
public MongoManager(Credentials credentials, ExecutorService executorService) {
repositoryRegistry = new ConcurrentHashMap<>();
repositoryMetaRegistry = new ConcurrentHashMap<>();

this.executorService = executorService;

// If no credentials given, try loading them from default file.
if (credentials == null) {
credentials = Credentials.fromFile();
Expand Down Expand Up @@ -137,12 +145,24 @@ public MongoManager(Credentials credentials) {
database = client.getDatabase(databaseString);
}

public MongoManager(Credentials credentials) {
this(credentials, null);
}

public MongoManager() {
this(null);
this(null, null);
}


public boolean close() {
return close(true);
}

public boolean close(boolean shutdownExecutor) {
try {
if(executorService != null && shutdownExecutor) {
executorService.shutdown();
}
if (repositoryRegistry != null) {
repositoryRegistry.clear();
}
Expand Down Expand Up @@ -273,11 +293,13 @@ public <E, ID, R extends Repository<E, ID>> R create(Class<R> repositoryClass) {
for (Method method : repositoryClass.getMethods()) {
String methodName = method.getName();

// Apply transform annotation
Transform transform = method.getAnnotation(Transform.class);
if (transform != null) {
methodName = transform.value();
}

// Check if we catch a predefined method
if (repositoryMeta.isRepositoryMethod(methodName)) {
continue;
}
Expand All @@ -286,9 +308,29 @@ public <E, ID, R extends Repository<E, ID>> R create(Class<R> repositoryClass) {
if (IGNORED_DEFAULT_METHODS.contains(methodName)) {
continue;
}
// Check for the return-types of the methods, and their defined names to match our pattern.

// Get the default return type of the method
Class<?> returnType = method.getReturnType();

// Check if the method is async and if so, check for completable future return type.
boolean isAsyncMethod = method.isAnnotationPresent(Async.class);
if (isAsyncMethod) {
// Check async method name
if (methodName.startsWith("async")) {
String predefinedName = repositoryMeta.getPredefinedNameByAsyncName(methodName);
if (repositoryMeta.isRepositoryMethod(predefinedName)) {
continue;
}
throw new MethodInvalidAsyncNameException(method, repositoryClass);
}
// Check CompletableFuture return type
if (GenericUtils.isNotTypeOf(returnType, CompletableFuture.class)) {
throw new MethodInvalidAsyncReturnException(method, repositoryClass);
}
returnType = GenericUtils.getGenericTypeOfReturnType(method);
}


// Parse the MethodOperator by the methodName
MethodOperator methodOperator = MethodOperator.parseMethodStartsWith(methodName);
if (methodOperator == null) {
Expand Down Expand Up @@ -341,7 +383,7 @@ public <E, ID, R extends Repository<E, ID>> R create(Class<R> repositoryClass) {
if (GenericUtils.isNotTypeOf(List.class, paramClass)) {
throw new MethodMismatchingTypeException(method, repositoryClass, List.class, paramClass);
}
Class<?> listType = GenericUtils.getGenericTypeOfParameterList(method, paramIndex);
Class<?> listType = GenericUtils.getGenericTypeOfParameter(method, paramIndex);
if (GenericUtils.isNotTypeOf(fieldClass, listType)) {
throw new MethodInvalidListParameterException(method, repositoryClass, fieldClass, listType);
}
Expand Down Expand Up @@ -505,7 +547,7 @@ public <E, ID, R extends Repository<E, ID>> R create(Class<R> repositoryClass) {
ClassLoader repoClassLoader = repositoryClass.getClassLoader();
Class<?>[] interfaces = new Class[]{repositoryClass};
Repository<E, ID> repository = (Repository<E, ID>) Proxy.newProxyInstance(repoClassLoader, interfaces,
new RepositoryInvocationHandler<>(repositoryMeta));
new RepositoryInvocationHandler<>(repositoryMeta, executorService));
repositoryRegistry.put(repositoryClass, repository);
repositoryMetaRegistry.put(repositoryClass, repositoryMeta);
return (R) repository;
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/eu/koboo/en2do/internal/MethodCallable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package eu.koboo.en2do.internal;

public interface MethodCallable {

Object call() throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import eu.koboo.en2do.internal.exception.methods.MethodUnsupportedException;
import eu.koboo.en2do.internal.exception.repository.RepositoryInvalidCallException;
import eu.koboo.en2do.internal.methods.dynamic.DynamicMethod;
import eu.koboo.en2do.internal.methods.predefined.PredefinedMethod;
import eu.koboo.en2do.repository.Repository;
import eu.koboo.en2do.repository.methods.async.Async;
import eu.koboo.en2do.repository.methods.transform.Transform;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand All @@ -16,22 +16,27 @@
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@AllArgsConstructor
public class RepositoryInvocationHandler<E, ID, R extends Repository<E, ID>> implements InvocationHandler {

RepositoryMeta<E, ID, R> repositoryMeta;
ExecutorService executorService;

@Override
@SuppressWarnings("all")
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
String methodName = method.getName();

// Create value of the final methodName
String tempMethodName = method.getName();
Transform transform = method.getAnnotation(Transform.class);
if (transform != null) {
methodName = transform.value();
tempMethodName = transform.value();
}
String methodName = tempMethodName;

// Get and check if a static handler for the methodName is available.
PredefinedMethod<E, ID, R> methodHandler = repositoryMeta.lookupPredefinedMethod(methodName);
Expand All @@ -41,17 +46,42 @@ public Object invoke(Object proxy, Method method, Object[] arguments) throws Thr
}
// No static handler found.

// Check for predefined method with async prefix.
boolean isAsyncMethod = method.isAnnotationPresent(Async.class);
if (transform == null && isAsyncMethod) {
String predefinedName = repositoryMeta.getPredefinedNameByAsyncName(methodName);
PredefinedMethod<E, ID, R> methodHandlerFuture = repositoryMeta.lookupPredefinedMethod(predefinedName);
if (methodHandlerFuture != null) {
// Just handle the arguments and return the object
CompletableFuture<Object> future = new CompletableFuture<>();
executeFuture(future, () -> methodHandlerFuture.handle(method, arguments));
return future;
}
}

// Get and check if any dynamic method matches the methodName
DynamicMethod<E, ID, R> dynamicMethod = repositoryMeta.lookupDynamicMethod(methodName);
if (dynamicMethod == null) {
// No handling found for method with this name.
throw new MethodUnsupportedException(method, repositoryMeta.getRepositoryClass());
}

MethodCallable methodCallable = () -> executeMethod(dynamicMethod, arguments, method, methodName);
if (isAsyncMethod) {
CompletableFuture<Object> future = new CompletableFuture<>();
executeFuture(future, methodCallable);
return future;
} else {
return methodCallable.call();
}
}

private Object executeMethod(DynamicMethod<E, ID, R> dynamicMethod, Object[] arguments, Method method, String methodName) throws Exception {
// Generate bson filter by dynamic Method object.
Bson filter = dynamicMethod.createBsonFilter(arguments);
// Switch-case the method operator to use the correct mongo query.
final MongoCollection<E> collection = repositoryMeta.getCollection();

return switch (dynamicMethod.getMethodOperator()) {
case COUNT -> collection.countDocuments(filter);
case DELETE -> collection.deleteMany(filter).wasAcknowledged();
Expand All @@ -73,8 +103,17 @@ public Object invoke(Object proxy, Method method, Object[] arguments) throws Thr
findIterable = repositoryMeta.applyPageObject(method, findIterable, arguments);
yield findIterable.into(new ArrayList<>());
}
default -> // Couldn't find any match method operator
throw new RepositoryInvalidCallException(method, repositoryMeta.getRepositoryClass());
// Couldn't find any match method operator, but that shouldn't happen
};
}

private void executeFuture(CompletableFuture<Object> future, MethodCallable callable) {
future.completeAsync(() -> {
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}, executorService == null ? future.defaultExecutor() : executorService);
}
}
10 changes: 6 additions & 4 deletions src/main/java/eu/koboo/en2do/internal/RepositoryMeta.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;

@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@Getter
Expand Down Expand Up @@ -254,4 +251,9 @@ public FindIterable<E> applyPageObject(Method method, FindIterable<E> findIterab
findIterable.allowDiskUse(true);
return findIterable;
}

public String getPredefinedNameByAsyncName(String asyncName) {
String predefinedName = asyncName.replaceFirst("async", "");
return predefinedName.substring(0, 1).toLowerCase(Locale.ROOT) + predefinedName.substring(1);
}
}
2 changes: 1 addition & 1 deletion src/main/java/eu/koboo/en2do/internal/Validator.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public static <E, ID, R extends Repository<E, ID>> void validateCompatibility(
.findFirst()
.orElse(null);
if (field == null) {
throw new RepositoryDescriptorException(typeClass, repositoryClass, descriptor.getName());
continue;
}

// Ignore all fields annotated with transient, because pojo doesn't touch that.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
@Log
public class InternalPropertyCodecProvider implements PropertyCodecProvider {

/**
* @see PropertyCodecProvider
* @param type the class and bound type parameters for which to get a Codec
* @param registry the registry to use for resolving dependent Codec instances
* @return The codec from the type
* @param <T> The type of the codec
*/
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public <T> Codec<T> get(TypeWithTypeParameters<T> type, PropertyCodecRegistry registry) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ public Class decode(BsonReader reader, DecoderContext decoderContext) {
}

/**
* See org.bson.codecs.Encoder
*
* @see org.bson.codecs.Encoder
* @return the class of the encoded class
*/
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public GenericMapCodec(Class<Map<K, T>> encoderClass, Codec<K> keyCodec, Codec<T
this.valueCodec = valueCodec;
}

/**
* @see org.bson.codecs.Encoder
* @param writer the BSON writer to encode into
* @param map the value to encode
* @param encoderContext the encoder context
*/
@Override
public void encode(BsonWriter writer, Map<K, T> map, EncoderContext encoderContext) {
try (BsonDocumentWriter documentWriter = new BsonDocumentWriter(new BsonDocument())) {
Expand Down Expand Up @@ -70,6 +76,12 @@ public void encode(BsonWriter writer, Map<K, T> map, EncoderContext encoderConte
writer.writeEndDocument();
}

/**
* @see org.bson.codecs.Decoder
* @param reader the BSON reader
* @param context the decoder context
* @return The decoded map instance
*/
@Override
@SuppressWarnings("unchecked")
public Map<K, T> decode(BsonReader reader, DecoderContext context) {
Expand Down Expand Up @@ -97,6 +109,10 @@ public Map<K, T> decode(BsonReader reader, DecoderContext context) {
return map;
}

/**
* Used to get a new instance of the saved map
* @return The new created map instance
*/
private Map<K, T> getInstance() {
if (encoderClass.isInterface()) {
return new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package eu.koboo.en2do.internal.exception.methods;

import eu.koboo.en2do.repository.methods.async.Async;

import java.lang.reflect.Method;

public class MethodInvalidAsyncNameException extends Exception {

public MethodInvalidAsyncNameException(Method method, Class<?> repoClass) {
super("Methods, which start with the keyword \"async\" are not allowed in repository, except the predefined methods " +
"of the repository itself. If you want to create \"async\" methods, just annotate any method with " + Async.class +
" and encapsulate the return type in a CompletableFuture<T>. Invalid method is \"" + method.getName() + "\"" +
" in repository " + repoClass.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package eu.koboo.en2do.internal.exception.methods;

import eu.koboo.en2do.repository.methods.async.Async;

import java.lang.reflect.Method;

public class MethodInvalidAsyncReturnException extends Exception {

public MethodInvalidAsyncReturnException(Method method, Class<?> repoClass) {
super("Methods, which are annotated with " + Async.class + " have to return a CompletableFuture<T> with their " +
" encapsulate return type as T. Invalid method is \"" + method.getName() + "\"" +
" in repository " + repoClass.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public enum MethodOperator {
if (GenericUtils.isNotTypeOf(List.class, returnType)) {
throw new MethodFindListReturnTypeException(method, entityClass, repoClass);
}
Class<?> listType = GenericUtils.getGenericTypeOfReturnList(method);
Class<?> listType = GenericUtils.getGenericTypeOfReturnType(method);
if (!listType.isAssignableFrom(entityClass)) {
throw new MethodFindListTypeException(method, repoClass, listType);
}
Expand All @@ -47,7 +47,7 @@ public enum MethodOperator {
if (GenericUtils.isNotTypeOf(List.class, returnType)) {
throw new MethodFindListReturnTypeException(method, entityClass, repoClass);
}
Class<?> listType = GenericUtils.getGenericTypeOfReturnList(method);
Class<?> listType = GenericUtils.getGenericTypeOfReturnType(method);
if (!listType.isAssignableFrom(entityClass)) {
throw new MethodFindListTypeException(method, repoClass, listType);
}
Expand Down
Loading

0 comments on commit d52ca2b

Please sign in to comment.