org.cloudfoundry.tools.timeout.ReplayingTimeoutProtectionStrategy.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.tools.timeout.ReplayingTimeoutProtectionStrategy.java

Source

/*
 * Copyright 2010-2012 the original author or authors.
 *
 * 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 org.cloudfoundry.tools.timeout;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletResponse;

import org.cloudfoundry.tools.timeout.monitor.HttpServletResponseMonitor;
import org.cloudfoundry.tools.timeout.monitor.HttpServletResponseMonitorFactory;
import org.cloudfoundry.tools.timeout.monitor.ReplayableHttpServletResponseMonitor;
import org.cloudfoundry.tools.timeout.monitor.ReplayableHttpServletResponseMonitorFactory;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;

/**
 * {@link TimeoutProtectionStrategy} that works by recording the original request such that it can be replayed to a
 * subsequent poll request. Requests that take longer than the {@link #setThreshold(long) threshold} to respond will be
 * recorded. The threshold should therefore be set to a value slightly lower than the expected gateway timeout. The
 * {@link #setFailTimeout(long)} method should be used to the timeout that will protect against requests that never
 * receive a poll (for example due to network failure). The {@link #setLongPollTime(long)} method can be used to set the
 * long-poll time for the poll request. This value should obviously be less than the gateway timeout.
 * <p>
 * This strategy consumes more memory than {@link HotSwappingTimeoutProtectionStrategy} but does not require that
 * timeouts only occur.
 * 
 * @author Phillip Webb
 */
public class ReplayingTimeoutProtectionStrategy implements TimeoutProtectionStrategy, TimeoutValues {

    private long threshold = TimeUnit.SECONDS.toMillis(14);

    private long longPollTime = TimeUnit.SECONDS.toMillis(6);

    private long failTimeout = TimeUnit.SECONDS.toMillis(120);

    private Map<String, MonitorFactory> completedRequests = new HashMap<String, MonitorFactory>();

    private long completedRequestsWaitTime = 500;

    protected final Map<String, MonitorFactory> getCompletedRequests() {
        return this.completedRequests;
    }

    public HttpServletResponseMonitorFactory handleRequest(final TimeoutProtectionHttpRequest request) {
        return new MonitorFactory();
    }

    public void afterRequest(TimeoutProtectionHttpRequest request,
            HttpServletResponseMonitorFactory monitorFactory) {
        afterRequest(request, (MonitorFactory) monitorFactory);
    }

    private void afterRequest(TimeoutProtectionHttpRequest request, MonitorFactory monitorFactory) {
        if (monitorFactory.wasMonitored()) {
            synchronized (this.completedRequests) {
                purgeUnpolledRequests();
                this.completedRequests.put(request.getUid(), monitorFactory);
                this.completedRequests.notifyAll();
            }
        }
    }

    /**
     * Cleanup any started monitors that may have never received a poll. This can happen if the client is closed after a
     * timeout but before a poll.
     */
    private void purgeUnpolledRequests() {
        Iterator<Map.Entry<String, MonitorFactory>> iterator = this.completedRequests.entrySet().iterator();
        while (iterator.hasNext()) {
            if (iterator.next().getValue().isPurgable(this.threshold + this.failTimeout)) {
                iterator.remove();
            }
        }
    }

    public void handlePoll(TimeoutProtectionHttpRequest request, HttpServletResponse response) throws IOException {
        String uid = request.getUid();
        long startTime = System.currentTimeMillis();
        do {
            MonitorFactory completedRequest;
            synchronized (this.completedRequests) {
                if (!this.completedRequests.containsKey(uid)) {
                    try {
                        this.completedRequests.wait(this.completedRequestsWaitTime);
                    } catch (InterruptedException e) {
                    }
                }
                completedRequest = this.completedRequests.remove(uid);
            }
            if (completedRequest != null) {
                completedRequest.replay(response);
                return;
            }
        } while (System.currentTimeMillis() - startTime < this.longPollTime);
        response.setHeader(TimeoutProtectionHttpHeader.POLL, uid);
        response.setStatus(HttpStatus.NO_CONTENT.value());
    }

    @Override
    public long getThreshold() {
        return this.threshold;
    }

    /**
     * Set the threshold that must be passed before timeout protection will be used
     * @param threshold the threshold in milliseconds
     */
    public void setThreshold(long threshold) {
        this.threshold = threshold;
    }

    /**
     * Set the maximum amount of time that a single long poll request can take.
     * @param longPollTime the long poll time in milliseconds
     */
    public void setLongPollTime(long longPollTime) {
        this.longPollTime = longPollTime;
    }

    @Override
    public long getFailTimeout() {
        return this.failTimeout;
    }

    /**
     * Set the amount of time before a request is considered failed.
     * @param failTimeout the fail timeout in milliseconds
     */
    public void setFailTimeout(long failTimeout) {
        this.failTimeout = failTimeout;
    }

    /**
     * Sets the amount of time to wait for a completed request.
     * @param completedRequestsWaitTime the amount of time to wait for completed requests
     */
    protected final void setCompletedRequestsWaitTime(long completedRequestsWaitTime) {
        this.completedRequestsWaitTime = completedRequestsWaitTime;
    }

    /**
     * The {@link HttpServletResponseMonitorFactory} used internally.
     */
    private class MonitorFactory implements HttpServletResponseMonitorFactory {

        private long startTime;

        private ReplayableHttpServletResponseMonitor monitor;

        public MonitorFactory() {
            this.startTime = System.currentTimeMillis();
        }

        public boolean wasMonitored() {
            return this.monitor != null;
        }

        public void replay(HttpServletResponse response) throws IOException {
            Assert.state(wasMonitored(), "Request was not monitored, no poll expected");
            this.monitor.getReplayableResponse().replay(response);
        }

        public HttpServletResponseMonitor getMonitor() {
            long pollThreshold = ReplayingTimeoutProtectionStrategy.this.threshold;
            if ((pollThreshold == 0) || (System.currentTimeMillis() - this.startTime >= pollThreshold)) {
                this.monitor = new ReplayableHttpServletResponseMonitorFactory().getMonitor();
            }
            return this.monitor;
        }

        public boolean isPurgable(long timeout) {
            return (System.currentTimeMillis() - this.startTime > timeout);
        }
    }
}