diff --git a/checkstyle.xml b/checkstyle.xml index 3a5a206..2c82ad4 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -38,15 +38,6 @@ - - - - - - - - - diff --git a/src/main/java/me/braydon/feather/FeatherSettings.java b/src/main/java/me/braydon/feather/FeatherSettings.java index e263150..e0d3251 100644 --- a/src/main/java/me/braydon/feather/FeatherSettings.java +++ b/src/main/java/me/braydon/feather/FeatherSettings.java @@ -11,11 +11,6 @@ import lombok.Setter; * @author Braydon */ public final class FeatherSettings { - /** - * The amount of threads to use for {@link FeatherThreads}. - */ - @Setter @Getter private static int threadCount = 4; - /** * The {@link Gson} instance to use for serialization. */ diff --git a/src/main/java/me/braydon/feather/FeatherThreads.java b/src/main/java/me/braydon/feather/FeatherThreads.java deleted file mode 100644 index 8905bb0..0000000 --- a/src/main/java/me/braydon/feather/FeatherThreads.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.braydon.feather; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * The Feather thread pool. This is used by default - * when executing tasks in asynchronous pipelines. - * - * @author Braydon - */ -public final class FeatherThreads { - private static final AtomicInteger ID = new AtomicInteger(0); // The thread id - public static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(FeatherSettings.getThreadCount(), new ThreadFactoryBuilder() - .setNameFormat("Feather Thread #" + ID.incrementAndGet()) - .build()); // The thread pool to execute on -} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/IDatabase.java b/src/main/java/me/braydon/feather/IDatabase.java deleted file mode 100644 index d153c98..0000000 --- a/src/main/java/me/braydon/feather/IDatabase.java +++ /dev/null @@ -1,78 +0,0 @@ -package me.braydon.feather; - -import lombok.NonNull; -import me.braydon.feather.annotation.Collection; -import me.braydon.feather.annotation.Field; - -import java.io.Closeable; - -/** - * Represents a database. - * - * @author Braydon - * @param the bootstrap class of this database - * @param the type of credentials this database uses - * @param the type of sync pipeline for this database - * @param the type of async pipeline for this database - */ -public interface IDatabase extends Closeable { - /** - * Get the name of this database. - * - * @return the database name - */ - @NonNull String getName(); - - /** - * Initialize a connection to this database. - * - * @param credentials the optional credentials to use - */ - void connect(C credentials); - - /** - * Check if this database is connected. - * - * @return the database connection state - */ - boolean isConnected(); - - /** - * Get the bootstrap class - * instance for this database. - * - * @return the bootstrap class instance, null if none - * @see B for bootstrap class - */ - B getBootstrap(); - - /** - * Get the synchronized - * pipeline for this database. - * - * @return the synchronized pipeline - * @see S for synchronized pipeline - */ - @NonNull S sync(); - - /** - * Get the asynchronous - * pipeline for this database. - * - * @return the asynchronous pipeline - * @see A for asynchronous pipeline - */ - @NonNull A async(); - - /** - * Write the given object to the database. - *

- * This object is an instance of a class - * annotated with {@link Collection}, and - * contains fields annotated with {@link Field}. - *

- * - * @param element the element to write - */ - void write(@NonNull Object element); -} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/annotation/Collection.java b/src/main/java/me/braydon/feather/annotation/Collection.java index c11c634..de59baa 100644 --- a/src/main/java/me/braydon/feather/annotation/Collection.java +++ b/src/main/java/me/braydon/feather/annotation/Collection.java @@ -1,10 +1,13 @@ package me.braydon.feather.annotation; +import me.braydon.feather.data.Document; + import java.lang.annotation.*; /** * Classes tagged with this annotation - * will be treated as a collection. + * will be treated as a collection that + * holds {@link Document}'s. * * @author Braydon */ diff --git a/src/main/java/me/braydon/feather/annotation/Id.java b/src/main/java/me/braydon/feather/annotation/Id.java index 17cbd33..2791c3f 100644 --- a/src/main/java/me/braydon/feather/annotation/Id.java +++ b/src/main/java/me/braydon/feather/annotation/Id.java @@ -3,9 +3,9 @@ package me.braydon.feather.annotation; import java.lang.annotation.*; /** - * Fields tagged with this annotation will be treated - * as the primary identifying key for documents within - * a {@link Collection}. + * {@link Field}'s tagged with this annotation will be + * treated as the primary identifying key for documents + * within a {@link Collection}. * * @author Braydon */ diff --git a/src/main/java/me/braydon/feather/annotation/Serializable.java b/src/main/java/me/braydon/feather/annotation/Serializable.java index c05d552..66134f4 100644 --- a/src/main/java/me/braydon/feather/annotation/Serializable.java +++ b/src/main/java/me/braydon/feather/annotation/Serializable.java @@ -1,8 +1,13 @@ package me.braydon.feather.annotation; +import com.google.gson.Gson; + import java.lang.annotation.*; /** + * {@link Field}'s tagged with this annotation + * will have serialization handled by {@link Gson}. + * * @author Braydon */ @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/me/braydon/feather/common/EntityUtils.java b/src/main/java/me/braydon/feather/common/EntityUtils.java new file mode 100644 index 0000000..2916d6e --- /dev/null +++ b/src/main/java/me/braydon/feather/common/EntityUtils.java @@ -0,0 +1,29 @@ +package me.braydon.feather.common; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import me.braydon.feather.annotation.Collection; + +/** + * @author Braydon + */ +@UtilityClass +public final class EntityUtils { + /** + * Ensure that the given entity is valid. + * + * @param entity the entity to validate + * @param allowEmptyCollections should empty collections be allowed? + */ + public static void ensureValid(@NonNull Object entity, boolean allowEmptyCollections) { + Class clazz = entity.getClass(); // Get the element class + if (!clazz.isAnnotationPresent(Collection.class)) { // Missing annotation + throw new IllegalStateException("Element is missing @Collection annotation"); + } + Collection annotation = clazz.getAnnotation(Collection.class); // Get the @Collection annotation + String collectionName = annotation.name(); // The name of the collection + if (collectionName.isEmpty() && !allowEmptyCollections) { // Missing collection name + throw new IllegalStateException("Missing collection name in @Collection for " + clazz.getSimpleName()); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/common/Tuple.java b/src/main/java/me/braydon/feather/common/Tuple.java index c6085dd..139c79d 100644 --- a/src/main/java/me/braydon/feather/common/Tuple.java +++ b/src/main/java/me/braydon/feather/common/Tuple.java @@ -10,6 +10,8 @@ import lombok.Setter; * holds a pair of two values. * * @author Braydon + * @param the left value + * @param the right value */ @NoArgsConstructor @AllArgsConstructor @Setter @Getter public class Tuple { diff --git a/src/main/java/me/braydon/feather/data/Document.java b/src/main/java/me/braydon/feather/data/Document.java index 0a4221f..ac06c45 100644 --- a/src/main/java/me/braydon/feather/data/Document.java +++ b/src/main/java/me/braydon/feather/data/Document.java @@ -4,14 +4,18 @@ import lombok.Getter; import lombok.NonNull; import lombok.ToString; import me.braydon.feather.FeatherSettings; -import me.braydon.feather.IDatabase; import me.braydon.feather.annotation.Collection; import me.braydon.feather.annotation.Field; import me.braydon.feather.annotation.Id; import me.braydon.feather.annotation.Serializable; import me.braydon.feather.common.Tuple; +import me.braydon.feather.database.IDatabase; -import java.util.*; +import javax.annotation.concurrent.ThreadSafe; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; /** * A document is a key-value pair that is stored @@ -23,7 +27,7 @@ import java.util.*; * @author Braydon * @param the type of value this document holds */ -@Getter @ToString +@ThreadSafe @Getter @ToString public class Document { /** * The key to use for the id field. @@ -74,7 +78,7 @@ public class Document { if (field.isAnnotationPresent(Serializable.class)) { // Serialize the field if @Serializable is present value = FeatherSettings.getGson().toJson(field.get(element)); } else if (fieldType == UUID.class) { // Convert UUIDs into strings - value = ((UUID) value).toString(); + value = value.toString(); } mappedData.put(key, new Tuple<>(field, (V) value)); // Store in our map diff --git a/src/main/java/me/braydon/feather/database/IDatabase.java b/src/main/java/me/braydon/feather/database/IDatabase.java new file mode 100644 index 0000000..0a832dd --- /dev/null +++ b/src/main/java/me/braydon/feather/database/IDatabase.java @@ -0,0 +1,64 @@ +package me.braydon.feather.database; + +import lombok.NonNull; +import me.braydon.feather.repository.Repository; + +import java.io.Closeable; + +/** + * Represents a database. + * + * @author Braydon + * @param the bootstrap class of this database + * @param the type of credentials this database uses + * @param the type of repository for this database + */ +public interface IDatabase> extends Closeable { + /** + * Get the name of this database. + * + * @return the database name + */ + @NonNull String getName(); + + /** + * Initialize a connection to this database. + * + * @param credentials the optional credentials to use + */ + void connect(C credentials); + + /** + * Check if this database is connected. + * + * @return the database connection state + */ + boolean isConnected(); + + /** + * Get the latency to this database. + * + * @return the latency, -1 if not connected + */ + long getLatency(); + + /** + * Get the bootstrap class + * instance for this database. + * + * @return the bootstrap class instance, null if none + * @see B for bootstrap class + */ + B getBootstrap(); + + /** + * Create a new repository + * using this database. + * + * @param the id type + * @param the entity type + * @return the repository instance + * @see R for repository + */ + @NonNull R newRepository(); +} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/database/impl/mongodb/MongoDB.java b/src/main/java/me/braydon/feather/database/impl/mongodb/MongoDB.java new file mode 100644 index 0000000..8b8411e --- /dev/null +++ b/src/main/java/me/braydon/feather/database/impl/mongodb/MongoDB.java @@ -0,0 +1,129 @@ +package me.braydon.feather.database.impl.mongodb; + +import com.mongodb.BasicDBObject; +import com.mongodb.ConnectionString; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import lombok.Getter; +import lombok.NonNull; +import me.braydon.feather.database.IDatabase; + +/** + * The {@link IDatabase} implementation for MongoDB. + * + * @author Braydon + * @see MongoClient for the bootstrap class + * @see ConnectionString for the credentials class + * @see MongoRepository for the repository class + * @see
MongoDB Official Site + */ +public class MongoDB implements IDatabase> { + /** + * The current {@link MongoClient} instance. + */ + private MongoClient client; + + /** + * The {@link MongoDatabase} instance. + */ + @Getter private MongoDatabase database; + + /** + * Get the name of this database. + * + * @return the database name + */ + @Override @NonNull + public String getName() { + return "MongoDB"; + } + + /** + * Initialize a connection to this database. + * + * @param credentials the optional credentials to use + */ + @Override + public void connect(ConnectionString credentials) { + if (credentials == null) { // We need valid credentials + throw new IllegalArgumentException("No credentials defined"); + } + if (isConnected()) { // Already connected + throw new IllegalStateException("Already connected"); + } + String databaseName = credentials.getDatabase(); // Get the database name + if (databaseName == null) { + throw new IllegalArgumentException("A database name is required"); + } + if (client != null) { // We have a client, close it first + client.close(); + } + client = MongoClients.create(credentials); // Create a new client + database = client.getDatabase(databaseName); // Get the database + } + + /** + * Check if this database is connected. + * + * @return the database connection state + */ + @Override + public boolean isConnected() { + return client != null; + } + + /** + * Get the latency to this database. + * + * @return the latency, -1 if not connected + */ + @Override + public long getLatency() { + if (!isConnected()) { // Not connected + return -1L; + } + // Return ping + long before = System.currentTimeMillis(); + database.runCommand(new BasicDBObject("ping", "1")); + return System.currentTimeMillis() - before; + } + + /** + * Get the bootstrap class + * instance for this database. + * + * @return the bootstrap class instance, null if none + * @see MongoClients for bootstrap class + */ + @Override + public MongoClient getBootstrap() { + return client; + } + + /** + * Create a new repository + * using this database. + * + * @return the repository instance + * @see MongoRepository for repository + */ + @Override @NonNull + public MongoRepository newRepository() { + return new MongoRepository<>(this); + } + + /** + * Closes this stream and releases any system resources associated + * with it. If the stream is already closed then invoking this + * method has no effect. + */ + @Override + public void close() { + if (client != null) { + client.close(); + } + client = null; + database = null; + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/database/impl/mongodb/MongoRepository.java b/src/main/java/me/braydon/feather/database/impl/mongodb/MongoRepository.java new file mode 100644 index 0000000..3f1a8f6 --- /dev/null +++ b/src/main/java/me/braydon/feather/database/impl/mongodb/MongoRepository.java @@ -0,0 +1,160 @@ +package me.braydon.feather.database.impl.mongodb; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.UpdateOneModel; +import com.mongodb.client.model.UpdateOptions; +import lombok.NonNull; +import me.braydon.feather.annotation.Collection; +import me.braydon.feather.common.EntityUtils; +import me.braydon.feather.common.Tuple; +import me.braydon.feather.database.impl.mongodb.annotation.Index; +import me.braydon.feather.repository.Repository; +import org.bson.Document; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +/** + * The {@link MongoDB} {@link Repository} implementation. + * + * @author Braydon + * @param the identifier for type for entities + * @param the entity type this repository stores + */ +public class MongoRepository extends Repository { + public MongoRepository(@NonNull MongoDB database) { + super(database); + } + + /** + * Get the entity with the given id. + * + * @param id the entity id + * @return the entity with the id, null if none + * @see ID for id + * @see E for entity + */ + @Override + public E find(@NonNull ID id) { + String idString = id.toString(); // Stringify the ID + throw new UnsupportedOperationException(); + } + + /** + * Find the entity matching the given predicate. + * + * @param predicate the predicate to test + * @return the found entity + * @see E for entity + * @see Predicate for predicate + */ + @Override + public E findOne(@NonNull Predicate predicate) { + throw new UnsupportedOperationException(); + } + + /** + * Find all entities matching the given predicate. + * + * @param predicate the predicate to test + * @return the found entities + * @see E for entity + * @see Predicate for predicate + */ + @Override + public List findAll(@NonNull Predicate predicate) { + throw new UnsupportedOperationException(); + } + + /** + * Get all entities within this repository. + * + * @return the entities + * @see E for entity + */ + @Override + public List findAll() { + throw new UnsupportedOperationException(); + } + + /** + * Save the given entities. + * + * @param entities the entities to save + * @see E for entity + */ + @Override + public void saveAll(@NonNull E... entities) { + Map>> toSave = new HashMap<>(); // The documents to save + + // Iterate over the given entities and ensure they + // are all valid, and if they are, collect them so + // we can bulk save them later + for (E entity : entities) { + EntityUtils.ensureValid(entity, false); // Ensure our entity is valid + String collectionName = entity.getClass().getAnnotation(Collection.class).name(); // The name of the collection + + // Add the document to our list of documents to save + List> documents = toSave.getOrDefault(collectionName, new ArrayList<>()); + documents.add(new me.braydon.feather.data.Document<>(entity)); + toSave.put(collectionName, documents); + } + + // Iterate over the documents we want to save, and create + // an update model for them, as well as update indexes. + for (Map.Entry>> entry : toSave.entrySet()) { + MongoCollection collection = getDatabase().getDatabase().getCollection(entry.getKey()); // The collection to save to + + List> updateModels = new ArrayList<>(); + for (me.braydon.feather.data.Document document : entry.getValue()) { + // Add or update model to the list + updateModels.add(new UpdateOneModel<>( + Filters.eq(document.getIdKey(), document.getKey()), + new Document("$set", new Document(document.toMappedData())), + new UpdateOptions().upsert(true) + )); + + // Create indexes for @Index fields + for (Map.Entry> mappedEntry : document.getMappedData().entrySet()) { + java.lang.reflect.Field field = mappedEntry.getValue().getLeft(); + if (field.isAnnotationPresent(Index.class)) { + collection.createIndex(Indexes.text(mappedEntry.getKey())); + } + } + } + + // We have updates models present, bulk write them to the database + if (!updateModels.isEmpty()) { + collection.bulkWrite(updateModels); + } + } + } + + /** + * Get the amount of stored entities. + * + * @return the amount of stored entities + * @see E for entity + */ + @Override + public long count() { + throw new UnsupportedOperationException(); + } + + /** + * Drop the given entity. + * + * @param entity the entity to drop + * @see E for entity + */ + @Override + public void drop(@NonNull E entity) { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/databases/mongodb/annotation/Index.java b/src/main/java/me/braydon/feather/database/impl/mongodb/annotation/Index.java similarity index 70% rename from src/main/java/me/braydon/feather/databases/mongodb/annotation/Index.java rename to src/main/java/me/braydon/feather/database/impl/mongodb/annotation/Index.java index 1a6bba4..00d9607 100644 --- a/src/main/java/me/braydon/feather/databases/mongodb/annotation/Index.java +++ b/src/main/java/me/braydon/feather/database/impl/mongodb/annotation/Index.java @@ -1,6 +1,6 @@ -package me.braydon.feather.databases.mongodb.annotation; +package me.braydon.feather.database.impl.mongodb.annotation; -import me.braydon.feather.databases.mongodb.MongoDB; +import me.braydon.feather.database.impl.mongodb.MongoDB; import java.lang.annotation.*; diff --git a/src/main/java/me/braydon/feather/databases/redis/Redis.java b/src/main/java/me/braydon/feather/database/impl/redis/Redis.java similarity index 61% rename from src/main/java/me/braydon/feather/databases/redis/Redis.java rename to src/main/java/me/braydon/feather/database/impl/redis/Redis.java index 8e1bd99..4c2f6d0 100644 --- a/src/main/java/me/braydon/feather/databases/redis/Redis.java +++ b/src/main/java/me/braydon/feather/database/impl/redis/Redis.java @@ -1,15 +1,10 @@ -package me.braydon.feather.databases.redis; +package me.braydon.feather.database.impl.redis; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.api.StatefulRedisConnection; -import io.lettuce.core.api.async.RedisAsyncCommands; -import io.lettuce.core.api.sync.RedisCommands; import lombok.NonNull; -import me.braydon.feather.IDatabase; -import me.braydon.feather.annotation.Collection; -import me.braydon.feather.annotation.Field; -import me.braydon.feather.data.Document; +import me.braydon.feather.database.IDatabase; /** * The {@link IDatabase} implementation for Redis. @@ -17,11 +12,10 @@ import me.braydon.feather.data.Document; * @author Braydon * @see StatefulRedisConnection for the bootstrap class * @see RedisURI for the credentials class - * @see RedisCommands for the sync pipeline class - * @see RedisAsyncCommands for the async pipeline class + * @see RedisRepository for the repository class * @see Redis Official Site */ -public class Redis implements IDatabase, RedisURI, RedisCommands, RedisAsyncCommands> { +public class Redis implements IDatabase, RedisURI, RedisRepository> { /** * The current {@link RedisClient} instance. */ @@ -75,6 +69,16 @@ public class Redis implements IDatabase, return client != null && (connection != null && connection.isOpen()); } + /** + * Get the latency to this database. + * + * @return the latency, -1 if not connected + */ + @Override + public long getLatency() { + return 0; + } + /** * Get the bootstrap class * instance for this database. @@ -88,47 +92,25 @@ public class Redis implements IDatabase, } /** - * Get the synchronized - * pipeline for this database. + * Create a new repository + * using this database. * - * @return the synchronized pipeline - * @see RedisPipeline for synchronized pipeline + * @return the repository instance + * @see RedisRepository for repository */ @Override @NonNull - public RedisCommands sync() { - return connection.sync(); + public RedisRepository newRepository() { + return new RedisRepository<>(this); } - /** - * Get the asynchronous - * pipeline for this database. - * - * @return the asynchronous pipeline - * @see RedisPipeline for asynchronous pipeline - */ - @Override @NonNull - public RedisAsyncCommands async() { - return connection.async(); - } - - /** - * Write the given object to the database. - *

- * This object is an instance of a class - * annotated with {@link Collection}, and - * contains fields annotated with {@link Field}. - *

- * - * @param element the element to write - */ - @Override - public void write(@NonNull Object element) { - if (!element.getClass().isAnnotationPresent(Collection.class)) { // Missing annotation - throw new IllegalStateException("Element is missing @Collection annotation"); - } - Document document = new Document<>(element); // Construct the document from the element - sync().hmset(String.valueOf(document.getKey()), document.toMappedData()); // Set the map in the database - } +// @Override +// public void write(@NonNull Object element) { +// if (!element.getClass().isAnnotationPresent(Collection.class)) { // Missing annotation +// throw new IllegalStateException("Element is missing @Collection annotation"); +// } +// Document document = new Document<>(element); // Construct the document from the element +// sync().hmset(String.valueOf(document.getKey()), document.toMappedData()); // Set the map in the database +// } /** * Closes this stream and releases any system resources associated diff --git a/src/main/java/me/braydon/feather/database/impl/redis/RedisRepository.java b/src/main/java/me/braydon/feather/database/impl/redis/RedisRepository.java new file mode 100644 index 0000000..7232c00 --- /dev/null +++ b/src/main/java/me/braydon/feather/database/impl/redis/RedisRepository.java @@ -0,0 +1,103 @@ +package me.braydon.feather.database.impl.redis; + +import lombok.NonNull; +import me.braydon.feather.repository.Repository; + +import java.util.List; +import java.util.function.Predicate; + +/** + * The {@link Redis} {@link Repository} implementation. + * + * @author Braydon + * @param the identifier for type for entities + * @param the entity type this repository stores + */ +public class RedisRepository extends Repository { + public RedisRepository(@NonNull Redis database) { + super(database); + } + + /** + * Get the entity with the given id. + * + * @param id the entity id + * @return the entity with the id, null if none + * @see ID for id + * @see E for entity + */ + @Override + public E find(@NonNull ID id) { + throw new UnsupportedOperationException(); + } + + /** + * Find the entity matching the given predicate. + * + * @param predicate the predicate to test + * @return the found entity + * @see E for entity + * @see Predicate for predicate + */ + @Override + public E findOne(@NonNull Predicate predicate) { + throw new UnsupportedOperationException(); + } + + /** + * Find all entities matching the given predicate. + * + * @param predicate the predicate to test + * @return the found entities + * @see E for entity + * @see Predicate for predicate + */ + @Override + public List findAll(@NonNull Predicate predicate) { + throw new UnsupportedOperationException(); + } + + /** + * Get all entities within this repository. + * + * @return the entities + * @see E for entity + */ + @Override + public List findAll() { + throw new UnsupportedOperationException(); + } + + /** + * Save the given entities. + * + * @param entities the entities to save + * @see E for entity + */ + @Override + public void saveAll(@NonNull E... entities) { + throw new UnsupportedOperationException(); + } + + /** + * Get the amount of stored entities. + * + * @return the amount of stored entities + * @see E for entity + */ + @Override + public long count() { + throw new UnsupportedOperationException(); + } + + /** + * Drop the given entity. + * + * @param entity the entity to drop + * @see E for entity + */ + @Override + public void drop(@NonNull E entity) { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/databases/mongodb/MongoAsyncPipeline.java b/src/main/java/me/braydon/feather/databases/mongodb/MongoAsyncPipeline.java deleted file mode 100644 index f88995d..0000000 --- a/src/main/java/me/braydon/feather/databases/mongodb/MongoAsyncPipeline.java +++ /dev/null @@ -1,51 +0,0 @@ -package me.braydon.feather.databases.mongodb; - -import com.mongodb.BasicDBObject; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.NonNull; -import me.braydon.feather.FeatherThreads; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; - -/** - * The pipeline for handling asynchronous {@link MongoDB} operations. - * - * @author Braydon - */ -@AllArgsConstructor(access = AccessLevel.PROTECTED) -public final class MongoAsyncPipeline { - /** - * The database to handle operations for. - */ - @NonNull private final MongoDB database; - - /** - * Get the latency to the database. - * - * @return the latency - */ - public CompletableFuture getPing() { - return getPing(FeatherThreads.THREAD_POOL); - } - - /** - * Get the latency to the database. - * - * @param executor the thread executor to use - * @return the latency - * @see Executor for executor - */ - public CompletableFuture getPing(@NonNull Executor executor) { - return CompletableFuture.supplyAsync(() -> { - if (!database.isConnected()) { // Not connected - return -1L; - } - // Return ping - long before = System.currentTimeMillis(); - database.getDatabase().runCommand(new BasicDBObject("ping", "1")); - return System.currentTimeMillis() - before; - }, executor); - } -} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/databases/mongodb/MongoDB.java b/src/main/java/me/braydon/feather/databases/mongodb/MongoDB.java deleted file mode 100644 index 1f56bca..0000000 --- a/src/main/java/me/braydon/feather/databases/mongodb/MongoDB.java +++ /dev/null @@ -1,176 +0,0 @@ -package me.braydon.feather.databases.mongodb; - -import com.mongodb.ConnectionString; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; -import com.mongodb.client.model.Filters; -import com.mongodb.client.model.Indexes; -import com.mongodb.client.model.UpdateOptions; -import lombok.Getter; -import lombok.NonNull; -import me.braydon.feather.IDatabase; -import me.braydon.feather.annotation.Collection; -import me.braydon.feather.annotation.Field; -import me.braydon.feather.common.Tuple; -import me.braydon.feather.data.Document; -import me.braydon.feather.databases.mongodb.annotation.Index; - -import java.util.Map; - -/** - * The {@link IDatabase} implementation for MongoDB. - * - * @author Braydon - * @see MongoClient for the bootstrap class - * @see ConnectionString for the credentials class - * @see MongoSyncPipeline for the sync pipeline class - * @see MongoAsyncPipeline for the async pipeline class - * @see MongoDB Official Site - */ -public class MongoDB implements IDatabase { - /** - * The current {@link MongoClient} instance. - */ - private MongoClient client; - - /** - * The {@link MongoDatabase} instance. - */ - @Getter private MongoDatabase database; - - /** - * Get the name of this database. - * - * @return the database name - */ - @Override @NonNull - public String getName() { - return "MongoDB"; - } - - /** - * Initialize a connection to this database. - * - * @param credentials the optional credentials to use - */ - @Override - public void connect(ConnectionString credentials) { - if (credentials == null) { // We need valid credentials - throw new IllegalArgumentException("No credentials defined"); - } - if (isConnected()) { // Already connected - throw new IllegalStateException("Already connected"); - } - String databaseName = credentials.getDatabase(); // Get the database name - if (databaseName == null) { - throw new IllegalArgumentException("A database name is required"); - } - if (client != null) { // We have a client, close it first - client.close(); - } - client = MongoClients.create(credentials); // Create a new client - database = client.getDatabase(databaseName); // Get the database - } - - /** - * Check if this database is connected. - * - * @return the database connection state - */ - @Override - public boolean isConnected() { - return client != null; - } - - /** - * Get the bootstrap class - * instance for this database. - * - * @return the bootstrap class instance, null if none - * @see MongoClients for bootstrap class - */ - @Override - public MongoClient getBootstrap() { - return client; - } - - /** - * Get the synchronized - * pipeline for this database. - * - * @return the synchronized pipeline - * @see MongoSyncPipeline for synchronized pipeline - */ - @Override @NonNull - public MongoSyncPipeline sync() { - return new MongoSyncPipeline(this); - } - - /** - * Get the asynchronous - * pipeline for this database. - * - * @return the asynchronous pipeline - * @see MongoAsyncPipeline for asynchronous pipeline - */ - @Override @NonNull - public MongoAsyncPipeline async() { - return new MongoAsyncPipeline(this); - } - - /** - * Write the given object to the database. - *

- * This object is an instance of a class - * annotated with {@link Collection}, and - * contains fields annotated with {@link Field}. - *

- * - * @param element the element to write - */ - @Override - public void write(@NonNull Object element) { - Class clazz = element.getClass(); // Get the element class - if (!clazz.isAnnotationPresent(Collection.class)) { // Missing annotation - throw new IllegalStateException("Element is missing @Collection annotation"); - } - Collection annotation = clazz.getAnnotation(Collection.class); // Get the @Collection annotation - String collectionName = annotation.name(); // The name of the collection - if (collectionName.isEmpty()) { // Missing collection name - throw new IllegalStateException("Missing collection name in @Collection for " + clazz.getSimpleName()); - } - MongoCollection collection = database.getCollection(collectionName); // Get the collection - Document document = new Document<>(element); // Construct the document from the element - - // Set the map in the database - collection.updateOne( - Filters.eq(document.getIdKey(), document.getKey()), - new org.bson.Document("$set", new org.bson.Document(document.toMappedData())), - new UpdateOptions().upsert(true) - ); - - // Create indexes for @Index fields - for (Map.Entry> entry : document.getMappedData().entrySet()) { - java.lang.reflect.Field field = entry.getValue().getLeft(); - if (field.isAnnotationPresent(Index.class)) { - collection.createIndex(Indexes.text(entry.getKey())); - } - } - } - - /** - * Closes this stream and releases any system resources associated - * with it. If the stream is already closed then invoking this - * method has no effect. - */ - @Override - public void close() { - if (client != null) { - client.close(); - } - client = null; - database = null; - } -} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/databases/mongodb/MongoSyncPipeline.java b/src/main/java/me/braydon/feather/databases/mongodb/MongoSyncPipeline.java deleted file mode 100644 index 762f178..0000000 --- a/src/main/java/me/braydon/feather/databases/mongodb/MongoSyncPipeline.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.braydon.feather.databases.mongodb; - -import com.mongodb.BasicDBObject; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.NonNull; - -/** - * The pipeline for handling synchronous {@link MongoDB} operations. - * - * @author Braydon - */ -@AllArgsConstructor(access = AccessLevel.PROTECTED) -public final class MongoSyncPipeline { - /** - * The database to handle operations for. - * - * @see MongoDB for database - */ - @NonNull private final MongoDB database; - - /** - * Get the latency to the database. - * - * @return the latency - */ - public long getPing() { - if (!database.isConnected()) { // Not connected - return -1L; - } - // Return ping - long before = System.currentTimeMillis(); - database.getDatabase().runCommand(new BasicDBObject("ping", "1")); - return System.currentTimeMillis() - before; - } -} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/databases/redis/RedisPipeline.java b/src/main/java/me/braydon/feather/databases/redis/RedisPipeline.java deleted file mode 100644 index 32c654c..0000000 --- a/src/main/java/me/braydon/feather/databases/redis/RedisPipeline.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.braydon.feather.databases.redis; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.NonNull; - -/** - * The pipeline for handling {@link Redis} operations. - * - * @author Braydon - */ -@AllArgsConstructor(access = AccessLevel.PROTECTED) -public final class RedisPipeline { - /** - * The database to handle operations for. - * - * @see Redis for database - */ - @NonNull private final Redis database; -} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/repository/Repository.java b/src/main/java/me/braydon/feather/repository/Repository.java index 4251009..97a44d9 100644 --- a/src/main/java/me/braydon/feather/repository/Repository.java +++ b/src/main/java/me/braydon/feather/repository/Repository.java @@ -4,16 +4,21 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NonNull; -import me.braydon.feather.IDatabase; +import me.braydon.feather.database.IDatabase; + +import java.util.List; +import java.util.function.Predicate; /** * A repository belonging to a {@link IDatabase}. * * @author Braydon - * @param the database + * @param the database this repository uses + * @param the identifier for type for entities + * @param the entity type this repository stores */ @AllArgsConstructor @Getter(AccessLevel.PROTECTED) -public abstract class Repository> { +public abstract class Repository, ID, E> { /** * The database this repository belongs to. * @@ -22,11 +27,74 @@ public abstract class Repository> { @NonNull private final D database; /** - * Save the given element to the database. + * Get the entity with the given id. * - * @param element the element to save + * @param id the entity id + * @return the entity with the id, null if none + * @see ID for id + * @see E for entity */ - public final void save(@NonNull Object element) { - database.write(element); + public abstract E find(@NonNull ID id); + + /** + * Find the entity matching the given predicate. + * + * @param predicate the predicate to test + * @return the found entity + * @see E for entity + * @see Predicate for predicate + */ + public abstract E findOne(@NonNull Predicate predicate); + + /** + * Find all entities matching the given predicate. + * + * @param predicate the predicate to test + * @return the found entities + * @see E for entity + * @see Predicate for predicate + */ + public abstract List findAll(@NonNull Predicate predicate); + + /** + * Get all entities within this repository. + * + * @return the entities + * @see E for entity + */ + public abstract List findAll(); + + /** + * Save the given entity to the database. + * + * @param entity the entity to save + * @see E for entity + */ + public void save(@NonNull E entity) { + saveAll(entity); } + + /** + * Save the given entities. + * + * @param entities the entities to save + * @see E for entity + */ + public abstract void saveAll(@NonNull E... entities); + + /** + * Get the amount of stored entities. + * + * @return the amount of stored entities + * @see E for entity + */ + public abstract long count(); + + /** + * Drop the given entity. + * + * @param entity the entity to drop + * @see E for entity + */ + public abstract void drop(@NonNull E entity); } \ No newline at end of file