Update structure
This commit is contained in:
parent
94173eb473
commit
8cb196233a
@ -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)
|
||||
|
29
src/main/java/me/braydon/feather/common/EntityUtils.java
Normal file
29
src/main/java/me/braydon/feather/common/EntityUtils.java
Normal file
@ -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
|
||||
|
64
src/main/java/me/braydon/feather/database/IDatabase.java
Normal file
64
src/main/java/me/braydon/feather/database/IDatabase.java
Normal file
@ -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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user