diff --git a/src/main/java/me/braydon/feather/annotation/Serializable.java b/src/main/java/me/braydon/feather/annotation/Serializable.java new file mode 100644 index 0000000..c05d552 --- /dev/null +++ b/src/main/java/me/braydon/feather/annotation/Serializable.java @@ -0,0 +1,11 @@ +package me.braydon.feather.annotation; + +import java.lang.annotation.*; + +/** + * @author Braydon + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented @Inherited +public @interface Serializable { } \ 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 new file mode 100644 index 0000000..c6085dd --- /dev/null +++ b/src/main/java/me/braydon/feather/common/Tuple.java @@ -0,0 +1,25 @@ +package me.braydon.feather.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Represents an object that + * holds a pair of two values. + * + * @author Braydon + */ +@NoArgsConstructor @AllArgsConstructor @Setter @Getter +public class Tuple { + /** + * The left value of this tuple. + */ + private L left; + + /** + * The right value of this tuple. + */ + private R right; +} \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/data/Document.java b/src/main/java/me/braydon/feather/data/Document.java index c377fb0..0a4221f 100644 --- a/src/main/java/me/braydon/feather/data/Document.java +++ b/src/main/java/me/braydon/feather/data/Document.java @@ -8,11 +8,10 @@ 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 java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; /** * A document is a key-value pair that is stored @@ -38,12 +37,18 @@ public class Document { /** * The mapped data of this document. + *

+ * The key of this key-value pair is the identifier + * for the field within this document. The value is + * a tuple that contains the Java field, as well as + * the field value. + *

* * @see V for value type */ - private final Map mappedData = Collections.synchronizedMap(new LinkedHashMap<>()); + private final Map> mappedData = Collections.synchronizedMap(new LinkedHashMap<>()); - public Document(@NonNull Object element, boolean rawObject) { + public Document(@NonNull Object element) { Class clazz = element.getClass(); // Get the element class String idKey = null; // The key for the id field for (java.lang.reflect.Field field : clazz.getDeclaredFields()) { @@ -64,20 +69,15 @@ public class Document { } Class fieldType = field.getType(); // The type of the field try { - Object value; // The value of the field + Object value = field.get(element); // The value of the field - if (fieldType == UUID.class) { // Convert UUIDs into strings - value = ((UUID) field.get(element)).toString(); - } else if (rawObject) { // Use the raw object from the field - value = field.get(element); - } else { // Otherwise, turn the value into a string - if (fieldType == String.class) { // Already a string, cast it - value = field.get(element); - } else { // Convert the field into json using Gson - value = FeatherSettings.getGson().toJson(field.get(element)); - } + 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(); } - mappedData.put(key, (V) value); // Store in our map + + mappedData.put(key, new Tuple<>(field, (V) value)); // Store in our map } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } @@ -85,10 +85,25 @@ public class Document { assert idKey != null; // We need an id key this.idKey = idKey; // We have our id key - V key = mappedData.get(idKey); // Get the id from the data map + Tuple key = mappedData.get(idKey); // Get the id from the data map if (key == null) { // The element is missing an id field throw new IllegalArgumentException("No @Id annotated field found in " + clazz.getSimpleName()); } - this.key = key; + this.key = key.getRight(); + } + + /** + * Turn this document into a map. + * + * @return the mapped data + * @see #mappedData for stored data + */ + @NonNull + public Map toMappedData() { + Map mappedData = new LinkedHashMap<>(); // The mapped data + for (Map.Entry> entry : this.mappedData.entrySet()) { + mappedData.put(entry.getKey(), entry.getValue().getRight()); + } + return mappedData; } } \ 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 index cbb4c17..1f56bca 100644 --- a/src/main/java/me/braydon/feather/databases/mongodb/MongoDB.java +++ b/src/main/java/me/braydon/feather/databases/mongodb/MongoDB.java @@ -6,13 +6,18 @@ 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. @@ -137,14 +142,22 @@ public class MongoDB implements IDatabase collection = database.getCollection(collectionName); // Get the collection - Document document = new Document<>(element, true); // Construct the document from the element + 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.getMappedData())), + 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())); + } + } } /** diff --git a/src/main/java/me/braydon/feather/databases/mongodb/annotation/Index.java b/src/main/java/me/braydon/feather/databases/mongodb/annotation/Index.java new file mode 100644 index 0000000..1a6bba4 --- /dev/null +++ b/src/main/java/me/braydon/feather/databases/mongodb/annotation/Index.java @@ -0,0 +1,16 @@ +package me.braydon.feather.databases.mongodb.annotation; + +import me.braydon.feather.databases.mongodb.MongoDB; + +import java.lang.annotation.*; + +/** + * Fields flagged with this annotation will be + * treated as an indexed field within {@link MongoDB}. + * + * @author Braydon + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented @Inherited +public @interface Index { } \ No newline at end of file diff --git a/src/main/java/me/braydon/feather/databases/redis/Redis.java b/src/main/java/me/braydon/feather/databases/redis/Redis.java index 70936de..8e1bd99 100644 --- a/src/main/java/me/braydon/feather/databases/redis/Redis.java +++ b/src/main/java/me/braydon/feather/databases/redis/Redis.java @@ -126,8 +126,8 @@ public class Redis implements IDatabase, if (!element.getClass().isAnnotationPresent(Collection.class)) { // Missing annotation throw new IllegalStateException("Element is missing @Collection annotation"); } - Document document = new Document<>(element, false); // Construct the document from the element - sync().hmset(String.valueOf(document.getKey()), document.getMappedData()); // Set the map in the database + 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 } /** diff --git a/src/main/java/me/braydon/feather/repository/Repository.java b/src/main/java/me/braydon/feather/repository/Repository.java index f757fd7..4251009 100644 --- a/src/main/java/me/braydon/feather/repository/Repository.java +++ b/src/main/java/me/braydon/feather/repository/Repository.java @@ -20,4 +20,13 @@ public abstract class Repository> { * @see D for database */ @NonNull private final D database; + + /** + * Save the given element to the database. + * + * @param element the element to save + */ + public final void save(@NonNull Object element) { + database.write(element); + } } \ No newline at end of file