com.ibm.amc.feedback.FeedbackHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.amc.feedback.FeedbackHandler.java

Source

/**
 * Copyright 2014 IBM Corp.
 *
 * 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.ibm.amc.feedback;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.servlet.AsyncContext;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.ibm.amc.Constants;
import com.ibm.amc.IShutdown;
import com.ibm.amc.ShutdownListener;
import com.ibm.amc.ras.Logger47;
import com.ibm.amc.resources.data.ActionStatusResponse;
import com.ibm.amc.server.action.ActionFactory;
import com.ibm.amc.server.action.ActionStatus;
import com.ibm.amc.server.action.ActionStatusListener;

public class FeedbackHandler implements ActionStatusListener, IShutdown {
    // @CLASS-COPYRIGHT@

    private static final int POLLING_TIMEOUT = 20;

    private static final int ACTION_STATUS_TIMEOUT = 60 * 1000;

    static Logger47 logger = Logger47.get(FeedbackHandler.class.getCanonicalName(), Constants.CWZBA_BUNDLE_NAME);

    private final Map<String, Queue<ActionStatusResponse>> statuses = Collections
            .synchronizedMap(new HashMap<String, Queue<ActionStatusResponse>>());

    private ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);

    private ObjectMapper mapper;

    /**
     * Map of feedback listeners keyed off user name. Populated by <code>FeedbackServlet</code>.
     */
    final Map<String, Set<AsyncContext>> feedbackListeners = Collections
            .synchronizedMap(new HashMap<String, Set<AsyncContext>>());

    public FeedbackHandler() {
        if (logger.isEntryEnabled())
            logger.entry("FeedbackHandler");

        mapper = new ObjectMapper();
        mapper.getFactory().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

        ShutdownListener.addShutdownListener(this);

        ActionFactory.getActionLog().addActionListener(this);

        // Check for action statuses that are too old and remove them
        executor.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                final Date date = new Date(System.currentTimeMillis() - ACTION_STATUS_TIMEOUT);
                synchronized (statuses) {
                    for (Queue<ActionStatusResponse> queue : statuses.values()) {
                        while (queue.peek().updated.before(date)) {
                            final ActionStatusResponse response = queue.poll();
                            if (logger.isDebugEnabled())
                                logger.debug("FeedbackHandler$Runnable.run",
                                        "Expiring action feedback response for action " + response.actionId);
                        }
                    }
                }

            }
        }, ACTION_STATUS_TIMEOUT, ACTION_STATUS_TIMEOUT, TimeUnit.MILLISECONDS);

        if (logger.isEntryEnabled())
            logger.exit("FeedbackHandler");
    }

    public void shutdown() {
        if (executor != null)
            executor.shutdown();
    }

    @Override
    public void actionStatusUpdated(final ActionStatus status) {
        if (logger.isEntryEnabled())
            logger.entry("actionStatusUpdated", status);

        if (!status.isSynchronous()) {
            final String user = status.getUserId();

            final Set<AsyncContext> contexts = feedbackListeners.remove(user);
            if (contexts == null) {
                // No one waiting - queue status
                if (logger.isDebugEnabled())
                    logger.debug("actionStatusUpdated", "Queuing status update for action " + status.getActionId());
                queueActionStatus(status);
            } else {
                // Request waiting - send response immediately
                if (logger.isDebugEnabled())
                    logger.debug("actionStatusUpdated", "Sending status update for action " + status.getActionId());
                final Queue<ActionStatusResponse> queue = new LinkedList<ActionStatusResponse>();
                queue.offer(new ActionStatusResponse(status));
                for (AsyncContext ctx : contexts) {
                    writeResponse(ctx.getResponse(), queue);
                    ctx.complete();
                }
            }
        }

        if (logger.isEntryEnabled())
            logger.exit("actionStatusUpdated");
    }

    public void handleRequest(final HttpServletRequest request, final HttpServletResponse response) {
        if (logger.isEntryEnabled())
            logger.entry("handleRequest", request, response);

        final String user = request.getRemoteUser();
        final Queue<ActionStatusResponse> statuses = getActionStatuses(user);
        if (statuses.isEmpty()) {
            // No updates pending - register listener
            final AsyncContext asyncContext = request.startAsync(request, response);
            asyncContext.setTimeout(900000000L);
            logger.debug("handleRequest", "Registering new listener for user " + user);
            synchronized (feedbackListeners) {
                Set<AsyncContext> contexts = feedbackListeners.get(user);
                if (contexts == null) {
                    contexts = new HashSet<AsyncContext>();
                    feedbackListeners.put(user, contexts);
                }
                contexts.add(asyncContext);
            }

            // Timeout listener
            executor.schedule(new Runnable() {

                @Override
                public void run() {
                    synchronized (feedbackListeners) {
                        final Set<AsyncContext> contexts = feedbackListeners.get(user);
                        if (contexts.remove(asyncContext)) {
                            if (logger.isDebugEnabled())
                                logger.debug("handleRequest$Runnable.run", "Timing out listener for user " + user);
                            writeResponse(asyncContext.getResponse(), new LinkedList<ActionStatusResponse>());
                            asyncContext.complete();
                            if (contexts.isEmpty())
                                feedbackListeners.remove(user);
                        }
                    }
                }

            }, POLLING_TIMEOUT, TimeUnit.SECONDS);
        } else {
            // Update pending - send response immediately
            writeResponse(response, statuses);
        }

        if (logger.isEntryEnabled())
            logger.exit("handleRequest");
    }

    private void writeResponse(final ServletResponse response, final Queue<ActionStatusResponse> statuses) {
        try {
            mapper.writeValue(response.getOutputStream(), statuses);
        } catch (Exception e) {
            logger.debug("writeResponse", "Exception writing response", e);
        }
    }

    private Queue<ActionStatusResponse> getActionStatuses(final String user) {
        if (logger.isEntryEnabled())
            logger.entry("getActionStatuses", user);
        Queue<ActionStatusResponse> userStatuses;
        synchronized (statuses) {
            userStatuses = statuses.remove(user);
            if (userStatuses == null) {
                userStatuses = new LinkedList<ActionStatusResponse>();
            }
        }
        if (logger.isEntryEnabled())
            logger.exit("getActionStatuses", userStatuses);
        return userStatuses;
    }

    private void queueActionStatus(final ActionStatus status) {
        if (logger.isEntryEnabled())
            logger.entry("queueActionStatus", status);
        if (logger.isDebugEnabled())
            logger.debug("queueActionStatus", "", status.getUserId() + " " + status.getState());
        synchronized (statuses) {
            Queue<ActionStatusResponse> userStatuses = statuses.get(status.getUserId());
            if (userStatuses == null) {
                userStatuses = new LinkedList<ActionStatusResponse>();
                statuses.put(status.getUserId(), userStatuses);
            }
            userStatuses.add(new ActionStatusResponse(status));
        }
        if (logger.isEntryEnabled())
            logger.exit("queueActionStatus");
    }

}