Skip to content

Commit

Permalink
Merge pull request #7 from Koboo/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
Koboo authored Jan 29, 2023
2 parents e91bf3b + d52ca2b commit c0959c1
Show file tree
Hide file tree
Showing 43 changed files with 737 additions and 51 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
7 changes: 7 additions & 0 deletions src/main/java/eu/koboo/en2do/Credentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
import java.util.Locale;
import java.util.Properties;

/**
* This object is used to simplify creating credentials to the mongodb server.
* See documentation: <a href="https://koboo.gitbook.io/en2do/get-started/create-the-mongomanager">...</a>
*
* @param connectString The connection string to the mongodb database server
* @param database The database, which should be used
*/
@SuppressWarnings("unused")
public record Credentials(String connectString, String database) {

Expand Down
58 changes: 53 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,13 +51,22 @@
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;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;

/**
* This object is the main entry point of en2do.
* The connection will be opened on construction.
* Keep in mind, that you should call "#MongoManger#close()" on application shutdown/termination.
* 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 @@ -68,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 @@ -131,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 @@ -267,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 @@ -280,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 @@ -335,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 @@ -499,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 @@ -10,9 +10,19 @@

import java.util.Map;

/**
* This codec provider enables the usage of the en2do custom codecs and adds them to the CodecRegistry
*/
@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
21 changes: 21 additions & 0 deletions src/main/java/eu/koboo/en2do/internal/codec/lang/ClassCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,31 @@
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;

/**
* ClassCodec is used to encode and decode java.lang.Class objects to mongodb document fields
*/
@SuppressWarnings("rawtypes")
public class ClassCodec implements Codec<Class> {

/**
* See org.bson.codecs.Encoder
*
* @param writer the BSON writer to encode into
* @param value the value to encode
* @param encoderContext the encoder context
*/
@Override
public void encode(BsonWriter writer, Class value, EncoderContext encoderContext) {
writer.writeString(value.getName());
}

/**
* See org.bson.codecs.Decoder
*
* @param reader the BSON reader
* @param decoderContext the decoder context
* @return the decoded Class
*/
@Override
public Class decode(BsonReader reader, DecoderContext decoderContext) {
String className = reader.readString();
Expand All @@ -25,6 +42,10 @@ public Class decode(BsonReader reader, DecoderContext decoderContext) {
}
}

/**
* @see org.bson.codecs.Encoder
* @return the class of the encoded class
*/
@Override
public Class<Class> getEncoderClass() {
return Class.class;
Expand Down
Loading

0 comments on commit c0959c1

Please sign in to comment.