diff --git a/build.gradle b/build.gradle
index 22cea029..93953fab 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,7 @@
-import java.util.stream.Collectors
-
plugins {
id('java')
id('maven-publish')
- id('com.github.johnrengelman.shadow') version('7.1.2')
- id('com.github.breadmoirai.github-release') version('2.4.1')
+ id('com.github.johnrengelman.shadow') version('5.2.0')
}
group("$projectGroup")
@@ -15,9 +12,14 @@ repositories {
}
dependencies {
+ // MongoDB
implementation("org.mongodb:mongodb-driver-sync:$mongoDriverVersion")
testImplementation("org.mongodb:mongodb-driver-sync:$mongoDriverVersion")
+ // JetBrains annotations
+ implementation("org.jetbrains:annotations:$jetbrainsAnnotationsVersion")
+ testImplementation("org.jetbrains:annotations:$jetbrainsAnnotationsVersion")
+
// Lombok
compileOnly("org.projectlombok:lombok:$lombokVersion")
annotationProcessor("org.projectlombok:lombok:$lombokVersion")
@@ -35,10 +37,8 @@ test {
}
java {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- withJavadocJar()
- withSourcesJar()
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
}
tasks.withType(JavaCompile).configureEach {
@@ -46,6 +46,15 @@ tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
}
+task sourcesJar(type: Jar) {
+ from sourceSets.main.allJava
+ archiveClassifier.set('sources')
+}
+task javadocJar(type: Jar) {
+ from javadoc
+ archiveClassifier.set('javadoc')
+}
+
publishing {
repositories {
maven {
@@ -59,38 +68,12 @@ publishing {
publications {
maven(MavenPublication) {
from components.java
+ artifact sourcesJar
+ artifact javadocJar
}
}
}
-githubRelease {
- token System.getenv('GITHUB_TOKEN')
- generateReleaseNotes = true
- draft = true
-
- releaseAssets jar.destinationDir.listFiles()
-
- body { """\
-## Links to $version
-
-* [Documentation](https://koboo.gitbook.com/en2do)
-* [Maven](https://reposilite.koboo.eu/#/releases/eu/koboo/en2do/$version/)
-* [JavaDocs](https://reposilite.koboo.eu/javadoc/releases/eu/koboo/en2do/$version/)
-* [Jenkins](https://jenkins.koboo.eu/job/en2do/job/Build%20and%20Publish%20(main)/)
-
-${
- changelog().call()
- .readLines()
- .stream()
- .limit(10)
- .map { "- $it" }
- .collect(Collectors.joining('\n', '## Changelog\n', ''))
- }
-- And more..
-""" }
-}
-
project.tasks.shadowJar.finalizedBy(project.tasks.javadocJar)
project.tasks.shadowJar.finalizedBy(project.tasks.sourcesJar)
project.tasks.publish.dependsOn(project.tasks.shadowJar)
-project.tasks.githubRelease.dependsOn(project.tasks.shadowJar)
diff --git a/gradle.properties b/gradle.properties
index 6313ad35..c96f485b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,12 +1,13 @@
### Project properties ###
projectGroup=eu.koboo
-projectVersion=2.2.0
+projectVersion=2.3.0-SNAPSHOT
#
### Dependency versions ###
lombokVersion=1.18.24
mongoDriverVersion=4.8.2
jupiterVersion=5.9.2
slf4jVersion=2.0.6
+jetbrainsAnnotationsVersion=24.0.0
#
### Gradle properties ###
#
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 070cb702..838e6bc8 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/src/main/java/eu/koboo/en2do/Credentials.java b/src/main/java/eu/koboo/en2do/Credentials.java
index a45e567d..4725836f 100644
--- a/src/main/java/eu/koboo/en2do/Credentials.java
+++ b/src/main/java/eu/koboo/en2do/Credentials.java
@@ -1,5 +1,12 @@
package eu.koboo.en2do;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -11,34 +18,63 @@
/**
* This object is used to simplify creating credentials to the mongodb server.
* See documentation: ...
- *
- * @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) {
+@RequiredArgsConstructor
+@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
+@Getter
+public class Credentials {
+ /**
+ * Empty representation of the credentials object
+ */
+ private static final Credentials EMPTY = new Credentials(null, null);
+ /**
+ * The default key of the connection string.
+ */
private static final String CONNECT_KEY = "en2do.connectstring";
+ /**
+ * The default key of the database.
+ */
private static final String DATABASE_KEY = "en2do.database";
-
+ /**
+ * The default name of the credentials file.
+ */
private static final String DEFAULT_CREDENTIAL_FILE = "credentials.properties";
- private static Credentials fromStreamProperties(InputStream inputStream) {
+ /**
+ * Utility method for reading credentials from an input stream.
+ *
+ * @param inputStream The input stream, which should be read.
+ * @return The new created credentials object.
+ */
+ private static @NotNull Credentials fromStreamProperties(@NotNull InputStream inputStream) {
try {
Properties properties = new Properties();
properties.load(inputStream);
return new Credentials(properties.getProperty(CONNECT_KEY), properties.getProperty(DATABASE_KEY));
} catch (IOException e) {
- e.printStackTrace();
+ throw new RuntimeException("Error while loading credentials");
}
- return null;
}
- public static Credentials fromResource() {
+ /**
+ * Automatically reading credentials from the default resourcePath, which is
+ * "{applicationJar}/credentials.properties"
+ *
+ * @return The new created credentials object.
+ */
+ public static @Nullable Credentials fromResource() {
return fromResource("/" + DEFAULT_CREDENTIAL_FILE);
}
- public static Credentials fromResource(String resourcePath) {
+ /**
+ * Automatically reading credentials from a resource file from given resourcePath.
+ *
+ * @param resourcePath The resource path with the containing credentials.
+ * @return The new created credentials object.
+ */
+ public static @Nullable Credentials fromResource(@Nullable String resourcePath) {
if (resourcePath == null) {
throw new RuntimeException("Couldn't read resource from null path!");
}
@@ -48,17 +84,32 @@ public static Credentials fromResource(String resourcePath) {
return null;
}
try (InputStream inputStream = managerClass.getResourceAsStream(resourcePath)) {
+ if (inputStream == null) {
+ throw new RuntimeException("Couldn't create a stream from the resource in the path \"" + resourcePath + "\"!");
+ }
return fromStreamProperties(inputStream);
} catch (IOException e) {
throw new RuntimeException("Couldn't read resource from path \"" + resourcePath + "\": ", e);
}
}
- public static Credentials fromFile() {
+ /**
+ * Automatically reading credentials from the default filePath, which is
+ * "{applicationDirectory}/credentials.properties"
+ *
+ * @return The new created credentials object.
+ */
+ public static @Nullable Credentials fromFile() {
return fromFile(DEFAULT_CREDENTIAL_FILE);
}
- public static Credentials fromFile(String filePath) {
+ /**
+ * Automatically reading credentials from a file from given filePath.
+ *
+ * @param filePath The file path with the containing credentials.
+ * @return The new created credentials object.
+ */
+ public static @Nullable Credentials fromFile(@Nullable String filePath) {
if (filePath == null) {
throw new RuntimeException("Couldn't read file from null path!");
}
@@ -73,24 +124,68 @@ public static Credentials fromFile(String filePath) {
}
}
- public static Credentials fromSystemProperties() {
+ /**
+ * Automatically reading credentials from the system properties.
+ *
+ * @return The new created credentials object.
+ */
+ public static @NotNull Credentials fromSystemProperties() {
return fromSystemProperties(CONNECT_KEY, DATABASE_KEY);
}
- public static Credentials fromSystemProperties(String propertyConnectKey, String propertyDatabaseKey) {
+ /**
+ * Automatically reading credentials from the system properties,
+ * using custom keys for the connectString and database
+ *
+ * @param propertyConnectKey The property key for the connection string
+ * @param propertyDatabaseKey The property key for the database
+ * @return The new created credentials object.
+ */
+ public static @NotNull Credentials fromSystemProperties(@NotNull String propertyConnectKey, @NotNull String propertyDatabaseKey) {
return new Credentials(System.getProperty(propertyConnectKey), System.getProperty(propertyDatabaseKey));
}
- public static Credentials fromSystemEnvVars() {
+ /**
+ * Automatically reading credentials from the system environmental variables.
+ *
+ * @return The new created credentials object.
+ */
+ public static @NotNull Credentials fromSystemEnvVars() {
return fromSystemEnvVars(CONNECT_KEY.toUpperCase(Locale.ROOT).replaceFirst("\\.", "_"),
DATABASE_KEY.toUpperCase(Locale.ROOT).replaceFirst("\\.", "_"));
}
- public static Credentials fromSystemEnvVars(String envVarConnectKey, String envVarDatabaseKey) {
+ /**
+ * Automatically reading credentials from the system environmental variables.
+ * using custom keys for the connectString and database
+ *
+ * @param envVarConnectKey The environmental variable key for the connection string
+ * @param envVarDatabaseKey The environmental variable key for the database
+ * @return The new created credentials object.
+ */
+ public static @NotNull Credentials fromSystemEnvVars(@NotNull String envVarConnectKey, @NotNull String envVarDatabaseKey) {
return new Credentials(System.getenv(envVarConnectKey), System.getenv(envVarDatabaseKey));
}
- public static Credentials of(String connectString, String database) {
+ /**
+ * Create a new credentials object by passing the two values directly.
+ *
+ * @param connectString The connection string to the mongodb server.
+ * @param database The database which should be used.
+ * @return A new created credentials object.
+ */
+ public static @NotNull Credentials of(@Nullable String connectString, @Nullable String database) {
return new Credentials(connectString, database);
}
+
+ /**
+ * The connection string to the mongodb database server
+ */
+ @Nullable
+ String connectString;
+ /**
+ * The database, which should be used
+ */
+ @Nullable
+ String database;
}
diff --git a/src/main/java/eu/koboo/en2do/MongoManager.java b/src/main/java/eu/koboo/en2do/MongoManager.java
index f5de32a2..0919d235 100644
--- a/src/main/java/eu/koboo/en2do/MongoManager.java
+++ b/src/main/java/eu/koboo/en2do/MongoManager.java
@@ -31,6 +31,7 @@
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.fields.UpdateBatch;
import eu.koboo.en2do.repository.methods.pagination.Pagination;
import eu.koboo.en2do.repository.methods.sort.*;
import eu.koboo.en2do.repository.methods.transform.Transform;
@@ -45,6 +46,8 @@
import org.bson.codecs.pojo.Conventions;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.bson.conversions.Bson;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -71,21 +74,31 @@ public class MongoManager {
// Predefined methods by Java objects
// These methods are ignored by our method processing proxy / invocation handler.
+ @NotNull
private static final List IGNORED_DEFAULT_METHODS = Arrays.asList(
"notify", "notifyAll", "wait", "finalize", "clone"
);
+ @NotNull
Map, Repository, ?>> repositoryRegistry;
+
+ @NotNull
Map, RepositoryMeta, ?, ?>> repositoryMetaRegistry;
+ @Nullable
ExecutorService executorService;
@Getter
+ @NotNull
CodecRegistry codecRegistry;
+
+ @NotNull
MongoClient client;
+
+ @NotNull
MongoDatabase database;
- public MongoManager(Credentials credentials, ExecutorService executorService) {
+ public MongoManager(@Nullable Credentials credentials, @Nullable ExecutorService executorService) {
repositoryRegistry = new ConcurrentHashMap<>();
repositoryMetaRegistry = new ConcurrentHashMap<>();
@@ -105,14 +118,14 @@ public MongoManager(Credentials credentials, ExecutorService executorService) {
"accessible credentials.");
}
- String connectString = credentials.connectString();
+ String connectString = credentials.getConnectString();
// If credentials connectString is null, throw exception
if (connectString == null) {
throw new NullPointerException("No connectString given! Please make sure to provide a " +
"accessible connectString.");
}
// If credentials databaseString is null, throw exception
- String databaseString = credentials.database();
+ String databaseString = credentials.getDatabase();
if (databaseString == null) {
throw new NullPointerException("No databaseString given! Please make sure to provide a " +
"accessible databaseString.");
@@ -145,7 +158,7 @@ public MongoManager(Credentials credentials, ExecutorService executorService) {
database = client.getDatabase(databaseString);
}
- public MongoManager(Credentials credentials) {
+ public MongoManager(@Nullable Credentials credentials) {
this(credentials, null);
}
@@ -160,21 +173,15 @@ public boolean close() {
public boolean close(boolean shutdownExecutor) {
try {
- if(executorService != null && shutdownExecutor) {
+ if (executorService != null && shutdownExecutor) {
executorService.shutdown();
}
- if (repositoryRegistry != null) {
- repositoryRegistry.clear();
- }
- if (repositoryMetaRegistry != null) {
- for (RepositoryMeta, ?, ?> meta : repositoryMetaRegistry.values()) {
- meta.destroy();
- }
- repositoryMetaRegistry.clear();
- }
- if (client != null) {
- client.close();
+ repositoryRegistry.clear();
+ for (RepositoryMeta, ?, ?> meta : repositoryMetaRegistry.values()) {
+ meta.destroy();
}
+ repositoryMetaRegistry.clear();
+ client.close();
return true;
} catch (Exception e) {
e.printStackTrace();
@@ -183,7 +190,7 @@ public boolean close(boolean shutdownExecutor) {
}
@SuppressWarnings("unchecked")
- public > R create(Class repositoryClass) {
+ public > @NotNull R create(@NotNull Class repositoryClass) {
try {
// Check for already created repository to avoid multiply instances of the same repository
@@ -288,6 +295,7 @@ public > R create(Class repositoryClass) {
repositoryMeta.registerPredefinedMethod(new MethodSaveAll<>(repositoryMeta, entityCollection));
repositoryMeta.registerPredefinedMethod(new MethodSortAll<>(repositoryMeta, entityCollection));
repositoryMeta.registerPredefinedMethod(new MethodToString<>(repositoryMeta, entityCollection));
+ repositoryMeta.registerPredefinedMethod(new MethodUpdateAllFields<>(repositoryMeta, entityCollection));
// Iterate through the repository methods
for (Method method : repositoryClass.getMethods()) {
@@ -363,7 +371,7 @@ public > R create(Class repositoryClass) {
for (String filterOperatorString : methodFilterPartArray) {
FilterType filterType = createFilterType(entityClass, repositoryClass, method, filterOperatorString,
entityFieldSet);
- int filterTypeParameterCount = filterType.operator().getExpectedParameterCount();
+ int filterTypeParameterCount = filterType.getOperator().getExpectedParameterCount();
for (int i = 0; i < filterTypeParameterCount; i++) {
int paramIndex = nextParameterIndex + i;
Class> paramClass = method.getParameters()[paramIndex].getType();
@@ -372,14 +380,14 @@ public > R create(Class repositoryClass) {
method.getParameterCount());
}
// Special checks for some operators
- Class> fieldClass = filterType.field().getType();
- switch (filterType.operator()) {
- case REGEX -> {
+ Class> fieldClass = filterType.getField().getType();
+ switch (filterType.getOperator()) {
+ case REGEX:
if (GenericUtils.isNotTypeOf(String.class, paramClass) && GenericUtils.isNotTypeOf(Pattern.class, paramClass)) {
throw new MethodInvalidRegexParameterException(method, repositoryClass, paramClass);
}
- }
- case IN -> {
+ break;
+ case IN:
if (GenericUtils.isNotTypeOf(List.class, paramClass)) {
throw new MethodMismatchingTypeException(method, repositoryClass, List.class, paramClass);
}
@@ -387,12 +395,12 @@ public > R create(Class repositoryClass) {
if (GenericUtils.isNotTypeOf(fieldClass, listType)) {
throw new MethodInvalidListParameterException(method, repositoryClass, fieldClass, listType);
}
- }
- default -> {
+ break;
+ default:
if (GenericUtils.isNotTypeOf(fieldClass, paramClass)) {
throw new MethodMismatchingTypeException(method, repositoryClass, fieldClass, paramClass);
}
- }
+ break;
}
}
MethodFilterPart filterPart = new MethodFilterPart(filterType, nextParameterIndex);
@@ -430,6 +438,14 @@ public > R create(Class repositoryClass) {
throw new MethodParameterCountException(method, repositoryClass, (expectedParameterCount + 1), methodParameterCount);
}
}
+ if (lastMethodParameter.isAssignableFrom(UpdateBatch.class)) {
+ if (methodOperator != MethodOperator.UPDATE_FIELD) {
+ throw new MethodBatchNotAllowedException(method, repositoryClass);
+ }
+ if ((expectedParameterCount + 1) != methodParameterCount) {
+ throw new MethodParameterCountException(method, repositoryClass, (expectedParameterCount + 1), methodParameterCount);
+ }
+ }
} else {
throw new MethodParameterCountException(method, repositoryClass, expectedParameterCount, methodParameterCount);
}
@@ -556,12 +572,10 @@ public > R create(Class repositoryClass) {
}
}
- private FilterType createFilterType(Class entityClass, Class> repoClass, Method method,
- String filterOperatorString, Set fieldSet) throws Exception {
+ private @NotNull FilterType createFilterType(@NotNull Class entityClass, @NotNull Class> repoClass,
+ @NotNull Method method, @NotNull String filterOperatorString,
+ @NotNull Set fieldSet) throws Exception {
FilterOperator filterOperator = FilterOperator.parseFilterEndsWith(filterOperatorString);
- if (filterOperator == null) {
- throw new MethodNoFilterOperatorException(method, repoClass);
- }
String expectedFieldName = filterOperator.removeOperatorFrom(filterOperatorString);
boolean notFilter = false;
if (expectedFieldName.endsWith("Not")) {
diff --git a/src/main/java/eu/koboo/en2do/internal/MethodCallable.java b/src/main/java/eu/koboo/en2do/internal/MethodCallable.java
index 82f84100..f3eaedf7 100644
--- a/src/main/java/eu/koboo/en2do/internal/MethodCallable.java
+++ b/src/main/java/eu/koboo/en2do/internal/MethodCallable.java
@@ -3,10 +3,12 @@
/**
* This interface is used to retrieve a return value of the dynamic method
*/
+@FunctionalInterface
public interface MethodCallable {
/**
* Called to get the object
+ *
* @return The return value of the method
* @throws Exception if anything bad happens
*/
diff --git a/src/main/java/eu/koboo/en2do/internal/RepositoryInvocationHandler.java b/src/main/java/eu/koboo/en2do/internal/RepositoryInvocationHandler.java
index 8735ec3b..e69b6fef 100644
--- a/src/main/java/eu/koboo/en2do/internal/RepositoryInvocationHandler.java
+++ b/src/main/java/eu/koboo/en2do/internal/RepositoryInvocationHandler.java
@@ -2,16 +2,22 @@
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
+import com.mongodb.client.model.UpdateOptions;
+import com.mongodb.client.result.UpdateResult;
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.fields.UpdateBatch;
import eu.koboo.en2do.repository.methods.transform.Transform;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.bson.conversions.Bson;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@@ -23,7 +29,10 @@
@AllArgsConstructor
public class RepositoryInvocationHandler> implements InvocationHandler {
+ @NotNull
RepositoryMeta repositoryMeta;
+
+ @Nullable
ExecutorService executorService;
@Override
@@ -82,32 +91,40 @@ private Object executeMethod(DynamicMethod dynamicMethod, Object[] arg
// Switch-case the method operator to use the correct mongo query.
final MongoCollection collection = repositoryMeta.getCollection();
- return switch (dynamicMethod.getMethodOperator()) {
- case COUNT -> collection.countDocuments(filter);
- case DELETE -> collection.deleteMany(filter).wasAcknowledged();
- case EXISTS -> collection.countDocuments(filter) > 0;
- case FIND_MANY -> {
- FindIterable findIterable = repositoryMeta.createIterable(filter, methodName);
+ FindIterable findIterable;
+ switch (dynamicMethod.getMethodOperator()) {
+ case COUNT:
+ return collection.countDocuments(filter);
+ case DELETE:
+ return collection.deleteMany(filter).wasAcknowledged();
+ case EXISTS:
+ return collection.countDocuments(filter) > 0;
+ case FIND_MANY:
+ findIterable = repositoryMeta.createIterable(filter, methodName);
findIterable = repositoryMeta.applySortObject(method, findIterable, arguments);
findIterable = repositoryMeta.applySortAnnotations(method, findIterable);
- yield findIterable.into(new ArrayList<>());
- }
- case FIND_FIRST -> {
- FindIterable findIterable = repositoryMeta.createIterable(filter, methodName);
+ return findIterable.into(new ArrayList<>());
+ case FIND_FIRST:
+ findIterable = repositoryMeta.createIterable(filter, methodName);
findIterable = repositoryMeta.applySortObject(method, findIterable, arguments);
findIterable = repositoryMeta.applySortAnnotations(method, findIterable);
- yield findIterable.limit(1).first();
- }
- case PAGE -> {
- FindIterable findIterable = repositoryMeta.createIterable(filter, methodName);
+ return findIterable.limit(1).first();
+ case PAGE:
+ findIterable = repositoryMeta.createIterable(filter, methodName);
findIterable = repositoryMeta.applyPageObject(method, findIterable, arguments);
- yield findIterable.into(new ArrayList<>());
- }
- // Couldn't find any match method operator, but that shouldn't happen
- };
+ return findIterable.into(new ArrayList<>());
+ case UPDATE_FIELD:
+ UpdateBatch updateBatch = (UpdateBatch) arguments[arguments.length - 1];
+ UpdateResult result = collection.updateMany(filter, repositoryMeta.createUpdateDocument(updateBatch),
+ new UpdateOptions().upsert(false));
+ return result.wasAcknowledged();
+ default:
+ // Couldn't find any match method operator, but that shouldn't happen
+ throw new RepositoryInvalidCallException(method, repositoryMeta.getRepositoryClass());
+ }
}
- private void executeFuture(CompletableFuture