Complete saving?

This commit is contained in:
Braydon 2023-12-13 20:57:25 -05:00
parent 5e7f42b76e
commit 94173eb473
7 changed files with 113 additions and 24 deletions

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

@ -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<L, R> {
/**
* The left value of this tuple.
*/
private L left;
/**
* The right value of this tuple.
*/
private R right;
}

@ -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<V> {
/**
* The mapped data of this document.
* <p>
* 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.
* </p>
*
* @see V for value type
*/
private final Map<String, V> mappedData = Collections.synchronizedMap(new LinkedHashMap<>());
private final Map<String, Tuple<java.lang.reflect.Field, V>> 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<V> {
}
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
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<V> {
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<java.lang.reflect.Field, V> 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<String, V> toMappedData() {
Map<String, V> mappedData = new LinkedHashMap<>(); // The mapped data
for (Map.Entry<String, Tuple<java.lang.reflect.Field, V>> entry : this.mappedData.entrySet()) {
mappedData.put(entry.getKey(), entry.getValue().getRight());
}
return mappedData;
}
}

@ -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<MongoClient, ConnectionString, MongoSy
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, true); // Construct the document from the element
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.getMappedData())),
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()));
}
}
}
/**

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

@ -126,8 +126,8 @@ public class Redis implements IDatabase<StatefulRedisConnection<String, String>,
if (!element.getClass().isAnnotationPresent(Collection.class)) { // Missing annotation
throw new IllegalStateException("Element is missing @Collection annotation");
}
Document<String> 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<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
}
/**

@ -20,4 +20,13 @@ public abstract class Repository<D extends IDatabase<?, ?, ?, ?>> {
* @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);
}
}