/*
* Copyright 2010 Red Hat, Inc.
* Red Hat 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.hornetq.core.protocol.stomp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Executor;
import org.hornetq.api.core.HornetQBuffer;
import org.hornetq.api.core.HornetQExceptionType;
import org.hornetq.api.core.Interceptor;
import org.hornetq.api.core.SimpleString;
import org.hornetq.api.core.client.HornetQClient;
import org.hornetq.api.core.management.ManagementHelper;
import org.hornetq.api.core.management.NotificationType;
import org.hornetq.core.journal.IOAsyncTask;
import org.hornetq.core.postoffice.BindingType;
import org.hornetq.core.server.HornetQMessageBundle;
import org.hornetq.core.server.HornetQServerLogger;
import org.hornetq.core.server.HornetQServer;
import org.hornetq.core.server.ServerSession;
import org.hornetq.core.server.impl.ServerMessageImpl;
import org.hornetq.core.server.management.ManagementService;
import org.hornetq.core.server.management.Notification;
import org.hornetq.core.server.management.NotificationListener;
import org.hornetq.spi.core.protocol.ConnectionEntry;
import org.hornetq.spi.core.protocol.ProtocolManager;
import org.hornetq.spi.core.protocol.RemotingConnection;
import org.hornetq.spi.core.remoting.Acceptor;
import org.hornetq.spi.core.remoting.Connection;
import org.hornetq.spi.core.security.HornetQSecurityManager;
import org.hornetq.utils.ConcurrentHashSet;
import org.hornetq.utils.TypedProperties;
import org.hornetq.utils.UUIDGenerator;
/**
* StompProtocolManager
*
* @author <a href="mailto:jmesnil@redhat.com">Jeff Mesnil</a>
*/
class StompProtocolManager implements ProtocolManager, NotificationListener
{
// Constants -----------------------------------------------------
// Attributes ----------------------------------------------------
private final HornetQServer server;
private final Executor executor;
private final Map<String, StompSession> transactedSessions = new HashMap<String, StompSession>();
// key => connection ID, value => Stomp session
private final Map<Object, StompSession> sessions = new HashMap<Object, StompSession>();
private final Set<String> destinations = new ConcurrentHashSet<String>();
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
public StompProtocolManager(final HornetQServer server, final List<Interceptor> interceptors)
{
this.server = server;
this.executor = server.getExecutorFactory().getExecutor();
ManagementService service = server.getManagementService();
if (service != null)
{
//allow management message to pass
destinations.add(service.getManagementAddress().toString());
service.addNotificationListener(this);
}
}
// ProtocolManager implementation --------------------------------
public ConnectionEntry createConnectionEntry(final Acceptor acceptorUsed, final Connection connection)
{
StompConnection conn = new StompConnection(acceptorUsed, connection, this);
// Note that STOMP 1.0 has no heartbeat, so if connection ttl is non zero, data must continue to be sent or connection
// will be timed out and closed!
String ttlStr = (String)acceptorUsed.getConfiguration().get("connection-ttl");
Long ttl = ttlStr == null ? null : Long.valueOf(ttlStr);
if (ttl != null)
{
if (ttl > 0)
{
return new ConnectionEntry(conn, null, System.currentTimeMillis(), ttl);
}
throw new IllegalStateException("Stomp Connection TTL cannot be negative : " + ttl);
}
ttl = server.getConfiguration().getConnectionTTLOverride();
if (ttl != -1)
{
return new ConnectionEntry(conn, null, System.currentTimeMillis(), ttl);
}
else
{
// Default to 1 minute - which is same as core protocol
return new ConnectionEntry(conn, null, System.currentTimeMillis(), 1 * 60 * 1000);
}
}
public void removeHandler(String name)
{
}
public int isReadyToHandle(HornetQBuffer buffer)
{
// This never gets called
return -1;
}
public void handleBuffer(final RemotingConnection connection, final HornetQBuffer buffer)
{
StompConnection conn = (StompConnection)connection;
conn.setDataReceived();
do
{
StompFrame request;
try
{
request = conn.decode(buffer);
}
catch (Exception e)
{
HornetQServerLogger.LOGGER.errorDecodingPacket(e);
return;
}
if (request == null)
{
break;
}
try
{
conn.handleFrame(request);
}
finally
{
server.getStorageManager().clearContext();
}
} while (conn.hasBytes());
}
// Public --------------------------------------------------------
public boolean send(final StompConnection connection, final StompFrame frame)
{
if (HornetQServerLogger.LOGGER.isTraceEnabled())
{
HornetQServerLogger.LOGGER.trace("sent " + frame);
}
synchronized (connection)
{
if (connection.isDestroyed())
{
HornetQServerLogger.LOGGER.connectionClosed(connection);
return false;
}
try
{
connection.physicalSend(frame);
}
catch (Exception e)
{
HornetQServerLogger.LOGGER.errorSendingFrame(e, frame);
return false;
}
return true;
}
}
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
// Private -------------------------------------------------------
public StompSession getSession(StompConnection connection) throws Exception
{
StompSession stompSession = sessions.get(connection.getID());
if (stompSession == null)
{
stompSession = new StompSession(connection, this, server.getStorageManager()
.newContext(server.getExecutorFactory().getExecutor()));
String name = UUIDGenerator.getInstance().generateStringUUID();
ServerSession session = server.createSession(name,
connection.getLogin(),
connection.getPasscode(),
HornetQClient.DEFAULT_MIN_LARGE_MESSAGE_SIZE,
connection,
true,
false,
false,
false,
null,
stompSession);
stompSession.setServerSession(session);
sessions.put(connection.getID(), stompSession);
}
server.getStorageManager().setContext(stompSession.getContext());
return stompSession;
}
public StompSession getTransactedSession(StompConnection connection, String txID) throws Exception
{
StompSession stompSession = transactedSessions.get(txID);
if (stompSession == null)
{
stompSession = new StompSession(connection, this, server.getStorageManager().newContext(executor));
String name = UUIDGenerator.getInstance().generateStringUUID();
ServerSession session = server.createSession(name,
connection.getLogin(),
connection.getPasscode(),
HornetQClient.DEFAULT_MIN_LARGE_MESSAGE_SIZE,
connection,
false,
false,
false,
false,
null,
stompSession);
stompSession.setServerSession(session);
transactedSessions.put(txID, stompSession);
}
server.getStorageManager().setContext(stompSession.getContext());
return stompSession;
}
public void cleanup(final StompConnection connection)
{
connection.setValid(false);
// Close the session outside of the lock on the StompConnection, otherwise it could dead lock
this.executor.execute(new Runnable()
{
public void run()
{
StompSession session = sessions.remove(connection.getID());
if (session != null)
{
try
{
session.getSession().rollback(true);
session.getSession().close(false);
}
catch (Exception e)
{
HornetQServerLogger.LOGGER.errorCleaningStompConn(e);
}
}
// removed the transacted session belonging to the connection
Iterator<Entry<String, StompSession>> iterator = transactedSessions.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry<String, StompSession> entry = iterator.next();
if (entry.getValue().getConnection() == connection)
{
ServerSession serverSession = entry.getValue().getSession();
try
{
serverSession.rollback(true);
serverSession.close(false);
}
catch (Exception e)
{
HornetQServerLogger.LOGGER.errorCleaningStompConn(e);
}
iterator.remove();
}
}
}
});
}
public void sendReply(final StompConnection connection, final StompFrame frame)
{
server.getStorageManager().afterCompleteOperations(new IOAsyncTask()
{
public void onError(final int errorCode, final String errorMessage)
{
HornetQServerLogger.LOGGER.errorProcessingIOCallback(errorCode, errorMessage);
HornetQStompException e = new HornetQStompException("Error sending reply",
HornetQExceptionType.createException(errorCode, errorMessage));
StompFrame error = e.getFrame();
send(connection, error);
}
public void done()
{
send(connection, frame);
}
});
}
public String getSupportedVersionsAsString()
{
return "v1.0 v1.1 v1.2";
}
public String getVirtualHostName()
{
return "hornetq";
}
public boolean validateUser(String login, String passcode)
{
boolean validated = true;
HornetQSecurityManager sm = server.getSecurityManager();
if (sm != null && server.getConfiguration().isSecurityEnabled())
{
validated = sm.validateUser(login, passcode);
}
return validated;
}
public ServerMessageImpl createServerMessage()
{
return new ServerMessageImpl(server.getStorageManager().generateUniqueID(), 512);
}
public void commitTransaction(StompConnection connection, String txID) throws Exception
{
StompSession session = getTransactedSession(connection, txID);
if (session == null)
{
throw new HornetQStompException("No transaction started: " + txID);
}
transactedSessions.remove(txID);
session.getSession().commit();
}
public void abortTransaction(StompConnection connection, String txID) throws Exception
{
StompSession session = getTransactedSession(connection, txID);
if (session == null)
{
throw new HornetQStompException("No transaction started: " + txID);
}
transactedSessions.remove(txID);
session.getSession().rollback(false);
}
// Inner classes -------------------------------------------------
public void createSubscription(StompConnection connection,
String subscriptionID, String durableSubscriptionName,
String destination, String selector, String ack, boolean noLocal) throws Exception
{
StompSession stompSession = getSession(connection);
stompSession.setNoLocal(noLocal);
if (stompSession.containsSubscription(subscriptionID))
{
throw new HornetQStompException("There already is a subscription for: " + subscriptionID +
". Either use unique subscription IDs or do not create multiple subscriptions for the same destination");
}
long consumerID = server.getStorageManager().generateUniqueID();
String clientID = (connection.getClientID() != null) ? connection.getClientID() : null;
stompSession.addSubscription(consumerID,
subscriptionID,
clientID,
durableSubscriptionName,
destination,
selector,
ack);
}
public void unsubscribe(StompConnection connection,
String subscriptionID) throws Exception
{
StompSession stompSession = getSession(connection);
boolean unsubscribed = stompSession.unsubscribe(subscriptionID);
if (!unsubscribed)
{
throw new HornetQStompException("Cannot unsubscribe as no subscription exists for id: " + subscriptionID);
}
}
public void acknowledge(StompConnection connection, String messageID, String subscriptionID) throws Exception
{
StompSession stompSession = getSession(connection);
stompSession.acknowledge(messageID, subscriptionID);
}
public void beginTransaction(StompConnection connection, String txID) throws Exception
{
HornetQServerLogger.LOGGER.stompBeginTX(txID);
if (transactedSessions.containsKey(txID))
{
HornetQServerLogger.LOGGER.stompErrorTXExists(txID);
throw new HornetQStompException(connection, "Transaction already started: " + txID);
}
// create the transacted session
getTransactedSession(connection, txID);
}
public boolean destinationExists(String destination)
{
return destinations.contains(destination);
}
@Override
public void onNotification(Notification notification)
{
NotificationType type = notification.getType();
switch (type)
{
case BINDING_ADDED:
{
TypedProperties props = notification.getProperties();
if (!props.containsProperty(ManagementHelper.HDR_BINDING_TYPE))
{
throw HornetQMessageBundle.BUNDLE.bindingTypeNotSpecified();
}
Integer bindingType = props.getIntProperty(ManagementHelper.HDR_BINDING_TYPE);
if (bindingType == BindingType.DIVERT_INDEX)
{
return;
}
SimpleString address = props.getSimpleStringProperty(ManagementHelper.HDR_ADDRESS);
destinations.add(address.toString());
break;
}
default:
//ignore all others
break;
}
}
}