Update structure

This commit is contained in:
Braydon 2023-12-15 00:07:10 -05:00
parent 94173eb473
commit 8cb196233a
21 changed files with 612 additions and 458 deletions

@ -38,15 +38,6 @@
<!-- See: https://checkstyle.org/checks/imports/avoidstaticimport.html#AvoidStaticImport -->
<module name="AvoidStaticImport" />
<!-- See: https://checkstyle.org/checks/naming/classtypeparametername.html#ClassTypeParameterName -->
<module name="ClassTypeParameterName" />
<!-- See: https://checkstyle.org/checks/naming/interfacetypeparametername.html#InterfaceTypeParameterName -->
<module name="InterfaceTypeParameterName" />
<!-- See: https://checkstyle.org/checks/naming/methodtypeparametername.html#MethodTypeParameterName -->
<module name="MethodTypeParameterName" />
<!-- See: https://checkstyle.org/checks/coding/defaultcomeslast.html#DefaultComesLast -->
<module name="DefaultComesLast" />

@ -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.
*/

@ -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
}

@ -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 <B> the bootstrap class of this database
* @param <C> the type of credentials this database uses
* @param <S> the type of sync pipeline for this database
* @param <A> the type of async pipeline for this database
*/
public interface IDatabase<B, C, S, A> 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.
* <p>
* This object is an instance of a class
* annotated with {@link Collection}, and
* contains fields annotated with {@link Field}.
* </p>
*
* @param element the element to write
*/
void write(@NonNull Object element);
}

@ -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
*/

@ -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
*/

@ -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)

@ -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());
}
}
}

@ -10,6 +10,8 @@ import lombok.Setter;
* holds a pair of two values.
*
* @author Braydon
* @param <L> the left value
* @param <R> the right value
*/
@NoArgsConstructor @AllArgsConstructor @Setter @Getter
public class Tuple<L, R> {

@ -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 <V> the type of value this document holds
*/
@Getter @ToString
@ThreadSafe @Getter @ToString
public class Document<V> {
/**
* The key to use for the id field.
@ -74,7 +78,7 @@ public class Document<V> {
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

@ -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 <B> the bootstrap class of this database
* @param <C> the type of credentials this database uses
* @param <R> the type of repository for this database
*/
public interface IDatabase<B, C, R extends Repository<?, ?, ?>> 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 <ID> the id type
* @param <E> the entity type
* @return the repository instance
* @see R for repository
*/
@NonNull <ID, E> R newRepository();
}

@ -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 <a href="https://www.mongodb.com">MongoDB Official Site</a>
*/
public class MongoDB implements IDatabase<MongoClient, ConnectionString, MongoRepository<?, ?>> {
/**
* 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 <ID, E> MongoRepository<ID, E> 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;
}
}

@ -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 <ID> the identifier for type for entities
* @param <E> the entity type this repository stores
*/
public class MongoRepository<ID, E> extends Repository<MongoDB, ID, E> {
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<E> 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<E> findAll(@NonNull Predicate<E> predicate) {
throw new UnsupportedOperationException();
}
/**
* Get all entities within this repository.
*
* @return the entities
* @see E for entity
*/
@Override
public List<E> 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<String, List<me.braydon.feather.data.Document<Object>>> 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<me.braydon.feather.data.Document<Object>> 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<String, List<me.braydon.feather.data.Document<Object>>> entry : toSave.entrySet()) {
MongoCollection<Document> collection = getDatabase().getDatabase().getCollection(entry.getKey()); // The collection to save to
List<UpdateOneModel<Document>> updateModels = new ArrayList<>();
for (me.braydon.feather.data.Document<Object> 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<String, Tuple<Field, Object>> 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();
}
}

@ -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.*;

@ -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 <a href="https://redis.io">Redis Official Site</a>
*/
public class Redis implements IDatabase<StatefulRedisConnection<String, String>, RedisURI, RedisCommands<String, String>, RedisAsyncCommands<String, String>> {
public class Redis implements IDatabase<StatefulRedisConnection<String, String>, RedisURI, RedisRepository<?, ?>> {
/**
* The current {@link RedisClient} instance.
*/
@ -75,6 +69,16 @@ public class Redis implements IDatabase<StatefulRedisConnection<String, String>,
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<StatefulRedisConnection<String, String>,
}
/**
* 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<String, String> sync() {
return connection.sync();
public <ID, E> RedisRepository<ID, E> 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<String, String> async() {
return connection.async();
}
/**
* Write the given object to the database.
* <p>
* This object is an instance of a class
* annotated with {@link Collection}, and
* contains fields annotated with {@link Field}.
* </p>
*
* @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<String> 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<String> 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

@ -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 <ID> the identifier for type for entities
* @param <E> the entity type this repository stores
*/
public class RedisRepository<ID, E> extends Repository<Redis, ID, E> {
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<E> 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<E> findAll(@NonNull Predicate<E> predicate) {
throw new UnsupportedOperationException();
}
/**
* Get all entities within this repository.
*
* @return the entities
* @see E for entity
*/
@Override
public List<E> 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();
}
}

@ -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<Long> 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<Long> 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);
}
}

@ -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 <a href="https://www.mongodb.com">MongoDB Official Site</a>
*/
public class MongoDB implements IDatabase<MongoClient, ConnectionString, MongoSyncPipeline, MongoAsyncPipeline> {
/**
* 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.
* <p>
* This object is an instance of a class
* annotated with {@link Collection}, and
* contains fields annotated with {@link Field}.
* </p>
*
* @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<org.bson.Document> collection = database.getCollection(collectionName); // Get the collection
Document<Object> 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<String, Tuple<java.lang.reflect.Field, Object>> 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;
}
}

@ -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;
}
}

@ -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;
}

@ -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 <D> the database
* @param <D> the database this repository uses
* @param <ID> the identifier for type for entities
* @param <E> the entity type this repository stores
*/
@AllArgsConstructor @Getter(AccessLevel.PROTECTED)
public abstract class Repository<D extends IDatabase<?, ?, ?, ?>> {
public abstract class Repository<D extends IDatabase<?, ?, ?>, ID, E> {
/**
* The database this repository belongs to.
*
@ -22,11 +27,74 @@ public abstract class Repository<D extends IDatabase<?, ?, ?, ?>> {
@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<E> 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<E> findAll(@NonNull Predicate<E> predicate);
/**
* Get all entities within this repository.
*
* @return the entities
* @see E for entity
*/
public abstract List<E> 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);
}