/*
* #%L
* Talend :: ESB :: Job :: Controller
* %%
* Copyright (C) 2011 - 2012 Talend Inc.
* %%
* 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.
* #L%
*/
package org.talend.esb.job.controller.internal;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.talend.esb.job.controller.internal.RuntimeESBProviderCallback.MessageExchange;
/**
* An buffer between providers of SOAP requests and consumers processing the request and providing a
* response. A <code>MessageExchangeBuffer</code> is unbounded for requests from providers and blocks
* consumers until a new request is available. A <code>MessageExchangeBuffer</code> can be stopped to
* accept requests. Still all buffered requests are forwarded to consumers until the buffer is empty.
*/
public class MessageExchangeBuffer {
public static final Logger LOG =
Logger.getLogger(MessageExchangeBuffer.class.getName());
private static final WorkloadListener DUMMY_LISTENER = new WorkloadListener() {
public void initialValues(MessageExchangeBuffer buffer, int consumersIdle, int waitingRequests) { }
public void valuesChanged(MessageExchangeBuffer buffer, int consumersIdle, int waitingRequests) { }
};
/**
* Marker which indicates if retrieved from queue that no more requests are to be expected. Should always
* be re-added to queue if retrieved to let other consumers know about.
*/
private static final MessageExchange POISON = new MessageExchange(null);
private volatile Status status = Status.RUNNING;
private final AtomicInteger idleConsumers = new AtomicInteger(0);
private final BlockingQueue<MessageExchange> requests = new LinkedBlockingQueue<MessageExchange>();
private WorkloadListener listener = DUMMY_LISTENER;
/**
* Removes and returns a request from the buffer. The response related to the request is put into the
* <code>MessageExchange</code> as soon as available. If necessary waits until one request is available.
*
* @return the request wrapped in a <code>MessageExchange</code>. The response related to the request must
* be added to the <code>MessageExchange</code>.
*
* @throws BufferStoppedException
* thrown if no request is available anymore and the buffer was already stopped or stopped
* when waiting for a request to become available
* @throws InterruptedException
* when waiting for a request to become available the current {@link Thread} was interrupted
*/
public MessageExchange take() throws BufferStoppedException, InterruptedException {
MessageExchange currentExchange = null;
try {
idleConsumers.getAndIncrement();
currentExchange = requests.take();
} finally {
idleConsumers.getAndDecrement();
}
if (status == Status.STOPPING && requests.size() <= 1) {
status = Status.STOPPED;
}
if (currentExchange == POISON) {
putPoison();
throw new BufferStoppedException();
} else {
listener.valuesChanged(this, consumersIdle(), requestsWaiting());
diagnose("Took one request from buffer.");
return currentExchange;
}
}
/**
* Inserts the request wrapped in the given <code>MessageExchange</code> at the end of the buffer. The
* response related to the request will be put into the <code>MessageExchange</code> as soon as
* available.
*
* @param messageExchange
* <code>MessageExchange</code> to insert into the buffer, must not be <code>null</code>.
*
* @throws BufferStoppedException
* thrown if the buffer is already closed and requests are not accepted anymore.
* @throws InterruptedException
* should never happen as items can always immediately be added to the buffer
*/
public void put(MessageExchange messageExchange) throws InterruptedException, BufferStoppedException {
synchronized (status) {
if (status.isRunning()) {
requests.put(messageExchange);
listener.valuesChanged(this, consumersIdle(), requestsWaiting());
diagnose("Put one request into buffer.");
} else {
throw new BufferStoppedException();
}
}
}
/**
* Immediately stops the buffer to accept further requests. Requests already in the buffer may stille be
* retrieved by consumers.
*/
public void stop() {
synchronized (status) {
if (status.isRunning()) {
putPoison();
status = Status.STOPPING;
}
}
}
/**
* Indicates whether the buffer was stopped and all pending requests were removed and processed by
* consumers.
*
* @return <code>true</code> iff buffer stopped and empty.
*/
public boolean isStopped() {
return status.isStopped();
}
public void setWorkloadListener(WorkloadListener workloadListener) {
listener = (workloadListener != null) ? workloadListener : DUMMY_LISTENER;
listener.initialValues(this, consumersIdle(), requestsWaiting());
}
/**
* Return the number of consumers waiting in the buffer to process a request.
*
* @return number of waiting consumers
*/
public int consumersIdle() {
return idleConsumers.get();
}
/**
* Return the number of requests waiting in the buffer to be processed.
*
* @return number of waiting requests
*/
public int requestsWaiting() {
int poisonItems = status.isRunning() ? 0 : 1;
return requests.size() - poisonItems;
}
private void putPoison() {
boolean success = false;
while (!success) {
try {
requests.put(POISON);
success = true;
} catch (InterruptedException e) {
LOG.throwing(this.getClass().getName(), "stop", e);
}
}
}
private void diagnose(String statusMsg) {
if (LOG.isLoggable(Level.FINE)) {
if (statusMsg != null && !statusMsg.isEmpty()) {
LOG.fine(statusMsg);
}
LOG.fine(idleConsumers + " consumers waiting for requests,"
+ requests.size() + " requests waiting to be processed.");
}
}
/**
* A <code>WorkloadListener</code> gets notified about the workload of a {@link MessageExchangeBuffer}.
* The workload may change as result of putting a request which is not immediately taken by a consumer or
* by a new consumer waiting to get served with a request.
*/
public static interface WorkloadListener {
void initialValues(MessageExchangeBuffer buffer, int idleConsumers, int waitingRequests);
void valuesChanged(MessageExchangeBuffer buffer, int idleConsumers, int waitingRequests);
}
private static enum Status {
RUNNING(0), STOPPING(1), STOPPED(2);
private int id;
Status(int id) {
this.id = id;
}
public boolean isRunning() {
return id == 0;
}
public boolean isStopped() {
return id == 2;
}
}
public static class BufferStoppedException extends Exception {
private static final long serialVersionUID = 6139255074631002393L;
public BufferStoppedException() { }
public BufferStoppedException(String message) {
super(message);
}
}
}