scott.barleyrs.rest.CrudService.java Source code

Java tutorial

Introduction

Here is the source code for scott.barleyrs.rest.CrudService.java

Source

package scott.barleyrs.rest;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import scott.barleydb.api.config.Definitions;
import scott.barleydb.api.config.EntityType;
import scott.barleydb.api.config.NodeType;
import scott.barleydb.api.core.Environment;
import scott.barleydb.api.core.entity.Entity;
import scott.barleydb.api.core.entity.EntityContext;
import scott.barleydb.api.core.entity.Node;
import scott.barleydb.api.core.entity.RefNode;
import scott.barleydb.api.core.entity.ToManyNode;
import scott.barleydb.api.core.entity.ValueNode;
import scott.barleydb.api.exception.SortException;
import scott.barleydb.api.exception.execution.SortServiceProviderException;
import scott.barleydb.api.exception.execution.persist.SortPersistException;
import scott.barleydb.api.persist.PersistRequest;
import scott.barleydb.api.query.QProperty;
import scott.barleydb.api.query.QPropertyCondition;
import scott.barleydb.api.query.QueryObject;
import scott.barleydb.api.specification.KeyGenSpec;
import scott.barleydb.server.jdbc.query.QueryResult;

/*
 * #%L
 * BarleyRS
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2014 - 2016 Scott Sinclair
 *       <scottysinclair@gmail.com>
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

@Path("/barleyrs")
@Component
public class CrudService {

    @Inject
    private Environment env;

    @GET
    @Path("/entities/{namespace}/{entityType}/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Entity getEntityById(@PathParam("namespace") String namespace,
            @PathParam("entityType") String entityTypeName, @PathParam("id") String id,
            @QueryParam("proj") String projecting) throws SortException {

        EntityContext ctx = new EntityContext(env, namespace);
        EntityType entityType = getEntityType(ctx.getDefinitions(), namespace, entityTypeName);
        QueryObject<?> qo = new QueryObject<>(entityType.getInterfaceName());

        if (projecting != null) {
            addProjection(qo, projecting);
        }

        qo.where(keyEquals(entityType, qo, id));
        List<Entity> list = ctx.performQuery(qo).getEntityList();
        return list.isEmpty() ? null : list.get(0);
    }

    private void addProjection(QueryObject<?> qo, String projecting) {
        for (String property : projecting.split(",")) {
            QProperty<Object> prop = new QProperty<>(qo, property);
            qo.andSelect(prop);
        }
    }

    /**
     * Sets a condition where the key of the entity is equal to id.
     * @param entityType the entity type
     * @param qo the query object
     * @param id the key value
     * @return the condition PK = ID
     */
    private QPropertyCondition keyEquals(EntityType entityType, QueryObject<?> qo, String id) {
        NodeType keyNodeType = entityType.getNodeType(entityType.getKeyNodeName(), true);
        QProperty<Object> keyProperty = new QProperty<>(qo, entityType.getKeyNodeName());
        return keyProperty.equal(convert(keyNodeType, id, true));
    }

    private Object convert(NodeType nodeType, String id, boolean convertEmptyStringToNull) {
        if (id == null || (id.isEmpty() && convertEmptyStringToNull)) {
            return null;
        }
        if (nodeType.getRelationInterfaceName() != null && nodeType.getJdbcType() != null) {
            EntityType refType = nodeType.getEntityType().getDefinitions()
                    .getEntityTypeMatchingInterface(nodeType.getRelationInterfaceName(), true);
            NodeType keyType = refType.getNodeType(refType.getKeyNodeName(), true);
            return convert(keyType, id, convertEmptyStringToNull);
        }
        switch (nodeType.getJavaType()) {
        case STRING:
            return id;
        case INTEGER:
            return Integer.parseInt(id);
        case BOOLEAN:
            return Boolean.parseBoolean(id);
        case LONG:
            return Long.parseLong(id);
        case BIGDECIMAL:
            return new BigDecimal(id);
        case ENUM:
            return id; //TODO
        case SQL_DATE:
            return id; //TODO
        case UTIL_DATE:
            return id; //TODO
        case UUID:
            return UUID.fromString(id);
        }
        return id;
    }

    @GET
    @Path("/entities/{namespace}/{entityType}/")
    @Produces(MediaType.APPLICATION_JSON)
    public QueryResult<?> listEntities(@PathParam("namespace") String namespace,
            @PathParam("entityType") String entityTypeName, @QueryParam("proj") String projecting)
            throws SortException {

        EntityContext ctx = new EntityContext(env, namespace);
        EntityType entityType = getEntityType(ctx.getDefinitions(), namespace, entityTypeName);
        QueryObject<?> qo = new QueryObject<>(entityType.getInterfaceName());

        if (projecting != null) {
            addProjection(qo, projecting);
        }

        QueryResult<?> result = ctx.performQuery(qo);
        return result;
    }

    @GET
    @Path("/tables/{namespace}/{entityType}/")
    @Produces(MediaType.APPLICATION_JSON)
    public QueryResult<?> listTableRow(@PathParam("namespace") String namespace,
            @PathParam("entityType") String entityTypeName, @QueryParam("proj") String projecting)
            throws SortException {

        EntityContext ctx = new EntityContext(env, namespace);
        EntityType entityType = getEntityType(ctx.getDefinitions(), namespace, entityTypeName);
        QueryObject<?> qo = new QueryObject<>(entityType.getInterfaceName());
        for (NodeType nt : entityType.getNodeTypes()) {
            /*
             * add an outer join to all 1:1 refs
             */
            if (nt.getRelationInterfaceName() != null && nt.getJdbcType() != null) {
                EntityType joinTo = ctx.getDefinitions()
                        .getEntityTypeMatchingInterface(nt.getRelationInterfaceName(), true);
                if (joinTo.getNodeType("name", false) != null) {
                    QueryObject<Object> outerJoinQuery = new QueryObject<>(joinTo.getInterfaceName());
                    qo.addLeftOuterJoin(outerJoinQuery, nt.getName());
                    outerJoinQuery.select(new QProperty<>(outerJoinQuery, "name"));
                }
            }
        }

        if (projecting != null) {
            addProjection(qo, projecting);
        }

        QueryResult<?> result = ctx.performQuery(qo);
        return result;
    }

    private EntityType getEntityType(Definitions definitions, String namespace, String entityTypeName) {
        return definitions.getEntityTypeMatchingInterface(entityTypeName, true);
    }

    @POST
    @Path("/entities/{namespace}/{entityType}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public List<Entity> persist(@PathParam("namespace") String namespace,
            @PathParam("entityType") String entityTypeName, @PathParam("id") String id, ObjectNode rootNode)
            throws SortException {

        EntityContext ctx = new EntityContext(env, namespace);
        EntityType entityType = getEntityType(ctx.getDefinitions(), namespace, entityTypeName);

        Entity entity = toEntity(ctx, rootNode, entityType);

        ctx.persist(new PersistRequest().save(entity));

        return Collections.emptyList();
    }

    @DELETE
    @Path("/entities/{namespace}/{entityType}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public boolean delete(@PathParam("namespace") String namespace, @PathParam("entityType") String entityTypeName,
            @PathParam("id") String id, ObjectNode rootNode) throws SortException {

        EntityContext ctx = new EntityContext(env, namespace);
        EntityType entityType = getEntityType(ctx.getDefinitions(), namespace, entityTypeName);

        Entity entity = toEntity(ctx, rootNode, entityType);

        ctx.persist(new PersistRequest().delete(entity));

        return true;
    }

    private Entity toEntity(EntityContext ctx, ObjectNode jsObject, EntityType entityType) {
        Object keyValue = getEntityKey(jsObject, entityType);
        Entity entity = newEntityInCorrectState(ctx, entityType, keyValue);

        for (Iterator<String> i = jsObject.fieldNames(); i.hasNext();) {
            String fieldName = i.next();
            JsonNode jsNode = jsObject.get(fieldName);
            if (jsNode == null || jsNode.isNull()) {
                continue;
            }
            Node eNode = entity.getChild(fieldName);
            if (eNode instanceof ValueNode) {
                ((ValueNode) eNode).setValue(convert(eNode.getNodeType(), jsNode.asText(), true));
            } else if (eNode instanceof RefNode) {
                RefNode refNode = ((RefNode) eNode);
                if (jsNode.isValueNode()) {
                    /*
                     * we just refer to a key so set it
                     */
                    refNode.setEntityKey(convert(eNode.getNodeType(), jsNode.asText(), true));
                } else if (jsNode.isObject()) {
                    /*
                     * we refer to a whole object definition, so convert it to an entity.
                     */
                    Entity reference = toEntity(ctx, ((ObjectNode) jsNode), refNode.getEntityType());
                    refNode.setReference(reference);
                } else {
                    throw new IllegalStateException("Unexpected JSON node type '" + jsNode + "'");
                }
            } else if (eNode instanceof ToManyNode) {
                ToManyNode toManyNode = (ToManyNode) eNode;
                if (!jsNode.isArray()) {
                    throw new IllegalArgumentException("Expected as JSON array for field '" + fieldName + "'");
                }
                for (Iterator<JsonNode> iel = ((ArrayNode) jsNode).elements(); iel.hasNext();) {
                    JsonNode element = iel.next();
                    if (element.isValueNode()) {
                        /*
                         * we just refer to a key so set it
                         */
                        Object key = convertForKey(toManyNode.getEntityType(), element.asText());
                        Entity e = ctx.getOrCreateBasedOnKeyGenSpec(toManyNode.getEntityType(), key);
                        toManyNode.add(e);
                    } else if (element.isObject()) {
                        /*
                         * we just refer to a JSON object so convert it to an entity
                         */
                        Entity reference = toEntity(ctx, ((ObjectNode) element), toManyNode.getEntityType());
                        toManyNode.add(reference);
                    } else {
                        throw new IllegalStateException("Unexpected JSON node type '" + jsNode + "'");
                    }
                }

            }
        }
        return entity;
    }

    /**
     *
     * @param ctx
     * @param entityType
     * @param keyValue can be null.
     * @return
     */
    private Entity newEntityInCorrectState(EntityContext ctx, EntityType entityType, Object keyValue) {
        Entity entity;
        if (keyValue == null) {
            if (entityType.getKeyGenSpec() == KeyGenSpec.CLIENT) {
                throw new IllegalStateException(
                        "The client should generate the primary key for " + entityType.getInterfaceShortName());
            }
            //there is no PK yet, we know it is a new entity which does not exist in the DB yet
            entity = ctx.newEntity(entityType);
        } else {
            /*
             * we have a PK value from the client, but we still need to know if we are doing an insert
             * or an update.
             */
            if (entityType.getKeyGenSpec() == KeyGenSpec.FRAMEWORK) {
                /*
                 * barleydb generates the key, as the key is already there we must be doing an update
                 * of an existing record.
                 * So we pretend that this entity was loaded from the DB so that an update will be perfomed.
                 */
                entity = ctx.newFakeLoadedEntity(entityType, keyValue);
            } else {
                /*
                 * so the client provides the PK always for this entity type. We have no way of knowing if it is an
                 * insert of an update, so we use the PERHAPS_IN_DATABASE state.
                 */
                entity = ctx.newPerhapsInDatabaseEntity(entityType, keyValue);
            }
        }
        return entity;
    }

    private Object getEntityKey(ObjectNode jsObject, EntityType entityType) {
        JsonNode keyNode = jsObject.get(entityType.getKeyNodeName());
        if (keyNode == null || keyNode.isNull()) {
            return null;
        }
        String keyAsString = keyNode.asText();
        if (keyAsString == null || keyAsString.isEmpty()) {
            return null;
        }
        return convertForKey(entityType, keyAsString);
    }

    private Object convertForKey(EntityType entityType, String keyAsString) {
        NodeType keyNodeType = entityType.getNodeType(entityType.getKeyNodeName(), true);
        return convert(keyNodeType, keyAsString, true);
    }

}