com.ikanow.aleph2.aleph2_rest_utils.RestCrudFunctions.java Source code

Java tutorial

Introduction

Here is the source code for com.ikanow.aleph2.aleph2_rest_utils.RestCrudFunctions.java

Source

/*******************************************************************************
 * Copyright 2016, The IKANOW Open Source Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.ikanow.aleph2.aleph2_rest_utils;

import java.io.IOException;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import scala.Tuple2;
import scala.Tuple3;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService;
import com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor;
import com.ikanow.aleph2.data_model.interfaces.shared_services.IServiceContext;
import com.ikanow.aleph2.data_model.objects.shared.SharedLibraryBean;
import com.ikanow.aleph2.data_model.utils.BeanTemplateUtils;
import com.ikanow.aleph2.data_model.utils.CrudUtils;
import com.ikanow.aleph2.data_model.utils.CrudUtils.SingleQueryComponent;
import com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent;
import com.ikanow.aleph2.data_model.utils.ErrorUtils;
import com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent;
import com.ikanow.aleph2.data_model.utils.Tuples;

import fj.data.Either;

/**
 * @author Burch
 *
 */
public class RestCrudFunctions {
    private static Logger _logger = LogManager.getLogger();
    private static final String COUNT_FIELD_NAME = "count";
    private static final String DELETE_SUCCESS_FIELD_NAME = "delete_success";

    public enum FunctionType {
        QUERY, COUNT
    }

    public static class CreateResponse {
        public final String id;
        public final Boolean success;
        public final String message;

        public CreateResponse(final String id, final Boolean success, final String message) {
            this.id = id;
            this.success = success;
            this.message = message;
        }
    }

    public static <T> Response readFunction(IServiceContext service_context, FunctionType function_type,
            String service_type, String access_level, String service_identifier, Optional<String> bucket_full_names,
            Optional<String> query_json, Optional<String> query_id, Optional<Long> limit) {
        _logger.debug("Handling READ request");
        //parse out the url params

        final Either<String, Tuple2<ICrudService<T>, Class<T>>> crud_service_either = RestUtils
                .getCrudService(service_context, service_type, access_level, service_identifier, bucket_full_names);
        if (crud_service_either.isLeft())
            return Response.status(Status.BAD_REQUEST).entity(crud_service_either.left().value()).build();

        try {
            switch (function_type) {
            case COUNT:
                return handleCountRequest(query_json, crud_service_either.right().value()._1,
                        crud_service_either.right().value()._2);
            case QUERY:
                return handleQueryRequest(query_json, query_id, crud_service_either.right().value()._1,
                        crud_service_either.right().value()._2, limit);
            default:
                return Response.status(Status.BAD_REQUEST)
                        .entity("Unknown GET function type (how did you do this?): " + function_type).build();
            }
        } catch (Exception ex) {
            return Response.status(Status.BAD_REQUEST).entity(ErrorUtils.getLongForm("Error: {0}", ex)).build();
        }
    }

    //   public static <T> Response createFunction(IServiceContext service_context, String service_type, String access_level, String service_identifier, Optional<String> bucket_full_names, 
    //         Optional<String> json) {
    //      return createFunction(service_context, service_type, access_level, service_identifier, bucket_full_names, json, Optional.empty());
    //      _logger.debug("Handling CREATE request");
    //      //parse out the url params
    //      final Either<String,Tuple2<ICrudService<T>, Class<T>>> crud_service_either = RestUtils.getCrudService(service_context, service_type, access_level, service_identifier, bucket_full_names);
    //
    //      if ( crud_service_either.isLeft() )
    //          return Response.status(Status.BAD_REQUEST).entity(crud_service_either.left().value()).build();         
    //      try {
    //         return handleCreateRequest(json, crud_service_either.right().value()._1, crud_service_either.right().value()._2);                            
    //       } catch ( Exception ex ) {
    //          return Response.status(Status.BAD_REQUEST).entity(ErrorUtils.getLongForm("Error: {0}", ex)).build();
    //       }
    //   }

    //create just json version
    public static <T> Response createFunction(IServiceContext service_context, String service_type,
            String access_level, String service_identifier, Optional<String> bucket_full_names, final String json) {
        _logger.debug("Handling CREATE request");
        final Either<String, Tuple2<ICrudService<T>, Class<T>>> crud_service_either = RestUtils
                .getCrudService(service_context, service_type, access_level, service_identifier, bucket_full_names);
        if (crud_service_either.isLeft())
            return Response.status(Status.BAD_REQUEST).entity(crud_service_either.left().value()).build();

        _logger.debug("handling regular create request (non file upload)");
        try {
            return handleCreateRequest(json, crud_service_either.right().value()._1,
                    crud_service_either.right().value()._2);
        } catch (Exception ex) {
            return Response.status(Status.BAD_REQUEST).entity(ErrorUtils.getLongForm("Error: {0}", ex)).build();
        }
    }

    //create file upload version w/ optional json
    public static <T> Response createFunction(IServiceContext service_context, String service_type,
            String access_level, String service_identifier, Optional<String> bucket_full_names,
            final FileDescriptor file_upload, Optional<String> json) {
        //TODO some validation that we are getting back a file crud service (so we cant send the wrong args)
        final Either<String, Tuple2<ICrudService<T>, Class<T>>> crud_service_either = RestUtils
                .getCrudService(service_context, service_type, access_level, service_identifier, bucket_full_names);
        if (crud_service_either.isLeft())
            return Response.status(Status.BAD_REQUEST).entity(crud_service_either.left().value()).build();

        final Optional<CompletableFuture<Supplier<Object>>> maybe_fut = json
                .<CompletableFuture<Supplier<Object>>>map(j -> {
                    //TODO exception when this isn't a shared lib bean
                    final SharedLibraryBean slb = BeanTemplateUtils.from(j, SharedLibraryBean.class).get();
                    final ICrudService<Tuple2<ICrudService<T>, Class<T>>> crud_service = (ICrudService<Tuple2<ICrudService<T>, Class<T>>>) crud_service_either
                            .right().value()._1();
                    return (CompletableFuture<Supplier<Object>>) crud_service
                            .storeObject(new Tuple2(slb, file_upload));
                });

        final CompletableFuture<Supplier<Object>> fut = maybe_fut // (sun didn't like this in one statement so split into 2 for compile reasons)
                .orElseGet(() -> {
                    final ICrudService<FileDescriptor> crud_service = (ICrudService<FileDescriptor>) crud_service_either
                            .right().value()._1();
                    return (CompletableFuture<Supplier<Object>>) crud_service.storeObject(file_upload);
                });

        try {
            final Tuple3<String, Boolean, String> id_or_error = fut.handle((ok, ex) -> {
                if (ok != null)
                    return new Tuple3<String, Boolean, String>(ok.get().toString(), true, "ok");
                else if (ex != null)
                    return new Tuple3<String, Boolean, String>(null, false,
                            ErrorUtils.getLongForm("Exception storing object: {0}", ex));
                else
                    return new Tuple3<String, Boolean, String>(null, false,
                            "something went very wrong, shouldn't have reached this piece of code");
            }).get();
            return Response.ok(RestUtils
                    .convertObjectToJson(new CreateResponse(id_or_error._1(), id_or_error._2(), id_or_error._3()))
                    .toString()).build();
        } catch (Exception e) {
            return Response.status(Status.BAD_REQUEST).entity(ErrorUtils.getLongForm("Error: {0}", e)).build();
        }
    }

    public static <T> Response updateFunction(IServiceContext service_context, String service_type,
            String access_level, String service_identifier, Optional<String> bucket_full_names,
            Optional<String> json) {
        _logger.debug("Handling UPDATE request");
        //parse out the url params
        final Either<String, Tuple2<ICrudService<T>, Class<T>>> crud_service_either = RestUtils
                .getCrudService(service_context, service_type, access_level, service_identifier, bucket_full_names);
        if (crud_service_either.isLeft())
            return Response.status(Status.BAD_REQUEST).entity(crud_service_either.left().value()).build();

        try {
            return handleUpdateRequest(json, crud_service_either.right().value()._1,
                    crud_service_either.right().value()._2);
        } catch (Exception ex) {
            return Response.status(Status.BAD_REQUEST).entity(ErrorUtils.getLongForm("Error: {0}", ex)).build();
        }

    }

    public static <T> Response deleteFunction(IServiceContext service_context, String service_type,
            String access_level, String service_identifier, Optional<String> bucket_full_names,
            Optional<String> query_json, Optional<String> query_id) {
        _logger.debug("Handling DELETE request");
        //parse out the url params
        final Either<String, Tuple2<ICrudService<T>, Class<T>>> crud_service_either = RestUtils
                .getCrudService(service_context, service_type, access_level, service_identifier, bucket_full_names);
        if (crud_service_either.isLeft())
            return Response.status(Status.BAD_REQUEST).entity(crud_service_either.left().value()).build();

        try {
            return handleDeleteRequest(query_json, query_id, crud_service_either.right().value()._1,
                    crud_service_either.right().value()._2);
        } catch (Exception ex) {
            return Response.status(Status.BAD_REQUEST).entity(ErrorUtils.getLongForm("Error: {0}", ex)).build();
        }
    }

    private static <T> Response handleQueryRequest(final Optional<String> query_json,
            final Optional<String> query_id, final ICrudService<T> crud_service, final Class<T> clazz,
            Optional<Long> limit) {
        //get id or a query object that was posted
        //TODO switch to query_id.map().orElse() ... just making a quick swap for the moment
        if (query_id.isPresent()) {
            //ID query
            try {
                final String id = query_id.get();
                _logger.debug("id: " + id);
                return Response.ok(RestUtils.convertObjectToJson(crud_service.getObjectById(id).get()).toString())
                        .build();
            } catch (JsonProcessingException | InterruptedException | ExecutionException e) {
                return Response.status(Status.BAD_REQUEST)
                        .entity(ErrorUtils.getLongForm("Error converting input stream to string: {0}", e)).build();
            }
        } else if (query_json.isPresent()) {
            //Body Query
            try {
                final String json = query_json.get();
                _logger.debug("query: " + json);
                final QueryComponent<T> query = RestUtils.convertStringToQueryComponent(json, clazz, limit);
                return Response
                        .ok(RestUtils.convertCursorToJson(crud_service.getObjectsBySpec(query).get()).toString())
                        .build();
            } catch (Exception e) {
                return Response.status(Status.BAD_REQUEST)
                        .entity(ErrorUtils.getLongForm("Error converting input stream to string: {0}", e)).build();
            }
        } else {
            //empty query, does a getall using limit (or 10)
            try {
                _logger.debug("empty query, get all");
                final SingleQueryComponent<T> query = JsonNode.class.isAssignableFrom(clazz)
                        ? (SingleQueryComponent<T>) CrudUtils.allOf()
                        : CrudUtils.allOf(clazz);
                final Cursor<T> cursor = crud_service.getObjectsBySpec(query.limit(limit.orElse(10L))).get();
                return Response.ok(RestUtils.convertCursorToJson(cursor).toString()).build();
            } catch (JsonProcessingException | InterruptedException | ExecutionException e) {
                return Response.status(Status.BAD_REQUEST)
                        .entity(ErrorUtils.getLongForm("Error converting input stream to string: {0}", e)).build();
            }
        }
    }

    private static <T> Response handleCountRequest(final Optional<String> query_json,
            final ICrudService<T> crud_service, final Class<T> clazz)
            throws InterruptedException, ExecutionException {
        //get query or if there is none just return count           
        return query_json.map(json -> {
            try {
                _logger.debug("query: " + json);
                final QueryComponent<T> query = RestUtils.convertStringToQueryComponent(json, clazz,
                        Optional.empty());
                return Response.ok(RestUtils
                        .convertSingleObjectToJson(crud_service.countObjectsBySpec(query).get(), COUNT_FIELD_NAME)
                        .toString()).build();
            } catch (Exception e) {
                return Response.status(Status.BAD_REQUEST)
                        .entity(ErrorUtils.getLongForm("Error converting input stream to string: {0}", e)).build();
            }
        }).orElse(Response.ok(
                RestUtils.convertSingleObjectToJson(crud_service.countObjects().get(), COUNT_FIELD_NAME).toString())
                .build());
    }

    private static <T> Response handleCreateRequest(final String json, final ICrudService<T> crud_service,
            final Class<T> clazz) throws JsonProcessingException, InterruptedException, ExecutionException {
        //get id or a query object that was posted
        _logger.debug("input: " + json);
        //TODO handle overwriting existing object
        return Response
                .ok(RestUtils
                        .convertObjectToJson(
                                crud_service.storeObject(BeanTemplateUtils.from(json, clazz).get()).get().get())
                        .toString())
                .build();
    }

    private static <T> Response handleUpdateRequest(final Optional<String> json, final ICrudService<T> crud_service,
            final Class<T> clazz)
            throws JsonParseException, JsonMappingException, IOException, InterruptedException, ExecutionException {
        //get id or a query object that was posted
        if (json.isPresent()) {
            _logger.debug("input: " + json.get());
            final Tuple2<QueryComponent<T>, UpdateComponent<T>> q_u = RestUtils
                    .convertStringToUpdateComponent(json.get(), clazz);
            boolean upsert = false; //TODO get from url params
            boolean before_updated = false; //TODO get from url params
            return Response
                    .ok(RestUtils
                            .convertObjectToJson(crud_service
                                    .updateAndReturnObjectBySpec(q_u._1, Optional.of(upsert), q_u._2,
                                            Optional.of(before_updated), Collections.emptyList(), false)
                                    .get().get())
                            .toString())
                    .build();
        } else {
            return Response.status(Status.BAD_REQUEST).entity("PUT requires json in the body").build();
        }
    }

    private static <T> Response handleDeleteRequest(final Optional<String> query_json,
            final Optional<String> query_id, final ICrudService<T> crud_service, final Class<T> clazz) {
        //get id or a query object that was posted
        if (query_id.isPresent()) {
            //ID delete
            try {
                final String id = query_id.get();
                _logger.debug("id: " + id);
                return Response.ok(RestUtils.convertSingleObjectToJson(crud_service.deleteObjectById(id).get(),
                        DELETE_SUCCESS_FIELD_NAME).toString()).build();
            } catch (InterruptedException | ExecutionException e) {
                return Response.status(Status.BAD_REQUEST)
                        .entity(ErrorUtils.getLongForm("Error converting input stream to string: {0}", e)).build();
            }
        } else if (query_json.isPresent()) {
            //Body delete
            try {
                final String json = query_json.get();
                _logger.debug("query: " + json);
                final QueryComponent<T> query = RestUtils.convertStringToQueryComponent(json, clazz,
                        Optional.empty());
                return Response.ok(RestUtils.convertSingleObjectToJson(crud_service.deleteObjectBySpec(query).get(),
                        DELETE_SUCCESS_FIELD_NAME).toString()).build();
            } catch (Exception e) {
                return Response.status(Status.BAD_REQUEST)
                        .entity(ErrorUtils.getLongForm("Error converting input stream to string: {0}", e)).build();
            }
        } else {
            //TODO actually we should probably just do something else when this is empty (like return first item, or search all and return limit 10, etc)
            return Response.status(Status.BAD_REQUEST)
                    .entity("DELETE requires an id in the url or query in the body").build();
        }
    }
}