All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atsid.play.controllers.OneToManyCrudController Maven / Gradle / Ivy

The newest version!
package com.atsid.play.controllers;

import com.atsid.play.common.EbeanUtil;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.BeanWrapperImpl;
import play.db.ebean.Model;
import play.libs.F.Promise;
import play.libs.F.Function0;
import play.mvc.BodyParser;
import play.mvc.Results;
import play.mvc.Result;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import static play.libs.F.Tuple;

/**
 * @author: alikalarsen
 * Date: 7/1/13
*/
public class OneToManyCrudController

extends CrudController { private Class

parentClass; private String parentFieldName; private Method parentSetter; public OneToManyCrudController(Class

parentClass, Class childClass, String parentFieldName) { super(childClass); this.parentClass = parentClass; this.parentFieldName = parentFieldName; } /** * Action * Query a flat list of models without the parent * @param offset An offset from the first item to start filtering from. Used for paging. * @param count The total count to query. This is the length of items to query after the offset. Used for paging. * @param orderBy Order the queried models in order by the given properties of the model. * @param fields The fields, or properties, of the models to retrieve. * @param fetches If the model has 1to1 relationships, use this to retrieve those relationships. By default, returns the id of each relationship. * @param queryString Filter the models with a comma-delimited query string in the format of "property:value". * @return A promise containing the results */ public Promise listAll(final int offset, final Integer count, final String orderBy, final String fields, final String fetches, final String queryString) { return super.list(offset, count, orderBy, fields, fetches, queryString); } /** * Action * Query a flat list of models without the parent * @param offset An offset from the first item to start filtering from. Used for paging. * @param count The total count to query. This is the length of items to query after the offset. Used for paging. * @param orderBy Order the queried models in order by the given properties of the model. * @param fields The fields, or properties, of the models to retrieve. * @param fetches If the model has 1to1 relationships, use this to retrieve those relationships. By default, returns the id of each relationship. * @param queryString Filter the models with a comma-delimited query string in the format of "property:value". * @return A promise containing the results */ public Promise list(final Long pid, final Integer offset, final Integer count, final String orderBy, final String fields, final String fetches, final String queryString) { return Promise.promise(new Function0() { public Result apply() { P parent = createParentQuery(new ServiceParams()).where().eq("id", pid).findUnique(); if (parent == null) { return Results.notFound(pid.toString()); } ServiceParams params = new ServiceParams(offset, count, orderBy, fields, fetches, queryString); Query query = createQuery(params); updateQuery(query, params); // Restrict the query to the parent query.where().eq(parentFieldName + ".id", pid); List modelList = queryList(query, params); return CrudResults.successCount( query.findRowCount(), modelList.size(), modelList); } }); } /** * Create a new entity with the json body of the request. * If the body is an array, it will bulk create. * @param pid The id of the parent object * @return Result object with the created entities */ @BodyParser.Of(BodyParser.Json.class) public Promise create(final Long pid) { return Promise.promise(new Function0() { public Result apply() { P parent = createParentQuery(new ServiceParams()).where().eq("id", pid).findUnique(); if (parent == null) { return Results.notFound(pid.toString()); } JsonNode json = request().body().asJson(); if (json.isArray()) { ResultOrValue> rov = createModelListFromJson(json); List children = rov.value; if (children != null) { for (C c : children) { setParent(parent, c); } return create(children); } return rov.result; } // create one, set parent ResultOrValue rov = createModelFromJson(json); C model = rov.value; if (model != null) { if (canAssignToParent(model, pid)) { setParent(parent, model); return create(model); } else { return CrudResults.notFoundError(getBaseModelClass(), EbeanUtil.getFieldValue(model, "id")); } } return rov.result; } }); } /** * Action * Read a single model. * @param parentId The id of the parent model. * @param fields Fields to retrieve off the model. If not provided, retrieves all. * @param fetches Get any 1to1 relationships off the model. By default, it returns only the id off the relationships. * @return A promise containing the results */ public Promise read(final Long parentId, final Long id, final String fields, final String fetches) { return Promise.promise(new Function0() { public Result apply() { ServiceParams params = new ServiceParams(fields, fetches); Query query = createQuery(params); handleFieldsAndFetches(query, getBaseModelClass(), params); // Tack on the parent query.where().eq("id", id).eq(parentFieldName + ".id", parentId); C model = query.findUnique(); if (model != null) { return CrudResults.success(model); } return CrudResults.notFoundError(getBaseModelClass(), id); } }); } /** * Implements a update method * @param pid The id of the parent model * @param id The id of the child model * @return */ @BodyParser.Of(BodyParser.Json.class) public Promise update(final Long pid,final Long id) { return Promise.promise(new Function0() { public Result apply() { P parent = createParentQuery(new ServiceParams()).where().eq("id", pid).findUnique(); if (parent == null) { return Results.notFound(pid.toString()); } JsonNode json = request().body().asJson(); if (json.isArray()) { ResultOrValue> rov = createModelListFromJson(json); List children = rov.value; if (children != null) { if (canAssignToParent(children, pid)) { for (C c : children) { setParent(parent, c); } return updateBulk(children); } else { return CrudResults.notFoundError(getBaseModelClass(), ""); } } return rov.result; } // create one, set parent ResultOrValue rov = createModelFromJson(json); C model = rov.value; if (model != null) { if (canAssignToParent(model, pid)) { setParent(parent, model); return update(id, model); } else { return CrudResults.notFoundError(getBaseModelClass(), EbeanUtil.getFieldValue(model, "id")); } } return rov.result; } }); } /** * Action * Update a list of models * @param pid The parent id * @return A promise containing the results */ @BodyParser.Of(BodyParser.Json.class) public Promise updateBulk(final Long pid) { return Promise.promise(new Function0() { public Result apply() { P parent = createParentQuery(new ServiceParams()).where().eq("id", pid).findUnique(); if (parent == null) { return Results.notFound(pid.toString()); } JsonNode json = request().body().asJson(); if (json.isArray()) { ResultOrValue> rov = createModelListFromJson(json); List children = rov.value; if (children != null) { if (canAssignToParent(children, pid)) { for (C c : children) { setParent(parent, c); } return updateBulk(children); } else { return CrudResults.notFoundError(getBaseModelClass(), ""); } } return rov.result; } else { return CrudResults.badRequest("Must be an array of items."); } } }); } /** * Delete a model * @param parentId The id of the parent model * @param id The id of the child model * @return A promise containing the results. Returns no content. */ public Promise delete(Long parentId, Long id) { C dbModel = createQuery(new ServiceParams()).where().eq("id", id).eq(parentFieldName + ".id", parentId).findUnique(); if (dbModel != null) { return super.delete(id); } else { final Long myId = id; return Promise.promise(new Function0() { public Result apply() { return CrudResults.notFoundError(getBaseModelClass(), myId); } }); } } /** * Bulk delete models. The body should be an array of objects with an id. * @param parentId The id of the parent * @return A promise containing the results. Returns no content. */ @play.db.ebean.Transactional public Promise deleteBulk(Long parentId) { final Long myParent = parentId; return Promise.promise(new Function0() { public Result apply() { List items = getItemsFromRequest(); if (items != null) { List models = createQuery(new ServiceParams()).where().in("id", items).eq(parentFieldName + ".id", myParent).findList(); // Some of the items are missing if (models.size() != items.size()) { return CrudResults.notFoundError(getBaseModelClass(), ""); } // TODO: Try to get Ebean.delete() working properly. for (C t : models) { t.delete(); } return noContent(); } return CrudResults.error("Array of objects with ids required"); } }); } /** * Gets a list of associations for the base model object * @return */ public Promise associations(Long pId, Long cId) { return super.associations(cId); } /** * Validates that the given child actually belongs to the parent in the db * @param child The child to validate * @return */ public Boolean validateParent (C child) { P parent = (P) EbeanUtil.getFieldValue(child, parentFieldName); P dbParent = createParentQuery(new ServiceParams()).where().eq("id", EbeanUtil.getFieldValue(parent, "id")).findUnique(); return (parent != null && dbParent != null); } /** * Gets the parent class * @return */ protected Class

getParentClass() { return this.parentClass; } @Override /** * Validate a child's parent. */ protected ResultOrValue isModelValid(C model) { if (!validateParent(model)) { return new ResultOrValue(CrudResults.methodNotAllowed("METHOD", parentClass.getName() + "." + getBaseModelClass().getName())); } return super.isModelValid(model); } /** * Creates a query for the parent model * @param params * @return */ protected Query

createParentQuery(ServiceParams params) { return Ebean.createQuery(this.parentClass); } /** * Determines if the given child is a valid child of the given parent * @param child * @param pid * @return */ private Boolean canAssignToParent(C child, Object pid) { // The user passed in an existing model as a child // Models without ids can already be assigned to the parent, cause they are new Long id = (Long)EbeanUtil.getFieldValue(child, "id"); if (id != null) { // We cannot actually find the model in the DB C dbModel = createQuery(new ServiceParams()).where().eq("id", id).eq(parentFieldName + ".id", pid).findUnique(); if (dbModel == null) { return false; } } return true; } /** * Determines if the given children are valid children of the given parent * @param children * @param pid * @return */ private Boolean canAssignToParent(List children, Object pid) { List ids = new ArrayList(); for (C child : children) { Long id = (Long)EbeanUtil.getFieldValue(child, "id"); // Models without ids can already be assigned to the parent, cause they are new if (id != null) { ids.add(id); } } // Verify they exist in the DB List dbModels = createQuery(new ServiceParams()).where().in("id", ids).eq(parentFieldName + ".id", pid).findList(); // If we don't have the same number of DB results as ids, then one of them is missing if (dbModels.size() != ids.size()) { for (C dbModel : dbModels) { Long id = (Long)EbeanUtil.getFieldValue(dbModel, "id"); if (!ids.contains(id)) { return false; } } } return true; } /** * Gets the parent setter method from the child class, and caches itroutes. * @return */ private Method getParentSetter() { if (parentSetter != null) { return parentSetter; } try { Class clazz = getBaseModelClass(); Field parentField = clazz.getField(parentFieldName); PropertyDescriptor fieldDescriptor = new BeanWrapperImpl(clazz).getPropertyDescriptor(parentField.getName()); parentSetter = fieldDescriptor.getWriteMethod(); } catch(RuntimeException e) { throw e; } catch(Exception e) { throw new RuntimeException(e); } return parentSetter; } /** * Sets the given parent on the child * @param parent The parent model * @param child The child model */ private void setParent(P parent, C child) { Method parentSetter = getParentSetter(); try { parentSetter.invoke(child, parent); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy