|
@@ -8,6 +8,9 @@ import java.lang.reflect.Modifier;
|
|
|
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
|
|
|
|
|
+import org.apache.commons.logging.Log;
|
|
|
|
|
+import org.apache.commons.logging.LogFactory;
|
|
|
|
|
+
|
|
|
import org.mongodb.morphia.Datastore;
|
|
import org.mongodb.morphia.Datastore;
|
|
|
import org.mongodb.morphia.Key;
|
|
import org.mongodb.morphia.Key;
|
|
|
import org.mongodb.morphia.query.Query;
|
|
import org.mongodb.morphia.query.Query;
|
|
@@ -19,12 +22,28 @@ import org.bson.types.ObjectId;
|
|
|
import name.tflucke.ieat2.models.DBObject;
|
|
import name.tflucke.ieat2.models.DBObject;
|
|
|
import name.tflucke.ieat2.errors.ResourceNotFoundException;
|
|
import name.tflucke.ieat2.errors.ResourceNotFoundException;
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Generic framework controller providing sane defaults for most REST operations.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This class does not map any request automatically, rather provides any easy
|
|
|
|
|
+ * way to have similar functionally spread out among child classes.
|
|
|
|
|
+ * The actual mapping is the responsibility of the children.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author Thomas Flucke
|
|
|
|
|
+ * @since 2.0.0
|
|
|
|
|
+ */
|
|
|
public abstract class AbstractController<T extends DBObject> {
|
|
public abstract class AbstractController<T extends DBObject> {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Log log = LogFactory.getLog(AbstractController.class);
|
|
|
|
|
+
|
|
|
@Autowired
|
|
@Autowired
|
|
|
protected Datastore db;
|
|
protected Datastore db;
|
|
|
private final Class<T> clazz;
|
|
private final Class<T> clazz;
|
|
|
private final Set<Field> fields;
|
|
private final Set<Field> fields;
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets up class information about how/what to store.
|
|
|
|
|
+ */
|
|
|
protected AbstractController(final Class<T> clazz)
|
|
protected AbstractController(final Class<T> clazz)
|
|
|
{
|
|
{
|
|
|
this.clazz = clazz;
|
|
this.clazz = clazz;
|
|
@@ -33,16 +52,34 @@ public abstract class AbstractController<T extends DBObject> {
|
|
|
.collect(Collectors.toSet());
|
|
.collect(Collectors.toSet());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Convert a hex string into a mongo key
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param id The id to convert
|
|
|
|
|
+ * @return A key which matches the given id
|
|
|
|
|
+ */
|
|
|
protected Key<T> toKey(final String id) {
|
|
protected Key<T> toKey(final String id) {
|
|
|
return new Key(clazz, clazz.getSimpleName(), new ObjectId(id));
|
|
return new Key(clazz, clazz.getSimpleName(), new ObjectId(id));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Create a query for documents with the given id
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param id The id to query
|
|
|
|
|
+ * @return A query which will return the element(s) matching the id
|
|
|
|
|
+ */
|
|
|
protected Query<T> toQuery(final String id) {
|
|
protected Query<T> toQuery(final String id) {
|
|
|
return db.createQuery(clazz).field("id").equal(new ObjectId(id));
|
|
return db.createQuery(clazz).field("id").equal(new ObjectId(id));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Get the document with the matching id
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param id The id of the element to fetch
|
|
|
|
|
+ * @return The element with the given id
|
|
|
|
|
+ */
|
|
|
protected T get(final String id) {
|
|
protected T get(final String id) {
|
|
|
- T result = db.getByKey(clazz, toKey(id));
|
|
|
|
|
|
|
+ final T result = db.getByKey(clazz, toKey(id));
|
|
|
if (result == null)
|
|
if (result == null)
|
|
|
{
|
|
{
|
|
|
throw new ResourceNotFoundException();
|
|
throw new ResourceNotFoundException();
|
|
@@ -53,34 +90,67 @@ public abstract class AbstractController<T extends DBObject> {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Insert a new element into the database.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Any provided id is ignored as a new one will be assigned automatically.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param newElement An element to insert into the database
|
|
|
|
|
+ * @return The original element inserted with a new id
|
|
|
|
|
+ */
|
|
|
protected T insert(T newElement) {
|
|
protected T insert(T newElement) {
|
|
|
|
|
+ newElement.id = null;
|
|
|
db.save(newElement);
|
|
db.save(newElement);
|
|
|
return newElement;
|
|
return newElement;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Save an element's not-null fields to the database with a given id.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Any fields with a null value will be ignored.
|
|
|
|
|
+ *
|
|
|
|
|
+ * An element with the provided id must exist to be updated.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This operation increments the version counter.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param id The id of the element to update
|
|
|
|
|
+ * @param element A reference element containing the changes to make
|
|
|
|
|
+ * @return The newly updated element
|
|
|
|
|
+ */
|
|
|
protected T update(final String id, final T element) {
|
|
protected T update(final String id, final T element) {
|
|
|
|
|
+ // Update is more complex to allow partial updates.
|
|
|
final UpdateOperations<T> updateOps = db.createUpdateOperations(clazz);
|
|
final UpdateOperations<T> updateOps = db.createUpdateOperations(clazz);
|
|
|
updateOps.inc("version");
|
|
updateOps.inc("version");
|
|
|
fields.forEach((Field field) -> {
|
|
fields.forEach((Field field) -> {
|
|
|
try {
|
|
try {
|
|
|
- Object value = field.get(element);
|
|
|
|
|
|
|
+ final Object value = field.get(element);
|
|
|
if (value != null)
|
|
if (value != null)
|
|
|
{
|
|
{
|
|
|
updateOps.set(field.getName(), value);
|
|
updateOps.set(field.getName(), value);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- catch (IllegalAccessException iae) {
|
|
|
|
|
- // Filter at beginning should leave only public field.
|
|
|
|
|
|
|
+ catch (IllegalAccessException ise) {
|
|
|
|
|
+ // Filter at beginning should leave only public fields.
|
|
|
// Should never get here.
|
|
// Should never get here.
|
|
|
- throw new RuntimeException("Tried to store non-public field", iae);
|
|
|
|
|
|
|
+ log.error("Somehow tried to store a field that shouldn't have been accessible");
|
|
|
|
|
+ log.error(ise);
|
|
|
|
|
+ throw new IllegalStateException("Tried to store non-public field", ise);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
db.update(toKey(id), updateOps);
|
|
db.update(toKey(id), updateOps);
|
|
|
return get(id);
|
|
return get(id);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Remove the element with the matching id from the database.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param id The id of the element to remove
|
|
|
|
|
+ * @return The element removed from the database
|
|
|
|
|
+ * @throws ResourceNotFoundException If no item could be found
|
|
|
|
|
+ * with that id
|
|
|
|
|
+ */
|
|
|
protected T delete(final String id) {
|
|
protected T delete(final String id) {
|
|
|
- T result = db.findAndDelete(toQuery(id));
|
|
|
|
|
|
|
+ final T result = db.findAndDelete(toQuery(id));
|
|
|
if (result == null)
|
|
if (result == null)
|
|
|
{
|
|
{
|
|
|
throw new ResourceNotFoundException();
|
|
throw new ResourceNotFoundException();
|