/*
* Copyright (C) 2012 Red Hat, Inc. and/or its affiliates.
*
* 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.jboss.errai.bus.server;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.errai.bus.client.api.QueueSession;
import org.jboss.errai.bus.client.api.messaging.Message;
import org.jboss.errai.bus.server.api.MessageQueue;
import org.jboss.errai.bus.server.api.QueueActivationCallback;
import org.jboss.errai.bus.server.io.BufferDeliveryHandler;
import org.jboss.errai.bus.server.io.Buffered;
import org.jboss.errai.bus.server.io.ByteWriteAdapter;
import org.jboss.errai.bus.server.io.Cleanable;
import org.jboss.errai.bus.server.io.DirectChannel;
import org.jboss.errai.bus.server.io.MessageDeliveryHandler;
import org.jboss.errai.bus.server.io.Wakeable;
import org.jboss.errai.bus.server.io.buffers.Buffer;
import org.jboss.errai.bus.server.io.buffers.BufferColor;
import org.jboss.errai.bus.server.io.buffers.TransmissionBuffer;
import org.slf4j.Logger;
/**
* A message queue is keeps track of which messages need to be sent outbound. It keeps track of the amount of messages
* that can be stored, transmitted and those which timeout. The <tt>MessageQueue</tt> is implemented using a
* {@link java.util.concurrent.LinkedBlockingQueue} to store the messages, and a <tt>ServerMessageBus</tt> to send the
* messages.
*/
public class MessageQueueImpl implements MessageQueue {
private final QueueSession session;
private boolean initLock = true;
private boolean queueRunning = true;
private volatile long lastTransmission = System.currentTimeMillis();
private volatile boolean pagedOut = false;
private volatile MessageDeliveryHandler deliveryHandler = BufferDeliveryHandler.getInstance();
private volatile QueueActivationCallback activationCallback;
private volatile long timeout;
private final TransmissionBuffer buffer;
private final BufferColor bufferColor;
private final Object activationLock = new Object();
private final Object pageLock = new Object();
private final AtomicInteger messageCount = new AtomicInteger();
private static final Logger log = getLogger(MessageQueueImpl.class);
public MessageQueueImpl(final TransmissionBuffer buffer, final QueueSession session, final int timeoutSecs) {
this.buffer = buffer;
this.session = session;
this.bufferColor = BufferColor.getNewColorFromHead(buffer);
this.timeout = (timeoutSecs * 1000);
}
@Override
public boolean poll(final ByteWriteAdapter stream) throws IOException {
if (!queueRunning) {
throw new QueueUnavailableException("queue is not available");
}
if (deliveryHandler instanceof Buffered) {
return ((Buffered) deliveryHandler).copyFromBuffer(this, stream);
}
else {
// this can happen during the hand off to WebSockets.
log.debug("call to poll() when DeliveryHandler does not implement Buffered.");
}
return false;
}
@Override
public boolean poll(final java.util.concurrent.TimeUnit timeUnit, final int time, final ByteWriteAdapter stream) throws IOException {
if (!queueRunning) {
throw new QueueUnavailableException("queue is not available");
}
if (deliveryHandler instanceof Buffered) {
return ((Buffered) deliveryHandler).copyFromBuffer(timeUnit, time, this, stream);
}
else {
// this can happen during the hand off to WebSockets.
log.debug("call to poll() when DeliveryHandler does not implement Buffered.");
}
return false;
}
/**
* Inserts the specified message into the queue, and returns true if it was successful
*
* @param message
* - the message to insert into the queue
*
* @return true if insertion was successful
*/
@Override
public boolean offer(final Message message) throws IOException {
if (!queueRunning) {
throw new QueueUnavailableException("queue is not available");
}
return deliveryHandler.deliver(this, message);
}
@Override
public long getCurrentBufferSequenceNumber() {
return bufferColor.getSequence().get();
}
@Override
public void wake() {
if (!queueRunning) return;
try {
if (deliveryHandler instanceof Wakeable) {
((Wakeable) deliveryHandler).onWake(this);
}
else {
deliveryHandler.noop(this);
}
fireActivationCallback();
}
catch (Throwable e) {
log.debug("unable to wake queue: " + session.getSessionId());
stopQueue();
}
}
/**
* Sets the activation callback function which is called when the queue is scheduled for activation
*
* @param activationCallback
* - new activation callback function
*/
@Override
public void setActivationCallback(final QueueActivationCallback activationCallback) {
synchronized (activationLock) {
this.activationCallback = activationCallback;
}
}
@Override
public void fireActivationCallback() {
synchronized (activationLock) {
if (activationCallback != null) {
activationCallback.activate(this);
}
}
}
/**
* Returns the current activation callback function
*
* @return the current activation callback function
*/
@Override
public QueueActivationCallback getActivationCallback() {
return activationCallback;
}
@Override
public QueueSession getSession() {
return session;
}
/**
* Returns true if the queue is not running, or it has timed out
*
* @return true if the queue is stale
*/
@Override
public boolean isStale() {
if (!queueRunning) {
return true;
}
else {
return !isDirectChannelOpen() && (((System.currentTimeMillis() - lastTransmission) > timeout));
}
}
private boolean isDirectChannelOpen() {
return deliveryHandler instanceof DirectChannel && ((DirectChannel) deliveryHandler).isConnected();
}
@Override
public boolean isInitialized() {
return !initLock;
}
@Override
public boolean messagesWaiting() {
return messageCount.intValue() > 0;
}
/**
* Fakes a transmission, shows life with a heartbeat
*/
@Override
public void heartBeat() {
lastTransmission = System.currentTimeMillis();
}
@Override
public void finishInit() {
initLock = false;
}
@Override
public boolean isPaged() {
return pagedOut;
}
@Override
public void setPaged(final boolean pagedOut) {
this.pagedOut = pagedOut;
}
@Override
public void discard() {
queueRunning = false;
if (deliveryHandler instanceof Cleanable) {
((Cleanable) deliveryHandler).clean(this);
}
}
/**
* Stops the queue, closes it on the bus and clears it completely
*/
@Override
public void stopQueue() {
try {
queueRunning = false;
/**
* we write a single byte to the buffer, with the color for this queue. this is to knock any
* waiting thread loose and return it to the work pool.
*/
buffer.write(1, new ByteArrayInputStream(new byte[]{-1}), bufferColor);
}
catch (Exception e) {
throw new RuntimeException("error trying to stop queue");
}
}
@Override
public Object getActivationLock() {
return activationLock;
}
@Override
public Object getPageLock() {
return pageLock;
}
@Override
public MessageDeliveryHandler getDeliveryHandler() {
return deliveryHandler;
}
@Override
public void setDeliveryHandler(final MessageDeliveryHandler handler) {
this.deliveryHandler = handler;
}
@Override
public void setDeliveryHandlerToDefault() {
this.deliveryHandler = BufferDeliveryHandler.getInstance();
}
@Override
public BufferColor getBufferColor() {
return bufferColor;
}
@Override
public Buffer getBuffer() {
return buffer;
}
@Override
public int incrementMessageCount() {
return messageCount.incrementAndGet();
}
@Override
public void resetMessageCount() {
messageCount.set(0);
}
@Override
public long getLastTransmissionTime() {
return lastTransmission;
}
@Override
public void setTimeout(final long timeout) {
this.timeout = timeout;
}
@Override
public String toString() {
return "MessageQueueImpl{" +
"session=" + session +
'}';
}
}