org.apache.usergrid.persistence.qakka.api.QueueResource.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.usergrid.persistence.qakka.api.QueueResource.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.usergrid.persistence.qakka.api;

import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponses;
import org.apache.usergrid.persistence.qakka.URIStrategy;
import org.apache.usergrid.persistence.qakka.core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

@Api(value = "/queues", description = "Queue management, send, get and ack.")
@Path("queues")
public class QueueResource {
    private static final Logger logger = LoggerFactory.getLogger(QueueResource.class);

    private final QueueManager queueManager;
    private final QueueMessageManager queueMessageManager;
    private final URIStrategy uriStrategy;
    private final Regions regions;

    @Inject
    public QueueResource(QueueManager queueManager, QueueMessageManager queueMessageManager,
            URIStrategy uriStrategy, Regions regions) {

        this.queueManager = queueManager;
        this.queueMessageManager = queueMessageManager;
        this.uriStrategy = uriStrategy;
        this.regions = regions;

        logger.info("Constructed");
    }

    @ApiOperation(value = "Create new queue.", response = ApiResponse.class)
    @ApiResponses(value = {
            @io.swagger.annotations.ApiResponse(code = 400, message = "No Queue object posted, or name field is missing"), })
    @POST
    @Consumes({ MediaType.APPLICATION_JSON })
    @Produces({ MediaType.APPLICATION_JSON })
    public Response createQueue(Queue queue) throws Exception {

        Preconditions.checkArgument(queue != null, "Queue configuration is required");
        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queue.getName()), "Queue name is required");

        queueManager.createQueue(queue);

        ApiResponse apiResponse = new ApiResponse();
        apiResponse.setQueues(Collections.singletonList(queue));
        return Response.created(uriStrategy.queueURI(queue.getName())).entity(apiResponse).build();
    }

    @ApiOperation(value = "Update Queue configuration.", response = ApiResponse.class)
    @ApiResponses(value = {
            @io.swagger.annotations.ApiResponse(code = 400, message = "No Queue object posted, or name field is missing"), })
    @PUT
    @Path("{queueName}/config")
    @Consumes({ MediaType.APPLICATION_JSON })
    @Produces({ MediaType.APPLICATION_JSON })
    public Response updateQueueConfig(@PathParam("queueName") String queueName, Queue queue) {

        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queueName), "Queue name is required");
        Preconditions.checkArgument(queue != null, "Queue configuration is required");

        queue.setName(queueName);
        queueManager.updateQueueConfig(queue);

        ApiResponse apiResponse = new ApiResponse();
        apiResponse.setQueues(Collections.singletonList(queue));
        return Response.ok().entity(apiResponse).build();
    }

    @ApiOperation(value = "Delete Queue.", response = ApiResponse.class)
    @ApiResponses(value = {
            @io.swagger.annotations.ApiResponse(code = 400, message = "Queue name or confirm flag missing."), })
    @DELETE
    @Path("{queueName}")
    @Produces({ MediaType.APPLICATION_JSON })
    public Response deleteQueue(@PathParam("queueName") String queueName,
            @QueryParam("confirm") @DefaultValue("false") Boolean confirmedParam) {

        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queueName), "Queue name is required");
        Preconditions.checkArgument(confirmedParam != null, "Confirm parameter is required");

        ApiResponse apiResponse = new ApiResponse();

        if (confirmedParam) {
            queueManager.deleteQueue(queueName);
            return Response.ok().entity(apiResponse).build();
        }

        apiResponse.setMessage("confirm parameter must be true");
        return Response.status(Response.Status.BAD_REQUEST).entity(apiResponse).build();
    }

    @ApiOperation(value = "Get Queue config.", response = ApiResponse.class)
    @ApiResponses(value = {
            @io.swagger.annotations.ApiResponse(code = 400, message = "Queue name or confirm flag missing."), })
    @GET
    @Path("{queueName}/config")
    @Produces({ MediaType.APPLICATION_JSON })
    public Response getQueueConfig(
            @ApiParam(value = "Name of Queue", required = true) @PathParam("queueName") String queueName) {

        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queueName), "Queue name is required");

        ApiResponse apiResponse = new ApiResponse();
        Queue queue = queueManager.getQueueConfig(queueName);
        if (queue != null) {
            apiResponse.setQueues(Collections.singletonList(queue));
            return Response.ok().entity(apiResponse).build();
        }
        return Response.status(Response.Status.NOT_FOUND).build();
    }

    @ApiOperation(value = "Get list of all Queues.", response = ApiResponse.class)
    @GET
    @Produces({ MediaType.APPLICATION_JSON })
    public List<String> getListOfQueues() {

        // TODO: create design to handle large number of queues, e.g. paging and/or hierarchy of queues
        // TODO: create design to support multi-tenant usage, authentication, etc.
        return queueManager.getListOfQueues();
    }

    //    @GET
    //    @Path( "{queueName}/stats" )
    //    @Produces({MediaType.APPLICATION_JSON})
    //    public Response getQueueStats(
    //            @ApiParam(value = "Name of Queue", required = true) @PathParam("queueName") String queueName) 
    //        throws Exception {
    //        
    //        // TODO: implement GET /queues/{queueName}/stats
    //        throw new UnsupportedOperationException();
    //    }

    Long convertDelayParameter(String delayParam) {
        Long delayMs = 0L;
        if (!QakkaUtils.isNullOrEmpty(delayParam)) {
            switch (delayParam.toUpperCase()) {
            case "NONE":
            case "":
                delayMs = 0L;
                break;
            default:
                try {
                    delayMs = Long.parseLong(delayParam);
                } catch (Exception e) {
                    throw new IllegalArgumentException("Invalid delay parameter");
                }
                break;
            }
        }
        return delayMs;
    }

    Long convertExpirationParameter(String expirationParam) throws IllegalArgumentException {
        Long expirationSecs = null;
        if (!QakkaUtils.isNullOrEmpty(expirationParam)) {
            switch (expirationParam.toUpperCase()) {
            case "NEVER":
            case "":
                expirationSecs = null;
                break;
            default:
                try {
                    expirationSecs = Long.parseLong(expirationParam);
                } catch (Exception e) {
                    throw new IllegalArgumentException("Invalid expiration parameter");
                }
                break;
            }
        }
        return expirationSecs;
    }

    /**
     * Send a queue message with a JSON payload.
     *
     * @param queueName         Name of queue to target (queue must exist)
     * @param regionsParam      Comma-separated list of regions to send to
     * @param delayParam        Delay (ms) before sending message (not yet supported)
     * @param expirationParam   Time (ms) after which message will expire (not yet supported)
     * @param messageBody       JSON payload in string form
     */
    @ApiOperation(value = "Send Queue Message with a JSON payload.", response = ApiResponse.class)
    @POST
    @Path("{queueName}/messages")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response sendMessageJson(

            @ApiParam(value = "Name of Queue", required = true) @PathParam("queueName") String queueName,

            @ApiParam(value = "Regions to which message is to be sent", required = false) @QueryParam("regions") @DefaultValue("") String regionsParam,

            @QueryParam("delay") @DefaultValue("") String delayParam,
            @QueryParam("expiration") @DefaultValue("") String expirationParam,

            @ApiParam(value = "Data to be send with Queue Message", required = true) String messageBody)

            throws Exception {

        return sendMessage(queueName, regionsParam, delayParam, expirationParam, MediaType.APPLICATION_JSON,
                ByteBuffer.wrap(messageBody.getBytes()));
    }

    /**
     * Send a queue message with a binary data payload.
     *
     * @param queueName         Name of queue to target (queue must exist)
     * @param regionsParam      Comma-separated list of regions to send to
     * @param delayParam        Delay (ms) before sending message (not yet supported)
     * @param expirationParam   Time (ms) after which message will expire (not yet supported)
     * @param actualContentType Content type of messageBody data (if not application/octet-stream)
     * @param messageBody       Binary data that is the payload of the queue message
     */
    @ApiOperation(value = "Send Queue Message with a binary data (blob) payload.", response = ApiResponse.class)
    @POST
    @Path("{queueName}/messages")
    @Consumes(MediaType.APPLICATION_OCTET_STREAM)
    @Produces(MediaType.APPLICATION_JSON)
    public Response sendMessageBinary(

            @ApiParam(value = "Name of Queue", required = true) @PathParam("queueName") String queueName,

            @ApiParam(value = "Regions to which message is to be sent", required = false) @QueryParam("regions") @DefaultValue("") String regionsParam,

            @QueryParam("delay") @DefaultValue("") String delayParam,
            @QueryParam("expiration") @DefaultValue("") String expirationParam,

            @ApiParam(value = "Content type of the data to be sent with Queue Message", required = true) @QueryParam("contentType") String actualContentType,

            @ApiParam(value = "Data to be send with Queue Message", required = true) byte[] messageBody)

            throws Exception {

        String contentType = actualContentType != null ? actualContentType : MediaType.APPLICATION_OCTET_STREAM;

        return sendMessage(queueName, regionsParam, delayParam, expirationParam, contentType,
                ByteBuffer.wrap(messageBody));
    }

    private Response sendMessage(String queueName, String regionsParam, String delayParam, String expirationParam,
            String contentType, ByteBuffer byteBuffer) {

        if (queueManager.getQueueConfig(queueName) == null) {
            throw new NotFoundException("Queue " + queueName + " not found");
        }

        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queueName), "Queue name is required");

        // if regions, delay or expiration are empty string, would get the defaults from the queue
        if (regionsParam.equals("")) {
            regionsParam = Regions.LOCAL;
        }

        Long delayMs = convertDelayParameter(delayParam);

        Long expirationSecs = convertExpirationParameter(expirationParam);

        List<String> regionList = regions.getRegions(regionsParam);

        queueMessageManager.sendMessages(queueName, regionList, delayMs, expirationSecs, contentType, byteBuffer);

        ApiResponse apiResponse = new ApiResponse();
        apiResponse.setCount(1);
        return Response.ok().entity(apiResponse).build();

    }

    @ApiOperation(value = "Get next Queue Messages from a Queue", response = ApiResponse.class)
    @ApiResponses(value = { @io.swagger.annotations.ApiResponse(code = 400, message = "Invalid count parameter"), })
    @GET
    @Path("{queueName}/messages")
    @Produces({ MediaType.APPLICATION_JSON })
    public Response getNextMessages(

            @ApiParam(value = "Name of Queue", required = false) @PathParam("queueName") String queueName,

            @ApiParam(value = "Number of messages to get", required = false) @QueryParam("count") @DefaultValue("1") String countParam)

            throws Exception {

        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queueName), "Queue name is required");

        int count = 1;
        try {
            count = Integer.parseInt(countParam);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid count parameter");
        }
        if (count <= 0) {
            // invalid count
            throw new IllegalArgumentException("Count must be >= 1");
        }

        List<QueueMessage> messages = queueMessageManager.getNextMessages(queueName, count);

        ApiResponse apiResponse = new ApiResponse();

        if (messages != null && !messages.isEmpty()) {
            apiResponse.setQueueMessages(messages);

        } else { // always return queueMessages field
            apiResponse.setQueueMessages(Collections.EMPTY_LIST);
        }
        apiResponse.setCount(apiResponse.getQueueMessages().size());
        return Response.ok().entity(apiResponse).build();
    }

    @ApiOperation(value = "Acknowledge that Queue Message has been processed.", response = ApiResponse.class)
    @ApiResponses(value = {
            @io.swagger.annotations.ApiResponse(code = 400, message = "Queue Message ID invalid, or message not in-flight"), })
    @DELETE
    @Path("{queueName}/messages/{queueMessageId}")
    @Produces({ MediaType.APPLICATION_JSON })
    public Response ackMessage(

            @ApiParam(value = "Name of Queue", required = true) @PathParam("queueName") String queueName,

            @ApiParam(value = "ID of Queue Message to be acknowledged", required = true) @PathParam("queueMessageId") String queueMessageId)

            throws Exception {

        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queueName), "Queue name is required");

        UUID messageUuid;
        try {
            messageUuid = UUID.fromString(queueMessageId);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid queue message UUID");
        }
        queueMessageManager.ackMessage(queueName, messageUuid);

        ApiResponse apiResponse = new ApiResponse();
        return Response.ok().entity(apiResponse).build();
    }

    @ApiOperation(value = "Get data associated with a Queue Message.", response = ApiResponse.class)
    @ApiResponses(value = { @io.swagger.annotations.ApiResponse(code = 400, message = "Message ID invalid"),
            @io.swagger.annotations.ApiResponse(code = 404, message = "Queue Message or data not found") })
    @GET
    @Path("{queueName}/data/{queueMessageId}")
    public Response getMessageData(

            @ApiParam(value = "Name of Queue", required = true) @PathParam("queueName") String queueName,

            @ApiParam(value = "ID of Queue Message for which data is to be returned", required = true) @PathParam("queueMessageId") String queueMessageIdParam) {

        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queueName), "Queue name is required");

        UUID queueMessageId;
        try {
            queueMessageId = UUID.fromString(queueMessageIdParam);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid queue message UUID");
        }

        QueueMessage message = queueMessageManager.getMessage(queueName, queueMessageId);
        if (message == null) {
            throw new NotFoundException(
                    "Message not found for queueName: " + queueName + " queue message id: " + queueMessageId);
        }

        ByteBuffer messageData = queueMessageManager.getMessageData(message.getMessageId());
        if (messageData == null) {
            throw new NotFoundException("Message data not found queueName: " + queueName + " queue message id: "
                    + queueMessageId + " message id: " + message.getMessageId());
        }

        ByteBufferBackedInputStream input = new ByteBufferBackedInputStream(messageData);

        StreamingOutput stream = output -> {
            try {
                ByteStreams.copy(input, output);
            } catch (Exception e) {
                throw new WebApplicationException(e);
            }
        };

        return Response.ok(stream).header("Content-Type", message.getContentType()).build();
    }

    //    @PUT
    //    @Path( "{queueName}/messages/{queueMessageId}" )
    //    @Produces({MediaType.APPLICATION_JSON})
    //    public Response requeueMessage( @PathParam("queueName") String queueName,
    //                                    @PathParam("queueMessageId") String queueMessageIdParam,
    //                                    @QueryParam("delay") @DefaultValue("") String delayParam) throws Exception {
    //
    //        Preconditions.checkArgument(!QakkaUtils.isNullOrEmpty(queueName), "Queue name is required");
    //
    //        UUID queueMessageId;
    //        try {
    //            queueMessageId = UUID.fromString(queueMessageIdParam);
    //        }
    //        catch (Exception e) {
    //            throw new IllegalArgumentException("Invalid message UUID");
    //        }
    //        Long delayMs = convertDelayParameter(delayParam);
    //
    //        queueMessageManager.requeueMessage(queueName, queueMessageId, delayMs);
    //
    //        ApiResponse apiResponse = new ApiResponse();
    //        return Response.ok().entity(apiResponse).build();
    //    }
    //
    //
    //    @DELETE
    //    @Path( "{queueName}/messages" )
    //    @Produces({MediaType.APPLICATION_JSON})
    //    public Response clearMessages( @PathParam("queueName") String queueName,
    //                                   @QueryParam("confirm") @DefaultValue("false") Boolean confirmed) throws Exception {
    //
    //        // TODO: implement DELETE /queues/{queueName}/messages"
    //        throw new UnsupportedOperationException();
    //    }

}