diff options
Diffstat (limited to 'java/broker/src')
182 files changed, 19633 insertions, 0 deletions
diff --git a/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java new file mode 100644 index 0000000000..e3d9bc31ff --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java @@ -0,0 +1,565 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.ack.UnacknowledgedMessage; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl; +import org.apache.qpid.server.exchange.MessageRouter; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MessageHandleFactory; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.txn.LocalTransactionalContext; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.txn.TxnBuffer; +import org.apache.qpid.server.txn.NonTransactionalContext; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +public class AMQChannel +{ + public static final int DEFAULT_PREFETCH = 5000; + + private static final Logger _log = Logger.getLogger(AMQChannel.class); + + private final int _channelId; + + //private boolean _transactional; + + private long _prefetchCount; + + /** + * The delivery tag is unique per channel. This is pre-incremented before putting into the deliver frame so that + * value of this represents the <b>last</b> tag sent out + */ + private AtomicLong _deliveryTag = new AtomicLong(0); + + /** + * A channel has a default queue (the last declared) that is used when no queue name is + * explictily set + */ + private AMQQueue _defaultQueue; + + /** + * This tag is unique per subscription to a queue. The server returns this in response to a + * basic.consume request. + */ + private int _consumerTag; + + /** + * The current message - which may be partial in the sense that not all frames have been received yet - + * which has been received by this channel. As the frames are received the message gets updated and once all + * frames have been received the message can then be routed. + */ + private AMQMessage _currentMessage; + + /** + * Maps from consumer tag to queue instance. Allows us to unsubscribe from a queue. + */ + private final Map<String, AMQQueue> _consumerTag2QueueMap = new TreeMap<String, AMQQueue>(); + + private final MessageStore _messageStore; + + //private Map<Long, UnacknowledgedMessage> _unacknowledgedMessageMap = new LinkedHashMap<Long, UnacknowledgedMessage>(DEFAULT_PREFETCH); + private UnacknowledgedMessageMap _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(DEFAULT_PREFETCH); + + private final AtomicBoolean _suspended = new AtomicBoolean(false); + + private final MessageRouter _exchanges; + + private TransactionalContext _txnContext; + + private final List<RequiredDeliveryException> _returnMessages = new LinkedList<RequiredDeliveryException>(); + + private MessageHandleFactory _messageHandleFactory = new MessageHandleFactory(); + + public AMQChannel(int channelId, MessageStore messageStore, MessageRouter exchanges) + throws AMQException + { + _channelId = channelId; + _prefetchCount = DEFAULT_PREFETCH; + _messageStore = messageStore; + _exchanges = exchanges; + // TODO: fix me to pass in the txn context or at least have a factory for it + _txnContext = new NonTransactionalContext(_messageStore, this, _returnMessages); + } + + /** + * Sets this channel to be part of a local transaction + */ + public void setLocalTransactional() + { + _txnContext = new LocalTransactionalContext(_messageStore, new TxnBuffer(_messageStore), _returnMessages); + } + + public int getChannelId() + { + return _channelId; + } + + public long getPrefetchCount() + { + return _prefetchCount; + } + + public void setPrefetchCount(long prefetchCount) + { + _prefetchCount = prefetchCount; + } + + public void setPublishFrame(BasicPublishBody publishBody, AMQProtocolSession publisher) throws AMQException + { + _currentMessage = new AMQMessage(_messageStore.getNewMessageId(), publishBody, _txnContext); + // TODO: used in clustering only I think (RG) + //_currentMessage.setPublisher(publisher); + } + + public void publishContentHeader(ContentHeaderBody contentHeaderBody) + throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content header without previously receiving a BasicPublish frame"); + } + else + { + _currentMessage.setContentHeaderBody(contentHeaderBody); + routeCurrentMessage(); + _currentMessage.routingComplete(_messageStore, _messageHandleFactory); + + // check and deliver if header says body length is zero + if (contentHeaderBody.bodySize == 0) + { + _currentMessage = null; + } + } + } + + public void publishContentBody(ContentBody contentBody) + throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content body without previously receiving a JmsPublishBody"); + } + + // returns true iff the message was delivered (i.e. if all data was + // received + try + { + if (_currentMessage.addContentBodyFrame(contentBody)) + { + _currentMessage = null; + } + } + catch (AMQException e) + { + // we want to make sure we don't keep a reference to the message in the + // event of an error + _currentMessage = null; + throw e; + } + } + + protected void routeCurrentMessage() throws AMQException + { + _exchanges.routeContent(_currentMessage); + } + + /*protected void routeCurrentMessage2() throws AMQException + { + if (_transactional) + { + // don't create a transaction unless needed + if (_currentMessage.isPersistent()) + { + _txnBuffer.containsPersistentChanges(); + } + + //A publication will result in the enlisting of several + //TxnOps. The first is an op that will store the message. + //Following that (and ordering is important), an op will + //be added for every queue onto which the message is + //enqueued. Finally a cleanup op will be added to decrement + //the reference associated with the routing. + Store storeOp = new Store(_currentMessage); + _txnBuffer.enlist(storeOp); + _currentMessage.setTxnBuffer(_txnBuffer); + try + { + _exchanges.routeContent(_currentMessage); + _txnBuffer.enlist(new Cleanup(_currentMessage)); + } + catch (RequiredDeliveryException e) + { + //Can only be due to the mandatory flag, as no attempt + //has yet been made to deliver the message. The + //message will thus not have been delivered to any + //queue so we can return the message (without killing + //the transaction) and for efficiency remove the store + //operation from the buffer. + _txnBuffer.cancel(storeOp); + throw e; + } + finally + { + _currentMessage = null; + } + } + else + { + try + { + _exchanges.routeContent(_currentMessage); + //following check implements the functionality + //required by the 'immediate' flag: + _currentMessage.checkDeliveredToConsumer(); + } + finally + { + _currentMessage.decrementReference(); + _currentMessage = null; + } + } + } */ + + public long getNextDeliveryTag() + { + return _deliveryTag.incrementAndGet(); + } + + public int getNextConsumerTag() + { + return ++_consumerTag; + } + + /** + * Subscribe to a queue. We register all subscriptions in the channel so that + * if the channel is closed we can clean up all subscriptions, even if the + * client does not explicitly unsubscribe from all queues. + * + * @param tag the tag chosen by the client (if null, server will generate one) + * @param queue the queue to subscribe to + * @param session the protocol session of the subscriber + * @return the consumer tag. This is returned to the subscriber and used in + * subsequent unsubscribe requests + * @throws ConsumerTagNotUniqueException if the tag is not unique + * @throws AMQException if something goes wrong + */ + public String subscribeToQueue(String tag, AMQQueue queue, AMQProtocolSession session, boolean acks) + throws AMQException, ConsumerTagNotUniqueException + { + if (tag == null) + { + tag = "sgen_" + getNextConsumerTag(); + } + if (_consumerTag2QueueMap.containsKey(tag)) + { + throw new ConsumerTagNotUniqueException(); + } + + queue.registerProtocolSession(session, _channelId, tag, acks); + _consumerTag2QueueMap.put(tag, queue); + return tag; + } + + + public void unsubscribeConsumer(AMQProtocolSession session, String consumerTag) throws AMQException + { + AMQQueue q = _consumerTag2QueueMap.remove(consumerTag); + if (q != null) + { + q.unregisterProtocolSession(session, _channelId, consumerTag); + } + else + { + throw new AMQException(_log, "Consumer tag " + consumerTag + " not known to channel " + + _channelId); + } + } + + /** + * Called from the protocol session to close this channel and clean up. + * + * @throws AMQException if there is an error during closure + */ + public void close(AMQProtocolSession session) throws AMQException + { + /*if (_transactional) + { + synchronized (_txnBuffer) + { + _txnBuffer.rollback();//releases messages + } + } */ + _txnContext.rollback(); + unsubscribeAllConsumers(session); + requeue(); + } + + private void unsubscribeAllConsumers(AMQProtocolSession session) throws AMQException + { + _log.info("Unsubscribing all consumers on channel " + toString()); + for (Map.Entry<String, AMQQueue> me : _consumerTag2QueueMap.entrySet()) + { + me.getValue().unregisterProtocolSession(session, _channelId, me.getKey()); + } + _consumerTag2QueueMap.clear(); + } + + /** + * Add a message to the channel-based list of unacknowledged messages + * + * @param message the message that was delivered + * @param deliveryTag the delivery tag used when delivering the message (see protocol spec for description of + * the delivery tag) + * @param queue the queue from which the message was delivered + */ + public void addUnacknowledgedMessage(AMQMessage message, long deliveryTag, String consumerTag, AMQQueue queue) + { + _unacknowledgedMessageMap.add(deliveryTag, new UnacknowledgedMessage(queue, message, consumerTag, deliveryTag)); + checkSuspension(); + } + + /** + * Called to attempt re-enqueue all outstanding unacknowledged messages on the channel. + * May result in delivery to this same channel or to other subscribers. + * @throws org.apache.qpid.AMQException if the requeue fails + */ + public void requeue() throws AMQException + { + // we must create a new map since all the messages will get a new delivery tag when they are redelivered + Collection<UnacknowledgedMessage> messagesToBeDelivered = _unacknowledgedMessageMap.cancelAllMessages(); + + for (UnacknowledgedMessage unacked : messagesToBeDelivered) + { + if (unacked.queue != null) + { + _txnContext.deliver(unacked.message, unacked.queue); + } + } + } + + /** + * Called to resend all outstanding unacknowledged messages to this same channel. + */ + public void resend(final AMQProtocolSession session) throws AMQException + { + _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor() + { + public boolean callback(UnacknowledgedMessage message) throws AMQException + { + long deliveryTag = message.deliveryTag; + String consumerTag = message.consumerTag; + AMQMessage msg = message.message; + + msg.writeDeliver(session, _channelId, deliveryTag, consumerTag); + // false means continue processing + return false; + } + + public void visitComplete() + { + } + }); + } + + /** + * Callback indicating that a queue has been deleted. We must update the structure of unacknowledged + * messages to remove the queue reference and also decrement any message reference counts, without + * actually removing the item since we may get an ack for a delivery tag that was generated from the + * deleted queue. + * + * @param queue the queue that has been deleted + * @throws org.apache.qpid.AMQException if there is an error processing the unacked messages + */ + public void queueDeleted(final AMQQueue queue) throws AMQException + { + _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor() + { + public boolean callback(UnacknowledgedMessage message) throws AMQException + { + if (message.queue == queue) + { + message.queue = null; + try + { + message.message.decrementReference(); + } + catch (AMQException e) + { + _log.error("Error decrementing ref count on message " + message.message.getMessageId() + ": " + + e, e); + } + } + return false; + } + + public void visitComplete() + { + } + }); + } + + /** + * Acknowledge one or more messages. + * + * @param deliveryTag the last delivery tag + * @param multiple if true will acknowledge all messages up to an including the delivery tag. if false only + * acknowledges the single message specified by the delivery tag + * @throws AMQException if the delivery tag is unknown (e.g. not outstanding) on this channel + */ + public void acknowledgeMessage(long deliveryTag, boolean multiple) throws AMQException + { + _unacknowledgedMessageMap.acknowledgeMessage(deliveryTag, multiple, _txnContext); + checkSuspension(); + /*if (_transactional) + { + //check that the tag exists to give early failure + if (!multiple || deliveryTag > 0) + { + checkAck(deliveryTag); + } + //we use a single txn op for all acks and update this op + //as new acks come in. If this is the first ack in the txn + //we will need to create and enlist the op. + if (_ackOp == null) + { + _ackOp = new TxAck(new AckMap()); + _txnBuffer.enlist(_ackOp); + } + //update the op to include this ack request + if(multiple && deliveryTag == 0) + { + synchronized(_unacknowledgedMessageMapLock) + { + //if have signalled to ack all, that refers only + //to all at this time + _ackOp.update(_lastDeliveryTag, multiple); + } + } + else + { + _ackOp.update(deliveryTag, multiple); + } + } + else + { + handleAcknowledgement(deliveryTag, multiple); + } */ + } + + /** + * Used only for testing purposes. + * + * @return the map of unacknowledged messages + */ + public UnacknowledgedMessageMap getUnacknowledgedMessageMap() + { + return _unacknowledgedMessageMap; + } + + private void checkSuspension() + { + boolean suspend = _unacknowledgedMessageMap.size() >= _prefetchCount; + setSuspended(suspend); + } + + public void setSuspended(boolean suspended) + { + boolean wasSuspended = _suspended.getAndSet(suspended); + if (wasSuspended != suspended) + { + if (wasSuspended) + { + _log.debug("Unsuspending channel " + this); + //may need to deliver queued messages + for (AMQQueue q : _consumerTag2QueueMap.values()) + { + q.deliverAsync(); + } + } + else + { + _log.debug("Suspending channel " + this); + } + } + } + + public boolean isSuspended() + { + return _suspended.get(); + } + + public void commit() throws AMQException + { + _txnContext.commit(); + } + + public void rollback() throws AMQException + { + _txnContext.rollback(); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(30); + sb.append("Channel: id ").append(_channelId).append(", transaction context: ").append(_txnContext); + sb.append(", prefetch count: ").append(_prefetchCount); + return sb.toString(); + } + + public ObjectName getObjectName() + throws MalformedObjectNameException + { + StringBuilder sb = new StringBuilder(30); + sb.append("Channel:id=").append(_channelId); + return new ObjectName(sb.toString()); + } + + public void setDefaultQueue(AMQQueue queue) + { + _defaultQueue = queue; + } + + public AMQQueue getDefaultQueue() + { + return _defaultQueue; + } + + public void processReturns(AMQProtocolSession session) throws AMQException + { + for (RequiredDeliveryException bouncedMessage : _returnMessages) + { + AMQMessage message = bouncedMessage.getAMQMessage(); + message.writeReturn(session, _channelId, bouncedMessage.getReplyCode(), bouncedMessage.getMessage()); + } + _returnMessages.clear(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java b/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java new file mode 100644 index 0000000000..6a7e54bc45 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java @@ -0,0 +1,22 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server; + +public class ConsumerTagNotUniqueException extends Exception +{ +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/Main.java b/java/broker/src/main/java/org/apache/qpid/server/Main.java new file mode 100644 index 0000000000..13dc5fbca6 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/Main.java @@ -0,0 +1,613 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server; + +import org.apache.qpid.framing.ProtocolVersionList; +import org.apache.qpid.pool.ReadWriteThreadModel; +import org.apache.qpid.server.protocol.AMQPFastProtocolHandler; +import org.apache.qpid.server.protocol.AMQPProtocolProvider; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.transport.ConnectorConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.management.*; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.AMQException; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.commons.cli.*; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.DOMConfigurator; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.transport.socket.nio.SocketAcceptorConfig; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.StringTokenizer; +import java.util.Collection; +import java.util.List; + +/** + * Main entry point for AMQPD. + * + */ +public class Main implements ProtocolVersionList +{ + private static final Logger _logger = Logger.getLogger(Main.class); + + private static final String DEFAULT_CONFIG_FILE = "etc/config.xml"; + + private static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml"; + + protected static class InitException extends Exception + { + InitException(String msg) + { + super(msg); + } + } + + protected final Options options = new Options(); + protected CommandLine commandLine; + + protected Main(String[] args) + { + setOptions(options); + if (parseCommandline(args)) + { + execute(); + } + } + + protected boolean parseCommandline(String[] args) + { + try + { + commandLine = new PosixParser().parse(options, args); + return true; + } + catch (ParseException e) + { + System.err.println("Error: " + e.getMessage()); + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", options, true); + return false; + } + } + + protected void setOptions(Options options) + { + Option help = new Option("h", "help", false, "print this message"); + Option version = new Option("v", "version", false, "print the version information and exit"); + Option configFile = OptionBuilder.withArgName("file").hasArg().withDescription("use given configuration file"). + withLongOpt("config").create("c"); + Option port = OptionBuilder.withArgName("port").hasArg().withDescription("listen on the specified port. Overrides any value in the config file"). + withLongOpt("port").create("p"); + Option bind = OptionBuilder.withArgName("bind").hasArg().withDescription("bind to the specified address. Overrides any value in the config file"). + withLongOpt("bind").create("b"); + Option logconfig = OptionBuilder.withArgName("logconfig").hasArg().withDescription("use the specified log4j xml configuration file. By " + + "default looks for a file named " + DEFAULT_LOG_CONFIG_FILENAME + " in the same directory as the configuration file"). + withLongOpt("logconfig").create("l"); + Option logwatchconfig = OptionBuilder.withArgName("logwatch").hasArg().withDescription("monitor the log file configuration file for changes. Units are seconds. " + + "Zero means do not check for changes.").withLongOpt("logwatch").create("w"); + + options.addOption(help); + options.addOption(version); + options.addOption(configFile); + options.addOption(logconfig); + options.addOption(logwatchconfig); + options.addOption(port); + options.addOption(bind); + } + + protected void execute() + { + // note this understands either --help or -h. If an option only has a long name you can use that but if + // an option has a short name and a long name you must use the short name here. + if (commandLine.hasOption("h")) + { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", options, true); + } + else if (commandLine.hasOption("v")) + { + String ver = "Qpid 0.9.0.0"; + String protocol = "AMQP version(s) [major.minor]: "; + for (int i=0; i<pv.length; i++) + { + if (i > 0) + protocol += ", "; + protocol += pv[i][PROTOCOL_MAJOR] + "." + pv[i][PROTOCOL_MINOR]; + } + System.out.println(ver + " (" + protocol + ")"); + } + else + { + try + { + startup(); + } + catch (InitException e) + { + System.out.println(e.getMessage()); + } + catch (ConfigurationException e) + { + System.out.println("Error configuring message broker: " + e); + e.printStackTrace(); + } + catch (Exception e) + { + System.out.println("Error intialising message broker: " + e); + e.printStackTrace(); + } + } + } + + + protected void startup() throws InitException, ConfigurationException, Exception + { + final String QpidHome = System.getProperty("QPID_HOME"); + final File defaultConfigFile = new File(QpidHome, DEFAULT_CONFIG_FILE); + final File configFile = new File(commandLine.getOptionValue("c", defaultConfigFile.getPath())); + if (!configFile.exists()) + { + String error = "File " + configFile + " could not be found. Check the file exists and is readable."; + + if (QpidHome == null) + { + error = error + "\nNote: Qpid_HOME is not set."; + } + + throw new InitException(error); + } + else + { + System.out.println("Using configuration file " + configFile.getAbsolutePath()); + } + + String logConfig = commandLine.getOptionValue("l"); + String logWatchConfig = commandLine.getOptionValue("w", "0"); + if (logConfig != null) + { + File logConfigFile = new File(logConfig); + configureLogging(logConfigFile, logWatchConfig); + } + else + { + File configFileDirectory = configFile.getParentFile(); + File logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME); + configureLogging(logConfigFile, logWatchConfig); + } + + ApplicationRegistry.initialise(new ConfigurationFileApplicationRegistry(configFile)); + + _logger.info("Starting Qpid.AMQP broker"); + + ConnectorConfiguration connectorConfig = ApplicationRegistry.getInstance(). + getConfiguredObject(ConnectorConfiguration.class); + + ByteBuffer.setUseDirectBuffers(connectorConfig.enableDirectBuffers); + + // the MINA default is currently to use the pooled allocator although this may change in future + // once more testing of the performance of the simple allocator has been done + if (!connectorConfig.enablePooledAllocator) + { + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + } + + int port = connectorConfig.port; + + String portStr = commandLine.getOptionValue("p"); + if (portStr != null) + { + try + { + port = Integer.parseInt(portStr); + } + catch (NumberFormatException e) + { + throw new InitException("Invalid port: " + portStr); + } + } + + String VIRTUAL_HOSTS = "virtualhosts"; + + Object virtualHosts = ApplicationRegistry.getInstance().getConfiguration().getProperty(VIRTUAL_HOSTS); + + if (virtualHosts != null) + { + if (virtualHosts instanceof Collection) + { + int totalVHosts = ((Collection) virtualHosts).size(); + for (int vhost = 0; vhost < totalVHosts; vhost++) + { + setupVirtualHosts(configFile.getParent() , (String)((List)virtualHosts).get(vhost)); + } + } + else + { + setupVirtualHosts(configFile.getParent() , (String)virtualHosts); + } + } + bind(port, connectorConfig); + + createAndRegisterBrokerMBean(); + } + + protected void setupVirtualHosts(String configFileParent, String configFilePath) throws ConfigurationException, AMQException, URLSyntaxException + { + String configVar = "${conf}"; + + if (configFilePath.startsWith(configVar)) + { + configFilePath = configFileParent + configFilePath.substring(configVar.length()); + } + + if (configFilePath.indexOf(".xml") != -1 ) + { + VirtualHostConfiguration vHostConfig = new VirtualHostConfiguration(configFilePath); + vHostConfig.performBindings(); + } + else + { + // the virtualhosts value is a path. Search it for XML files. + + File virtualHostDir = new File(configFilePath); + + String[] fileNames = virtualHostDir.list(); + + for (int each=0; each < fileNames.length; each++) + { + if (fileNames[each].endsWith(".xml")) + { + VirtualHostConfiguration vHostConfig = new VirtualHostConfiguration(configFilePath+"/"+fileNames[each]); + vHostConfig.performBindings(); + } + } + } + } + + protected void bind(int port, ConnectorConfiguration connectorConfig) + { + String bindAddr = commandLine.getOptionValue("b"); + if (bindAddr == null) + { + bindAddr = connectorConfig.bindAddress; + } + + try + { + //IoAcceptor acceptor = new SocketAcceptor(connectorConfig.processors); + IoAcceptor acceptor = connectorConfig.createAcceptor(); + SocketAcceptorConfig sconfig = (SocketAcceptorConfig) acceptor.getDefaultConfig(); + SocketSessionConfig sc = (SocketSessionConfig) sconfig.getSessionConfig(); + + sc.setReceiveBufferSize(connectorConfig.socketReceiveBufferSize); + sc.setSendBufferSize(connectorConfig.socketWriteBuferSize); + sc.setTcpNoDelay(connectorConfig.tcpNoDelay); + + // if we do not use the executor pool threading model we get the default leader follower + // implementation provided by MINA + if (connectorConfig.enableExecutorPool) + { + sconfig.setThreadModel(new ReadWriteThreadModel()); + } + + if (connectorConfig.enableNonSSL) + { + AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler(); + InetSocketAddress bindAddress; + if (bindAddr.equals("wildcard")) + { + bindAddress = new InetSocketAddress(port); + } + else + { + bindAddress = new InetSocketAddress(InetAddress.getByAddress(parseIP(bindAddr)), port); + } + acceptor.bind(bindAddress, handler, sconfig); + _logger.info("Qpid.AMQP listening on non-SSL address " + bindAddress); + } + + if (connectorConfig.enableSSL) + { + AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler(); + handler.setUseSSL(true); + try + { + acceptor.bind(new InetSocketAddress(connectorConfig.sslPort), + handler, sconfig); + _logger.info("Qpid.AMQP listening on SSL port " + connectorConfig.sslPort); + } + catch (IOException e) + { + _logger.error("Unable to listen on SSL port: " + e, e); + } + } + } + catch (Exception e) + { + _logger.error("Unable to bind service to registry: " + e, e); + } + } + + public static void main(String[] args) + { + + new Main(args); + } + + private byte[] parseIP(String address) throws Exception + { + StringTokenizer tokenizer = new StringTokenizer(address, "."); + byte[] ip = new byte[4]; + int index = 0; + while (tokenizer.hasMoreTokens()) + { + String token = tokenizer.nextToken(); + try + { + ip[index++] = Byte.parseByte(token); + } + catch (NumberFormatException e) + { + throw new Exception("Error parsing IP address: " + address, e); + } + } + if (index != 4) + { + throw new Exception("Invalid IP address: " + address); + } + return ip; + } + + private void configureLogging(File logConfigFile, String logWatchConfig) + { + int logWatchTime = 0; + try + { + logWatchTime = Integer.parseInt(logWatchConfig); + } + catch (NumberFormatException e) + { + System.err.println("Log watch configuration value of " + logWatchConfig + " is invalid. Must be " + + "a non-negative integer. Using default of zero (no watching configured"); + } + if (logConfigFile.exists() && logConfigFile.canRead()) + { + System.out.println("Configuring logger using configuration file " + logConfigFile.getAbsolutePath()); + if (logWatchTime > 0) + { + System.out.println("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every " + + logWatchTime + " seconds"); + // log4j expects the watch interval in milliseconds + DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), logWatchTime * 1000); + } + else + { + DOMConfigurator.configure(logConfigFile.getAbsolutePath()); + } + } + else + { + System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath()); + System.err.println("Using basic log4j configuration"); + BasicConfigurator.configure(); + } + } + + private void createAndRegisterBrokerMBean() throws AMQException + { + try + { + new AMQBrokerManager().register(); + } + catch (NotCompliantMBeanException ex) + { + throw new AMQException("Exception occured in creating AMQBrokerManager MBean."); + } + } + + /** + * AMQPBrokerMBean implements the broker management interface and exposes the + * Broker level management features like creating and deleting exchanges and queue. + */ + @MBeanDescription("This MBean exposes the broker level management features") + private final class AMQBrokerManager extends AMQManagedObject + implements ManagedBroker + { + private final QueueRegistry _queueRegistry; + private final ExchangeRegistry _exchangeRegistry; + private final ExchangeFactory _exchangeFactory; + private final MessageStore _messageStore; + + @MBeanConstructor("Creates the Broker Manager MBean") + protected AMQBrokerManager() throws NotCompliantMBeanException + { + super(ManagedBroker.class, ManagedBroker.TYPE); + + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + _queueRegistry = appRegistry.getQueueRegistry(); + _exchangeRegistry = appRegistry.getExchangeRegistry(); + _exchangeFactory = ApplicationRegistry.getInstance().getExchangeFactory(); + _messageStore = ApplicationRegistry.getInstance().getMessageStore(); + } + + public String getObjectInstanceName() + { + return this.getClass().getName(); + } + + /** + * Creates new exchange and registers it with the registry. + * @param exchangeName + * @param type + * @param durable + * @param autoDelete + * @throws JMException + */ + public void createNewExchange(String exchangeName, + String type, + boolean durable, + boolean autoDelete) + throws JMException + { + try + { + synchronized(_exchangeRegistry) + { + Exchange exchange = _exchangeRegistry.getExchange(exchangeName); + + if (exchange == null) + { + exchange = _exchangeFactory.createExchange(exchangeName, + type, //eg direct + durable, + autoDelete, + 0); //ticket no + _exchangeRegistry.registerExchange(exchange); + } + else + { + throw new JMException("The exchange \"" + exchangeName + "\" already exists."); + } + } + } + catch(AMQException ex) + { + _logger.error("Error in creating exchange " + exchangeName, ex); + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * Unregisters the exchange from registry. + * @param exchangeName + * @throws JMException + */ + public void unregisterExchange(String exchangeName) + throws JMException + { + boolean inUse = false; + // TODO + // Check if the exchange is in use. + // Check if there are queue-bindings with the exchnage and unregister + // when there are no bindings. + try + { + _exchangeRegistry.unregisterExchange(exchangeName, false); + } + catch(AMQException ex) + { + _logger.error("Error in unregistering exchange " + exchangeName, ex); + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * Creates a new queue and registers it with the registry and puts it + * in persistance storage if durable queue. + * @param queueName + * @param durable + * @param owner + * @param autoDelete + * @throws JMException + */ + public void createQueue(String queueName, + boolean durable, + String owner, + boolean autoDelete) + throws JMException + { + AMQQueue queue = _queueRegistry.getQueue(queueName); + if (queue == null) + { + try + { + queue = new AMQQueue(queueName, durable, owner, autoDelete, _queueRegistry); + if (queue.isDurable() && !queue.isAutoDelete()) + { + _messageStore.createQueue(queue); + } + _queueRegistry.registerQueue(queue); + } + catch (AMQException ex) + { + _logger.error("Error in creating queue " + queueName, ex); + throw new MBeanException(ex, ex.toString()); + } + } + else + { + throw new JMException("The queue \"" + queueName + "\" already exists."); + } + } + + /** + * Deletes the queue from queue registry and persistant storage. + * @param queueName + * @throws JMException + */ + public void deleteQueue(String queueName) throws JMException + { + AMQQueue queue = _queueRegistry.getQueue(queueName); + if (queue == null) + { + throw new JMException("The Queue " + queueName + " is not a registerd queue."); + } + + try + { + queue.delete(); + _messageStore.removeQueue(queueName); + + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + public ObjectName getObjectName() throws MalformedObjectNameException + { + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + objectName.append(":type=").append(getType()); + + return new ObjectName(objectName.toString()); + } + } // End of MBean class +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java b/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java new file mode 100644 index 0000000000..815dfdcfbd --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server; + +import javax.management.JMException; +import java.io.IOException; + +/** + * The managed interface exposed to allow management of channels. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedChannel +{ + static final String TYPE = "Channel"; + + /** + * Tells whether the channel is transactional. + * @return true if the channel is transactional. + * @throws IOException + */ + boolean isTransactional() throws IOException; + + /** + * Tells the number of unacknowledged messages in this channel. + * @return number of unacknowledged messages. + * @throws IOException + */ + int getUnacknowledgedMessageCount() throws IOException; + + + //********** Operations *****************// + + /** + * Commits the transactions if the channel is transactional. + * @throws IOException + * @throws JMException + */ + void commitTransactions() throws IOException, JMException; + + /** + * Rollsback the transactions if the channel is transactional. + * @throws IOException + * @throws JMException + */ + void rollbackTransactions() throws IOException, JMException; + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java b/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java new file mode 100644 index 0000000000..d2b017275a --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; + +/** + * Signals that a required delivery could not be made. This could be bacuse of + * the immediate flag being set and the queue having no consumers, or the mandatory + * flag being set and the exchange having no valid bindings. + */ +public abstract class RequiredDeliveryException extends AMQException +{ + private final AMQMessage _amqMessage; + + public RequiredDeliveryException(String message, AMQMessage payload) + { + super(message); + _amqMessage = payload; + } + + public AMQMessage getAMQMessage() + { + return _amqMessage; + } + + public int getErrorCode() + { + return getReplyCode(); + } + + public abstract int getReplyCode(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java b/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java new file mode 100644 index 0000000000..85072b6976 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java @@ -0,0 +1,125 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.ack; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.txn.TxnOp; + +import java.util.LinkedList; +import java.util.List; + +/** + * A TxnOp implementation for handling accumulated acks + */ +public class TxAck implements TxnOp +{ + private final UnacknowledgedMessageMap _map; + private final List <UnacknowledgedMessage> _unacked = new LinkedList<UnacknowledgedMessage>(); + private final List<Long> _individual = new LinkedList<Long>(); + private long _deliveryTag; + private boolean _multiple; + + public TxAck(UnacknowledgedMessageMap map) + { + _map = map; + } + + public void update(long deliveryTag, boolean multiple) + { + if (!multiple) + { + //have acked a single message that is not part of + //the previously acked region so record + //individually + _individual.add(deliveryTag);//_multiple && !multiple + } + else if (deliveryTag > _deliveryTag) + { + //have simply moved the last acked message on a + //bit + _deliveryTag = deliveryTag; + _multiple = true; + } + } + + public void consolidate() + { + //lookup all the unacked messages that have been acked in this transaction + if (_multiple) + { + //get all the unacked messages for the accumulated + //multiple acks + _map.collect(_deliveryTag, true, _unacked); + } + //get any unacked messages for individual acks outside the + //range covered by multiple acks + for (long tag : _individual) + { + if(_deliveryTag < tag) + { + _map.collect(tag, false, _unacked); + } + } + } + + public boolean checkPersistent() throws AMQException + { + //if any of the messages in unacked are persistent the txn + //buffer must be marked as persistent: + for (UnacknowledgedMessage msg : _unacked) + { + if (msg.message.isPersistent()) + { + return true; + } + } + return false; + } + + public void prepare() throws AMQException + { + //make persistent changes, i.e. dequeue and decrementReference + for (UnacknowledgedMessage msg : _unacked) + { + msg.discard(); + } + } + + public void undoPrepare() + { + //decrementReference is annoyingly untransactional (due to + //in memory counter) so if we failed in prepare for full + //txn, this op will have to compensate by fixing the count + //in memory (persistent changes will be rolled back by store) + for (UnacknowledgedMessage msg : _unacked) + { + msg.message.incrementReference(); + } + } + + public void commit() + { + //remove the unacked messages from the channels map + _map.remove(_unacked); + } + + public void rollback() + { + } +} + diff --git a/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java new file mode 100644 index 0000000000..f13ecef7ac --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.ack; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; + +public class UnacknowledgedMessage +{ + public final AMQMessage message; + public final String consumerTag; + public final long deliveryTag; + public AMQQueue queue; + + public UnacknowledgedMessage(AMQQueue queue, AMQMessage message, String consumerTag, long deliveryTag) + { + this.queue = queue; + this.message = message; + this.consumerTag = consumerTag; + this.deliveryTag = deliveryTag; + } + + public void discard() throws AMQException + { + if (queue != null) + { + message.dequeue(queue); + } + message.decrementReference(); + } +} + diff --git a/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java new file mode 100644 index 0000000000..8fece7bcc1 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.ack; + +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.AMQException; + +import java.util.Collection; +import java.util.List; + +public interface UnacknowledgedMessageMap +{ + public interface Visitor + { + /** + * @param message the message being iterated over + * @return true to stop iteration, false to continue + * @throws AMQException + */ + boolean callback(UnacknowledgedMessage message) throws AMQException; + + void visitComplete(); + } + + void visit(Visitor visitor) throws AMQException; + + void add(long deliveryTag, UnacknowledgedMessage message); + + void collect(long deliveryTag, boolean multiple, List<UnacknowledgedMessage> msgs); + + boolean contains(long deliveryTag) throws AMQException; + + void remove(List<UnacknowledgedMessage> msgs); + + UnacknowledgedMessage remove(long deliveryTag); + + void drainTo(Collection<UnacknowledgedMessage> destination, long deliveryTag) throws AMQException; + + Collection<UnacknowledgedMessage> cancelAllMessages(); + + void acknowledgeMessage(long deliveryTag, boolean multiple, TransactionalContext txnContext) throws AMQException; + + int size(); + + void clear(); +} + diff --git a/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java new file mode 100644 index 0000000000..8061687175 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java @@ -0,0 +1,211 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.ack; + +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.AMQException; + +import java.util.*; + +public class UnacknowledgedMessageMapImpl implements UnacknowledgedMessageMap +{ + private final Object _lock = new Object(); + + private Map<Long, UnacknowledgedMessage> _map; + + private long _lastDeliveryTag; + + private final int _prefetchLimit; + + public UnacknowledgedMessageMapImpl(int prefetchLimit) + { + _prefetchLimit = prefetchLimit; + _map = new LinkedHashMap<Long, UnacknowledgedMessage>(prefetchLimit); + } + + /*public UnacknowledgedMessageMapImpl(Object lock, Map<Long, UnacknowledgedMessage> map) + { + _lock = lock; + _map = map; + } */ + + public void collect(long deliveryTag, boolean multiple, List<UnacknowledgedMessage> msgs) + { + if (multiple) + { + collect(deliveryTag, msgs); + } + else + { + msgs.add(get(deliveryTag)); + } + + } + + public boolean contains(long deliveryTag) throws AMQException + { + synchronized (_lock) + { + return _map.containsKey(deliveryTag); + } + } + + public void remove(List<UnacknowledgedMessage> msgs) + { + synchronized (_lock) + { + for(UnacknowledgedMessage msg : msgs) + { + _map.remove(msg.deliveryTag); + } + } + } + + public UnacknowledgedMessage remove(long deliveryTag) + { + synchronized (_lock) + { + return _map.remove(deliveryTag); + } + } + + public void visit(Visitor visitor) throws AMQException + { + synchronized (_lock) + { + Collection<UnacknowledgedMessage> currentEntries = _map.values(); + for (UnacknowledgedMessage msg: currentEntries) + { + visitor.callback(msg); + } + } + } + + public void add(long deliveryTag, UnacknowledgedMessage message) + { + synchronized( _lock) + { + _map.put(deliveryTag, message); + _lastDeliveryTag = deliveryTag; + } + } + + public Collection<UnacknowledgedMessage> cancelAllMessages() + { + synchronized (_lock) + { + Collection<UnacknowledgedMessage> currentEntries = _map.values(); + _map = new LinkedHashMap<Long, UnacknowledgedMessage>(_prefetchLimit); + return currentEntries; + } + } + + public void acknowledgeMessage(long deliveryTag, boolean multiple, TransactionalContext txnContext) + throws AMQException + { + synchronized (_lock) + { + txnContext.acknowledgeMessage(deliveryTag, _lastDeliveryTag, multiple, this); + } + } + + public int size() + { + synchronized (_lock) + { + return _map.size(); + } + } + + public void clear() + { + synchronized (_lock) + { + _map.clear(); + } + } + + public void drainTo(Collection<UnacknowledgedMessage> destination, long deliveryTag) throws AMQException + { + synchronized (_lock) + { + Iterator<Map.Entry<Long, UnacknowledgedMessage>> it = _map.entrySet().iterator(); + while (it.hasNext()) + { + Map.Entry<Long, UnacknowledgedMessage> unacked = it.next(); + + if (unacked.getKey() > deliveryTag) + { + //This should not occur now. + throw new AMQException("UnacknowledgedMessageMap is out of order:" + unacked.getKey() + + " When deliveryTag is:" + deliveryTag + "ES:" + _map.entrySet().toString()); + } + + it.remove(); + + destination.add(unacked.getValue()); + if (unacked.getKey() == deliveryTag) + { + break; + } + } + } + } + + public void resendMessages(AMQProtocolSession protocolSession, int channelId) throws AMQException + { + synchronized (_lock) + { + for (Map.Entry<Long, UnacknowledgedMessage> entry : _map.entrySet()) + { + long deliveryTag = entry.getKey(); + String consumerTag = entry.getValue().consumerTag; + AMQMessage msg = entry.getValue().message; + + msg.writeDeliver(protocolSession, channelId, deliveryTag, consumerTag); + } + } + } + private UnacknowledgedMessage get(long key) + { + synchronized (_lock) + { + return _map.get(key); + } + } + + private void collect(long key, List<UnacknowledgedMessage> msgs) + { + synchronized (_lock) + { + for(Map.Entry<Long, UnacknowledgedMessage> entry : _map.entrySet()) + { + msgs.add(entry.getValue()); + if (entry.getKey() == key) + { + break; + } + } + } + } + + +} + diff --git a/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java b/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java new file mode 100644 index 0000000000..e02b958941 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java @@ -0,0 +1,102 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.configuration; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.configuration.Configured; +import org.apache.qpid.configuration.PropertyUtils; +import org.apache.qpid.configuration.PropertyException; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.lang.reflect.Field; + +/** + * This class contains utilities for populating classes automatically from values pulled from configuration + * files. + */ +public class Configurator +{ + private static final Logger _logger = Logger.getLogger(Configurator.class); + + /** + * Configure a given instance using the application configuration. Note that superclasses are <b>not</b> + * currently configured but this could easily be added if required. + * @param instance the instance to configure + */ + public static void configure(Object instance) + { + final Configuration config = ApplicationRegistry.getInstance().getConfiguration(); + + for (Field f : instance.getClass().getDeclaredFields()) + { + Configured annotation = f.getAnnotation(Configured.class); + if (annotation != null) + { + setValueInField(f, instance, config, annotation); + } + } + } + + private static void setValueInField(Field f, Object instance, Configuration config, Configured annotation) + { + Class fieldClass = f.getType(); + String configPath = annotation.path(); + try + { + if (fieldClass == String.class) + { + String val = config.getString(configPath, annotation.defaultValue()); + val = PropertyUtils.replaceProperties(val); + f.set(instance, val); + } + else if (fieldClass == int.class) + { + int val = config.getInt(configPath, Integer.parseInt(annotation.defaultValue())); + f.setInt(instance, val); + } + else if (fieldClass == long.class) + { + long val = config.getLong(configPath, Long.parseLong(annotation.defaultValue())); + f.setLong(instance, val); + } + else if (fieldClass == double.class) + { + double val = config.getDouble(configPath, Double.parseDouble(annotation.defaultValue())); + f.setDouble(instance, val); + } + else if (fieldClass == boolean.class) + { + boolean val = config.getBoolean(configPath, Boolean.parseBoolean(annotation.defaultValue())); + f.setBoolean(instance, val); + } + else + { + _logger.error("Unsupported field type " + fieldClass + " for " + f + " IGNORING configured value"); + } + } + catch (PropertyException e) + { + _logger.error("Unable to expand property: " + e + " INGORING field " + f, e); + } + catch (IllegalAccessException e) + { + _logger.error("Unable to access field " + f + " IGNORING configured value"); + } + } +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java b/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java new file mode 100644 index 0000000000..a7ad50917c --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java @@ -0,0 +1,217 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.configuration; + +import org.apache.qpid.url.AMQBindingURL; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; + + +import java.util.Collection; + +public class VirtualHostConfiguration +{ + private static final Logger _logger = Logger.getLogger(VirtualHostConfiguration.class); + + XMLConfiguration _config; + + private static final String XML_VIRTUALHOST = "virtualhost"; + private static final String XML_PATH = "path"; + private static final String XML_BIND = "bind"; + private static final String XML_VIRTUALHOST_PATH = "virtualhost.path"; + private static final String XML_VIRTUALHOST_BIND = "virtualhost.bind"; + + + public VirtualHostConfiguration(String configFile) throws ConfigurationException + { + _logger.info("Loading Config file:" + configFile); + + _config = new XMLConfiguration(configFile); + + if (_config.getProperty(XML_VIRTUALHOST_PATH) == null) + { + throw new ConfigurationException( + "Virtualhost Configuration document does not contain a valid virtualhost."); + } + } + + public void performBindings() throws AMQException, ConfigurationException, URLSyntaxException + { + Object prop = _config.getProperty(XML_VIRTUALHOST_PATH); + + if (prop instanceof Collection) + { + _logger.debug("Number of VirtualHosts: " + ((Collection) prop).size()); + + int virtualhosts = ((Collection) prop).size(); + for (int vhost = 0; vhost < virtualhosts; vhost++) + { + loadVirtualHost(vhost); + } + } + else + { + loadVirtualHost(-1); + } + } + + private void loadVirtualHost(int index) throws AMQException, ConfigurationException, URLSyntaxException + { + String path = XML_VIRTUALHOST; + + if (index != -1) + { + path = path + "(" + index + ")"; + } + + Object prop = _config.getProperty(path + "." + XML_PATH); + + if (prop == null) + { + prop = _config.getProperty(path + "." + XML_BIND); + String error = "Virtual Host not defined for binding"; + + if (prop != null) + { + if (prop instanceof Collection) + { + error += "s"; + } + + error += ": " + prop; + } + + throw new ConfigurationException(error); + } + + _logger.info("VirtualHost:'" + prop + "'"); + + prop = _config.getProperty(path + "." + XML_BIND); + if (prop instanceof Collection) + { + int bindings = ((Collection) prop).size(); + _logger.debug("Number of Bindings: " + bindings); + for (int dest = 0; dest < bindings; dest++) + { + loadBinding(path, dest); + } + } + else + { + loadBinding(path, -1); + } + } + + private void loadBinding(String rootpath, int index) throws AMQException, ConfigurationException, URLSyntaxException + { + String path = rootpath + "." + XML_BIND; + if (index != -1) + { + path = path + "(" + index + ")"; + } + + String bindingString = _config.getString(path); + + AMQBindingURL binding = new AMQBindingURL(bindingString); + + _logger.debug("Loaded Binding:" + binding); + + try + { + bind(binding); + } + catch (AMQException amqe) + { + _logger.info("Unable to bind url: " + binding); + throw amqe; + } + } + + private void bind(AMQBindingURL binding) throws AMQException, ConfigurationException + { + + String queueName = binding.getQueueName(); + + // This will occur if the URL is a Topic + if (queueName == null) + { + //todo register valid topic + ///queueName = binding.getDestinationName(); + throw new AMQException("Topics cannot be bound. TODO Register valid topic"); + } + + //Get references to Broker Registries + QueueRegistry queueRegistry = ApplicationRegistry.getInstance().getQueueRegistry(); + MessageStore messageStore = ApplicationRegistry.getInstance().getMessageStore(); + ExchangeRegistry exchangeRegistry = ApplicationRegistry.getInstance().getExchangeRegistry(); + + synchronized (queueRegistry) + { + AMQQueue queue = queueRegistry.getQueue(queueName); + + if (queue == null) + { + _logger.info("Queue '" + binding.getQueueName() + "' does not exists. Creating."); + + queue = new AMQQueue(queueName, + Boolean.parseBoolean(binding.getOption(AMQBindingURL.OPTION_DURABLE)), + null /* These queues will have no owner */, + false /* Therefore autodelete makes no sence */, queueRegistry); + + if (queue.isDurable()) + { + messageStore.createQueue(queue); + } + + queueRegistry.registerQueue(queue); + } + else + { + _logger.info("Queue '" + binding.getQueueName() + "' already exists not creating."); + } + + Exchange defaultExchange = exchangeRegistry.getExchange(binding.getExchangeName()); + synchronized (defaultExchange) + { + if (defaultExchange == null) + { + throw new ConfigurationException("Attempt to bind queue to unknown exchange:" + binding); + } + + defaultExchange.registerQueue(queue.getName(), queue, null); + + if (binding.getRoutingKey() == null || binding.getRoutingKey().equals("")) + { + throw new ConfigurationException("Unknown binding not specified on url:" + binding); + } + + queue.bind(binding.getRoutingKey(), defaultExchange); + } + _logger.info("Queue '" + queue.getName() + "' bound to exchange:" + binding.getExchangeName() + " RK:'" + binding.getRoutingKey() + "'"); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java new file mode 100644 index 0000000000..cf787b4739 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java @@ -0,0 +1,136 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; + +import javax.management.NotCompliantMBeanException; + +public abstract class AbstractExchange implements Exchange, Managable +{ + private String _name; + + protected boolean _durable; + + protected int _ticket; + + protected ExchangeMBean _exchangeMbean; + + /** + * Whether the exchange is automatically deleted once all queues have detached from it + */ + protected boolean _autoDelete; + + /** + * Abstract MBean class. This has some of the methods implemented from + * management intrerface for exchanges. Any implementaion of an + * Exchange MBean should extend this class. + */ + protected abstract class ExchangeMBean extends AMQManagedObject implements ManagedExchange + { + public ExchangeMBean() throws NotCompliantMBeanException + { + super(ManagedExchange.class, ManagedExchange.TYPE); + } + + public String getObjectInstanceName() + { + return _name; + } + + public String getName() + { + return _name; + } + + public Integer getTicketNo() + { + return _ticket; + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + } // End of MBean class + + public String getName() + { + return _name; + } + + /** + * Concrete exchanges must implement this method in order to create the managed representation. This is + * called during initialisation (template method pattern). + * @return the MBean + */ + protected abstract ExchangeMBean createMBean() throws AMQException; + + public void initialise(String name, boolean durable, int ticket, boolean autoDelete) throws AMQException + { + _name = name; + _durable = durable; + _autoDelete = autoDelete; + _ticket = ticket; + _exchangeMbean = createMBean(); + _exchangeMbean.register(); + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public int getTicket() + { + return _ticket; + } + + public void close() throws AMQException + { + if (_exchangeMbean != null) + { + _exchangeMbean.unregister(); + } + } + + public String toString() + { + return getClass().getName() + "[" + getName() +"]"; + } + + public ManagedObject getManagedObject() + { + return _exchangeMbean; + } + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java new file mode 100644 index 0000000000..990a868e72 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; + +import java.util.HashMap; +import java.util.Map; + +public class DefaultExchangeFactory implements ExchangeFactory +{ + private static final Logger _logger = Logger.getLogger(DefaultExchangeFactory.class); + + private Map<String, Class<? extends Exchange>> _exchangeClassMap = new HashMap<String, Class<? extends Exchange>>(); + + public DefaultExchangeFactory() + { + _exchangeClassMap.put("direct", org.apache.qpid.server.exchange.DestNameExchange.class); + _exchangeClassMap.put("topic", org.apache.qpid.server.exchange.DestWildExchange.class); + _exchangeClassMap.put("headers", org.apache.qpid.server.exchange.HeadersExchange.class); + } + + public Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete, + int ticket) + throws AMQException + { + Class<? extends Exchange> exchClass = _exchangeClassMap.get(type); + if (exchClass == null) + { + throw new AMQException(_logger, "Unknown exchange type: " + type); + } + try + { + Exchange e = exchClass.newInstance(); + e.initialise(exchange, durable, ticket, autoDelete); + return e; + } + catch (InstantiationException e) + { + throw new AMQException(_logger, "Unable to create exchange: " + e, e); + } + catch (IllegalAccessException e) + { + throw new AMQException(_logger, "Unable to create exchange: " + e, e); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java new file mode 100644 index 0000000000..62b0415253 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java @@ -0,0 +1,93 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.protocol.ExchangeInitialiser; +import org.apache.qpid.server.queue.AMQMessage; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class DefaultExchangeRegistry implements ExchangeRegistry +{ + private static final Logger _log = Logger.getLogger(DefaultExchangeRegistry.class); + + /** + * Maps from exchange name to exchange instance + */ + private ConcurrentMap<String, Exchange> _exchangeMap = new ConcurrentHashMap<String, Exchange>(); + + public DefaultExchangeRegistry(ExchangeFactory exchangeFactory) + { + //create 'standard' exchanges: + try + { + new ExchangeInitialiser().initialise(exchangeFactory, this); + } + catch(AMQException e) + { + _log.error("Failed to initialise exchanges: ", e); + } + } + + public void registerExchange(Exchange exchange) + { + _exchangeMap.put(exchange.getName(), exchange); + } + + public void unregisterExchange(String name, boolean inUse) throws AMQException + { + // TODO: check inUse argument + Exchange e = _exchangeMap.remove(name); + if (e != null) + { + e.close(); + } + else + { + throw new AMQException("Unknown exchange " + name); + } + } + + public Exchange getExchange(String name) + { + return _exchangeMap.get(name); + } + + /** + * Routes content through exchanges, delivering it to 1 or more queues. + * @param payload + * @throws AMQException if something goes wrong delivering data + */ + public void routeContent(AMQMessage payload) throws AMQException + { + final String exchange = payload.getPublishBody().exchange; + final Exchange exch = _exchangeMap.get(exchange); + // there is a small window of opportunity for the exchange to be deleted in between + // the BasicPublish being received (where the exchange is validated) and the final + // content body being received (which triggers this method) + // TODO: check where the exchange is validated + if (exch == null) + { + throw new AMQException("Exchange '" + exchange + "' does not exist"); + } + exch.route(payload); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java new file mode 100644 index 0000000000..38ceebe235 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java @@ -0,0 +1,219 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.management.MBeanConstructor; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.NotCompliantMBeanException; +import javax.management.openmbean.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DestNameExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(DestNameExchange.class); + + /** + * Maps from queue name to queue instances + */ + private final Index _index = new Index(); + + /** + * MBean class implementing the management interfaces. + */ + @MBeanDescription("Management Bean for Direct Exchange") + private final class DestNameExchangeMBean extends ExchangeMBean + { + private String[] _bindingItemNames = {"BindingKey", "QueueNames"}; + private String[] _bindingItemDescriptions = {"Binding key", "Queue Names"}; + private String[] _bindingItemIndexNames = {"BindingKey"}; + private OpenType[] _bindingItemTypes = new OpenType[2]; + + private CompositeType _bindingDataType = null; + private TabularType _bindinglistDataType = null; + private TabularDataSupport _bindingList = null; + + @MBeanConstructor("Creates an MBean for AMQ direct exchange") + public DestNameExchangeMBean() throws NotCompliantMBeanException + { + super(); + init(); + } + + /** + * initialises the OpenType objects. + */ + private void init() + { + try + { + _bindingItemTypes[0] = SimpleType.STRING; + //_bindingItemTypes[1] = ArrayType.getArrayType(SimpleType.STRING); + _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING); + + _bindingDataType = new CompositeType("QueueBinding", + "Queue and binding keye", + _bindingItemNames, + _bindingItemDescriptions, + _bindingItemTypes); + _bindinglistDataType = new TabularType("Bindings", + "List of queues and binding keys", + _bindingDataType, + _bindingItemIndexNames); + } + catch(OpenDataException ex) + { + //It should never occur. + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public TabularData viewBindings() + throws OpenDataException + { + Map<String, List<AMQQueue>> bindings = _index.getBindingsMap(); + _bindingList = new TabularDataSupport(_bindinglistDataType); + + for (Map.Entry<String, List<AMQQueue>> entry : bindings.entrySet()) + { + String key = entry.getKey(); + List<String> queueList = new ArrayList<String>(); + + List<AMQQueue> queues = entry.getValue(); + for (AMQQueue q : queues) + { + queueList.add(q.getName()); + } + + Object[] bindingItemValues = {key, queueList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + _bindingItemNames, + bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + public void createBinding(String queueName, String binding) + throws JMException + { + AMQQueue queue = ApplicationRegistry.getInstance().getQueueRegistry().getQueue(queueName); + + if (queue == null) + { + throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange."); + } + + try + { + registerQueue(binding, queue, null); + queue.bind(binding, DestNameExchange.this); + } + catch (AMQException ex) + { + throw new MBeanException(ex); + } + } + + }// End of MBean class + + + protected ExchangeMBean createMBean() throws AMQException + { + try + { + return new DestNameExchangeMBean(); + } + catch (NotCompliantMBeanException ex) + { + _logger.error("Exception occured in creating the DestNameExchenge", ex); + throw new AMQException("Exception occured in creating the DestNameExchenge", ex); + } + } + + public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + assert routingKey != null; + if (!_index.add(routingKey, queue)) + { + _logger.debug("Queue " + queue + " is already registered with routing key " + routingKey); + } + else + { + _logger.debug("Binding queue " + queue + " with routing key " + routingKey + + " to exchange " + this); + } + } + + public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException + { + assert queue != null; + assert routingKey != null; + + if (!_index.remove(routingKey, queue)) + { + throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() + + " with routing key " + routingKey + ". No queue was registered with that routing key"); + } + } + + public void route(AMQMessage payload) throws AMQException + { + final BasicPublishBody publishBody = payload.getPublishBody(); + final String routingKey = publishBody.routingKey; + final List<AMQQueue> queues = _index.get(routingKey); + if (queues == null || queues.isEmpty()) + { + String msg = "Routing key " + routingKey + " is not known to " + this; + if (publishBody.mandatory) + { + throw new NoRouteException(msg, payload); + } + else + { + _logger.warn(msg); + } + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Publishing message to queue " + queues); + } + + for (AMQQueue q : queues) + { + payload.enqueue(q); + } + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java new file mode 100644 index 0000000000..6f38013ffb --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java @@ -0,0 +1,223 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.management.MBeanConstructor; + +import javax.management.openmbean.*; +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.NotCompliantMBeanException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public class DestWildExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(DestWildExchange.class); + + private ConcurrentHashMap<String, List<AMQQueue>> _routingKey2queues = new ConcurrentHashMap<String, List<AMQQueue>>(); + + /** + * DestWildExchangeMBean class implements the management interface for the + * Topic exchanges. + */ + @MBeanDescription("Management Bean for Topic Exchange") + private final class DestWildExchangeMBean extends ExchangeMBean + { + private String[] _bindingItemNames = {"BindingKey", "QueueNames"}; + private String[] _bindingItemDescriptions = {"Binding key", "Queue Names"}; + private String[] _bindingItemIndexNames = {"BindingKey"}; + private OpenType[] _bindingItemTypes = new OpenType[2]; + + private CompositeType _bindingDataType = null; + private TabularType _bindinglistDataType = null; + private TabularDataSupport _bindingList = null; + + @MBeanConstructor("Creates an MBean for AMQ topic exchange") + public DestWildExchangeMBean() throws NotCompliantMBeanException + { + super(); + init(); + } + + /** + * initialises the OpenType objects. + */ + private void init() + { + try + { + _bindingItemTypes[0] = SimpleType.STRING; + _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING); + + _bindingDataType = new CompositeType("QueueBinding", + "Queue and binding keye", + _bindingItemNames, + _bindingItemDescriptions, + _bindingItemTypes); + _bindinglistDataType = new TabularType("Bindings", + "List of queues and binding keys", + _bindingDataType, + _bindingItemIndexNames); + } + catch(OpenDataException ex) + { + //It should never occur. + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public TabularData viewBindings() + throws OpenDataException + { + _bindingList = new TabularDataSupport(_bindinglistDataType); + + for (Map.Entry<String, List<AMQQueue>> entry : _routingKey2queues.entrySet()) + { + String key = entry.getKey(); + List<String> queueList = new ArrayList<String>(); + + List<AMQQueue> queues = entry.getValue(); + for (AMQQueue q : queues) + { + queueList.add(q.getName()); + } + + Object[] bindingItemValues = {key, queueList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + _bindingItemNames, + bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + public void createBinding(String queueName, String binding) + throws JMException + { + AMQQueue queue = ApplicationRegistry.getInstance().getQueueRegistry().getQueue(queueName); + + if (queue == null) + throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange."); + + try + { + registerQueue(binding, queue, null); + queue.bind(binding, DestWildExchange.this); + } + catch (AMQException ex) + { + throw new MBeanException(ex); + } + } + + } // End of MBean class + + + public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + assert routingKey != null; + // we need to use putIfAbsent, which is an atomic operation, to avoid a race condition + List<AMQQueue> queueList = _routingKey2queues.putIfAbsent(routingKey, new CopyOnWriteArrayList<AMQQueue>()); + // if we got null back, no previous value was associated with the specified routing key hence + // we need to read back the new value just put into the map + if (queueList == null) + { + queueList = _routingKey2queues.get(routingKey); + } + if (!queueList.contains(queue)) + { + queueList.add(queue); + } + else if(_logger.isDebugEnabled()) + { + _logger.debug("Queue " + queue + " is already registered with routing key " + routingKey); + } + + } + + public void route(AMQMessage payload) throws AMQException + { + BasicPublishBody publishBody = payload.getPublishBody(); + + final String routingKey = publishBody.routingKey; + List<AMQQueue> queues = _routingKey2queues.get(routingKey); + // if we have no registered queues we have nothing to do + // TODO: add support for the immediate flag + if (queues == null) + { + //todo Check for valid topic - mritchie + return; + } + + for (AMQQueue q : queues) + { + // TODO: modify code generator to add clone() method then clone the deliver body + // without this addition we have a race condition - we will be modifying the body + // before the encoder has encoded the body for delivery + payload.enqueue(q); + } + } + + public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException + { + assert queue != null; + assert routingKey != null; + + List<AMQQueue> queues = _routingKey2queues.get(routingKey); + if (queues == null) + { + throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() + + " with routing key " + routingKey + ". No queue was registered with that routing key"); + + } + boolean removedQ = queues.remove(queue); + if (!removedQ) + { + throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() + + " with routing key " + routingKey); + } + } + + protected ExchangeMBean createMBean() throws AMQException + { + try + { + return new DestWildExchangeMBean(); + } + catch (NotCompliantMBeanException ex) + { + _logger.error("Exception occured in creating the DestWildExchenge", ex); + throw new AMQException("Exception occured in creating the DestWildExchenge", ex); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java new file mode 100644 index 0000000000..02a0badb65 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; + +public interface Exchange +{ + String getName(); + + void initialise(String name, boolean durable, int ticket, boolean autoDelete) throws AMQException; + + boolean isDurable(); + + /** + * @return true if the exchange will be deleted after all queues have been detached + */ + boolean isAutoDelete(); + + int getTicket(); + + void close() throws AMQException; + + void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException; + + void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException; + + void route(AMQMessage message) throws AMQException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java new file mode 100644 index 0000000000..36fd159f31 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java @@ -0,0 +1,28 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; + + +public interface ExchangeFactory +{ + Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete, + int ticket) + throws AMQException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java new file mode 100644 index 0000000000..4a6c735bee --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java @@ -0,0 +1,28 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; + +public class ExchangeInUseException extends AMQException +{ + public ExchangeInUseException(String exchangeName) + { + super("Exchange " + exchangeName + " is currently in use"); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java new file mode 100644 index 0000000000..5b71cd7b0c --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; + + +public interface ExchangeRegistry extends MessageRouter +{ + void registerExchange(Exchange exchange); + + /** + * Unregister an exchange + * @param name name of the exchange to delete + * @param inUse if true, do NOT delete the exchange if it is in use (has queues bound to it) + * @throws ExchangeInUseException when the exchange cannot be deleted because it is in use + * @throws AMQException + */ + void unregisterExchange(String name, boolean inUse) throws ExchangeInUseException, AMQException; + + Exchange getExchange(String name); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java new file mode 100644 index 0000000000..d69e956b5f --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java @@ -0,0 +1,142 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; + +import java.util.Collections; +import java.util.Map; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Defines binding and matching based on a set of headers. + */ +class HeadersBinding +{ + private static final Logger _logger = Logger.getLogger(HeadersBinding.class); + + private final Map _mappings = new HashMap(); + private final Set<Object> required = new HashSet<Object>(); + private final Set<Map.Entry> matches = new HashSet<Map.Entry>(); + private boolean matchAny; + + /** + * Creates a binding for a set of mappings. Those mappings whose value is + * null or the empty string are assumed only to be required headers, with + * no constraint on the value. Those with a non-null value are assumed to + * define a required match of value. + * @param mappings the defined mappings this binding should use + */ + HeadersBinding(Map mappings) + { + //noinspection unchecked + this(mappings == null ? new HashSet<Map.Entry>() : mappings.entrySet()); + _mappings.putAll(mappings); + } + + private HeadersBinding(Set<Map.Entry> entries) + { + for (Map.Entry e : entries) + { + if (isSpecial(e.getKey())) + { + processSpecial((String) e.getKey(), e.getValue()); + } + else if (e.getValue() == null || e.getValue().equals("")) + { + required.add(e.getKey()); + } + else + { + matches.add(e); + } + } + } + + protected Map getMappings() + { + return _mappings; + } + + /** + * Checks whether the supplied headers match the requirements of this binding + * @param headers the headers to check + * @return true if the headers define any required keys and match any required + * values + */ + public boolean matches(Map headers) + { + if(headers == null) + { + return required.isEmpty() && matches.isEmpty(); + } + else + { + return matchAny ? or(headers) : and(headers); + } + } + + private boolean and(Map headers) + { + //need to match all the defined mapping rules: + return headers.keySet().containsAll(required) + && headers.entrySet().containsAll(matches); + } + + private boolean or(Map headers) + { + //only need to match one mapping rule: + return !Collections.disjoint(headers.keySet(), required) + || !Collections.disjoint(headers.entrySet(), matches); + } + + private void processSpecial(String key, Object value) + { + if("X-match".equalsIgnoreCase(key)) + { + matchAny = isAny(value); + } + else + { + _logger.warn("Ignoring special header: " + key); + } + } + + private boolean isAny(Object value) + { + if(value instanceof String) + { + if("any".equalsIgnoreCase((String) value)) return true; + if("all".equalsIgnoreCase((String) value)) return false; + } + _logger.warn("Ignoring unrecognised match type: " + value); + return false;//default to all + } + + static boolean isSpecial(Object key) + { + return key instanceof String && isSpecial((String) key); + } + + static boolean isSpecial(String key) + { + return key.startsWith("X-") || key.startsWith("x-"); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java new file mode 100644 index 0000000000..67a6ff6596 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java @@ -0,0 +1,239 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.management.MBeanConstructor; + +import javax.management.openmbean.*; +import javax.management.ServiceNotFoundException; +import javax.management.NotCompliantMBeanException; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * An exchange that binds queues based on a set of required headers and header values + * and routes messages to these queues by matching the headers of the message against + * those with which the queues were bound. + * <p/> + * <pre> + * The Headers Exchange + * + * Routes messages according to the value/presence of fields in the message header table. + * (Basic and JMS content has a content header field called "headers" that is a table of + * message header fields). + * + * class = "headers" + * routing key is not used + * + * Has the following binding arguments: + * + * the X-match field - if "all", does an AND match (used for GRM), if "any", does an OR match. + * other fields prefixed with "X-" are ignored (and generate a console warning message). + * a field with no value or empty value indicates a match on presence only. + * a field with a value indicates match on field presence and specific value. + * + * Standard instances: + * + * amq.match - pub/sub on field content/value + * </pre> + */ +public class HeadersExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(HeadersExchange.class); + + private final List<Registration> _bindings = new CopyOnWriteArrayList<Registration>(); + + /** + * HeadersExchangeMBean class implements the management interface for the + * Header Exchanges. + */ + @MBeanDescription("Management Bean for Headers Exchange") + private final class HeadersExchangeMBean extends ExchangeMBean + { + private String[] _bindingItemNames = {"Queue", "HeaderBinding"}; + private String[] _bindingItemDescriptions = {"Queue Name", "Header attribute bindings"}; + private String[] _bindingItemIndexNames = {"Queue"}; + private OpenType[] _bindingItemTypes = new OpenType[2]; + + private CompositeType _bindingDataType = null; + private TabularType _bindinglistDataType = null; + private TabularDataSupport _bindingList = null; + + @MBeanConstructor("Creates an MBean for AMQ Headers exchange") + public HeadersExchangeMBean() throws NotCompliantMBeanException + { + super(); + init(); + } + /** + * initialises the OpenType objects. + */ + private void init() + { + try + { + _bindingItemTypes[0] = SimpleType.STRING; + _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING);; + + _bindingDataType = new CompositeType("QueueAndHeaderAttributesBinding", + "Queue and header attributes binding", + _bindingItemNames, + _bindingItemDescriptions, + _bindingItemTypes); + _bindinglistDataType = new TabularType("HeaderBindings", + "List of queues and related header attribute bindings", + _bindingDataType, + _bindingItemIndexNames); + } + catch(OpenDataException ex) + { + //It should never occur. + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public TabularData viewBindings() + throws OpenDataException + { + _bindingList = new TabularDataSupport(_bindinglistDataType); + for (Iterator<Registration> itr = _bindings.iterator(); itr.hasNext();) + { + Registration registration = itr.next(); + String queueName = registration.queue.getName(); + + HeadersBinding headers = registration.binding; + Map<Object, Object> headerMappings = headers.getMappings(); + List<String> mappingList = new ArrayList<String>(); + + for (Map.Entry<Object, Object> en : headerMappings.entrySet()) + { + String key = en.getKey().toString(); + String value = en.getValue().toString(); + + mappingList.add(key + "=" + value); + } + + Object[] bindingItemValues = {queueName, mappingList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + _bindingItemNames, + bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + public void createBinding(String QueueName, String binding) + throws ServiceNotFoundException + { + throw new ServiceNotFoundException("This service is not supported by \"" + this.getName() + "\""); + } + + } // End of MBean class + + public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + _logger.debug("Exchange " + getName() + ": Binding " + queue.getName() + " with " + args); + _bindings.add(new Registration(new HeadersBinding(args), queue)); + } + + public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException + { + _logger.debug("Exchange " + getName() + ": Unbinding " + queue.getName()); + _bindings.remove(new Registration(null, queue)); + } + + public void route(AMQMessage payload) throws AMQException + { + Map headers = getHeaders(payload.getContentHeaderBody()); + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": routing message with headers " + headers); + } + boolean routed = false; + for (Registration e : _bindings) + { + if (e.binding.matches(headers)) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": delivering message with headers " + + headers + " to " + e.queue.getName()); + } + payload.enqueue(e.queue); + routed = true; + } + } + if (!routed) + { + _logger.warn("Exchange " + getName() + ": message not routable."); + } + } + + protected Map getHeaders(ContentHeaderBody contentHeaderFrame) + { + //what if the content type is not 'basic'? 'file' and 'stream' content classes also define headers, + //but these are not yet implemented. + return ((BasicContentHeaderProperties) contentHeaderFrame.properties).getHeaders(); + } + + protected ExchangeMBean createMBean() throws AMQException + { + try + { + return new HeadersExchangeMBean(); + } + catch (NotCompliantMBeanException ex) + { + _logger.error("Exception occured in creating the HeadersExchangeMBean", ex); + throw new AMQException("Exception occured in creating the HeadersExchangeMBean", ex); + } + } + + private static class Registration + { + private final HeadersBinding binding; + private final AMQQueue queue; + + Registration(HeadersBinding binding, AMQQueue queue) + { + this.binding = binding; + this.queue = queue; + } + + public int hashCode() + { + return queue.hashCode(); + } + + public boolean equals(Object o) + { + return o instanceof Registration && ((Registration) o).queue.equals(queue); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java new file mode 100644 index 0000000000..9e88b6a68c --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java @@ -0,0 +1,85 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.server.queue.AMQQueue; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * An index of queues against routing key. Allows multiple queues to be stored + * against the same key. Used in the DestNameExchange. + */ +class Index +{ + private ConcurrentMap<String, List<AMQQueue>> _index + = new ConcurrentHashMap<String, List<AMQQueue>>(); + + boolean add(String key, AMQQueue queue) + { + List<AMQQueue> queues = _index.get(key); + if(queues == null) + { + queues = new CopyOnWriteArrayList<AMQQueue>(); + //next call is atomic, so there is no race to create the list + List<AMQQueue> active = _index.putIfAbsent(key, queues); + if(active != null) + { + //someone added the new one in faster than we did, so use theirs + queues = active; + } + } + if(queues.contains(queue)) + { + return false; + } + else + { + return queues.add(queue); + } + } + + boolean remove(String key, AMQQueue queue) + { + List<AMQQueue> queues = _index.get(key); + if (queues != null) + { + boolean removed = queues.remove(queue); + if (queues.size() == 0) + { + _index.remove(key); + } + return removed; + } + return false; + } + + List<AMQQueue> get(String key) + { + return _index.get(key); + } + + Map<String, List<AMQQueue>> getBindingsMap() + { + return _index; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java new file mode 100644 index 0000000000..bd02910d0a --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java @@ -0,0 +1,90 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.server.management.MBeanAttribute; +import org.apache.qpid.server.management.MBeanOperation; +import org.apache.qpid.server.management.MBeanOperationParameter; + +import javax.management.openmbean.TabularData; +import javax.management.JMException; +import javax.management.MBeanOperationInfo; +import java.io.IOException; + +/** + * The management interface exposed to allow management of an Exchange. + * @author Robert J. Greig + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedExchange +{ + static final String TYPE = "Exchange"; + + /** + * Returns the name of the managed exchange. + * @return the name of the exchange. + * @throws IOException + */ + @MBeanAttribute(name="Name", description="Name of exchange") + String getName() throws IOException; + + @MBeanAttribute(name="TicketNo", description="Exchange Ticket No") + Integer getTicketNo() throws IOException; + + /** + * Tells if the exchange is durable or not. + * @return true if the exchange is durable. + * @throws IOException + */ + @MBeanAttribute(name="Durable", description="true if Exchange is durable") + boolean isDurable() throws IOException; + + /** + * Tells if the exchange is set for autodelete or not. + * @return true if the exchange is set as autodelete. + * @throws IOException + */ + @MBeanAttribute(name="AutoDelete", description="true if Exchange is AutoDelete") + boolean isAutoDelete() throws IOException; + + // Operations + + /** + * Returns all the bindings this exchange has with the queues. + * @return the bindings with the exchange. + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="viewBindings", description="view the queue bindings for this exchange") + TabularData viewBindings() throws IOException, JMException; + + /** + * Creates new binding with the given queue and binding. + * @param queueName + * @param binding + * @throws JMException + */ + @MBeanOperation(name="createBinding", + description="create a new binding with this exchange", + impact= MBeanOperationInfo.ACTION) + void createBinding(@MBeanOperationParameter(name="queue name", description="queue name") String queueName, + @MBeanOperationParameter(name="binding", description="queue binding")String binding) + throws JMException; + +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java new file mode 100644 index 0000000000..832a60c681 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; + +/** + * Separated out from the ExchangeRegistry interface to allow components + * that use only this part to have a dependency with a reduced footprint. + * + */ +public interface MessageRouter +{ + /** + * Routes content through exchanges, delivering it to 1 or more queues. + * @param message the message to be routed + * + * @throws org.apache.qpid.AMQException if something goes wrong delivering data + */ + void routeContent(AMQMessage message) throws AMQException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java new file mode 100644 index 0000000000..6d5511d9c8 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.protocol.AMQConstant; + +/** + * Thrown by an exchange if there is no way to route a message with the + * mandatory flag set. + */ +public class NoRouteException extends RequiredDeliveryException +{ + public NoRouteException(String msg, AMQMessage message) + { + super(msg, message); + } + + public int getReplyCode() + { + return AMQConstant.NO_ROUTE.getCode(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java new file mode 100644 index 0000000000..5e236f7da9 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicAckBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class BasicAckMethodHandler implements StateAwareMethodListener<BasicAckBody> +{ + private static final BasicAckMethodHandler _instance = new BasicAckMethodHandler(); + + public static BasicAckMethodHandler getInstance() + { + return _instance; + } + + private BasicAckMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<BasicAckBody> evt) throws AMQException + { + BasicAckBody body = evt.getMethod(); + final AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + // this method throws an AMQException if the delivery tag is not known + channel.acknowledgeMessage(body.deliveryTag, body.multiple); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java new file mode 100644 index 0000000000..52d6d9f0f1 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicCancelBody; +import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.AMQException; + +public class BasicCancelMethodHandler implements StateAwareMethodListener<BasicCancelBody> +{ + private static final BasicCancelMethodHandler _instance = new BasicCancelMethodHandler(); + + public static BasicCancelMethodHandler getInstance() + { + return _instance; + } + + private BasicCancelMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<BasicCancelBody> evt) throws AMQException + { + final AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + final BasicCancelBody body = evt.getMethod(); + channel.unsubscribeConsumer(protocolSession, body.consumerTag); + if(!body.nowait) + { + final AMQFrame responseFrame = BasicCancelOkBody.createAMQFrame(evt.getChannelId(), body.consumerTag); + protocolSession.writeFrame(responseFrame); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java new file mode 100644 index 0000000000..40d49815e3 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java @@ -0,0 +1,90 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.framing.BasicConsumeBody; +import org.apache.qpid.framing.BasicConsumeOkBody; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.ConsumerTagNotUniqueException; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class BasicConsumeMethodHandler implements StateAwareMethodListener<BasicConsumeBody> +{ + private static final Logger _log = Logger.getLogger(BasicConsumeMethodHandler.class); + + private static final BasicConsumeMethodHandler _instance = new BasicConsumeMethodHandler(); + + public static BasicConsumeMethodHandler getInstance() + { + return _instance; + } + + private BasicConsumeMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession session, + AMQMethodEvent<BasicConsumeBody> evt) throws AMQException + { + BasicConsumeBody body = evt.getMethod(); + final int channelId = evt.getChannelId(); + + AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + _log.error("Channel " + channelId + " not found"); + // TODO: either alert or error that the + } + else + { + AMQQueue queue = body.queue == null ? channel.getDefaultQueue() : queueRegistry.getQueue(body.queue); + + if (queue == null) + { + _log.info("No queue for '" + body.queue + "'"); + } + try + { + String consumerTag = channel.subscribeToQueue(body.consumerTag, queue, session, !body.noAck); + if(!body.nowait) + { + session.writeFrame(BasicConsumeOkBody.createAMQFrame(channelId, consumerTag)); + } + + //now allow queue to start async processing of any backlog of messages + queue.deliverAsync(); + } + catch (ConsumerTagNotUniqueException e) + { + String msg = "Non-unique consumer tag, '" + body.consumerTag + "'"; + session.writeFrame(ConnectionCloseBody.createAMQFrame(channelId, AMQConstant.NOT_ALLOWED.getCode(), msg, BasicConsumeBody.CLASS_ID, BasicConsumeBody.METHOD_ID)); + } + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java new file mode 100644 index 0000000000..f2f660299d --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java @@ -0,0 +1,77 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class BasicPublishMethodHandler implements StateAwareMethodListener<BasicPublishBody> +{ + private static final BasicPublishMethodHandler _instance = new BasicPublishMethodHandler(); + + public static BasicPublishMethodHandler getInstance() + { + return _instance; + } + + private BasicPublishMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<BasicPublishBody> evt) throws AMQException + { + final BasicPublishBody body = evt.getMethod(); + + // TODO: check the delivery tag field details - is it unique across the broker or per subscriber? + if (body.exchange == null) + { + body.exchange = "amq.direct"; + } + Exchange e = exchangeRegistry.getExchange(body.exchange); + // if the exchange does not exist we raise a channel exception + if (e == null) + { + protocolSession.closeChannel(evt.getChannelId()); + // TODO: modify code gen to make getClazz and getMethod public methods rather than protected + // then we can remove the hardcoded 0,0 + AMQFrame cf = ChannelCloseBody.createAMQFrame(evt.getChannelId(), 500, "Unknown exchange name", 0, 0); + protocolSession.writeFrame(cf); + } + else + { + // The partially populated BasicDeliver frame plus the received route body + // is stored in the channel. Once the final body frame has been received + // it is routed to the exchange. + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + channel.setPublishFrame(body, protocolSession); + } + } +} + diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java new file mode 100644 index 0000000000..0d1c039207 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicQosBody; +import org.apache.qpid.framing.BasicQosOkBody; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.AMQException; + +public class BasicQosHandler implements StateAwareMethodListener<BasicQosBody> +{ + private static final BasicQosHandler _instance = new BasicQosHandler(); + + public static BasicQosHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateMgr, QueueRegistry queues, ExchangeRegistry exchanges, + AMQProtocolSession session, AMQMethodEvent<BasicQosBody> evt) throws AMQException + { + session.getChannel(evt.getChannelId()).setPrefetchCount(evt.getMethod().prefetchCount); + session.writeFrame(new AMQFrame(evt.getChannelId(), new BasicQosOkBody())); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java new file mode 100644 index 0000000000..1dce283c9e --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.framing.BasicRecoverBody; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +public class BasicRecoverMethodHandler implements StateAwareMethodListener<BasicRecoverBody> +{ + private static final Logger _logger = Logger.getLogger(BasicRecoverMethodHandler.class); + + private static final BasicRecoverMethodHandler _instance = new BasicRecoverMethodHandler(); + + public static BasicRecoverMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<BasicRecoverBody> evt) throws AMQException + { + _logger.debug("Recover received on protocol session " + protocolSession + " and channel " + evt.getChannelId()); + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + if (channel == null) + { + throw new AMQException("Unknown channel " + evt.getChannelId()); + } + channel.resend(protocolSession); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java new file mode 100644 index 0000000000..1b03f15d22 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ChannelCloseHandler implements StateAwareMethodListener<ChannelCloseBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseHandler.class); + + private static ChannelCloseHandler _instance = new ChannelCloseHandler(); + + public static ChannelCloseHandler getInstance() + { + return _instance; + } + + private ChannelCloseHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ChannelCloseBody> evt) throws AMQException + { + ChannelCloseBody body = evt.getMethod(); + _logger.info("Received channel close for id " + evt.getChannelId() + " citing class " + body.classId + + " and method " + body.methodId); + protocolSession.closeChannel(evt.getChannelId()); + AMQFrame response = ChannelCloseOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java new file mode 100644 index 0000000000..7731e56d4d --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java @@ -0,0 +1,51 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class ChannelCloseOkHandler implements StateAwareMethodListener<ChannelCloseOkBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseOkHandler.class); + + private static ChannelCloseOkHandler _instance = new ChannelCloseOkHandler(); + + public static ChannelCloseOkHandler getInstance() + { + return _instance; + } + + private ChannelCloseOkHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ChannelCloseOkBody> evt) throws AMQException + { + _logger.info("Received channel-close-ok for channel-id " + evt.getChannelId()); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java new file mode 100644 index 0000000000..1ad917b918 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java @@ -0,0 +1,61 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelFlowBody; +import org.apache.qpid.framing.ChannelFlowOkBody; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.AMQException; + +public class ChannelFlowHandler implements StateAwareMethodListener<ChannelFlowBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelFlowHandler.class); + + private static ChannelFlowHandler _instance = new ChannelFlowHandler(); + + public static ChannelFlowHandler getInstance() + { + return _instance; + } + + private ChannelFlowHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ChannelFlowBody> evt) throws AMQException + { + ChannelFlowBody body = evt.getMethod(); + + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + channel.setSuspended(!body.active); + _logger.debug("Channel.Flow for channel " + evt.getChannelId() + ", active=" + body.active); + + AMQFrame response = ChannelFlowOkBody.createAMQFrame(evt.getChannelId(), body.active); + protocolSession.writeFrame(response); + }} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java new file mode 100644 index 0000000000..634cd70469 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelOpenBody; +import org.apache.qpid.framing.ChannelOpenOkBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ChannelOpenHandler implements StateAwareMethodListener<ChannelOpenBody> +{ + private static ChannelOpenHandler _instance = new ChannelOpenHandler(); + + public static ChannelOpenHandler getInstance() + { + return _instance; + } + + private ChannelOpenHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ChannelOpenBody> evt) throws AMQException + { + IApplicationRegistry registry = ApplicationRegistry.getInstance(); + final AMQChannel channel = new AMQChannel(evt.getChannelId(), registry.getMessageStore(), + exchangeRegistry); + protocolSession.addChannel(channel); + AMQFrame response = ChannelOpenOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java new file mode 100644 index 0000000000..f78d6f7276 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +public class ConnectionCloseMethodHandler implements StateAwareMethodListener<ConnectionCloseBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseMethodHandler.class); + + private static ConnectionCloseMethodHandler _instance = new ConnectionCloseMethodHandler(); + + public static ConnectionCloseMethodHandler getInstance() + { + return _instance; + } + + private ConnectionCloseMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ConnectionCloseBody> evt) throws AMQException + { + final ConnectionCloseBody body = evt.getMethod(); + _logger.info("ConnectionClose received with reply code/reply text " + body.replyCode + "/" + + body.replyText + " for " + protocolSession); + try + { + protocolSession.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + final AMQFrame response = ConnectionCloseOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java new file mode 100644 index 0000000000..f918158edb --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQState; +import org.apache.log4j.Logger; + +public class ConnectionCloseOkMethodHandler implements StateAwareMethodListener<ConnectionCloseOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseOkMethodHandler.class); + + private static ConnectionCloseOkMethodHandler _instance = new ConnectionCloseOkMethodHandler(); + + public static ConnectionCloseOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionCloseOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ConnectionCloseOkBody> evt) throws AMQException + { + //todo should this not do more than just log the method? + _logger.info("Received Connection-close-ok"); + + try + { + stateManager.changeState(AMQState.CONNECTION_CLOSED); + protocolSession.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java new file mode 100644 index 0000000000..7bc28f9eb9 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java @@ -0,0 +1,68 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionOpenBody; +import org.apache.qpid.framing.ConnectionOpenOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionOpenMethodHandler implements StateAwareMethodListener<ConnectionOpenBody> +{ + private static ConnectionOpenMethodHandler _instance = new ConnectionOpenMethodHandler(); + + public static ConnectionOpenMethodHandler getInstance() + { + return _instance; + } + + private ConnectionOpenMethodHandler() + { + } + + private static String generateClientID() + { + return Long.toString(System.currentTimeMillis()); + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ConnectionOpenBody> evt) throws AMQException + { + ConnectionOpenBody body = evt.getMethod(); + String contextKey = body.virtualHost; + + //todo //FIXME The virtual host must be validated by the server for the connection to open-ok + // See Spec (0.8.2). Section 3.1.2 Virtual Hosts + if (contextKey == null) + { + contextKey = generateClientID(); + } + protocolSession.setContextKey(contextKey); + AMQFrame response = ConnectionOpenOkBody.createAMQFrame((short)0, contextKey); + stateManager.changeState(AMQState.CONNECTION_OPEN); + protocolSession.writeFrame(response); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java new file mode 100644 index 0000000000..1c0da4f658 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java @@ -0,0 +1,115 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.HeartbeatConfig; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.log4j.Logger; + +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; + +public class ConnectionSecureOkMethodHandler implements StateAwareMethodListener<ConnectionSecureOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionSecureOkMethodHandler.class); + + private static ConnectionSecureOkMethodHandler _instance = new ConnectionSecureOkMethodHandler(); + + public static ConnectionSecureOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionSecureOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ConnectionSecureOkBody> evt) throws AMQException + { + ConnectionSecureOkBody body = evt.getMethod(); + + AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager(); + SaslServer ss = protocolSession.getSaslServer(); + if (ss == null) + { + throw new AMQException("No SASL context set up in session"); + } + + AuthenticationResult authResult = authMgr.authenticate(ss, body.response); + switch (authResult.status) + { + case ERROR: + // Can't do this as we violate protocol. Need to send Close + // throw new AMQException(AMQConstant.NOT_ALLOWED.getCode(), AMQConstant.NOT_ALLOWED.getName()); + _logger.info("Authentication failed"); + stateManager.changeState(AMQState.CONNECTION_CLOSING); + AMQFrame close = ConnectionCloseBody.createAMQFrame(0, AMQConstant.NOT_ALLOWED.getCode(), + AMQConstant.NOT_ALLOWED.getName(), + ConnectionCloseBody.CLASS_ID, + ConnectionCloseBody.METHOD_ID); + protocolSession.writeFrame(close); + disposeSaslServer(protocolSession); + break; + case SUCCESS: + _logger.info("Connected as: " + ss.getAuthorizationID()); + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + AMQFrame tune = ConnectionTuneBody.createAMQFrame(0, Integer.MAX_VALUE, + ConnectionStartOkMethodHandler.getConfiguredFrameSize(), + HeartbeatConfig.getInstance().getDelay()); + protocolSession.writeFrame(tune); + disposeSaslServer(protocolSession); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + AMQFrame challenge = ConnectionSecureBody.createAMQFrame(0, authResult.challenge); + protocolSession.writeFrame(challenge); + } + } + + private void disposeSaslServer(AMQProtocolSession ps) + { + SaslServer ss = ps.getSaslServer(); + if (ss != null) + { + ps.setSaslServer(null); + try + { + ss.dispose(); + } + catch (SaslException e) + { + _logger.error("Error disposing of Sasl server: " + e); + } + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java new file mode 100644 index 0000000000..5715ce181b --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java @@ -0,0 +1,127 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.framing.ConnectionStartOkBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.HeartbeatConfig; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + + +public class ConnectionStartOkMethodHandler implements StateAwareMethodListener<ConnectionStartOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionStartOkMethodHandler.class); + + private static ConnectionStartOkMethodHandler _instance = new ConnectionStartOkMethodHandler(); + + private static final int DEFAULT_FRAME_SIZE = 65536; + + public static StateAwareMethodListener<ConnectionStartOkBody> getInstance() + { + return _instance; + } + + private ConnectionStartOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ConnectionStartOkBody> evt) throws AMQException + { + final ConnectionStartOkBody body = evt.getMethod(); + _logger.info("SASL Mechanism selected: " + body.mechanism); + _logger.info("Locale selected: " + body.locale); + + AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager(); + + SaslServer ss = null; + try + { + ss = authMgr.createSaslServer(body.mechanism, protocolSession.getLocalFQDN()); + protocolSession.setSaslServer(ss); + + AuthenticationResult authResult = authMgr.authenticate(ss, body.response); + + switch (authResult.status) + { + case ERROR: + throw new AMQException("Authentication failed"); + case SUCCESS: + _logger.info("Connected as: " + ss.getAuthorizationID()); + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + AMQFrame tune = ConnectionTuneBody.createAMQFrame(0, Integer.MAX_VALUE, getConfiguredFrameSize(), + HeartbeatConfig.getInstance().getDelay()); + protocolSession.writeFrame(tune); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + AMQFrame challenge = ConnectionSecureBody.createAMQFrame(0, authResult.challenge); + protocolSession.writeFrame(challenge); + } + } + catch (SaslException e) + { + disposeSaslServer(protocolSession); + throw new AMQException("SASL error: " + e, e); + } + } + + private void disposeSaslServer(AMQProtocolSession ps) + { + SaslServer ss = ps.getSaslServer(); + if (ss != null) + { + ps.setSaslServer(null); + try + { + ss.dispose(); + } + catch (SaslException e) + { + _logger.error("Error disposing of Sasl server: " + e); + } + } + } + + static int getConfiguredFrameSize() + { + final Configuration config = ApplicationRegistry.getInstance().getConfiguration(); + final int framesize = config.getInt("advanced.framesize", DEFAULT_FRAME_SIZE); + _logger.info("Framesize set to " + framesize); + return framesize; + } +} + diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java new file mode 100644 index 0000000000..05ca10fec5 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionTuneOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionTuneOkMethodHandler implements StateAwareMethodListener<ConnectionTuneOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionTuneOkMethodHandler.class); + + private static ConnectionTuneOkMethodHandler _instance = new ConnectionTuneOkMethodHandler(); + + public static ConnectionTuneOkMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ConnectionTuneOkBody> evt) throws AMQException + { + ConnectionTuneOkBody body = evt.getMethod(); + if (_logger.isDebugEnabled()) + { + _logger.debug(body); + } + stateManager.changeState(AMQState.CONNECTION_NOT_OPENED); + protocolSession.initHeartbeats(body.heartbeat); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java new file mode 100644 index 0000000000..444a54a4f2 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ExchangeDeclareBody; +import org.apache.qpid.framing.ExchangeDeclareOkBody; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.registry.ApplicationRegistry; + +public class ExchangeDeclareHandler implements StateAwareMethodListener<ExchangeDeclareBody> +{ + private static final Logger _logger = Logger.getLogger(ExchangeDeclareHandler.class); + + private static final ExchangeDeclareHandler _instance = new ExchangeDeclareHandler(); + + public static ExchangeDeclareHandler getInstance() + { + return _instance; + } + + private final ExchangeFactory exchangeFactory; + + private ExchangeDeclareHandler() + { + exchangeFactory = ApplicationRegistry.getInstance().getExchangeFactory(); + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ExchangeDeclareBody> evt) throws AMQException + { + final ExchangeDeclareBody body = evt.getMethod(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Request to declare exchange of type " + body.type + " with name " + body.exchange); + } + synchronized(exchangeRegistry) + { + Exchange exchange = exchangeRegistry.getExchange(body.exchange); + + if (exchange == null) + { + exchange = exchangeFactory.createExchange(body.exchange, body.type, body.durable, + body.passive, body.ticket); + exchangeRegistry.registerExchange(exchange); + } + } + if(!body.nowait) + { + AMQFrame response = ExchangeDeclareOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java new file mode 100644 index 0000000000..441e991872 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ExchangeDeleteBody; +import org.apache.qpid.framing.ExchangeDeleteOkBody; +import org.apache.qpid.server.exchange.ExchangeInUseException; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ExchangeDeleteHandler implements StateAwareMethodListener<ExchangeDeleteBody> +{ + private static final ExchangeDeleteHandler _instance = new ExchangeDeleteHandler(); + + public static ExchangeDeleteHandler getInstance() + { + return _instance; + } + + private ExchangeDeleteHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<ExchangeDeleteBody> evt) throws AMQException + { + ExchangeDeleteBody body = evt.getMethod(); + try + { + exchangeRegistry.unregisterExchange(body.exchange, body.ifUnused); + AMQFrame response = ExchangeDeleteOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } + catch (ExchangeInUseException e) + { + // TODO: sort out consistent channel close mechanism that does all clean up etc. + } + + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java b/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java new file mode 100644 index 0000000000..a689366a2f --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java @@ -0,0 +1,31 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import java.util.concurrent.Executor; + +/** + * An executor that executes the task on the current thread. + */ +public class OnCurrentThreadExecutor implements Executor +{ + public void execute(Runnable command) + { + command.run(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java new file mode 100644 index 0000000000..98eec37a4a --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java @@ -0,0 +1,94 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.QueueBindBody; +import org.apache.qpid.framing.QueueBindOkBody; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class QueueBindHandler implements StateAwareMethodListener<QueueBindBody> +{ + private static final Logger _log = Logger.getLogger(QueueBindHandler.class); + + private static final QueueBindHandler _instance = new QueueBindHandler(); + + public static QueueBindHandler getInstance() + { + return _instance; + } + + private QueueBindHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<QueueBindBody> evt) throws AMQException + { + final QueueBindBody body = evt.getMethod(); + final AMQQueue queue; + if (body.queue == null) + { + queue = protocolSession.getChannel(evt.getChannelId()).getDefaultQueue(); + if (queue == null) + { + throw new AMQException("No default queue defined on channel and queue was null"); + } + if (body.routingKey == null) + { + body.routingKey = queue.getName(); + } + } + else + { + queue = queueRegistry.getQueue(body.queue); + } + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND.getCode(), "Queue " + body.queue + " does not exist."); + } + final Exchange exch = exchangeRegistry.getExchange(body.exchange); + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND.getCode(), "Exchange " + body.exchange + " does not exist."); + } + exch.registerQueue(body.routingKey, queue, body.arguments); + queue.bind(body.routingKey, exch); + if (_log.isInfoEnabled()) + { + _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + body.routingKey); + } + if (!body.nowait) + { + final AMQFrame response = QueueBindOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java new file mode 100644 index 0000000000..d1eb387748 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java @@ -0,0 +1,124 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.configuration.Configured; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.QueueDeclareBody; +import org.apache.qpid.framing.QueueDeclareOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.configuration.Configurator; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.text.MessageFormat; +import java.util.concurrent.atomic.AtomicInteger; + +public class QueueDeclareHandler implements StateAwareMethodListener<QueueDeclareBody> +{ + private static final Logger _log = Logger.getLogger(QueueDeclareHandler.class); + + private static final QueueDeclareHandler _instance = new QueueDeclareHandler(); + + public static QueueDeclareHandler getInstance() + { + return _instance; + } + + @Configured(path = "queue.auto_register", defaultValue = "false") + public boolean autoRegister; + + private final AtomicInteger _counter = new AtomicInteger(); + + private final MessageStore _store; + + protected QueueDeclareHandler() + { + Configurator.configure(this); + _store = ApplicationRegistry.getInstance().getMessageStore(); + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<QueueDeclareBody> evt) throws AMQException + { + QueueDeclareBody body = evt.getMethod(); + + // if we aren't given a queue name, we create one which we return to the client + if (body.queue == null) + { + body.queue = createName(); + } + //TODO: do we need to check that the queue already exists with exactly the same "configuration"? + + synchronized (queueRegistry) + { + AMQQueue queue; + if ((queue = queueRegistry.getQueue(body.queue)) == null) + { + queue = createQueue(body, queueRegistry, protocolSession); + if (queue.isDurable() && !queue.isAutoDelete()) + { + _store.createQueue(queue); + } + queueRegistry.registerQueue(queue); + if (autoRegister) + { + Exchange defaultExchange = exchangeRegistry.getExchange("amq.direct"); + defaultExchange.registerQueue(body.queue, queue, null); + queue.bind(body.queue, defaultExchange); + _log.info("Queue " + body.queue + " bound to default exchange"); + } + } + //set this as the default queue on the channel: + protocolSession.getChannel(evt.getChannelId()).setDefaultQueue(queue); + } + if (!body.nowait) + { + AMQFrame response = QueueDeclareOkBody.createAMQFrame(evt.getChannelId(), body.queue, 0L, 0L); + _log.info("Queue " + body.queue + " declared successfully"); + protocolSession.writeFrame(response); + } + } + + protected String createName() + { + return "tmp_" + pad(_counter.incrementAndGet()); + } + + protected static String pad(int value) + { + return MessageFormat.format("{0,number,0000000000000}", value); + } + + protected AMQQueue createQueue(QueueDeclareBody body, QueueRegistry registry, AMQProtocolSession session) + throws AMQException + { + String owner = body.exclusive ? session.getContextKey() : null; + return new AMQQueue(body.queue, body.durable, owner, body.autoDelete, registry); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java new file mode 100644 index 0000000000..82c1d93065 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java @@ -0,0 +1,84 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.framing.QueueDeleteBody; +import org.apache.qpid.framing.QueueDeleteOkBody; +import org.apache.qpid.AMQException; + +public class QueueDeleteHandler implements StateAwareMethodListener<QueueDeleteBody> +{ + private static final QueueDeleteHandler _instance = new QueueDeleteHandler(); + + public static QueueDeleteHandler getInstance() + { + return _instance; + } + + private final boolean _failIfNotFound; + private final MessageStore _store; + + public QueueDeleteHandler() + { + this(true); + } + + public QueueDeleteHandler(boolean failIfNotFound) + { + _failIfNotFound = failIfNotFound; + _store = ApplicationRegistry.getInstance().getMessageStore(); + + } + + public void methodReceived(AMQStateManager stateMgr, QueueRegistry queues, ExchangeRegistry exchanges, AMQProtocolSession session, AMQMethodEvent<QueueDeleteBody> evt) throws AMQException + { + QueueDeleteBody body = evt.getMethod(); + AMQQueue queue; + if(body.queue == null) + { + queue = session.getChannel(evt.getChannelId()).getDefaultQueue(); + } + else + { + queue = queues.getQueue(body.queue); + } + + if(queue == null) + { + if(_failIfNotFound) + { + throw body.getChannelException(404, "Queue " + body.queue + " does not exist."); + } + } + else + { + int purged = queue.delete(body.ifUnused, body.ifEmpty); + _store.removeQueue(queue.getName()); + session.writeFrame(QueueDeleteOkBody.createAMQFrame(evt.getChannelId(), purged)); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java new file mode 100644 index 0000000000..14399b08f0 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java @@ -0,0 +1,61 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxCommitBody; +import org.apache.qpid.framing.TxCommitOkBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class TxCommitHandler implements StateAwareMethodListener<TxCommitBody> +{ + private static TxCommitHandler _instance = new TxCommitHandler(); + + public static TxCommitHandler getInstance() + { + return _instance; + } + + private TxCommitHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<TxCommitBody> evt) throws AMQException + { + + try + { + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + channel.commit(); + protocolSession.writeFrame(TxCommitOkBody.createAMQFrame(evt.getChannelId())); + channel.processReturns(protocolSession); + } + catch(AMQException e) + { + throw evt.getMethod().getChannelException(e.getErrorCode(), "Failed to commit: " + e.getMessage()); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java new file mode 100644 index 0000000000..ff2d79fb95 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java @@ -0,0 +1,59 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxRollbackBody; +import org.apache.qpid.framing.TxRollbackOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class TxRollbackHandler implements StateAwareMethodListener<TxRollbackBody> +{ + private static TxRollbackHandler _instance = new TxRollbackHandler(); + + public static TxRollbackHandler getInstance() + { + return _instance; + } + + private TxRollbackHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<TxRollbackBody> evt) throws AMQException + { + try{ + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + channel.rollback(); + protocolSession.writeFrame(TxRollbackOkBody.createAMQFrame(evt.getChannelId())); + //Now resend all the unacknowledged messages back to the original subscribers. + //(Must be done after the TxnRollback-ok response). + channel.resend(protocolSession); + }catch(AMQException e){ + throw evt.getMethod().getChannelException(e.getErrorCode(), "Failed to rollback: " + e.getMessage()); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java new file mode 100644 index 0000000000..cf665950ca --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxSelectBody; +import org.apache.qpid.framing.TxSelectOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class TxSelectHandler implements StateAwareMethodListener<TxSelectBody> +{ + private static TxSelectHandler _instance = new TxSelectHandler(); + + public static TxSelectHandler getInstance() + { + return _instance; + } + + private TxSelectHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<TxSelectBody> evt) throws AMQException + { + protocolSession.getChannel(evt.getChannelId()).setLocalTransactional(); + protocolSession.writeFrame(TxSelectOkBody.createAMQFrame(evt.getChannelId())); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java b/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java new file mode 100644 index 0000000000..da82d2166e --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java @@ -0,0 +1,107 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.jms; + +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; + +public class JmsConsumer +{ + private int _prefetchValue; + + private PrefetchUnits _prefetchUnits; + + private boolean _noLocal; + + private boolean _autoAck; + + private boolean _exclusive; + + private AMQProtocolSession _protocolSession; + + public enum PrefetchUnits + { + OCTETS, + MESSAGES + } + + public int getPrefetchValue() + { + return _prefetchValue; + } + + public void setPrefetchValue(int prefetchValue) + { + _prefetchValue = prefetchValue; + } + + public PrefetchUnits getPrefetchUnits() + { + return _prefetchUnits; + } + + public void setPrefetchUnits(PrefetchUnits prefetchUnits) + { + _prefetchUnits = prefetchUnits; + } + + public boolean isNoLocal() + { + return _noLocal; + } + + public void setNoLocal(boolean noLocal) + { + _noLocal = noLocal; + } + + public boolean isAutoAck() + { + return _autoAck; + } + + public void setAutoAck(boolean autoAck) + { + _autoAck = autoAck; + } + + public boolean isExclusive() + { + return _exclusive; + } + + public void setExclusive(boolean exclusive) + { + _exclusive = exclusive; + } + + public AMQProtocolSession getProtocolSession() + { + return _protocolSession; + } + + public void setProtocolSession(AMQProtocolSession protocolSession) + { + _protocolSession = protocolSession; + } + + public void deliverMessage() throws AMQException + { + + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java b/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java new file mode 100644 index 0000000000..a5733ed4ef --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java @@ -0,0 +1,94 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import javax.management.ListenerNotFoundException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.NotCompliantMBeanException; +import javax.management.NotificationBroadcaster; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; + +/** + * This class provides additinal feature of Notification Broadcaster to the + * DefaultManagedObject. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public abstract class AMQManagedObject extends DefaultManagedObject + implements NotificationBroadcaster +{ + /** + * broadcaster support class + */ + protected NotificationBroadcasterSupport _broadcaster = new NotificationBroadcasterSupport(); + + /** + * sequence number for notifications + */ + protected long _notificationSequenceNumber = 0; + + protected MBeanInfo _mbeanInfo; + + protected AMQManagedObject(Class<?> managementInterface, String typeName) + throws NotCompliantMBeanException + { + super(managementInterface, typeName); + buildMBeanInfo(); + } + + @Override + public MBeanInfo getMBeanInfo() + { + return _mbeanInfo; + } + + private void buildMBeanInfo() throws NotCompliantMBeanException + { + _mbeanInfo = new MBeanInfo(this.getClass().getName(), + MBeanIntrospector.getMBeanDescription(this.getClass()), + MBeanIntrospector.getMBeanAttributesInfo(getManagementInterface()), + MBeanIntrospector.getMBeanConstructorsInfo(this.getClass()), + MBeanIntrospector.getMBeanOperationsInfo(getManagementInterface()), + this.getNotificationInfo()); + } + + + + // notification broadcaster implementation + + public void addNotificationListener(NotificationListener listener, + NotificationFilter filter, + Object handback) + { + _broadcaster.addNotificationListener(listener, filter, handback); + } + + public void removeNotificationListener(NotificationListener listener) + throws ListenerNotFoundException + { + _broadcaster.removeNotificationListener(listener); + } + + public MBeanNotificationInfo[] getNotificationInfo() + { + return null; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java b/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java new file mode 100644 index 0000000000..cd0d58acdb --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java @@ -0,0 +1,168 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.management.JMException; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.StandardMBean; + +/** + * Provides implementation of the boilerplate ManagedObject interface. Most managed objects should find it useful + * to extend this class rather than implementing ManagedObject from scratch. + * + */ +public abstract class DefaultManagedObject extends StandardMBean implements ManagedObject +{ + private Class<?> _managementInterface; + + private String _typeName; + + protected DefaultManagedObject(Class<?> managementInterface, String typeName) + throws NotCompliantMBeanException + { + super(managementInterface); + _managementInterface = managementInterface; + _typeName = typeName; + } + + public String getType() + { + return _typeName; + } + + public Class<?> getManagementInterface() + { + return _managementInterface; + } + + public ManagedObject getParentObject() + { + return null; + } + + public void register() throws AMQException + { + try + { + ApplicationRegistry.getInstance().getManagedObjectRegistry().registerObject(this); + } + catch (JMException e) + { + throw new AMQException("Error registering managed object " + this + ": " + e, e); + } + } + + public void unregister() throws AMQException + { + try + { + ApplicationRegistry.getInstance().getManagedObjectRegistry().unregisterObject(this); + } + catch (JMException e) + { + throw new AMQException("Error unregistering managed object: " + this + ": " + e, e); + } + } + + public String toString() + { + return getObjectInstanceName() + "[" + getType() + "]"; + } + + /** + * Created the ObjectName as per the JMX Specs + * @return ObjectName + * @throws MalformedObjectNameException + */ + public ObjectName getObjectName() + throws MalformedObjectNameException + { + String name = getObjectInstanceName(); + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + + objectName.append(":type="); + objectName.append(getHierarchicalType(this)); + + objectName.append(","); + objectName.append(getHierarchicalName(this)); + objectName.append("name=").append(name); + + return new ObjectName(objectName.toString()); + } + + private String getHierarchicalType(ManagedObject obj) + { + String parentType = null; + if (obj.getParentObject() != null) + { + parentType = getHierarchicalType(obj.getParentObject()).toString(); + return parentType + "." + obj.getType(); + } + else + return obj.getType(); + } + + private String getHierarchicalName(ManagedObject obj) + { + String parentName = null; + if (obj.getParentObject() != null) + { + parentName = obj.getParentObject().getType() + "=" + + obj.getParentObject().getObjectInstanceName() + ","+ + getHierarchicalName(obj.getParentObject()); + + return parentName; + } + else + return ""; + } + + protected static StringBuffer jmxEncode(StringBuffer jmxName, int attrPos) + { + for (int i = attrPos; i < jmxName.length(); i++) + { + if (jmxName.charAt(i) == ',') + { + jmxName.setCharAt(i, ';'); + } + else if (jmxName.charAt(i) == ':') + { + jmxName.setCharAt(i, '-'); + } + else if (jmxName.charAt(i) == '?' || + jmxName.charAt(i) == '*' || + jmxName.charAt(i) == '\\') + { + jmxName.insert(i, '\\'); + i++; + } + else if (jmxName.charAt(i) == '\n') + { + jmxName.insert(i, '\\'); + i++; + jmxName.setCharAt(i, 'n'); + } + } + return jmxName; + } +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java new file mode 100644 index 0000000000..0cd43cecac --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import org.apache.log4j.Logger; + +import javax.management.JMException; +import javax.management.MBeanServer; +import java.lang.management.ManagementFactory; + +public class JMXManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class); + + private final MBeanServer _mbeanServer; + + public JMXManagedObjectRegistry() + { + _log.info("Initialising managed object registry using platform MBean server"); + // we use the platform MBean server currently but this must be changed or at least be configuurable + _mbeanServer = ManagementFactory.getPlatformMBeanServer(); + } + + public void registerObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.registerMBean(managedObject, managedObject.getObjectName()); + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.unregisterMBean(managedObject.getObjectName()); + } + +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java new file mode 100644 index 0000000000..595c97dd76 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java @@ -0,0 +1,39 @@ +package org.apache.qpid.server.management; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Target; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +/** + * Annotation for MBean attributes. This should be used with getter or setter + * methods of attributes. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Inherited +public @interface MBeanAttribute +{ + String name(); + String description(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java new file mode 100644 index 0000000000..2ce275d50d --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java @@ -0,0 +1,37 @@ +package org.apache.qpid.server.management; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Target; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +/** + * Annotation for MBean constructors. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.CONSTRUCTOR) +@Inherited +public @interface MBeanConstructor +{ + String value(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java new file mode 100644 index 0000000000..ef6993036f --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java @@ -0,0 +1,36 @@ +package org.apache.qpid.server.management; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Target; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +/** + * Annotation for MBean class. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface MBeanDescription { + String value(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java new file mode 100644 index 0000000000..dcf05fa6fe --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java @@ -0,0 +1,385 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.NotCompliantMBeanException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is a utility class to introspect the MBean class and the management + * interface class for various purposes. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +class MBeanIntrospector { + + private static final String _defaultAttributeDescription = "Management attribute"; + private static final String _defaultOerationDescription = "Management operation"; + private static final String _defaultConstructorDescription = "MBean constructor"; + private static final String _defaultMbeanDescription = "Management interface of the MBean"; + + /** + * Introspects the management interface class for MBean attributes. + * @param interfaceClass + * @return MBeanAttributeInfo[] + * @throws NotCompliantMBeanException + */ + static MBeanAttributeInfo[] getMBeanAttributesInfo(Class interfaceClass) + throws NotCompliantMBeanException + { + List<MBeanAttributeInfo> attributesList = new ArrayList<MBeanAttributeInfo>(); + + /** + * Using reflection, all methods of the managemetn interface will be analysed, + * and MBeanInfo will be created. + */ + for (Method method : interfaceClass.getMethods()) + { + int argCount = method.getParameterTypes().length; + String name = method.getName(); + Class<?> resultType = method.getReturnType(); + MBeanAttributeInfo attributeInfo = null; + + if (isAttributeGetterMethod(method)) + { + String desc = getAttributeDescription(method); + attributeInfo = new MBeanAttributeInfo(name.substring(3), + resultType.getName(), + desc, + true, + false, + false); + int index = getIndexIfAlreadyExists(attributeInfo, attributesList); + if (index == -1) + { + attributesList.add(attributeInfo); + } + else + { + attributeInfo = new MBeanAttributeInfo(name.substring(3), + resultType.getName(), + desc, + true, + true, + false); + attributesList.set(index, attributeInfo); + } + } + else if (isAttributeSetterMethod(method)) + { + String desc = getAttributeDescription(method); + attributeInfo = new MBeanAttributeInfo(name.substring(3), + method.getParameterTypes()[0].getName(), + desc, + false, + true, + false); + int index = getIndexIfAlreadyExists(attributeInfo, attributesList); + if (index == -1) + { + attributesList.add(attributeInfo); + } + else + { + attributeInfo = new MBeanAttributeInfo(name.substring(3), + method.getParameterTypes()[0].getName(), + desc, + true, + true, + false); + attributesList.set(index, attributeInfo); + } + } + else if (isAttributeBoolean(method)) + { + attributeInfo = new MBeanAttributeInfo(name.substring(2), + resultType.getName(), + getAttributeDescription(method), + true, + false, + true); + attributesList.add(attributeInfo); + } + } + + return attributesList.toArray(new MBeanAttributeInfo[0]); + } + + /** + * Introspects the management interface class for management operations. + * @param interfaceClass + * @return MBeanOperationInfo[] + */ + static MBeanOperationInfo[] getMBeanOperationsInfo(Class interfaceClass) + { + List<MBeanOperationInfo> operationsList = new ArrayList<MBeanOperationInfo>(); + + for (Method method : interfaceClass.getMethods()) + { + if (!isAttributeGetterMethod(method) && + !isAttributeSetterMethod(method) && + !isAttributeBoolean(method)) + { + operationsList.add(getOperationInfo(method)); + } + } + + return operationsList.toArray(new MBeanOperationInfo[0]); + } + + /** + * Checks if the method is an attribute getter method. + * @param method + * @return true if the method is an attribute getter method. + */ + private static boolean isAttributeGetterMethod(Method method) + { + if (!(method.getName().equals("get")) && + method.getName().startsWith("get") && + method.getParameterTypes().length == 0 && + !method.getReturnType().equals(void.class)) + { + return true; + } + + return false; + } + + /** + * Checks if the method is an attribute setter method. + * @param method + * @return true if the method is an attribute setter method. + */ + private static boolean isAttributeSetterMethod(Method method) + { + if (!(method.getName().equals("set")) && + method.getName().startsWith("set") && + method.getParameterTypes().length == 1 && + method.getReturnType().equals(void.class)) + { + return true; + } + + return false; + } + + /** + * Checks if the attribute is a boolean and the method is a isX kind og method. + * @param method + * @return true if the method is an attribute isX type of method + */ + private static boolean isAttributeBoolean(Method method) + { + if (!(method.getName().equals("is")) && + method.getName().startsWith("is") && + method.getParameterTypes().length == 0 && + method.getReturnType().equals(boolean.class)) + { + return true; + } + + return false; + } + + /** + * Helper method to retrieve the attribute index from the list of attributes. + * @param attribute + * @param list + * @return attribute index no. -1 if attribtue doesn't exist + * @throws NotCompliantMBeanException + */ + private static int getIndexIfAlreadyExists(MBeanAttributeInfo attribute, + List<MBeanAttributeInfo> list) + throws NotCompliantMBeanException + { + String exceptionMsg = "Conflicting attribute methods for attribute " + attribute.getName(); + + for (MBeanAttributeInfo memberAttribute : list) + { + if (attribute.getName().equals(memberAttribute.getName())) + { + if (!attribute.getType().equals(memberAttribute.getType())) + { + throw new NotCompliantMBeanException(exceptionMsg); + } + if (attribute.isReadable() && memberAttribute.isReadable()) + { + if (attribute.isIs() != memberAttribute.isIs()) + { + throw new NotCompliantMBeanException(exceptionMsg); + } + } + + return list.indexOf(memberAttribute); + } + } + + return -1; + } + + /** + * Retrieves the attribute description from annotation + * @param attributeMethod + * @return attribute description + */ + private static String getAttributeDescription(Method attributeMethod) + { + MBeanAttribute anno = attributeMethod.getAnnotation(MBeanAttribute.class); + if (anno != null) + { + return anno.description(); + } + return _defaultAttributeDescription; + } + + /** + * Introspects the method to retrieve the operation information. + * @param operation + * @return MBeanOperationInfo + */ + private static MBeanOperationInfo getOperationInfo(Method operation) + { + MBeanOperationInfo operationInfo = null; + Class<?> returnType = operation.getReturnType(); + + MBeanParameterInfo[] paramsInfo = getParametersInfo(operation.getParameterAnnotations(), + operation.getParameterTypes()); + + String operationDesc = _defaultOerationDescription; + int impact = MBeanOperationInfo.UNKNOWN; + + if (operation.getAnnotation(MBeanOperation.class) != null) + { + operationDesc = operation.getAnnotation(MBeanOperation.class).description(); + impact = operation.getAnnotation(MBeanOperation.class).impact(); + } + operationInfo = new MBeanOperationInfo(operation.getName(), + operationDesc, + paramsInfo, + returnType.getName(), + impact); + + return operationInfo; + } + + /** + * Constructs the parameter info. + * @param paramsAnno + * @param paramTypes + * @return MBeanParameterInfo[] + */ + private static MBeanParameterInfo[] getParametersInfo(Annotation[][] paramsAnno, + Class<?>[] paramTypes) + { + int noOfParams = paramsAnno.length; + + MBeanParameterInfo[] paramsInfo = new MBeanParameterInfo[noOfParams]; + + for (int i = 0; i < noOfParams; i++) + { + MBeanParameterInfo paramInfo = null; + String type = paramTypes[i].getName(); + for (Annotation anno : paramsAnno[i]) + { + String name,desc; + if (MBeanOperationParameter.class.isInstance(anno)) + { + name = MBeanOperationParameter.class.cast(anno).name(); + desc = MBeanOperationParameter.class.cast(anno).description(); + paramInfo = new MBeanParameterInfo(name, type, desc); + } + } + + + if (paramInfo == null) + { + paramInfo = new MBeanParameterInfo("p " + (i + 1), type, "parameter " + (i + 1)); + } + if (paramInfo != null) + paramsInfo[i] = paramInfo; + } + + return paramsInfo; + } + + /** + * Introspects the MBean class for constructors + * @param implClass + * @return MBeanConstructorInfo[] + */ + static MBeanConstructorInfo[] getMBeanConstructorsInfo(Class implClass) + { + List<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>(); + + for (Constructor cons : implClass.getConstructors()) + { + MBeanConstructorInfo constructorInfo = getMBeanConstructorInfo(cons); + //MBeanConstructorInfo constructorInfo = new MBeanConstructorInfo("desc", cons); + if (constructorInfo != null) + constructors.add(constructorInfo); + } + + return constructors.toArray(new MBeanConstructorInfo[0]); + } + + /** + * Retrieves the constructor info from given constructor. + * @param cons + * @return MBeanConstructorInfo + */ + private static MBeanConstructorInfo getMBeanConstructorInfo(Constructor cons) + { + String desc = null; + Annotation anno = cons.getAnnotation(MBeanConstructor.class); + if (anno != null && MBeanConstructor.class.isInstance(anno)) + { + desc = MBeanConstructor.class.cast(anno).value(); + } + + //MBeanParameterInfo[] paramsInfo = getParametersInfo(cons.getParameterAnnotations(), + // cons.getParameterTypes()); + + return new MBeanConstructorInfo(cons.getName(), + desc != null ? _defaultConstructorDescription : desc , + null); + } + + /** + * Retrieves the description from the annotations of given class + * @param annotatedClass + * @return class description + */ + static String getMBeanDescription(Class annotatedClass) + { + Annotation anno = annotatedClass.getAnnotation(MBeanDescription.class); + if (anno != null && MBeanDescription.class.isInstance(anno)) + { + return MBeanDescription.class.cast(anno).value(); + } + return _defaultMbeanDescription; + } + +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java new file mode 100644 index 0000000000..30fdf81eee --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java @@ -0,0 +1,40 @@ +package org.apache.qpid.server.management; + +import javax.management.MBeanOperationInfo; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Target; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +/** + * Annotation for MBean operations. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Inherited +public @interface MBeanOperation +{ + String name(); + String description(); + int impact() default MBeanOperationInfo.INFO; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java new file mode 100644 index 0000000000..919ac767c7 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java @@ -0,0 +1,35 @@ +package org.apache.qpid.server.management; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +/** + * Annotation for MBean operation parameters. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface MBeanOperationParameter { + String name(); + String description(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java b/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java new file mode 100644 index 0000000000..e62e1c7f87 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java @@ -0,0 +1,31 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +/** + * Any object that can return a related MBean should implement this interface. + * + * This enables other classes to get the managed object, which in turn is useful when + * constructing relationships between managed objects without having to maintain + * separate data structures containing MBeans. + * + */ +public interface Managable +{ + ManagedObject getManagedObject(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java new file mode 100644 index 0000000000..a38931a0d8 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import javax.management.JMException; +import javax.management.MBeanOperationInfo; +import java.io.IOException; + +/** + * The ManagedBroker is the management interface to expose management + * features of the Broker. + * + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedBroker +{ + static final String TYPE = "BrokerManager"; + + /** + * Creates a new Exchange. + * @param name + * @param type + * @param durable + * @param passive + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="createNewExchange", description="Creates a new Exchange", + impact= MBeanOperationInfo.ACTION) + void createNewExchange(@MBeanOperationParameter(name="name", description="Name of the new exchange")String name, + @MBeanOperationParameter(name="excahnge type", description="Type of the exchange")String type, + @MBeanOperationParameter(name="durable", description="true if the Exchang should be durable")boolean durable, + @MBeanOperationParameter(name="passive", description="true of the Exchange should be passive")boolean passive) + throws IOException, JMException; + + /** + * unregisters all the channels, queuebindings etc and unregisters + * this exchange from managed objects. + * @param exchange + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="unregisterExchange", + description="Unregisters all the related channels and queuebindings of this exchange", + impact= MBeanOperationInfo.ACTION) + void unregisterExchange(@MBeanOperationParameter(name="exchange name", description="Name of the exchange")String exchange) + throws IOException, JMException; + + /** + * Create a new Queue on the Broker server + * @param queueName + * @param durable + * @param owner + * @param autoDelete + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="createQueue", description="Create a new Queue on the Broker server", + impact= MBeanOperationInfo.ACTION) + void createQueue(@MBeanOperationParameter(name="queue name", description="Name of the new queue")String queueName, + @MBeanOperationParameter(name="durable", description="true if the queue should be durable")boolean durable, + @MBeanOperationParameter(name="owner", description="Owner name")String owner, + @MBeanOperationParameter(name="autoDelete", description="true if the queue should be auto delete") boolean autoDelete) + throws IOException, JMException; + + /** + * Unregisters the Queue bindings, removes the subscriptions and unregisters + * from the managed objects. + * @param queueName + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="deleteQueue", + description="Unregisters the Queue bindings, removes the subscriptions and deletes the queue", + impact= MBeanOperationInfo.ACTION) + void deleteQueue(@MBeanOperationParameter(name="queue name", description="Name of the queue")String queueName) + throws IOException, JMException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java new file mode 100644 index 0000000000..4007337173 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java @@ -0,0 +1,55 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import org.apache.qpid.AMQException; + +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; + +/** + * This should be implemented by all Managable objects. + */ +public interface ManagedObject +{ + static final String DOMAIN = "org.apache.qpid"; + + /** + * @return the name that uniquely identifies this object instance. It must be + * unique only among objects of this type at this level in the hierarchy so + * the uniqueness should not be too difficult to ensure. + */ + String getObjectInstanceName(); + + String getType(); + + Class<?> getManagementInterface(); + + ManagedObject getParentObject(); + + void register() throws AMQException; + + void unregister() throws AMQException; + + /** + * Returns the ObjectName required for the mbeanserver registration. + * @return ObjectName + * @throws MalformedObjectNameException + */ + ObjectName getObjectName() throws MalformedObjectNameException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java new file mode 100644 index 0000000000..7270ec83b4 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import javax.management.JMException; + +/** + * Handles the registration (and unregistration and so on) of managed objects. + * + * Managed objects are responsible for exposting attributes, operations and notifications. They will expose + * these outside the JVM therefore it is important not to use implementation objects directly as managed objects. + * Instead, creating inner classes and exposing those is an effective way of exposing internal state in a + * controlled way. + * + * Although we do not explictly use them while targetting Java 5, the enhanced MXBean approach in Java 6 will + * be the obvious choice for managed objects. + * + */ +public interface ManagedObjectRegistry +{ + void registerObject(ManagedObject managedObject) throws JMException; + + void unregisterObject(ManagedObject managedObject) throws JMException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java new file mode 100644 index 0000000000..ec80009d17 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java @@ -0,0 +1,27 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import org.apache.qpid.configuration.Configured; + +public class ManagementConfiguration +{ + @Configured(path = "management.enabled", + defaultValue = "true") + public boolean enabled; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java new file mode 100644 index 0000000000..3bf2c8d9ca --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.management; + +import org.apache.log4j.Logger; + +import javax.management.JMException; + +/** + * This managed object registry does not actually register MBeans. This can be used in tests when management is + * not required or when management has been disabled. + * + */ +public class NoopManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(NoopManagedObjectRegistry.class); + + public NoopManagedObjectRegistry() + { + _log.info("Management is disabled"); + } + + public void registerObject(ManagedObject managedObject) throws JMException + { + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java new file mode 100644 index 0000000000..0c8d049951 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.qpid.framing.AMQMethodBody; + +/** + * An event that is passed to AMQMethodListeners describing a particular method. + * It supplies the: + * <ul><li>channel id</li> + * <li>protocol method</li> + * to listeners. This means that listeners do not need to be stateful. + * + * In the StateAwareMethodListener, other useful objects such as the protocol session + * are made available. + * + */ +public class AMQMethodEvent<M extends AMQMethodBody> +{ + private final M _method; + + private final int _channelId; + + public AMQMethodEvent(int channelId, M method) + { + _channelId = channelId; + _method = method; + } + + public M getMethod() + { + return _method; + } + + public int getChannelId() + { + return _channelId; + } + + public String toString() + { + StringBuilder buf = new StringBuilder("Method event: "); + buf.append("\nChannel id: ").append(_channelId); + buf.append("\nMethod: ").append(_method); + return buf.toString(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java new file mode 100644 index 0000000000..e8d973cd91 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.framing.AMQMethodBody; + +/** + * Interface that allows classes to register for interest in protocol method frames. + * + */ +public interface AMQMethodListener +{ + /** + * Invoked when a method frame has been received + * @param evt the event that contains the method and channel + * @param protocolSession the protocol session associated with the event + * @return true if the handler has processed the method frame, false otherwise. Note + * that this does not prohibit the method event being delivered to subsequent listeners + * but can be used to determine if nobody has dealt with an incoming method frame. + * @throws AMQException if an error has occurred. This exception will be delivered + * to all registered listeners using the error() method (see below) allowing them to + * perform cleanup if necessary. + */ + <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt, + AMQProtocolSession protocolSession, + QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry) throws AMQException; + + /** + * Callback when an error has occurred. Allows listeners to clean up. + * @param e + */ + void error(AMQException e); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java new file mode 100644 index 0000000000..9ede6157f4 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java @@ -0,0 +1,681 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.log4j.Logger; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoSession; +import org.apache.mina.transport.vmpipe.VmPipeAddress; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.codec.AMQDecoder; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.*; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.state.AMQStateManager; + +import javax.management.*; +import javax.management.monitor.MonitorNotification; +import javax.management.openmbean.*; +import javax.security.sasl.SaslServer; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; + +public class AMQMinaProtocolSession implements AMQProtocolSession, + ProtocolVersionList, + Managable +{ + private static final Logger _logger = Logger.getLogger(AMQProtocolSession.class); + + private final IoSession _minaProtocolSession; + + private String _contextKey; + + private final Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>(); + + private final CopyOnWriteArraySet<AMQMethodListener> _frameListeners = new CopyOnWriteArraySet<AMQMethodListener>(); + + private final AMQStateManager _stateManager; + + private final QueueRegistry _queueRegistry; + + private final ExchangeRegistry _exchangeRegistry; + + private AMQCodecFactory _codecFactory; + + private ManagedAMQProtocolSession _managedObject; + + private SaslServer _saslServer; + + private Object _lastReceived; + + private Object _lastSent; + + private boolean _closed; + + private long _maxNoOfChannels; + + /* AMQP Version for this session */ + + private byte _major; + private byte _minor; + + public ManagedObject getManagedObject() + { + return _managedObject; + } + + /** + * This class implements the management interface (is an MBean). In order to + * make more attributes, operations and notifications available over JMX simply + * augment the ManagedConnection interface and add the appropriate implementation here. + */ + @MBeanDescription("Management Bean for an AMQ Broker Connection") + private final class ManagedAMQProtocolSession extends AMQManagedObject + implements ManagedConnection + { + private String _name = null; + /** + * Represents the channel attributes sent with channel data. + */ + private String[] _channelAtttibuteNames = { "ChannelId", + "ChannelName", + "Transactional", + "DefaultQueue", + "UnacknowledgedMessageCount"}; + private String[] _channelAttributeDescriptions = { "Channel Identifier", + "Channel Name", + "is Channel Transactional?", + "Default Queue Name", + "Unacknowledged Message Count"}; + private OpenType[] _channelAttributeTypes = { SimpleType.INTEGER, + SimpleType.OBJECTNAME, + SimpleType.BOOLEAN, + SimpleType.STRING, + SimpleType.INTEGER}; + /** + * Channels in the list will be indexed according to channelId. + */ + private String[] _indexNames = { "ChannelId" }; + + /** + * represents the data type for channel data. + */ + private CompositeType _channelType = null; + /** + * Datatype for list of channelsType. + */ + private TabularType _channelsType = null; + + private TabularDataSupport _channelsList = null; + + @MBeanConstructor("Creates an MBean exposing an AMQ Broker Connection") + public ManagedAMQProtocolSession() throws NotCompliantMBeanException + { + super(ManagedConnection.class, ManagedConnection.TYPE); + init(); + } + + /** + * initialises the CompositeTypes and TabularType attributes. + */ + private void init() + { + String remote = getRemoteAddress(); + remote = "anonymous".equals(remote) ? remote + hashCode() : remote; + _name = jmxEncode(new StringBuffer(remote), 0).toString(); + + try + { + _channelType = new CompositeType("channel", + "a Channel", + _channelAtttibuteNames, + _channelAttributeDescriptions, + _channelAttributeTypes); + + _channelsType = new TabularType("channelsType", + "List of available channelsType", + _channelType, + _indexNames); + } + catch(OpenDataException ex) + { + // It should never occur. + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public Date getLastIoTime() + { + return new Date(_minaProtocolSession.getLastIoTime()); + } + + public String getRemoteAddress() + { + return _minaProtocolSession.getRemoteAddress().toString(); + } + + public Long getWrittenBytes() + { + return _minaProtocolSession.getWrittenBytes(); + } + + public Long getReadBytes() + { + return _minaProtocolSession.getReadBytes(); + } + + public Long getMaximumNumberOfAllowedChannels() + { + return _maxNoOfChannels; + } + + public void setMaximumNumberOfAllowedChannels(Long value) + { + _maxNoOfChannels = value; + } + + public String getObjectInstanceName() + { + return _name; + } + + public void commitTransactions(int channelId) throws JMException + { + try + { + AMQChannel channel = _channelMap.get(channelId); + if (channel == null) + { + throw new JMException("The channel (channel Id = " + channelId + ") does not exist"); + } + channel.commit(); + } + catch(AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + public void rollbackTransactions(int channelId) throws JMException + { + try + { + AMQChannel channel = _channelMap.get(channelId); + if (channel == null) + { + throw new JMException("The channel (channel Id = " + channelId + ") does not exist"); + } + channel.rollback(); + } + catch(AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * Creates the list of channels in tabular form from the _channelMap. + * @return list of channels in tabular form. + * @throws OpenDataException + */ + public TabularData getChannels() throws OpenDataException + { + _channelsList = new TabularDataSupport(_channelsType); + + for (Map.Entry<Integer, AMQChannel> entry : _channelMap.entrySet()) + { + AMQChannel channel = entry.getValue(); + //ManagedChannel channel = (AMQChannelMBean)amqChannel.getManagedObject(); + ObjectName channelObjectName = null; + + try + { + channelObjectName = channel.getObjectName(); + } + catch (MalformedObjectNameException ex) + { + _logger.error("Unable to create object name: ", ex); + } + + Object[] itemValues = {channel.getChannelId(), + channelObjectName, + (channel.getDefaultQueue() != null) ? channel.getDefaultQueue().getName() : null, + channel.getUnacknowledgedMessageMap().size()}; + + CompositeData channelData = new CompositeDataSupport(_channelType, + _channelAtttibuteNames, + itemValues); + + _channelsList.put(channelData); + } + + return _channelsList; + } + + public void closeChannel(int id) + throws Exception + { + try + { + AMQMinaProtocolSession.this.closeChannel(id); + } + catch (AMQException ex) + { + throw new Exception(ex.toString()); + } + } + + public void closeConnection() + throws Exception + { + try + { + AMQMinaProtocolSession.this.closeSession(); + } + catch (AMQException ex) + { + throw new Exception(ex.toString()); + } + } + + @Override + public MBeanNotificationInfo[] getNotificationInfo() + { + String[] notificationTypes = new String[] + {MonitorNotification.THRESHOLD_VALUE_EXCEEDED}; + String name = MonitorNotification.class.getName(); + String description = "An attribute of this MBean has reached threshold value"; + MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, + name, + description); + + return new MBeanNotificationInfo[] {info1}; + } + + private void checkForNotification() + { + int channelsCount = _channelMap.size(); + if (channelsCount >= getMaximumNumberOfAllowedChannels()) + { + Notification n = new Notification( + MonitorNotification.THRESHOLD_VALUE_EXCEEDED, + this, + ++_notificationSequenceNumber, + System.currentTimeMillis(), + "ChannelsCount = " + channelsCount + ", ChannelsCount has reached the threshold value"); + + _broadcaster.sendNotification(n); + } + } + + } // End of MBean class + + public AMQMinaProtocolSession(IoSession session, QueueRegistry queueRegistry, ExchangeRegistry exchangeRegistry, + AMQCodecFactory codecFactory) + throws AMQException + { + this(session, queueRegistry, exchangeRegistry, codecFactory, new AMQStateManager()); + } + + public AMQMinaProtocolSession(IoSession session, QueueRegistry queueRegistry, ExchangeRegistry exchangeRegistry, + AMQCodecFactory codecFactory, AMQStateManager stateManager) + throws AMQException + { + _stateManager = stateManager; + _minaProtocolSession = session; + session.setAttachment(this); + _frameListeners.add(_stateManager); + _queueRegistry = queueRegistry; + _exchangeRegistry = exchangeRegistry; + _codecFactory = codecFactory; + _managedObject = createMBean(); + _managedObject.register(); + } + + private ManagedAMQProtocolSession createMBean() throws AMQException + { + try + { + return new ManagedAMQProtocolSession(); + } + catch(NotCompliantMBeanException ex) + { + _logger.error("AMQProtocolSession MBean creation has failed.", ex); + throw new AMQException("AMQProtocolSession MBean creation has failed.", ex); + } + } + + public static AMQProtocolSession getAMQProtocolSession(IoSession minaProtocolSession) + { + return (AMQProtocolSession) minaProtocolSession.getAttachment(); + } + + public void dataBlockReceived(AMQDataBlock message) + throws Exception + { + _lastReceived = message; + if (message instanceof ProtocolInitiation) + { + ProtocolInitiation pi = (ProtocolInitiation) message; + // this ensures the codec never checks for a PI message again + ((AMQDecoder)_codecFactory.getDecoder()).setExpectProtocolInitiation(false); + try { + pi.checkVersion(this); // Fails if not correct + // This sets the protocol version (and hence framing classes) for this session. + _major = pi.protocolMajor; + _minor = pi.protocolMinor; + String mechanisms = ApplicationRegistry.getInstance().getAuthenticationManager().getMechanisms(); + String locales = "en_US"; + AMQFrame response = ConnectionStartBody.createAMQFrame((short)0, pi.protocolMajor, pi.protocolMinor, null, + mechanisms.getBytes(), locales.getBytes()); + _minaProtocolSession.write(response); + } catch (AMQException e) { + _logger.error("Received incorrect protocol initiation", e); + /* Find last protocol version in protocol version list. Make sure last protocol version + listed in the build file (build-module.xml) is the latest version which will be used + here. */ + int i = pv.length - 1; + _minaProtocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR])); + // TODO: Close connection (but how to wait until message is sent?) + } + } + else + { + AMQFrame frame = (AMQFrame) message; + + if (frame.bodyFrame instanceof AMQMethodBody) + { + methodFrameReceived(frame); + } + else + { + contentFrameReceived(frame); + } + } + } + + private void methodFrameReceived(AMQFrame frame) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Method frame received: " + frame); + } + final AMQMethodEvent<AMQMethodBody> evt = new AMQMethodEvent<AMQMethodBody>(frame.channel, + (AMQMethodBody)frame.bodyFrame); + try + { + boolean wasAnyoneInterested = false; + for (AMQMethodListener listener : _frameListeners) + { + wasAnyoneInterested = listener.methodReceived(evt, this, _queueRegistry, _exchangeRegistry) || + wasAnyoneInterested; + } + if (!wasAnyoneInterested) + { + throw new AMQException("AMQMethodEvent " + evt + " was not processed by any listener."); + } + } + catch (AMQChannelException e) + { + _logger.error("Closing channel due to: " + e.getMessage()); + writeFrame(e.getCloseFrame(frame.channel)); + } + catch (AMQException e) + { + for (AMQMethodListener listener : _frameListeners) + { + listener.error(e); + } + _minaProtocolSession.close(); + } + } + + private void contentFrameReceived(AMQFrame frame) throws AMQException + { + if (frame.bodyFrame instanceof ContentHeaderBody) + { + contentHeaderReceived(frame); + } + else if (frame.bodyFrame instanceof ContentBody) + { + contentBodyReceived(frame); + } + else if (frame.bodyFrame instanceof HeartbeatBody) + { + _logger.debug("Received heartbeat from client"); + } + else + { + _logger.warn("Unrecognised frame " + frame.getClass().getName()); + } + } + + private void contentHeaderReceived(AMQFrame frame) throws AMQException + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Content header frame received: " + frame); + } + getChannel(frame.channel).publishContentHeader((ContentHeaderBody)frame.bodyFrame); + } + + private void contentBodyReceived(AMQFrame frame) throws AMQException + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Content body frame received: " + frame); + } + getChannel(frame.channel).publishContentBody((ContentBody)frame.bodyFrame); + } + + /** + * Convenience method that writes a frame to the protocol session. Equivalent + * to calling getProtocolSession().writeDeliver(). + * + * @param frame the frame to writeDeliver + */ + public void writeFrame(AMQDataBlock frame) + { + _lastSent = frame; + _minaProtocolSession.write(frame); + } + + public String getContextKey() + { + return _contextKey; + } + + public void setContextKey(String contextKey) + { + _contextKey = contextKey; + } + + public AMQChannel getChannel(int channelId) throws AMQException + { + return _channelMap.get(channelId); + } + + public void addChannel(AMQChannel channel) + { + _channelMap.put(channel.getChannelId(), channel); + _managedObject.checkForNotification(); + } + + /** + * Close a specific channel. This will remove any resources used by the channel, including: + * <ul><li>any queue subscriptions (this may in turn remove queues if they are auto delete</li> + * </ul> + * @param channelId id of the channel to close + * @throws AMQException if an error occurs closing the channel + * @throws IllegalArgumentException if the channel id is not valid + */ + public void closeChannel(int channelId) throws AMQException + { + final AMQChannel channel = _channelMap.get(channelId); + if (channel == null) + { + throw new IllegalArgumentException("Unknown channel id"); + } + else + { + try + { + channel.close(this); + } + finally + { + _channelMap.remove(channelId); + } + } + } + + /** + * In our current implementation this is used by the clustering code. + * @param channelId + */ + public void removeChannel(int channelId) + { + _channelMap.remove(channelId); + } + + /** + * Initialise heartbeats on the session. + * @param delay delay in seconds (not ms) + */ + public void initHeartbeats(int delay) + { + if(delay > 0) + { + _minaProtocolSession.setIdleTime(IdleStatus.WRITER_IDLE, delay); + _minaProtocolSession.setIdleTime(IdleStatus.READER_IDLE, HeartbeatConfig.getInstance().getTimeout(delay)); + } + } + + /** + * Closes all channels that were opened by this protocol session. This frees up all resources + * used by the channel. + * @throws AMQException if an error occurs while closing any channel + */ + private void closeAllChannels() throws AMQException + { + for (AMQChannel channel : _channelMap.values()) + { + channel.close(this); + } + } + + /** + * This must be called when the session is _closed in order to free up any resources + * managed by the session. + */ + public void closeSession() throws AMQException + { + if(!_closed) + { + _closed = true; + closeAllChannels(); + if (_managedObject != null) + { + _managedObject.unregister(); + } + } + } + + public String toString() + { + return "AMQProtocolSession(" + _minaProtocolSession.getRemoteAddress() + ")"; + } + + public String dump() + { + return this + " last_sent=" + _lastSent + " last_received=" + _lastReceived; + } + + /** + * @return an object that can be used to identity + */ + public Object getKey() + { + return _minaProtocolSession.getRemoteAddress(); + } + + /** + * Get the fully qualified domain name of the local address to which this session is bound. Since some servers + * may be bound to multiple addresses this could vary depending on the acceptor this session was created from. + * + * @return a String FQDN + */ + public String getLocalFQDN() + { + SocketAddress address = _minaProtocolSession.getLocalAddress(); + // we use the vmpipe address in some tests hence the need for this rather ugly test. The host + // information is used by SASL primary. + if (address instanceof InetSocketAddress) + { + return ((InetSocketAddress)address).getHostName(); + } + else if (address instanceof VmPipeAddress) + { + return "vmpipe:" + ((VmPipeAddress)address).getPort(); + } + else + { + throw new IllegalArgumentException("Unsupported socket address class: " + address); + } + } + + public SaslServer getSaslServer() + { + return _saslServer; + } + + public void setSaslServer(SaslServer saslServer) + { + _saslServer = saslServer; + } + + /** + * Convenience methods for managing AMQP version. + * NOTE: Both major and minor will be set to 0 prior to protocol initiation. + */ + + public byte getAmqpMajor() + { + return _major; + } + + public byte getAmqpMinor() + { + return _minor; + } + + public boolean amqpVersionEquals(byte major, byte minor) + { + return _major == major && _minor == minor; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java new file mode 100644 index 0000000000..78fb76ae27 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java @@ -0,0 +1,227 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.transport.ConnectorConfiguration; +import org.apache.qpid.ssl.BogusSSLContextFactory; +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.SSLFilter; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.util.SessionUtil; + +import java.io.IOException; + + +/** + * The protocol handler handles "protocol events" for all connections. The state + * associated with an individual connection is accessed through the protocol session. + * + * We delegate all frame (message) processing to the AMQProtocolSession which wraps + * the state for the connection. + * + */ +public class AMQPFastProtocolHandler extends IoHandlerAdapter implements ProtocolVersionList +{ + private static final Logger _logger = Logger.getLogger(AMQPFastProtocolHandler.class); + + /** + * The registry of all queues. This is passed to frame listeners when frame + * events occur. + */ + private final QueueRegistry _queueRegistry; + + /** + * The registry of all exchanges. This is passed to frame listeners when frame + * events occur. + */ + private final ExchangeRegistry _exchangeRegistry; + + private boolean _useSSL; + + public AMQPFastProtocolHandler(Integer applicationRegistryInstance) + { + IApplicationRegistry registry = ApplicationRegistry.getInstance(applicationRegistryInstance); + + _queueRegistry = registry.getQueueRegistry(); + _exchangeRegistry = registry.getExchangeRegistry(); + _logger.debug("AMQPFastProtocolHandler created"); + } + + public AMQPFastProtocolHandler(QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry) + { + _queueRegistry = queueRegistry; + _exchangeRegistry = exchangeRegistry; + + _logger.debug("AMQPFastProtocolHandler created"); + } + + protected AMQPFastProtocolHandler(AMQPFastProtocolHandler handler) + { + this(handler._queueRegistry, handler._exchangeRegistry); + } + + public void sessionCreated(IoSession protocolSession) throws Exception + { + SessionUtil.initialize(protocolSession); + final AMQCodecFactory codecFactory = new AMQCodecFactory(true); + + createSession(protocolSession, _queueRegistry, _exchangeRegistry, codecFactory); + _logger.info("Protocol session created"); + + final ProtocolCodecFilter pcf = new ProtocolCodecFilter(codecFactory); + + ConnectorConfiguration connectorConfig = ApplicationRegistry.getInstance(). + getConfiguredObject(ConnectorConfiguration.class); + if (connectorConfig.enableExecutorPool) + { + if (_useSSL) + { + protocolSession.getFilterChain().addAfter("AsynchronousReadFilter", "sslFilter", + new SSLFilter(BogusSSLContextFactory.getInstance(true))); + } + protocolSession.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf); + } + else + { + protocolSession.getFilterChain().addLast("protocolFilter", pcf); + } + } + + /** + * Separated into its own, protected, method to allow easier reuse + */ + protected void createSession(IoSession session, QueueRegistry queues, ExchangeRegistry exchanges, AMQCodecFactory codec) throws AMQException + { + new AMQMinaProtocolSession(session, queues, exchanges, codec); + } + + public void sessionOpened(IoSession protocolSession) throws Exception + { + _logger.info("Session opened"); + } + + public void sessionClosed(IoSession protocolSession) throws Exception + { + _logger.info("Protocol Session closed"); + final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + amqProtocolSession.closeSession(); + } + + public void sessionIdle(IoSession session, IdleStatus status) throws Exception + { + _logger.debug("Protocol Session [" + this + "] idle: " + status); + if(IdleStatus.WRITER_IDLE.equals(status)) + { + //writeDeliver heartbeat frame: + session.write(HeartbeatBody.FRAME); + } + else if(IdleStatus.READER_IDLE.equals(status)) + { + //failover: + throw new IOException("Timed out while waiting for heartbeat from peer."); + } + + } + + public void exceptionCaught(IoSession protocolSession, Throwable throwable) throws Exception + { + AMQProtocolSession session = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + if (throwable instanceof AMQProtocolHeaderException) + { + /* Find last protocol version in protocol version list. Make sure last protocol version + listed in the build file (build-module.xml) is the latest version which will be returned + here. */ + int i = pv.length - 1; + protocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR])); + protocolSession.close(); + _logger.error("Error in protocol initiation " + session + ": " + throwable.getMessage(), throwable); + } + else if(throwable instanceof IOException) + { + _logger.error("IOException caught in" + session + ", session closed implictly: " + throwable, throwable); + } + else + { + protocolSession.write(ConnectionCloseBody.createAMQFrame(0, 200, throwable.getMessage(), 0, 0)); + _logger.error("Exception caught in" + session + ", closing session explictly: " + throwable, throwable); + protocolSession.close(); + } + } + + /** + * Invoked when a message is received on a particular protocol session. Note that a + * protocol session is directly tied to a particular physical connection. + * @param protocolSession the protocol session that received the message + * @param message the message itself (i.e. a decoded frame) + * @throws Exception if the message cannot be processed + */ + public void messageReceived(IoSession protocolSession, Object message) throws Exception + { + final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + + if (message instanceof AMQDataBlock) + { + amqProtocolSession.dataBlockReceived((AMQDataBlock) message); + } + else if (message instanceof ByteBuffer) + { + throw new IllegalStateException("Handed undecoded ByteBuffer buf = " + message); + } + else + { + throw new IllegalStateException("Handed unhandled message. message.class = " + message.getClass() + " message = " + message); + } + } + + /** + * Called after a message has been sent out on a particular protocol session + * @param protocolSession the protocol session (i.e. connection) on which this + * message was sent + * @param object the message (frame) that was encoded and sent + * @throws Exception if we want to indicate an error + */ + public void messageSent(IoSession protocolSession, Object object) throws Exception + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Message sent: " + object); + } + } + + public boolean isUseSSL() + { + return _useSSL; + } + + public void setUseSSL(boolean useSSL) + { + _useSSL = useSSL; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java new file mode 100644 index 0000000000..0088db08bb --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; + +/** + * The protocol provide's role is to encapsulate the initialisation of the protocol handler. + * + * The protocol handler (see AMQPFastProtocolHandler class) handles protocol events + * such as connection closing or a frame being received. It can either do this directly + * or pass off to the protocol session in the cases where state information is required to + * deal with the event. + * + */ +public class AMQPProtocolProvider +{ + /** + * Handler for protocol events + */ + private AMQPFastProtocolHandler _handler; + + public AMQPProtocolProvider() + { + IApplicationRegistry registry = ApplicationRegistry.getInstance(); + _handler = new AMQPFastProtocolHandler(registry.getQueueRegistry(), + registry.getExchangeRegistry()); + } + + public AMQPFastProtocolHandler getHandler() + { + return _handler; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java new file mode 100644 index 0000000000..402ebc329d --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java @@ -0,0 +1,122 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.AMQException; + +import javax.security.sasl.SaslServer; + + +public interface AMQProtocolSession +{ + /** + * Called when a protocol data block is received + * @param message the data block that has been received + * @throws Exception if processing the datablock fails + */ + void dataBlockReceived(AMQDataBlock message) throws Exception; + + /** + * Write a datablock, encoding where necessary (e.g. into a sequence of bytes) + * @param frame the frame to be encoded and written + */ + void writeFrame(AMQDataBlock frame); + + /** + * Get the context key associated with this session. Context key is described + * in the AMQ protocol specification (RFC 6). + * @return the context key + */ + String getContextKey(); + + /** + * Set the context key associated with this session. Context key is described + * in the AMQ protocol specification (RFC 6). + * @param contextKey the context key + */ + void setContextKey(String contextKey); + + /** + * Get the channel for this session associated with the specified id. A channel + * id is unique per connection (i.e. per session). + * @param channelId the channel id which must be valid + * @return null if no channel exists, the channel otherwise + */ + AMQChannel getChannel(int channelId) throws AMQException; + + /** + * Associate a channel with this session. + * @param channel the channel to associate with this session. It is an error to + * associate the same channel with more than one session but this is not validated. + */ + void addChannel(AMQChannel channel); + + /** + * Close a specific channel. This will remove any resources used by the channel, including: + * <ul><li>any queue subscriptions (this may in turn remove queues if they are auto delete</li> + * </ul> + * @param channelId id of the channel to close + * @throws org.apache.qpid.AMQException if an error occurs closing the channel + * @throws IllegalArgumentException if the channel id is not valid + */ + void closeChannel(int channelId) throws AMQException; + + /** + * Remove a channel from the session but do not close it. + * @param channelId + */ + void removeChannel(int channelId); + + /** + * Initialise heartbeats on the session. + * @param delay delay in seconds (not ms) + */ + void initHeartbeats(int delay); + + /** + * This must be called when the session is _closed in order to free up any resources + * managed by the session. + */ + void closeSession() throws AMQException; + + /** + * @return a key that uniquely identifies this session + */ + Object getKey(); + + /** + * Get the fully qualified domain name of the local address to which this session is bound. Since some servers + * may be bound to multiple addresses this could vary depending on the acceptor this session was created from. + * + * @return a String FQDN + */ + String getLocalFQDN(); + + /** + * @return the sasl server that can perform authentication for this session. + */ + SaslServer getSaslServer(); + + /** + * Set the sasl server that is to perform authentication for this session. + * @param saslServer + */ + void setSaslServer(SaslServer saslServer); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java new file mode 100644 index 0000000000..08c31ed3ff --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; + +public class ExchangeInitialiser +{ + public void initialise(ExchangeFactory factory, ExchangeRegistry registry) throws AMQException{ + define(registry, factory, ExchangeDefaults.DIRECT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS); + define(registry, factory, ExchangeDefaults.TOPIC_EXCHANGE_NAME, ExchangeDefaults.TOPIC_EXCHANGE_CLASS); + define(registry, factory, ExchangeDefaults.HEADERS_EXCHANGE_NAME, ExchangeDefaults.HEADERS_EXCHANGE_CLASS); + } + + private void define(ExchangeRegistry r, ExchangeFactory f, + String name, String type) throws AMQException + { + r.registerExchange(f.createExchange(name, type, true, false, 0)); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java new file mode 100644 index 0000000000..d7678185d4 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.qpid.configuration.Configured; +import org.apache.qpid.server.registry.ApplicationRegistry; + +public class HeartbeatConfig +{ + @Configured(path = "heartbeat.delay", defaultValue = "5") + public int delay = 5;//in secs + @Configured(path = "heartbeat.timeoutFactor", defaultValue = "2.0") + public double timeoutFactor = 2; + + public double getTimeoutFactor() + { + return timeoutFactor; + } + + public void setTimeoutFactor(double timeoutFactor) + { + this.timeoutFactor = timeoutFactor; + } + + public int getDelay() + { + return delay; + } + + public void setDelay(int delay) + { + this.delay = delay; + } + + int getTimeout(int writeDelay) + { + return (int) (timeoutFactor * writeDelay); + } + + public static HeartbeatConfig getInstance() + { + return ApplicationRegistry.getInstance().getConfiguredObject(HeartbeatConfig.class); + } + + public String toString() + { + return "HeartBeatConfig{delay = " + delay + " timeoutFactor = " + timeoutFactor + "}"; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java new file mode 100644 index 0000000000..2a800f327d --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java @@ -0,0 +1,138 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.qpid.server.management.MBeanOperationParameter; +import org.apache.qpid.server.management.MBeanAttribute; +import org.apache.qpid.server.management.MBeanOperation; + +import javax.management.openmbean.TabularData; +import javax.management.JMException; +import javax.management.MBeanOperationInfo; +import java.util.Date; +import java.io.IOException; + +/** + * The management interface exposed to allow management of Connections. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedConnection +{ + static final String TYPE = "Connection"; + + /** + * channel details of all the channels opened for this connection. + * @return general channel details + * @throws IOException + * @throws JMException + */ + @MBeanAttribute(name="Channels", + description="channel details of all the channels opened for this connection") + TabularData getChannels() throws IOException, JMException; + + /** + * Tells the last time, the IO operation was done. + * @return last IO time. + */ + @MBeanAttribute(name="LastIOTime", + description="The last time, the IO operation was done") + Date getLastIoTime(); + + /** + * Tells the remote address of this connection. + * @return remote address + */ + @MBeanAttribute(name="RemoteAddress", + description="The remote address of this connection") + String getRemoteAddress(); + + /** + * Tells the total number of bytes written till now. + * @return number of bytes written. + */ + @MBeanAttribute(name="WrittenBytes", + description="The total number of bytes written till now") + Long getWrittenBytes(); + + /** + * Tells the total number of bytes read till now. + * @return number of bytes read. + */ + @MBeanAttribute(name="ReadBytes", + description="The total number of bytes read till now") + Long getReadBytes(); + + /** + * Tells the maximum number of channels that can be opened using + * this connection. This is useful in setting notifications or + * taking required action is there are more channels being created. + * @return maximum number of channels allowed to be created. + */ + @MBeanAttribute(name="MaximumNumberOfAllowedChannels", + description="The maximum number of channels that can be opened using this connection") + Long getMaximumNumberOfAllowedChannels(); + + /** + * Sets the maximum number of channels allowed to be created using + * this connection. + * @param value + */ + void setMaximumNumberOfAllowedChannels(Long value); + + //********** Operations *****************// + + /** + * Closes all the related channels and unregisters this connection from managed objects. + */ + @MBeanOperation(name="closeConnection", + description="Closes this connection and all related channels", + impact= MBeanOperationInfo.ACTION) + void closeConnection() throws Exception; + + /** + * Unsubscribes the consumers and unregisters the channel from managed objects. + */ + @MBeanOperation(name="closeChannel", + description="Closes the channel with given channeld and" + + "connected consumers will be unsubscribed", + impact= MBeanOperationInfo.ACTION) + void closeChannel(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId) + throws Exception; + + /** + * Commits the transactions if the channel is transactional. + * @param channelId + * @throws JMException + */ + @MBeanOperation(name="commitTransaction", + description="Commits the transactions for given channelID, if the channel is transactional", + impact= MBeanOperationInfo.ACTION) + void commitTransactions(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId) throws JMException; + + /** + * Rollsback the transactions if the channel is transactional. + * @param channelId + * @throws JMException + */ + @MBeanOperation(name="rollbackTransactions", + description="Rollsback the transactions for given channelId, if the channel is transactional", + impact= MBeanOperationInfo.ACTION) + void rollbackTransactions(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId) throws JMException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedSession.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedSession.java new file mode 100644 index 0000000000..2a1a0b62c2 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedSession.java @@ -0,0 +1,33 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import java.util.Date; + +public interface ManagedSession +{ + static final String TYPE = "Connection"; + + Date getLastIoTime(); + + String getRemoteAddress(); + + long getWrittenBytes(); + + long getReadBytes(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java new file mode 100644 index 0000000000..afab90dcc4 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java @@ -0,0 +1,520 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.log4j.Logger; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Combines the information that make up a deliverable message into a more manageable form. + */ +public class AMQMessage +{ + private static final Logger _log = Logger.getLogger(AMQMessage.class); + + /** + * Used in clustering + */ + private final Set<Object> _tokens = new HashSet<Object>(); + + /** + * Used in clustering + * TODO need to get rid of this + */ + private AMQProtocolSession _publisher; + + private final long _messageId; + + private final AtomicInteger _referenceCount = new AtomicInteger(1); + + private AMQMessageHandle _messageHandle; + + /** + * Stored temporarily until the header has been received at which point it is used when + * constructing the handle + */ + private BasicPublishBody _publishBody; + + /** + * Also stored temporarily. + */ + private ContentHeaderBody _contentHeaderBody; + + /** + * Keeps a track of how many bytes we have received in body frames + */ + private long _bodyLengthReceived = 0; + + private final TransactionalContext _txnContext; + + /** + * Flag to indicate whether message has been delivered to a + * consumer. Used in implementing return functionality for + * messages published with the 'immediate' flag. + */ + private boolean _deliveredToConsumer; + + /** + * This is stored during routing, to know the queues to which this message should immediately be + * delivered. It is <b>cleared after delivery has been attempted</b>. Any persistent record of destinations is done + * by the message handle. + */ + private List<AMQQueue> _destinationQueues = new LinkedList<AMQQueue>(); + + /** + * Used to iterate through all the body frames associated with this message. Will not + * keep all the data in memory therefore is memory-efficient. + */ + private class BodyFrameIterator implements Iterator<AMQDataBlock> + { + private int _channel; + + private int _index = -1; + + private BodyFrameIterator(int channel) + { + _channel = channel; + } + + public boolean hasNext() + { + return _index < _messageHandle.getBodyCount() - 1; + } + + public AMQDataBlock next() + { + try + { + ContentBody cb = _messageHandle.getContentBody(_messageId, ++_index); + return ContentBody.createAMQFrame(_channel, cb); + } + catch (AMQException e) + { + // have no choice but to throw a runtime exception + throw new RuntimeException("Error getting content body: " + e, e); + } + + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + } + + private class BodyContentIterator implements Iterator<ContentBody> + { + + private int _index = -1; + + public boolean hasNext() + { + return _index < _messageHandle.getBodyCount() - 1; + } + + public ContentBody next() + { + try + { + return _messageHandle.getContentBody(_messageId, ++_index); + } + catch (AMQException e) + { + throw new RuntimeException("Error getting content body: " + e, e); + } + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + } + + public AMQMessage(long messageId, BasicPublishBody publishBody, TransactionalContext txnContext) + { + _messageId = messageId; + _txnContext = txnContext; + _publishBody = publishBody; + if (_log.isDebugEnabled()) + { + _log.debug("Message created with id " + messageId); + } + } + + protected AMQMessage(AMQMessage msg) throws AMQException + { + _publisher = msg._publisher; + _messageId = msg._messageId; + _messageHandle = msg._messageHandle; + _txnContext = msg._txnContext; + _deliveredToConsumer = msg._deliveredToConsumer; + } + + public Iterator<AMQDataBlock> getBodyFrameIterator(int channel) + { + return new BodyFrameIterator(channel); + } + + public Iterator<ContentBody> getContentBodyIterator() + { + return new BodyContentIterator(); + } + + public ContentHeaderBody getContentHeaderBody() throws AMQException + { + return _messageHandle.getContentHeaderBody(_messageId); + } + + public void setContentHeaderBody(ContentHeaderBody contentHeaderBody) + throws AMQException + { + _contentHeaderBody = contentHeaderBody; + } + + public void routingComplete(MessageStore store, MessageHandleFactory factory) throws AMQException + { + final boolean persistent = isPersistent(); + _messageHandle = factory.createMessageHandle(_messageId, store, persistent); + if (persistent) + { + _txnContext.beginTranIfNecessary(); + } + + // enqueuing the messages ensure that if required the destinations are recorded to a + // persistent store + for (AMQQueue q : _destinationQueues) + { + _messageHandle.enqueue(_messageId, q); + } + + if (_contentHeaderBody.bodySize == 0) + { + deliver(); + } + } + + public boolean addContentBodyFrame(ContentBody contentBody) throws AMQException + { + _bodyLengthReceived += contentBody.getSize(); + _messageHandle.addContentBodyFrame(_messageId, contentBody); + if (isAllContentReceived()) + { + deliver(); + return true; + } + else + { + return false; + } + } + + public boolean isAllContentReceived() throws AMQException + { + return _bodyLengthReceived == _contentHeaderBody.bodySize; + } + + public long getMessageId() + { + return _messageId; + } + + /** + * Threadsafe. Increment the reference count on the message. + */ + public void incrementReference() + { + _referenceCount.incrementAndGet(); + if (_log.isDebugEnabled()) + { + _log.debug("Ref count on message " + _messageId + " incremented to " + _referenceCount); + } + } + + /** + * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the + * message store. + * + * @throws MessageCleanupException when an attempt was made to remove the message from the message store and that + * failed + */ + public void decrementReference() throws MessageCleanupException + { + // note that the operation of decrementing the reference count and then removing the message does not + // have to be atomic since the ref count starts at 1 and the exchange itself decrements that after + // the message has been passed to all queues. i.e. we are + // not relying on the all the increments having taken place before the delivery manager decrements. + if (_referenceCount.decrementAndGet() == 0) + { + try + { + if (_log.isDebugEnabled()) + { + _log.debug("Ref count on message " + _messageId + " is zero; removing message"); + } + _messageHandle.removeMessage(_messageId); + } + catch (AMQException e) + { + //to maintain consistency, we revert the count + incrementReference(); + throw new MessageCleanupException(_messageId, e); + } + } + else + { + if (_log.isDebugEnabled()) + { + _log.debug("Ref count is now " + _referenceCount + " for message id " + _messageId); + if (_referenceCount.get() < 0) + { + Thread.dumpStack(); + } + } + } + } + + public void setPublisher(AMQProtocolSession publisher) + { + _publisher = publisher; + } + + public AMQProtocolSession getPublisher() + { + return _publisher; + } + + public boolean checkToken(Object token) + { + if (_tokens.contains(token)) + { + return true; + } + else + { + _tokens.add(token); + return false; + } + } + + /** + * Registers a queue to which this message is to be delivered. This is + * called from the exchange when it is routing the message. This will be called before any content bodies have + * been received so that the choice of AMQMessageHandle implementation can be picked based on various criteria. + * + * @param queue the queue + * @throws org.apache.qpid.AMQException if there is an error enqueuing the message + */ + public void enqueue(AMQQueue queue) throws AMQException + { + _destinationQueues.add(queue); + } + + public void dequeue(AMQQueue queue) throws AMQException + { + _messageHandle.dequeue(_messageId, queue); + } + + public boolean isPersistent() throws AMQException + { + if (_contentHeaderBody != null) + { + //todo remove literal values to a constant file such as AMQConstants in common + return _contentHeaderBody.properties instanceof BasicContentHeaderProperties && + ((BasicContentHeaderProperties) _contentHeaderBody.properties).getDeliveryMode() == 2; + } + else + { + return _messageHandle.isPersistent(_messageId); + } + } + + /** + * Called to enforce the 'immediate' flag. + * + * @throws NoConsumersException if the message is marked for + * immediate delivery but has not been marked as delivered to a + * consumer + */ + public void checkDeliveredToConsumer() throws NoConsumersException, AMQException + { + BasicPublishBody pb = getPublishBody(); + if (pb.immediate && !_deliveredToConsumer) + { + throw new NoConsumersException(this); + } + } + + public BasicPublishBody getPublishBody() throws AMQException + { + BasicPublishBody pb; + if (_publishBody != null) + { + pb = _publishBody; + } + else + { + pb = _messageHandle.getPublishBody(_messageId); + } + return pb; + } + + /** + * Called when this message is delivered to a consumer. (used to + * implement the 'immediate' flag functionality). + */ + public void setDeliveredToConsumer() + { + _deliveredToConsumer = true; + } + + /*public void registerQueue(AMQQueue queue) + { + _destinationQueues.add(queue); + } */ + + private void deliver() throws AMQException + { + // first we allow the handle to know that the message has been fully received. This is useful if it is + // maintaining any calculated values based on content chunks + try + { + _messageHandle.setPublishAndContentHeaderBody(_messageId, _publishBody, _contentHeaderBody); + _publishBody = null; + _contentHeaderBody = null; + + // we then allow the transactional context to do something with the message content + // now that it has all been received, before we attempt delivery + _txnContext.messageFullyReceived(isPersistent()); + for (AMQQueue q : _destinationQueues) + { + _txnContext.deliver(this, q); + } + } + finally + { + _destinationQueues.clear(); + _destinationQueues = null; + decrementReference(); + } + } + + public void writeDeliver(AMQProtocolSession protocolSession, int channelId, long deliveryTag, String consumerTag) + throws AMQException + { + ByteBuffer deliver = createEncodedDeliverFrame(channelId, deliveryTag, consumerTag); + AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId, + getContentHeaderBody()); + + Iterator<AMQDataBlock> bodyFrameIterator = getBodyFrameIterator(channelId); + // + // Optimise the case where we have a single content body. In that case we create a composite block + // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver. + // + if (bodyFrameIterator.hasNext()) + { + AMQDataBlock firstContentBody = bodyFrameIterator.next(); + AMQDataBlock[] headerAndFirstContent = new AMQDataBlock[]{contentHeader, firstContentBody}; + CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(deliver, headerAndFirstContent); + protocolSession.writeFrame(compositeBlock); + } + else + { + CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(deliver, + new AMQDataBlock[]{contentHeader}); + protocolSession.writeFrame(compositeBlock); + } + + // + // Now start writing out the other content bodies + // TODO: MINA needs to be fixed so the the pending writes buffer is not unbounded + // + while (bodyFrameIterator.hasNext()) + { + protocolSession.writeFrame(bodyFrameIterator.next()); + } + + } + + private ByteBuffer createEncodedDeliverFrame(int channelId, long deliveryTag, String consumerTag) + throws AMQException + { + BasicPublishBody pb = getPublishBody(); + AMQFrame deliverFrame = BasicDeliverBody.createAMQFrame(channelId, consumerTag, + deliveryTag, false, pb.exchange, + pb.routingKey); + ByteBuffer buf = ByteBuffer.allocate((int) deliverFrame.getSize()); // XXX: Could cast be a problem? + deliverFrame.writePayload(buf); + buf.flip(); + return buf; + } + + private ByteBuffer createEncodedReturnFrame(int channelId, int replyCode, String replyText) + { + AMQFrame returnFrame = BasicReturnBody.createAMQFrame(channelId, replyCode, replyText, _publishBody.exchange, + _publishBody.routingKey); + ByteBuffer buf = ByteBuffer.allocate((int) returnFrame.getSize()); // XXX: Could cast be a problem? + returnFrame.writePayload(buf); + buf.flip(); + return buf; + } + + public void writeReturn(AMQProtocolSession protocolSession, int channelId, int replyCode, String replyText) + throws AMQException + { + ByteBuffer returnFrame = createEncodedReturnFrame(channelId, replyCode, replyText); + + AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId, + getContentHeaderBody()); + + Iterator<AMQDataBlock> bodyFrameIterator = getBodyFrameIterator(channelId); + // + // Optimise the case where we have a single content body. In that case we create a composite block + // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver. + // + if (bodyFrameIterator.hasNext()) + { + AMQDataBlock firstContentBody = bodyFrameIterator.next(); + AMQDataBlock[] headerAndFirstContent = new AMQDataBlock[]{contentHeader, firstContentBody}; + CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(returnFrame, headerAndFirstContent); + protocolSession.writeFrame(compositeBlock); + } + else + { + CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(returnFrame, + new AMQDataBlock[]{contentHeader}); + protocolSession.writeFrame(compositeBlock); + } + + // + // Now start writing out the other content bodies + // TODO: MINA needs to be fixed so the the pending writes buffer is not unbounded + // + while (bodyFrameIterator.hasNext()) + { + protocolSession.writeFrame(bodyFrameIterator.next()); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java new file mode 100644 index 0000000000..0fc9ec5dcd --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java @@ -0,0 +1,62 @@ +/** + * User: Robert Greig + * Date: 23-Oct-2006 + ****************************************************************************** + * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of + * this program may be photocopied reproduced or translated to another + * program language without prior written consent of JP Morgan Chase Ltd + ******************************************************************************/ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; + +/** + * A pluggable way of getting message data. Implementations can provide intelligent caching for example or + * even no caching at all to minimise the broker memory footprint. + * + * The method all take a messageId to avoid having to store it in the instance - the AMQMessage container + * must already keen the messageId so it is pointless storing it twice. + */ +public interface AMQMessageHandle +{ + ContentHeaderBody getContentHeaderBody(long messageId) throws AMQException; + + /** + * @return the number of body frames associated with this message + */ + int getBodyCount(); + + /** + * @return the size of the body + */ + long getBodySize(long messageId) throws AMQException; + + /** + * Get a particular content body + * @param index the index of the body to retrieve, must be between 0 and getBodyCount() - 1 + * @return a content body + * @throws IllegalArgumentException if the index is invalid + */ + ContentBody getContentBody(long messageId, int index) throws IllegalArgumentException, AMQException; + + void addContentBodyFrame(long messageId, ContentBody contentBody) throws AMQException; + + BasicPublishBody getPublishBody(long messageId) throws AMQException; + + boolean isRedelivered(); + + boolean isPersistent(long messageId) throws AMQException; + + void setPublishAndContentHeaderBody(long messageId, BasicPublishBody publishBody, + ContentHeaderBody contentHeaderBody) + throws AMQException; + + void removeMessage(long messageId) throws AMQException; + + void enqueue(long messageId, AMQQueue queue) throws AMQException; + + void dequeue(long messageId, AMQQueue queue) throws AMQException; +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java new file mode 100644 index 0000000000..d8bab6e3bc --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java @@ -0,0 +1,764 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.management.*; +import org.apache.qpid.server.protocol.AMQProtocolSession; + +import javax.management.*; +import javax.management.monitor.MonitorNotification; +import javax.management.openmbean.*; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * This is an AMQ Queue, and should not be confused with a JMS queue or any other abstraction like + * that. It is described fully in RFC 006. + */ +public class AMQQueue implements Managable +{ + private static final Logger _logger = Logger.getLogger(AMQQueue.class); + + private final String _name; + + /** + * null means shared + */ + private final String _owner; + + private final boolean _durable; + + /** + * If true, this queue is deleted when the last subscriber is removed + */ + private final boolean _autoDelete; + + /** + * Holds subscribers to the queue. + */ + private final SubscriptionSet _subscribers; + + private final SubscriptionFactory _subscriptionFactory; + + /** + * Manages message delivery. + */ + private final DeliveryManager _deliveryMgr; + + /** + * The queue registry with which this queue is registered. + */ + private final QueueRegistry _queueRegistry; + + /** + * Used to track bindings to exchanges so that on deletion they can easily + * be cancelled. + */ + private final ExchangeBindings _bindings = new ExchangeBindings(this); + + /** + * Executor on which asynchronous delivery will be carriedout where required + */ + private final Executor _asyncDelivery; + + private final AMQQueueMBean _managedObject; + + /** + * max allowed size of a single message(in KBytes). + */ + private long _maxAllowedMessageSize = 10000; // 10 MB + + /** + * max allowed number of messages on a queue. + */ + private Integer _maxAllowedMessageCount = 10000; + + /** + * max allowed size in KBytes for all the messages combined together in a queue. + */ + private long _queueDepth = 10000000; // 10 GB + + /** + * total messages received by the queue since startup. + */ + private long _totalMessagesReceived = 0; + + /** + * MBean class for AMQQueue. It implements all the management features exposed + * for an AMQQueue. + */ + @MBeanDescription("Management Interface for AMQQueue") + private final class AMQQueueMBean extends AMQManagedObject implements ManagedQueue + { + private String _queueName = null; + //private MBeanInfo _mbeanInfo; + + // AMQ message attribute names exposed. + private String[] _msgAttributeNames = { "MessageId", + "Redelivered", + "Content's size", + "Contents" }; + // AMQ Message attribute descriptions. + private String[] _msgAttributeDescriptions = { "Message Id", + "Redelivered", + "Message content's size in bytes", + "Message content bodies" }; + // AMQ message attribute types. + private OpenType[] _msgAttributeTypes = new OpenType[4]; + // Messages will be indexed according to the messageId. + private String[] _msgAttributeIndex = { "MessageId"}; + // Composite type for representing AMQ Message data. + private CompositeType _messageDataType = null; + // Datatype for representing AMQ messages list. + private TabularType _messagelistDataType = null; + + private String[] _contentNames = {"SerialNumber", "ContentBody"}; + private String[] _contentDesc = {"SerialNumber", "Message Content"}; + private String[] _contentIndex = {"SerialNumber"}; + private OpenType[] _contentType = new OpenType[2]; + private CompositeType _contentBodyType = null; + private TabularType _contentBodyListType = null; + + @MBeanConstructor("Creates an MBean exposing an AMQQueue.") + public AMQQueueMBean() throws NotCompliantMBeanException + { + super(ManagedQueue.class, ManagedQueue.TYPE); + init(); + } + + private void init() + { + _queueName = jmxEncode(new StringBuffer(_name), 0).toString(); + try + { + _contentType[0] = SimpleType.INTEGER; + _contentType[1] = new ArrayType(1, SimpleType.BYTE); + _contentBodyType = new CompositeType("Content", + "Message body content", + _contentNames, + _contentDesc, + _contentType); + _contentBodyListType = new TabularType("MessageContents", + "MessageContent", + _contentBodyType, + _contentIndex); + + _msgAttributeTypes[0] = SimpleType.LONG; + _msgAttributeTypes[1] = SimpleType.BOOLEAN; + _msgAttributeTypes[2] = SimpleType.LONG; + _msgAttributeTypes[3] = _contentBodyListType; + + _messageDataType = new CompositeType("Message", + "AMQ Message", + _msgAttributeNames, + _msgAttributeDescriptions, + _msgAttributeTypes); + _messagelistDataType = new TabularType("Messages", + "List of messages", + _messageDataType, + _msgAttributeIndex); + } + catch (OpenDataException ex) + { + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public String getObjectInstanceName() + { + return _queueName; + } + + public String getName() + { + return _name; + } + + public boolean isDurable() + { + return _durable; + } + + public String getOwner() + { + return _owner; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public Integer getMessageCount() + { + return _deliveryMgr.getQueueMessageCount(); + } + + public Long getMaximumMessageSize() + { + return _maxAllowedMessageSize; + } + + public void setMaximumMessageSize(Long value) + { + _maxAllowedMessageSize = value; + } + + public Integer getConsumerCount() + { + return _subscribers.size(); + } + + public Integer getActiveConsumerCount() + { + return _subscribers.getWeight(); + } + + public Long getReceivedMessageCount() + { + return _totalMessagesReceived; + } + + public Integer getMaximumMessageCount() + { + return _maxAllowedMessageCount; + } + + public void setMaximumMessageCount(Integer value) + { + _maxAllowedMessageCount = value; + } + + public Long getQueueDepth() + { + return _queueDepth; + } + + // Sets the queue depth, the max queue size + public void setQueueDepth(Long value) + { + _queueDepth = value; + } + + // Returns the size of messages in the queue + public Long getQueueSize() throws AMQException + { + List<AMQMessage> list = _deliveryMgr.getMessages(); + if (list.size() == 0) + return 0l; + + long queueSize = 0; + for (AMQMessage message : list) + { + queueSize = queueSize + getMessageSize(message); + } + return (long) Math.round(queueSize/100); + } + // Operations + + // calculates the size of an AMQMessage + private long getMessageSize(AMQMessage msg) throws AMQException + { + if (msg == null) + { + return 0L; + } + + return msg.getContentHeaderBody().bodySize; + } + + // Checks if there is any notification to be send to the listeners + private void checkForNotification(AMQMessage msg) throws AMQException + { + // Check for message count + Integer msgCount = getMessageCount(); + if (msgCount >= getMaximumMessageCount()) + { + notifyClients("MessageCount = " + msgCount + ", Queue has reached its size limit and is now full."); + } + + // Check for received message size + long messageSize = getMessageSize(msg); + if (messageSize >= getMaximumMessageSize()) + { + notifyClients("MessageSize = " + messageSize + ", Message size (MessageID="+ msg.getMessageId() + + ")is higher than the threshold value"); + } + + // Check for queue size in bytes + long queueSize = getQueueSize(); + if (queueSize >= getQueueDepth()) + { + notifyClients("QueueSize = " + queueSize + ", Queue size has reached the threshold value"); + } + } + + // Send the notification to the listeners + private void notifyClients(String notificationMsg) + { + Notification n = new Notification( + MonitorNotification.THRESHOLD_VALUE_EXCEEDED, + this, + ++_notificationSequenceNumber, + System.currentTimeMillis(), + notificationMsg); + + _broadcaster.sendNotification(n); + } + + public void deleteMessageFromTop() throws JMException + { + try + { + _deliveryMgr.removeAMessageFromTop(); + } + catch(AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + public void clearQueue() throws JMException + { + try + { + _deliveryMgr.clearAllMessages(); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * Returns the messages stored in this queue in tabular form. + * @param beginIndex + * @param endIndex + * @return AMQ messages in tabular form. + * @throws JMException + */ + public TabularData viewMessages(int beginIndex, int endIndex) throws JMException + { + if ((beginIndex > endIndex) || (beginIndex < 1)) + { + throw new JMException("FromIndex = " + beginIndex + ", ToIndex = " + endIndex + + "\nFromIndex should be greater than 0 and less than ToIndex"); + } + + List<AMQMessage> list = _deliveryMgr.getMessages(); + + if (beginIndex > list.size()) + { + throw new JMException("FromIndex = " + beginIndex + ". There are only " + list.size() + + " messages in the queue"); + } + + endIndex = endIndex < list.size() ? endIndex : list.size(); + TabularDataSupport _messageList = new TabularDataSupport(_messagelistDataType); + + for (int i = beginIndex; i <= endIndex; i++) + { + AMQMessage msg = list.get(i - 1); + long msgId = msg.getMessageId(); + + Iterator<ContentBody> cBodies = msg.getContentBodyIterator(); + + TabularDataSupport _contentList = new TabularDataSupport(_contentBodyListType); + int contentSerialNo = 1; + long size = 0; + + while (cBodies.hasNext()) + { + ContentBody body = cBodies.next(); + if (body.getSize() != 0) + { + Byte[] byteArray = getByteArray(body.payload.slice().array()); + size = size + byteArray.length; + + Object[] contentValues = {contentSerialNo, byteArray}; + CompositeData contentData = new CompositeDataSupport(_contentBodyType, + _contentNames, + contentValues); + + _contentList.put(contentData); + } + } + + Object[] itemValues = {msgId, true, size, _contentList}; + CompositeData messageData = new CompositeDataSupport(_messageDataType, + _msgAttributeNames, + itemValues); + _messageList.put(messageData); + } + + return _messageList; + } + + /** + * A utility to convert byte[] to Byte[]. Required to create composite + * type for message contents. + * @param byteArray message content as byte[] + * @return Byte[] + */ + private Byte[] getByteArray(byte[] byteArray) + { + int size = byteArray.length; + List<Byte> list = new ArrayList<Byte>(); + + for (int i = 0; i < size; i++) + { + list.add(byteArray[i]); + } + + return list.toArray(new Byte[0]); + } + + /** + * Creates all the notifications this MBean can send. + * @return Notifications broadcasted by this MBean. + */ + @Override + public MBeanNotificationInfo[] getNotificationInfo() + { + String[] notificationTypes = new String[] + {MonitorNotification.THRESHOLD_VALUE_EXCEEDED}; + String name = MonitorNotification.class.getName(); + String description = "An attribute of this MBean has reached threshold value"; + MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, + name, + description); + + return new MBeanNotificationInfo[] {info1}; + } + + } // End of AMQMBean class + + public AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry) + throws AMQException + { + this(name, durable, owner, autoDelete, queueRegistry, + AsyncDeliveryConfig.getAsyncDeliveryExecutor(), new SubscriptionImpl.Factory()); + } + + public AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, SubscriptionFactory subscriptionFactory) + throws AMQException + { + this(name, durable, owner, autoDelete, queueRegistry, + AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscriptionFactory); + } + + public AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, Executor asyncDelivery, + SubscriptionFactory subscriptionFactory) + throws AMQException + { + + this(name, durable, owner, autoDelete, queueRegistry, asyncDelivery, new SubscriptionSet(), subscriptionFactory); + } + + public AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, Executor asyncDelivery) + throws AMQException + { + + this(name, durable, owner, autoDelete, queueRegistry, asyncDelivery, new SubscriptionSet(), + new SubscriptionImpl.Factory()); + } + + protected AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, + SubscriptionSet subscribers, SubscriptionFactory subscriptionFactory) + throws AMQException + { + this(name, durable, owner, autoDelete, queueRegistry, + AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscribers, subscriptionFactory); + } + + protected AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, + SubscriptionSet subscribers) + throws AMQException + { + this(name, durable, owner, autoDelete, queueRegistry, + AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscribers, new SubscriptionImpl.Factory()); + } + + protected AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, + Executor asyncDelivery, SubscriptionSet subscribers, SubscriptionFactory subscriptionFactory) + throws AMQException + { + if (name == null) + { + throw new IllegalArgumentException("Queue name must not be null"); + } + if (queueRegistry == null) + { + throw new IllegalArgumentException("Queue registry must not be null"); + } + _name = name; + _durable = durable; + _owner = owner; + _autoDelete = autoDelete; + _queueRegistry = queueRegistry; + _asyncDelivery = asyncDelivery; + _managedObject = createMBean(); + _managedObject.register(); + _subscribers = subscribers; + _subscriptionFactory = subscriptionFactory; + _deliveryMgr = new DeliveryManager(_subscribers, this); + } + + private AMQQueueMBean createMBean() throws AMQException + { + try + { + return new AMQQueueMBean(); + } + catch(NotCompliantMBeanException ex) + { + throw new AMQException("AMQQueue MBean creation has failed.", ex); + } + } + + public String getName() + { + return _name; + } + + public boolean isShared() + { + return _owner == null; + } + + public boolean isDurable() + { + return _durable; + } + + public String getOwner() + { + return _owner; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public int getMessageCount() + { + return _deliveryMgr.getQueueMessageCount(); + } + + public ManagedObject getManagedObject() + { + return _managedObject; + } + + public void bind(String routingKey, Exchange exchange) + { + _bindings.addBinding(routingKey, exchange); + } + + public void registerProtocolSession(AMQProtocolSession ps, int channel, String consumerTag, boolean acks) + throws AMQException + { + debug("Registering protocol session {0} with channel {1} and consumer tag {2} with {3}", ps, channel, consumerTag, this); + + Subscription subscription = _subscriptionFactory.createSubscription(channel, ps, consumerTag, acks); + _subscribers.addSubscriber(subscription); + } + + public void unregisterProtocolSession(AMQProtocolSession ps, int channel, String consumerTag) throws AMQException + { + debug("Unregistering protocol session {0} with channel {1} and consumer tag {2} from {3}", ps, channel, consumerTag, + this); + + Subscription removedSubscription; + if ((removedSubscription = _subscribers.removeSubscriber(_subscriptionFactory.createSubscription(channel, + ps, + consumerTag))) + == null) + { + throw new AMQException("Protocol session with channel " + channel + " and consumer tag " + consumerTag + + " and protocol session key " + ps.getKey() + " not registered with queue " + this); + } + + // if we are eligible for auto deletion, unregister from the queue registry + if (_autoDelete && _subscribers.isEmpty()) + { + autodelete(); + // we need to manually fire the event to the removed subscription (which was the last one left for this + // queue. This is because the delete method uses the subscription set which has just been cleared + removedSubscription.queueDeleted(this); + } + } + + public int delete(boolean checkUnused, boolean checkEmpty) throws AMQException + { + if(checkUnused && !_subscribers.isEmpty()) + { + _logger.info("Will not delete " + this + " as it is in use."); + return 0; + } + else if(checkEmpty && _deliveryMgr.getQueueMessageCount() > 0) + { + _logger.info("Will not delete " + this + " as it is not empty."); + return 0; + } + else + { + delete(); + return _deliveryMgr.getQueueMessageCount(); + } + } + + public void delete() throws AMQException + { + _subscribers.queueDeleted(this); + _bindings.deregister(); + _queueRegistry.unregisterQueue(_name); + _managedObject.unregister(); + } + + protected void autodelete() throws AMQException + { + debug("autodeleting {0}", this); + delete(); + } + + /*public void deliver(AMQMessage msg) throws AMQException + { + TxnBuffer buffer = msg.getTxnBuffer(); + if(buffer == null) + { + //non-transactional + record(msg); + process(msg); + } + else + { + buffer.enlist(new Deliver(msg)); + } + } */ + + /*private void record(AMQMessage msg) throws AMQException + { + msg.enqueue(this); + msg.incrementReference(); + } */ + + public void process(AMQMessage msg) throws FailedDequeueException, AMQException + { + _deliveryMgr.deliver(getName(), msg); + updateReceivedMessageCount(msg); + try + { + msg.checkDeliveredToConsumer(); + updateReceivedMessageCount(msg); + } + catch (NoConsumersException e) + { + // as this message will be returned, it should be removed + // from the queue: + dequeue(msg); + } + } + + void dequeue(AMQMessage msg) throws FailedDequeueException + { + try + { + msg.dequeue(this); + msg.decrementReference(); + } + catch (MessageCleanupException e) + { + //Message was dequeued, but could notthen be deleted + //though it is no longer referenced. This should be very + //rare and can be detected and cleaned up on recovery or + //done through some form of manual intervention. + _logger.error(e, e); + } + catch(AMQException e) + { + throw new FailedDequeueException(_name, e); + } + } + + public void deliverAsync() + { + _deliveryMgr.processAsync(_asyncDelivery); + } + + protected SubscriptionManager getSubscribers() + { + return _subscribers; + } + + protected void updateReceivedMessageCount(AMQMessage msg) throws AMQException + { + _totalMessagesReceived++; + _managedObject.checkForNotification(msg); + } + + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + final AMQQueue amqQueue = (AMQQueue) o; + + return (_name.equals(amqQueue._name)); + } + + public int hashCode() + { + return _name.hashCode(); + } + + public String toString() + { + return "Queue(" + _name + ")@" + System.identityHashCode(this); + } + + private void debug(String msg, Object... args) + { + if(_logger.isDebugEnabled()) + { + _logger.debug(MessageFormat.format(msg, args)); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java new file mode 100644 index 0000000000..60788c1ccb --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.configuration.Configured; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class AsyncDeliveryConfig +{ + private Executor _executor; + + @Configured(path = "delivery.poolsize", defaultValue = "0") + public int poolSize; + + public Executor getExecutor() + { + if (_executor == null) + { + if (poolSize > 0) + { + _executor = Executors.newFixedThreadPool(poolSize); + } + else + { + _executor = Executors.newCachedThreadPool(); + } + } + return _executor; + } + + public static Executor getAsyncDeliveryExecutor() + { + return ApplicationRegistry.getInstance().getConfiguredObject(AsyncDeliveryConfig.class).getExecutor(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java new file mode 100644 index 0000000000..a7dc98ec22 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; + +public class DefaultQueueRegistry implements QueueRegistry +{ + private ConcurrentMap<String, AMQQueue> _queueMap = new ConcurrentHashMap<String, AMQQueue>(); + + public DefaultQueueRegistry() + { + } + + public void registerQueue(AMQQueue queue) throws AMQException + { + _queueMap.put(queue.getName(), queue); + } + + public void unregisterQueue(String name) throws AMQException + { + _queueMap.remove(name); + } + + public AMQQueue getQueue(String name) + { + return _queueMap.get(name); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java new file mode 100644 index 0000000000..55ecf799f6 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java @@ -0,0 +1,259 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Manages delivery of messages on behalf of a queue + * + */ +class DeliveryManager +{ + private static final Logger _log = Logger.getLogger(DeliveryManager.class); + + /** + * Holds any queued messages + */ + private final Queue<AMQMessage> _messages = new LinkedList<AMQMessage>(); + /** + * Ensures that only one asynchronous task is running for this manager at + * any time. + */ + private final AtomicBoolean _processing = new AtomicBoolean(); + /** + * The subscriptions on the queue to whom messages are delivered + */ + private final SubscriptionManager _subscriptions; + + /** + * An indication of the mode we are in. If this is true then messages are + * being queued up in _messages for asynchronous delivery. If it is false + * then messages can be delivered directly as they come in. + */ + private boolean _queueing; + + /** + * A reference to the queue we are delivering messages for. We need this to be able + * to pass the code that handles acknowledgements a handle on the queue. + */ + private final AMQQueue _queue; + + DeliveryManager(SubscriptionManager subscriptions, AMQQueue queue) + { + _subscriptions = subscriptions; + _queue = queue; + } + + private synchronized boolean enqueue(AMQMessage msg) throws AMQException + { + if (_queueing) + { + if(msg.getPublishBody().immediate) + { + //can't enqueue messages for whom immediate delivery is required + return false; + } + else + { + _messages.offer(msg); + return true; + } + } + else + { + return false; + } + } + + private synchronized void startQueueing(AMQMessage msg) throws AMQException + { + _queueing = true; + enqueue(msg); + } + + /** + * Determines whether there are queued messages. Sets _queueing to false if + * there are no queued messages. This needs to be atomic. + * + * @return true if there are queued messages + */ + private synchronized boolean hasQueuedMessages() + { + boolean empty = _messages.isEmpty(); + if (empty) + { + _queueing = false; + } + return !empty; + } + + public synchronized int getQueueMessageCount() + { + return _messages.size(); + } + + protected synchronized List<AMQMessage> getMessages() + { + return new ArrayList<AMQMessage>(_messages); + } + + protected synchronized void removeAMessageFromTop() throws AMQException + { + AMQMessage msg = poll(); + if (msg != null) + { + msg.dequeue(_queue); + } + } + + protected synchronized void clearAllMessages() throws AMQException + { + AMQMessage msg = poll(); + while (msg != null) + { + msg.dequeue(_queue); + msg = poll(); + } + } + + /** + * Only one thread should ever execute this method concurrently, but + * it can do so while other threads invoke deliver(). + * @throws org.apache.qpid.AMQException if we cannot process the messages on the queue + */ + private void processQueue() throws AMQException + { + try + { + boolean hasSubscribers = _subscriptions.hasActiveSubscribers(); + while (hasQueuedMessages() && hasSubscribers) + { + Subscription next = _subscriptions.nextSubscriber(peek()); + //We don't synchronize access to subscribers so need to re-check + if (next != null) + { + next.send(poll(), _queue); + } + else + { + hasSubscribers = false; + } + } + } + catch (FailedDequeueException e) + { + _log.error("Unable to deliver message as dequeue failed: " + e, e); + } + finally + { + _processing.set(false); + } + } + + private synchronized AMQMessage peek() + { + return _messages.peek(); + } + + private synchronized AMQMessage poll() + { + return _messages.poll(); + } + + /** + * Requests that the delivery manager start processing the queue asynchronously + * if there is work that can be done (i.e. there are messages queued up and + * subscribers that can receive them. + * <p/> + * This should be called when subscribers are added, but only after the consume-ok + * message has been returned as message delivery may start immediately. It should also + * be called after unsuspending a client. + * <p/> + * + * @param executor the executor on which the delivery should take place + */ + void processAsync(Executor executor) + { + if (hasQueuedMessages() && _subscriptions.hasActiveSubscribers()) + { + //are we already running? if so, don't re-run + if (_processing.compareAndSet(false, true)) + { + executor.execute(new Runner()); + } + } + } + + /** + * Handles message delivery. The delivery manager is always in one of two modes; + * it is either queueing messages for asynchronous delivery or delivering + * directly. + * + * @param name the name of the entity on whose behalf we are delivering the message + * @param msg the message to deliver + * @throws NoConsumersException if there are no active subscribers to deliver + * the message to + * @throws FailedDequeueException if the message could not be dequeued + */ + void deliver(String name, AMQMessage msg) throws FailedDequeueException, AMQException + { + // first check whether we are queueing, and enqueue if we are + if (!enqueue(msg)) + { + // not queueing so deliver message to 'next' subscriber + Subscription s = _subscriptions.nextSubscriber(msg); + if (s == null) + { + if (!msg.getPublishBody().immediate) + { + // no subscribers yet so enter 'queueing' mode and queue this message + startQueueing(msg); + } + } + else + { + s.send(msg, _queue); + msg.setDeliveredToConsumer(); + } + } + } + + private class Runner implements Runnable + { + public void run() + { + try + { + processQueue(); + } + catch (AMQException e) + { + _log.error("Error processing queue: " + e, e); + } + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java b/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java new file mode 100644 index 0000000000..424330bd11 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java @@ -0,0 +1,109 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.AMQException; + +import java.util.List; +import java.util.HashSet; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * When a queue is deleted, it should be deregistered from any + * exchange it has been bound to. This class assists in this task, + * by keeping track of all bindings for a given queue. + */ +class ExchangeBindings +{ + static class ExchangeBinding + { + private final Exchange exchange; + private final String routingKey; + + ExchangeBinding(String routingKey, Exchange exchange) + { + this.routingKey = routingKey; + this.exchange = exchange; + } + + void unbind(AMQQueue queue) throws AMQException + { + exchange.deregisterQueue(routingKey, queue); + } + + public Exchange getExchange() + { + return exchange; + } + + public String getRoutingKey() + { + return routingKey; + } + + public int hashCode() + { + return exchange.hashCode() + routingKey.hashCode(); + } + + public boolean equals(Object o) + { + if (!(o instanceof ExchangeBinding)) return false; + ExchangeBinding eb = (ExchangeBinding) o; + return exchange.equals(eb.exchange) && routingKey.equals(eb.routingKey); + } + } + + private final List<ExchangeBinding> _bindings = new CopyOnWriteArrayList<ExchangeBinding>(); + private final AMQQueue _queue; + + ExchangeBindings(AMQQueue queue) + { + _queue = queue; + } + + /** + * Adds the specified binding to those being tracked. + * @param routingKey the routing key with which the queue whose bindings + * are being tracked by the instance has been bound to the exchange + * @param exchange the exchange bound to + */ + void addBinding(String routingKey, Exchange exchange) + { + _bindings.add(new ExchangeBinding(routingKey, exchange)); + } + + /** + * Deregisters this queue from any exchange it has been bound to + */ + void deregister() throws AMQException + { + //remove duplicates at this point + HashSet<ExchangeBinding> copy = new HashSet<ExchangeBinding>(_bindings); + for (ExchangeBinding b : copy) + { + b.unbind(_queue); + } + } + + List<ExchangeBinding> getExchangeBindings() + { + return _bindings; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java b/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java new file mode 100644 index 0000000000..f27d935c25 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java @@ -0,0 +1,36 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + +/** + * Signals that the dequeue of a message from a queue failed + */ +public class FailedDequeueException extends AMQException +{ + public FailedDequeueException(String queue) + { + super("Failed to dequeue message from " + queue); + } + + public FailedDequeueException(String queue, AMQException e) + { + super("Failed to dequeue message from " + queue, e); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java b/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java new file mode 100644 index 0000000000..e2e334c5f4 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java @@ -0,0 +1,110 @@ +/** + * User: Robert Greig + * Date: 21-Nov-2006 + ****************************************************************************** + * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of + * this program may be photocopied reproduced or translated to another + * program language without prior written consent of JP Morgan Chase Ltd + ******************************************************************************/ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; + +import java.util.LinkedList; +import java.util.List; + +/** + */ +public class InMemoryMessageHandle implements AMQMessageHandle +{ + + private ContentHeaderBody _contentHeaderBody; + + private BasicPublishBody _publishBody; + + private List<ContentBody> _contentBodies = new LinkedList<ContentBody>(); + + private boolean _redelivered; + + public InMemoryMessageHandle() + { + } + + public ContentHeaderBody getContentHeaderBody(long messageId) throws AMQException + { + return _contentHeaderBody; + } + + public int getBodyCount() + { + return _contentBodies.size(); + } + + public long getBodySize(long messageId) throws AMQException + { + return getContentHeaderBody(messageId).bodySize; + } + + public ContentBody getContentBody(long messageId, int index) throws AMQException, IllegalArgumentException + { + if (index > _contentBodies.size() - 1) + { + throw new IllegalArgumentException("Index " + index + " out of valid range 0 to " + + (_contentBodies.size() - 1)); + } + return _contentBodies.get(index); + } + + public void addContentBodyFrame(long messageId, ContentBody contentBody) throws AMQException + { + _contentBodies.add(contentBody); + } + + public BasicPublishBody getPublishBody(long messageId) throws AMQException + { + return _publishBody; + } + + public boolean isRedelivered() + { + return _redelivered; + } + + public boolean isPersistent(long messageId) throws AMQException + { + //todo remove literal values to a constant file such as AMQConstants in common + ContentHeaderBody chb = getContentHeaderBody(messageId); + return chb.properties instanceof BasicContentHeaderProperties && + ((BasicContentHeaderProperties) chb.properties).getDeliveryMode() == 2; + } + + /** + * This is called when all the content has been received. + * @param publishBody + * @param contentHeaderBody + * @throws AMQException + */ + public void setPublishAndContentHeaderBody(long messageId, BasicPublishBody publishBody, + ContentHeaderBody contentHeaderBody) + throws AMQException + { + _publishBody = publishBody; + _contentHeaderBody = contentHeaderBody; + } + + public void removeMessage(long messageId) throws AMQException + { + } + + public void enqueue(long messageId, AMQQueue queue) throws AMQException + { + } + + public void dequeue(long messageId, AMQQueue queue) throws AMQException + { + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java b/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java new file mode 100644 index 0000000000..7528011bac --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java @@ -0,0 +1,213 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.server.management.MBeanAttribute; +import org.apache.qpid.server.management.MBeanOperation; +import org.apache.qpid.server.management.MBeanOperationParameter; +import org.apache.qpid.AMQException; + +import javax.management.JMException; +import javax.management.MBeanOperationInfo; +import javax.management.openmbean.TabularData; +import java.io.IOException; + +/** + * The management interface exposed to allow management of a queue. + * @author Robert J. Greig + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedQueue +{ + static final String TYPE = "Queue"; + + /** + * Returns the Name of the ManagedQueue. + * @return the name of the managedQueue. + * @throws IOException + */ + @MBeanAttribute(name="Name", description = "Name of the " + TYPE) + String getName() throws IOException; + + /** + * Tells whether this ManagedQueue is durable or not. + * @return true if this ManagedQueue is a durable queue. + * @throws IOException + */ + @MBeanAttribute(name="Durable", description = "true if the AMQQueue is durable") + boolean isDurable() throws IOException; + + /** + * Tells the Owner of the ManagedQueue. + * @return the owner's name. + * @throws IOException + */ + @MBeanAttribute(name="Owner", description = "Owner") + String getOwner() throws IOException; + + /** + * Tells if the ManagedQueue is set to AutoDelete. + * @return true if the ManagedQueue is set to AutoDelete. + * @throws IOException + */ + @MBeanAttribute(name="AutoDelete", description = "true if the AMQQueue is AutoDelete") + boolean isAutoDelete() throws IOException; + + /** + * Total number of messages on the queue, which are yet to be delivered to the consumer(s). + * @return number of undelivered message in the Queue. + * @throws IOException + */ + @MBeanAttribute(name="MessageCount", + description = "Total number of undelivered messages on the queue") + Integer getMessageCount() throws IOException; + + /** + * Returns the maximum size of a message (in kbytes) allowed to be accepted by the + * ManagedQueue. This is useful in setting notifications or taking + * appropriate action, if the size of the message received is more than + * the allowed size. + * @return the maximum size of a message allowed to be aceepted by the + * ManagedQueue. + * @throws IOException + */ + Long getMaximumMessageSize() throws IOException; + + /** + * Sets the maximum size of the message (in kbytes) that is allowed to be + * accepted by the Queue. + * @param size maximum size of message. + * @throws IOException + */ + @MBeanAttribute(name="MaximumMessageSize", + description="Maximum size(KB) of a message allowed for this Queue") + void setMaximumMessageSize(Long size) throws IOException; + + /** + * Returns the total number of subscribers to the queue. + * @return the number of subscribers. + * @throws IOException + */ + @MBeanAttribute(name="ConsumerCount", description="The total number of subscribers to the queue") + Integer getConsumerCount() throws IOException; + + /** + * Returns the total number of active subscribers to the queue. + * @return the number of active subscribers + * @throws IOException + */ + @MBeanAttribute(name="ActiveConsumerCount", description="The total number of active subscribers to the queue") + Integer getActiveConsumerCount() throws IOException; + + /** + * Tells the total number of messages receieved by the queue since startup. + * @return total number of messages received. + * @throws IOException + */ + @MBeanAttribute(name="ReceivedMessageCount", + description="The total number of messages receieved by the queue since startup") + Long getReceivedMessageCount() throws IOException; + + /** + * Tells the maximum number of messages that can be stored in the queue. + * This is useful in setting the notifications or taking required + * action is the number of message increase this limit. + * @return maximum muber of message allowed to be stored in the queue. + * @throws IOException + */ + Integer getMaximumMessageCount() throws IOException; + + /** + * Sets the maximum number of messages allowed to be stored in the queue. + * @param value the maximum number of messages allowed to be stored in the queue. + * @throws IOException + */ + @MBeanAttribute(name="MaximumMessageCount", + description="The maximum number of messages allowed to be stored in the queue") + void setMaximumMessageCount(Integer value) throws IOException; + + /** + * Size of messages in the queue + * @return + * @throws IOException + */ + @MBeanAttribute(name="QueueSize", description="Size of messages(KB) in the queue") + Long getQueueSize() throws IOException, AMQException; + + /** + * Tells the maximum size of all the messages combined together, + * that can be stored in the queue. This is useful for setting notifications + * or taking required action if the size of messages stored in the queue + * increases over this limit. + * @return maximum size of the all the messages allowed for the queue. + * @throws IOException + */ + Long getQueueDepth() throws IOException; + + /** + * Sets the maximum size of all the messages together, that can be stored + * in the queue. + * @param value + * @throws IOException + */ + @MBeanAttribute(name="QueueDepth", + description="The size(KB) of all the messages together, that can be stored in the queue") + void setQueueDepth(Long value) throws IOException; + + + + //********** Operations *****************// + + + /** + * Returns a subset of all the messages stored in the queue. The messages + * are returned based on the given index numbers. + * @param fromIndex + * @param toIndex + * @return + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="viewMessages", + description="shows messages in this queue with given indexes. eg. from index 1 - 100") + TabularData viewMessages(@MBeanOperationParameter(name="from index", description="from index")int fromIndex, + @MBeanOperationParameter(name="to index", description="to index")int toIndex) + throws IOException, JMException; + + /** + * Deletes the first message from top. + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="deleteMessageFromTop", + description="Deletes the first message from top", + impact= MBeanOperationInfo.ACTION) + void deleteMessageFromTop() throws IOException, JMException; + + /** + * Clears the queue by deleting all the undelivered messages from the queue. + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="clearQueue", + description="Clears the queue by deleting all the undelivered messages from the queue", + impact= MBeanOperationInfo.ACTION) + void clearQueue() throws IOException, JMException; + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java new file mode 100644 index 0000000000..6f0fa285b1 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + +/** + * Signals that the removal of a message once its refcount reached + * zero failed. + */ +public class MessageCleanupException extends AMQException +{ + public MessageCleanupException(long messageId, AMQException e) + { + super("Failed to cleanup message with id " + messageId, e); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java new file mode 100644 index 0000000000..93aceb76ec --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java @@ -0,0 +1,34 @@ +/** + * User: Robert Greig + * Date: 31-Oct-2006 + ****************************************************************************** + * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of + * this program may be photocopied reproduced or translated to another + * program language without prior written consent of JP Morgan Chase Ltd + ******************************************************************************/ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.store.MessageStore; + +/** + * Constructs a message handle based on the publish body, the content header and the queue to which the message + * has been routed. + * + * @author Robert Greig (robert.j.greig@jpmorgan.com) + */ +public class MessageHandleFactory +{ + + public AMQMessageHandle createMessageHandle(long messageId, MessageStore store, boolean persistent) + { + // just hardcoded for now + if (persistent) + { + return new WeakReferenceMessageHandle(store); + } + else + { + return new InMemoryMessageHandle(); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java new file mode 100644 index 0000000000..deed18c188 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java @@ -0,0 +1,70 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; + +/** + * Encapsulates a publish body and a content header. In the context of the message store these are treated as a + * single unit. + */ +public class MessageMetaData +{ + private BasicPublishBody _publishBody; + + private ContentHeaderBody _contentHeaderBody; + + private int _contentChunkCount; + + public MessageMetaData(BasicPublishBody publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount) + { + _contentHeaderBody = contentHeaderBody; + _publishBody = publishBody; + _contentChunkCount = contentChunkCount; + } + + public int getContentChunkCount() + { + return _contentChunkCount; + } + + public void setContentChunkCount(int contentChunkCount) + { + _contentChunkCount = contentChunkCount; + } + + public ContentHeaderBody getContentHeaderBody() + { + return _contentHeaderBody; + } + + public void setContentHeaderBody(ContentHeaderBody contentHeaderBody) + { + _contentHeaderBody = contentHeaderBody; + } + + public BasicPublishBody getPublishBody() + { + return _publishBody; + } + + public void setPublishBody(BasicPublishBody publishBody) + { + _publishBody = publishBody; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java b/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java new file mode 100644 index 0000000000..09f01d6d97 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.RequiredDeliveryException; + +/** + * Signals that no consumers exist for a message at a given point in time. + * Used if a message has immediate=true and there are no consumers registered + * with the queue. + */ +public class NoConsumersException extends RequiredDeliveryException +{ + public NoConsumersException(AMQMessage message) + { + super("Immediate delivery is not possible.", message); + } + + public int getReplyCode() + { + return AMQConstant.NO_CONSUMERS.getCode(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java new file mode 100644 index 0000000000..bc5599fb20 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + + +public interface QueueRegistry +{ + void registerQueue(AMQQueue queue) throws AMQException; + + void unregisterQueue(String name) throws AMQException; + + AMQQueue getQueue(String name); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java b/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java new file mode 100644 index 0000000000..a248efecb1 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java @@ -0,0 +1,29 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + +public interface Subscription +{ + void send(AMQMessage msg, AMQQueue queue) throws AMQException; + + boolean isSuspended(); + + void queueDeleted(AMQQueue queue) throws AMQException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java new file mode 100644 index 0000000000..127b19b0e4 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; + +/** + * Allows the customisation of the creation of a subscription. This is typically done within an AMQQueue. This + * factory primarily assists testing although in future more sophisticated subscribers may need a different + * subscription implementation. + * + * @see org.apache.qpid.server.queue.AMQQueue + */ +public interface SubscriptionFactory +{ + Subscription createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag, boolean acks) + throws AMQException; + + Subscription createSubscription(int channel, AMQProtocolSession protocolSession,String consumerTag) + throws AMQException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java new file mode 100644 index 0000000000..4422d1ffc2 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java @@ -0,0 +1,170 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; + +/** + * Encapsulation of a supscription to a queue. + * <p/> + * Ties together the protocol session of a subscriber, the consumer tag that + * was given out by the broker and the channel id. + * <p/> + */ +public class SubscriptionImpl implements Subscription +{ + private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class); + + public final AMQChannel channel; + + public final AMQProtocolSession protocolSession; + + public final String consumerTag; + + private final Object sessionKey; + + /** + * True if messages need to be acknowledged + */ + private final boolean _acks; + + public static class Factory implements SubscriptionFactory + { + public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag, boolean acks) + throws AMQException + { + return new SubscriptionImpl(channel, protocolSession, consumerTag, acks); + } + + public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag) + throws AMQException + { + return new SubscriptionImpl(channel, protocolSession, consumerTag); + } + } + + public SubscriptionImpl(int channelId, AMQProtocolSession protocolSession, + String consumerTag, boolean acks) + throws AMQException + { + AMQChannel channel = protocolSession.getChannel(channelId); + if (channel == null) + { + throw new NullPointerException("channel not found in protocol session"); + } + + this.channel = channel; + this.protocolSession = protocolSession; + this.consumerTag = consumerTag; + sessionKey = protocolSession.getKey(); + _acks = acks; + } + + public SubscriptionImpl(int channel, AMQProtocolSession protocolSession, + String consumerTag) + throws AMQException + { + this(channel, protocolSession, consumerTag, false); + } + + public boolean equals(Object o) + { + return (o instanceof SubscriptionImpl) && equals((SubscriptionImpl) o); + } + + /** + * Equality holds if the session matches and the channel and consumer tag are the same. + */ + private boolean equals(SubscriptionImpl psc) + { + return sessionKey.equals(psc.sessionKey) + && psc.channel == channel + && psc.consumerTag.equals(consumerTag); + } + + public int hashCode() + { + return sessionKey.hashCode(); + } + + public String toString() + { + return "[channel=" + channel + ", consumerTag=" + consumerTag + ", session=" + protocolSession.getKey() + "]"; + } + + /** + * This method can be called by each of the publisher threads. + * As a result all changes to the channel object must be thread safe. + * + * @param msg + * @param queue + * @throws AMQException + */ + public void send(AMQMessage msg, AMQQueue queue) throws AMQException + { + if (msg != null) + { + // if we do not need to wait for client acknowledgements + // we can decrement the reference count immediately. + + // By doing this _before_ the send we ensure that it + // doesn't get sent if it can't be dequeued, preventing + // duplicate delivery on recovery. + + // The send may of course still fail, in which case, as + // the message is unacked, it will be lost. + if (!_acks) + { + queue.dequeue(msg); + } + synchronized(channel) + { + long deliveryTag = channel.getNextDeliveryTag(); + + if (_acks) + { + channel.addUnacknowledgedMessage(msg, deliveryTag, consumerTag, queue); + } + + msg.writeDeliver(protocolSession, channel.getChannelId(), deliveryTag, consumerTag); + } + } + else + { + _logger.error("Attempt to send Null message", new NullPointerException()); + } + } + + public boolean isSuspended() + { + return channel.isSuspended(); + } + + /** + * Callback indicating that a queue has been deleted. + * + * @param queue + */ + public void queueDeleted(AMQQueue queue) throws AMQException + { + channel.queueDeleted(queue); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java new file mode 100644 index 0000000000..185b0e4268 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java @@ -0,0 +1,28 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +/** + * Abstraction of actor that will determine the subscriber to whom + * a message will be sent. + */ +public interface SubscriptionManager +{ + public boolean hasActiveSubscribers(); + public Subscription nextSubscriber(AMQMessage msg); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java new file mode 100644 index 0000000000..fe55bd071b --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java @@ -0,0 +1,182 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; + +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Holds a set of subscriptions for a queue and manages the round + * robin-ing of deliver etc. + */ +class SubscriptionSet implements WeightedSubscriptionManager +{ + private static final Logger _log = Logger.getLogger(SubscriptionSet.class); + + /** + * List of registered subscribers + */ + private List<Subscription> _subscriptions = new CopyOnWriteArrayList<Subscription>(); + + /** + * Used to control the round robin delivery of content + */ + private int _currentSubscriber; + + /** + * Accessor for unit tests. + */ + int getCurrentSubscriber() + { + return _currentSubscriber; + } + + public void addSubscriber(Subscription subscription) + { + _subscriptions.add(subscription); + } + + /** + * Remove the subscription, returning it if it was found + * @param subscription + * @return null if no match was found + */ + public Subscription removeSubscriber(Subscription subscription) + { + boolean isRemoved = _subscriptions.remove(subscription); // TODO: possibly need O(1) operation here. + if (isRemoved) + { + return subscription; + } + else + { + debugDumpSubscription(subscription); + return null; + } + } + + private void debugDumpSubscription(Subscription subscription) + { + if (_log.isDebugEnabled()) + { + _log.debug("Subscription " + subscription + " not found. Dumping subscriptions:"); + for (Subscription s : _subscriptions) + { + _log.debug("Subscription: " + s); + } + _log.debug("Subscription dump complete"); + } + } + + /** + * Return the next unsuspended subscription or null if not found. + * + * Performance note: + * This method can scan all items twice when looking for a subscription that is not + * suspended. The worst case occcurs when all subscriptions are suspended. However, it is does this + * without synchronisation and subscriptions may be added and removed concurrently. Also note that because of + * race conditions and when subscriptions are removed between calls to nextSubscriber, the + * IndexOutOfBoundsException also causes the scan to start at the beginning. + */ + public Subscription nextSubscriber(AMQMessage msg) + { + if (_subscriptions.isEmpty()) + { + return null; + } + + try { + final Subscription result = nextSubscriber(); + if (result == null) { + _currentSubscriber = 0; + return nextSubscriber(); + } else { + return result; + } + } catch (IndexOutOfBoundsException e) { + _currentSubscriber = 0; + return nextSubscriber(); + } + } + + private Subscription nextSubscriber() + { + final ListIterator<Subscription> iterator = _subscriptions.listIterator(_currentSubscriber); + while (iterator.hasNext()) { + Subscription subscription = iterator.next(); + ++_currentSubscriber; + subscriberScanned(); + if (!subscription.isSuspended()) { + return subscription; + } + } + return null; + } + + /** + * Overridden in test classes. + */ + protected void subscriberScanned() + { + } + + public boolean isEmpty() + { + return _subscriptions.isEmpty(); + } + + public boolean hasActiveSubscribers() + { + for (Subscription s : _subscriptions) + { + if (!s.isSuspended()) return true; + } + return false; + } + + public int getWeight() + { + int count = 0; + for (Subscription s : _subscriptions) + { + if (!s.isSuspended()) count++; + } + return count; + } + + /** + * Notification that a queue has been deleted. This is called so that the subscription can inform the + * channel, which in turn can update its list of unacknowledged messages. + * @param queue + */ + public void queueDeleted(AMQQueue queue) throws AMQException + { + for (Subscription s : _subscriptions) + { + s.queueDeleted(queue); + } + } + + int size() { + return _subscriptions.size(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java b/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java new file mode 100644 index 0000000000..bf82940ec8 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java @@ -0,0 +1,144 @@ +/** + * User: Robert Greig + * Date: 23-Oct-2006 + ****************************************************************************** + * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of + * this program may be photocopied reproduced or translated to another + * program language without prior written consent of JP Morgan Chase Ltd + ******************************************************************************/ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.store.MessageStore; + +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Robert Greig (robert.j.greig@jpmorgan.com) + */ +public class WeakReferenceMessageHandle implements AMQMessageHandle +{ + private WeakReference<ContentHeaderBody> _contentHeaderBody; + + private WeakReference<BasicPublishBody> _publishBody; + + private List<WeakReference<ContentBody>> _contentBodies = new LinkedList<WeakReference<ContentBody>>(); + + private boolean _redelivered; + + private final MessageStore _messageStore; + + public WeakReferenceMessageHandle(MessageStore messageStore) + { + _messageStore = messageStore; + } + + public ContentHeaderBody getContentHeaderBody(long messageId) throws AMQException + { + ContentHeaderBody chb = _contentHeaderBody.get(); + if (chb == null) + { + MessageMetaData mmd = _messageStore.getMessageMetaData(messageId); + chb = mmd.getContentHeaderBody(); + _contentHeaderBody = new WeakReference<ContentHeaderBody>(chb); + _publishBody = new WeakReference<BasicPublishBody>(mmd.getPublishBody()); + } + return chb; + } + + public int getBodyCount() + { + return _contentBodies.size(); + } + + public long getBodySize(long messageId) throws AMQException + { + return getContentHeaderBody(messageId).bodySize; + } + + public ContentBody getContentBody(long messageId, int index) throws AMQException, IllegalArgumentException + { + if (index > _contentBodies.size() - 1) + { + throw new IllegalArgumentException("Index " + index + " out of valid range 0 to " + + (_contentBodies.size() - 1)); + } + WeakReference<ContentBody> wr = _contentBodies.get(index); + ContentBody cb = wr.get(); + if (cb == null) + { + cb = _messageStore.getContentBodyChunk(messageId, index); + _contentBodies.set(index, new WeakReference<ContentBody>(cb)); + } + return cb; + } + + public void addContentBodyFrame(long messageId, ContentBody contentBody) throws AMQException + { + _contentBodies.add(new WeakReference<ContentBody>(contentBody)); + _messageStore.storeContentBodyChunk(messageId, _contentBodies.size() - 1, contentBody); + } + + public BasicPublishBody getPublishBody(long messageId) throws AMQException + { + BasicPublishBody bpb = _publishBody.get(); + if (bpb == null) + { + MessageMetaData mmd = _messageStore.getMessageMetaData(messageId); + bpb = mmd.getPublishBody(); + _publishBody = new WeakReference<BasicPublishBody>(bpb); + _contentHeaderBody = new WeakReference<ContentHeaderBody>(mmd.getContentHeaderBody()); + } + return bpb; + } + + public boolean isRedelivered() + { + return _redelivered; + } + + public boolean isPersistent(long messageId) throws AMQException + { + //todo remove literal values to a constant file such as AMQConstants in common + ContentHeaderBody chb = getContentHeaderBody(messageId); + return chb.properties instanceof BasicContentHeaderProperties && + ((BasicContentHeaderProperties) chb.properties).getDeliveryMode() == 2; + } + + /** + * This is called when all the content has been received. + * @param publishBody + * @param contentHeaderBody + * @throws AMQException + */ + public void setPublishAndContentHeaderBody(long messageId, BasicPublishBody publishBody, + ContentHeaderBody contentHeaderBody) + throws AMQException + { + _messageStore.storeMessageMetaData(messageId, new MessageMetaData(publishBody, contentHeaderBody, + _contentBodies.size())); + _publishBody = new WeakReference<BasicPublishBody>(publishBody); + _contentHeaderBody = new WeakReference<ContentHeaderBody>(contentHeaderBody); + } + + public void removeMessage(long messageId) throws AMQException + { + _messageStore.removeMessage(messageId); + } + + public void enqueue(long messageId, AMQQueue queue) throws AMQException + { + _messageStore.enqueueMessage(queue.getName(), messageId); + } + + public void dequeue(long messageId, AMQQueue queue) throws AMQException + { + _messageStore.dequeueMessage(queue.getName(), messageId); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java new file mode 100644 index 0000000000..adf6aefdfb --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java @@ -0,0 +1,23 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +public interface WeightedSubscriptionManager extends SubscriptionManager +{ + public int getWeight(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java new file mode 100644 index 0000000000..50f3d711ab --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java @@ -0,0 +1,197 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.registry; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.Configurator; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * An abstract application registry that provides access to configuration information and handles the + * construction and caching of configurable objects. + * <p/> + * Subclasses should handle the construction of the "registered objects" such as the exchange registry. + */ +public abstract class ApplicationRegistry implements IApplicationRegistry +{ + private static final Logger _logger = Logger.getLogger(ApplicationRegistry.class); + + private static Map _instanceMap = new HashMap(); + + private final Map<Class<?>, Object> _configuredObjects = new HashMap<Class<?>, Object>(); + + protected final Configuration _configuration; + + public static final int DEFAULT_INSTANCE = 1; + public static final String DEFAULT_APPLICATION_REGISTRY = "org.apache.qpid.server.util.NullApplicationRegistry"; + public static String _APPLICATION_REGISTRY = DEFAULT_APPLICATION_REGISTRY; + + static + { + Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownService())); + } + + private static class ShutdownService implements Runnable + { + public void run() + { + _logger.info("Shutting down application registries..."); + try + { + synchronized (ApplicationRegistry.class) + { + Iterator keyIterator = _instanceMap.keySet().iterator(); + + while (keyIterator.hasNext()) + { + int key = (Integer) keyIterator.next(); + IApplicationRegistry instance = (IApplicationRegistry) _instanceMap.get(key); + + if ((instance != null)) + { + if (instance.getMessageStore() != null) + { + instance.getMessageStore().close(); + } + } + } + } + } + catch (Exception e) + { + _logger.error("Error shutting down message store: " + e, e); + } + } + } + + public static void initialise(IApplicationRegistry instance) throws Exception + { + initialise(instance, DEFAULT_INSTANCE); + } + + public static void initialise(IApplicationRegistry instance, int instanceID) throws Exception + { + if (instance != null) + { + _logger.info("Initialising Application Registry:" + instanceID); + _instanceMap.put(instanceID, instance); + + try + { + instance.initialise(); + } + catch (Exception e) + { + _instanceMap.remove(instanceID); + throw e; + } + } + else + { + remove(instanceID); + } + } + + public static void remove(int instanceID) + { + try + { + ((IApplicationRegistry) _instanceMap.get(instanceID)).getMessageStore().close(); + } + catch (Exception e) + { + + } + finally + { + _instanceMap.remove(instanceID); + } + } + + + protected ApplicationRegistry(Configuration configuration) + { + _configuration = configuration; + } + + public static IApplicationRegistry getInstance() + { + return getInstance(DEFAULT_INSTANCE); + } + + public static IApplicationRegistry getInstance(int instanceID) + { + IApplicationRegistry instance = (IApplicationRegistry) _instanceMap.get(instanceID); + + if (instance == null) + { + try + { + _logger.info("Creating DEFAULT_APPLICATION_REGISTRY: " + _APPLICATION_REGISTRY + " : Instance:" + instanceID); + IApplicationRegistry registry = (IApplicationRegistry) Class.forName(_APPLICATION_REGISTRY).getConstructor((Class[]) null).newInstance((Object[]) null); + ApplicationRegistry.initialise(registry, instanceID); + _logger.info("Initialised Application Registry:" + instanceID); + return registry; + } + catch (Exception e) + { + _logger.error("Error configuring application: " + e, e); + //throw new AMQBrokerCreationException(instanceID, "Unable to create Application Registry instance " + instanceID); + throw new RuntimeException("Unable to create Application Registry"); + } + } + else + { + return instance; + } + } + + public Configuration getConfiguration() + { + return _configuration; + } + + public <T> T getConfiguredObject(Class<T> instanceType) + { + T instance = (T) _configuredObjects.get(instanceType); + if (instance == null) + { + try + { + instance = instanceType.newInstance(); + } + catch (Exception e) + { + _logger.error("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor"); + throw new IllegalArgumentException("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor"); + } + Configurator.configure(instance); + _configuredObjects.put(instanceType, instance); + } + return instance; + } + + public static void setDefaultApplicationRegistry(String clazz) + { + _APPLICATION_REGISTRY = clazz; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java new file mode 100644 index 0000000000..db79ae8876 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java @@ -0,0 +1,155 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.registry; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.SystemConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.DefaultExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.JMXManagedObjectRegistry; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.management.ManagementConfiguration; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.queue.DefaultQueueRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.SASLAuthenticationManager; +import org.apache.qpid.server.store.MessageStore; + +import java.io.File; + +public class ConfigurationFileApplicationRegistry extends ApplicationRegistry +{ + private QueueRegistry _queueRegistry; + + private ExchangeRegistry _exchangeRegistry; + + private ExchangeFactory _exchangeFactory; + + private ManagedObjectRegistry _managedObjectRegistry; + + private AuthenticationManager _authenticationManager; + + private MessageStore _messageStore; + + public ConfigurationFileApplicationRegistry(File configurationURL) throws ConfigurationException + { + super(config(configurationURL)); + } + + // Our configuration class needs to make the interpolate method + // public so it can be called below from the config method. + private static class MyConfiguration extends CompositeConfiguration { + public String interpolate(String obj) { + return super.interpolate(obj); + } + } + + private static final Configuration config(File url) throws ConfigurationException { + // We have to override the interpolate methods so that + // interpolation takes place accross the entirety of the + // composite configuration. Without doing this each + // configuration object only interpolates variables defined + // inside itself. + final MyConfiguration conf = new MyConfiguration(); + conf.addConfiguration(new SystemConfiguration() { + protected String interpolate(String o) { + return conf.interpolate(o); + } + }); + conf.addConfiguration(new XMLConfiguration(url) { + protected String interpolate(String o) { + return conf.interpolate(o); + } + }); + return conf; + } + + public void initialise() throws Exception + { + initialiseManagedObjectRegistry(); + _queueRegistry = new DefaultQueueRegistry(); + _exchangeFactory = new DefaultExchangeFactory(); + _exchangeRegistry = new DefaultExchangeRegistry(_exchangeFactory); + _authenticationManager = new SASLAuthenticationManager(); + initialiseMessageStore(); + } + + private void initialiseManagedObjectRegistry() + { + ManagementConfiguration config = getConfiguredObject(ManagementConfiguration.class); + if (config.enabled) + { + _managedObjectRegistry = new JMXManagedObjectRegistry(); + } + else + { + _managedObjectRegistry = new NoopManagedObjectRegistry(); + } + } + + private void initialiseMessageStore() throws Exception + { + String messageStoreClass = _configuration.getString("store.class"); + Class clazz = Class.forName(messageStoreClass); + Object o = clazz.newInstance(); + + if (!(o instanceof MessageStore)) + { + throw new Exception("Message store class must implement " + MessageStore.class + ". Class " + clazz + + " does not."); + } + _messageStore = (MessageStore) o; + _messageStore.configure(getQueueRegistry(), "store", _configuration); + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + public ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + public ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + public ManagedObjectRegistry getManagedObjectRegistry() + { + return _managedObjectRegistry; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java new file mode 100644 index 0000000000..0102f78424 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.registry; + +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.commons.configuration.Configuration; + +public interface IApplicationRegistry +{ + /** + * Initialise the application registry. All initialisation must be done in this method so that any components + * that need access to the application registry itself for initialisation are able to use it. Attempting to + * initialise in the constructor will lead to failures since the registry reference will not have been set. + */ + void initialise() throws Exception; + + /** + * This gets access to a "configured object". A configured object has fields populated from a the configuration + * object (Commons Configuration) automatically, where it has the appropriate attributes defined on fields. + * Application registry implementations can choose the refresh strategy or caching approach. + * @param instanceType the type of object you want initialised. This must be unique - i.e. you can only + * have a single object of this type in the system. + * @return the configured object + */ + <T> T getConfiguredObject(Class<T> instanceType); + + /** + * Get the low level configuration. For use cases where the configured object approach is not required + * you can get the complete configuration information. + * @return a Commons Configuration instance + */ + Configuration getConfiguration(); + + QueueRegistry getQueueRegistry(); + + ExchangeRegistry getExchangeRegistry(); + + ExchangeFactory getExchangeFactory(); + + ManagedObjectRegistry getManagedObjectRegistry(); + + AuthenticationManager getAuthenticationManager(); + + MessageStore getMessageStore(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java new file mode 100644 index 0000000000..0adb7b98e2 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +public interface AuthenticationManager +{ + String getMechanisms(); + + SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException; + + AuthenticationResult authenticate(SaslServer server, byte[] response); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java new file mode 100644 index 0000000000..71e3c81ae4 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import org.apache.commons.configuration.Configuration; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public interface AuthenticationProviderInitialiser +{ + /** + * @return the mechanism's name. This will be used in the list of mechanism's advertised to the + * client. + */ + String getMechanismName(); + + /** + * Initialise the authentication provider. + * @param baseConfigPath the path in the config file that points to any config options for this provider. Each + * provider can have its own set of configuration options + * @param configuration the Apache Commons Configuration instance used to configure this provider + * @param principalDatabases the set of principal databases that are available + */ + void initialise(String baseConfigPath, Configuration configuration, + Map<String, PrincipalDatabase> principalDatabases) throws Exception; + + /** + * @return the callback handler that should be used to process authentication requests for this mechanism. This will + * be called after initialise and will be stored by the authentication manager. The callback handler <b>must</b> be + * fully threadsafe. + */ + CallbackHandler getCallbackHandler(); + + /** + * Get the properties that must be passed in to the Sasl.createSaslServer method. + * @return the properties, which may be null + */ + Map<String, ?> getProperties(); + + /** + * Get the class that is the server factory. This is used for the JCA registration. + * @return null if no JCA registration is required, otherwise return the class + * that will be used in JCA registration + */ + Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration(); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java new file mode 100644 index 0000000000..d7dcf2c973 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +public class AuthenticationResult +{ + public enum AuthenticationStatus + { + SUCCESS, CONTINUE, ERROR + } + + public AuthenticationStatus status; + public byte[] challenge; + + public AuthenticationResult(byte[] challenge, AuthenticationStatus status) + { + this.status = status; + this.challenge = challenge; + } + + public AuthenticationResult(AuthenticationStatus status) + { + this.status = status; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java new file mode 100644 index 0000000000..bfd2ac1b9f --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import javax.security.sasl.SaslServerFactory; + +public class CRAMMD5Initialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "CRAM-MD5"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + // since the CRAM-MD5 provider is registered as part of the JDK, we do not + // return the factory class here since we do not need to register it ourselves. + return null; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java new file mode 100644 index 0000000000..1477b33ebe --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import javax.security.sasl.SaslServerFactory; +import java.security.Provider; +import java.security.Security; +import java.util.Map; + +public final class JCAProvider extends Provider +{ + public JCAProvider(Map<String, Class<? extends SaslServerFactory>> providerMap) + { + super("AMQSASLProvider", 1.0, "A JCA provider that registers all " + + "AMQ SASL providers that want to be registered"); + register(providerMap); + Security.addProvider(this); + } + + private void register(Map<String, Class<? extends SaslServerFactory>> providerMap) + { + for (Map.Entry<String, Class<? extends SaslServerFactory>> me : + providerMap.entrySet()) + { + put("SaslServerFactory." + me.getKey(), me.getValue().getName()); + } + } +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java new file mode 100644 index 0000000000..95a53951ad --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java @@ -0,0 +1,82 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.AuthenticationResult; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +public class NullAuthenticationManager implements AuthenticationManager +{ + public String getMechanisms() + { + return "PLAIN"; + } + + public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException + { + return new SaslServer() + { + public String getMechanismName() + { + return "PLAIN"; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + return new byte[0]; + } + + public boolean isComplete() + { + return true; + } + + public String getAuthorizationID() + { + return "guest"; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + return new byte[0]; + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + return new byte[0]; + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + } + }; + } + + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.SUCCESS); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..fb11b89367 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java @@ -0,0 +1,130 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import org.apache.log4j.Logger; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.security.Principal; +import java.io.*; +import java.util.regex.Pattern; + +/** + * Represents a user database where the account information is stored in a simple flat file. + * + * The file is expected to be in the form: + * username:password + * username1:password1 + * ... + * usernamen:passwordn + * + * where a carriage return separates each username/password pair. Passwords are assumed to be in + * plain text. + * + */ +public class PasswordFilePrincipalDatabase implements PrincipalDatabase +{ + private static final Logger _logger = Logger.getLogger(PasswordFilePrincipalDatabase.class); + + private File _passwordFile; + + private Pattern _regexp = Pattern.compile(":"); + + public PasswordFilePrincipalDatabase() + { + } + + public void setPasswordFile(String passwordFile) throws FileNotFoundException + { + File f = new File(passwordFile); + _logger.info("PasswordFilePrincipalDatabase using file " + f.getAbsolutePath()); + _passwordFile = f; + if (!f.exists()) + { + throw new FileNotFoundException("Cannot find password file " + f); + } + if (!f.canRead()) + { + throw new FileNotFoundException("Cannot read password file " + f + + ". Check permissions."); + } + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, + AccountNotFoundException + { + if (_passwordFile == null) + { + throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation"); + } + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + char[] pwd = lookupPassword(principal.getName()); + if (pwd != null) + { + callback.setPassword(pwd); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + /** + * Looks up the password for a specified user in the password file. + * Note this code is <b>not</b> secure since it creates strings of passwords. It should be modified + * to create only char arrays which get nulled out. + * @param name + * @return + * @throws IOException + */ + private char[] lookupPassword(String name) throws IOException + { + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2) + { + continue; + } + + if (name.equals(result[0])) + { + return result[1].toCharArray(); + } + } + return null; + } + finally + { + if (reader != null) + { + reader.close(); + } + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java new file mode 100644 index 0000000000..8add5455ee --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.security.Principal; +import java.io.IOException; + +/** + * Represents a "user database" which is really a way of storing principals (i.e. usernames) and + * passwords. + */ +public interface PrincipalDatabase +{ + /** + * Set the password for a given principal in the specified callback. This is used for certain + * SASL providers. The user database implementation should look up the password in any way it + * chooses and set it in the callback by calling its setPassword method. + * @param principal the principal + * @param callback the password callback that wants to receive the password + * @throws AccountNotFoundException if the account for specified principal could not be found + * @throws IOException if there was an error looking up the principal + */ + void setPassword(Principal principal, PasswordCallback callback) + throws IOException, AccountNotFoundException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java new file mode 100644 index 0000000000..7d0b60d95e --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java @@ -0,0 +1,224 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.configuration.PropertyUtils; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.security.Security; + +public class SASLAuthenticationManager implements AuthenticationManager +{ + private static final Logger _log = Logger.getLogger(SASLAuthenticationManager.class); + + /** + * The list of mechanisms, in the order in which they are configured (i.e. preferred order) + */ + private String _mechanisms; + + /** + * Maps from the mechanism to the callback handler to use for handling those requests + */ + private Map<String, CallbackHandler> _callbackHandlerMap = new HashMap<String, CallbackHandler>(); + + /** + * Maps from the mechanism to the properties used to initialise the server. See the method + * Sasl.createSaslServer for details of the use of these properties. This map is populated during initialisation + * of each provider. + */ + private Map<String, Map<String, ?>> _serverCreationProperties = new HashMap<String, Map<String, ?>>(); + + public SASLAuthenticationManager() throws Exception + { + _log.info("Initialising SASL authentication manager"); + Map<String, PrincipalDatabase> databases = initialisePrincipalDatabases(); + initialiseAuthenticationMechanisms(databases); + } + + private Map<String, PrincipalDatabase> initialisePrincipalDatabases() throws Exception + { + Configuration config = ApplicationRegistry.getInstance().getConfiguration(); + List<String> databaseNames = config.getList("security.principal-databases.principal-database.name"); + List<String> databaseClasses = config.getList("security.principal-databases.principal-database.class"); + Map<String, PrincipalDatabase> databases = new HashMap<String, PrincipalDatabase>(); + for (int i = 0; i < databaseNames.size(); i++) + { + Object o; + try + { + o = Class.forName(databaseClasses.get(i)).newInstance(); + } + catch (Exception e) + { + throw new Exception("Error initialising principal database: " + e, e); + } + + if (!(o instanceof PrincipalDatabase)) + { + throw new Exception("Principal databases must implement the PrincipalDatabase interface"); + } + + initialisePrincipalDatabase((PrincipalDatabase) o, config, i); + + String name = databaseNames.get(i); + if (name == null || name.length() == 0) + { + throw new Exception("Principal database names must have length greater than or equal to one character"); + } + PrincipalDatabase pd = databases.get(name); + if (pd != null) + { + throw new Exception("Duplicate principal database name provided"); + } + _log.info("Initialised principal database " + name + " successfully"); + databases.put(name, (PrincipalDatabase) o); + } + return databases; + } + + private void initialisePrincipalDatabase(PrincipalDatabase principalDatabase, Configuration config, int index) + throws Exception + { + String baseName = "security.principal-databases.principal-database(" + index + ").attributes.attribute."; + List<String> argumentNames = config.getList(baseName + "name"); + List<String> argumentValues = config.getList(baseName + "value"); + for (int i = 0; i < argumentNames.size(); i++) + { + String argName = argumentNames.get(i); + if (argName == null || argName.length() == 0) + { + throw new Exception("Argument names must have length >= 1 character"); + } + if (Character.isLowerCase(argName.charAt(0))) + { + argName = Character.toUpperCase(argName.charAt(0)) + argName.substring(1); + } + String methodName = "set" + argName; + Method method = principalDatabase.getClass().getMethod(methodName, String.class); + if (method == null) + { + throw new Exception("No method " + methodName + " found in class " + principalDatabase.getClass() + + " hence unable to configure principal database. The method must be public and " + + "have a single String argument with a void return type"); + } + method.invoke(principalDatabase, PropertyUtils.replaceProperties(argumentValues.get(i))); + } + } + + private void initialiseAuthenticationMechanisms(Map<String, PrincipalDatabase> databases) throws Exception + { + Configuration config = ApplicationRegistry.getInstance().getConfiguration(); + List<String> mechanisms = config.getList("security.sasl.mechanisms.mechanism.initialiser.class"); + + // Maps from the mechanism to the properties used to initialise the server. See the method + // Sasl.createSaslServer for details of the use of these properties. This map is populated during initialisation + // of each provider. + Map<String, Class<? extends SaslServerFactory>> providerMap = new TreeMap<String, Class<? extends SaslServerFactory>>(); + + for (int i = 0; i < mechanisms.size(); i++) + { + String baseName = "security.sasl.mechanisms.mechanism(" + i + ").initialiser"; + String clazz = config.getString(baseName + ".class"); + initialiseAuthenticationMechanism(baseName, clazz, databases, config, providerMap); + } + if (providerMap.size() > 0) + { + Security.addProvider(new JCAProvider(providerMap)); + } + } + + private void initialiseAuthenticationMechanism(String baseName, String clazz, + Map<String, PrincipalDatabase> databases, + Configuration configuration, + Map<String, Class<? extends SaslServerFactory>> providerMap) + throws Exception + { + Class initialiserClazz = Class.forName(clazz); + Object o = initialiserClazz.newInstance(); + if (!(o instanceof AuthenticationProviderInitialiser)) + { + throw new Exception("The class " + clazz + " must be an instance of " + + AuthenticationProviderInitialiser.class); + } + AuthenticationProviderInitialiser initialiser = (AuthenticationProviderInitialiser) o; + initialiser.initialise(baseName, configuration, databases); + String mechanism = initialiser.getMechanismName(); + if (_mechanisms == null) + { + _mechanisms = mechanism; + } + else + { + // simple append should be fine since the number of mechanisms is small and this is a one time initialisation + _mechanisms = _mechanisms + " " + mechanism; + } + _callbackHandlerMap.put(mechanism, initialiser.getCallbackHandler()); + _serverCreationProperties.put(mechanism, initialiser.getProperties()); + Class<? extends SaslServerFactory> factory = initialiser.getServerFactoryClassForJCARegistration(); + if (factory != null) + { + providerMap.put(mechanism, factory); + } + _log.info("Initialised " + mechanism + " SASL provider successfully"); + } + + public String getMechanisms() + { + return _mechanisms; + } + + public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException + { + return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, _serverCreationProperties.get(mechanism), + _callbackHandlerMap.get(mechanism)); + } + + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.SUCCESS); + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java new file mode 100644 index 0000000000..02a953f47c --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java @@ -0,0 +1,99 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import org.apache.commons.configuration.Configuration; + +import javax.security.auth.callback.*; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.AuthorizeCallback; +import java.util.Map; +import java.io.IOException; +import java.security.Principal; + +public abstract class UsernamePasswordInitialiser implements AuthenticationProviderInitialiser +{ + private ServerCallbackHandler _callbackHandler; + + private class ServerCallbackHandler implements CallbackHandler + { + private final PrincipalDatabase _principalDatabase; + + protected ServerCallbackHandler(PrincipalDatabase database) + { + _principalDatabase = database; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + Principal username = null; + for (Callback callback : callbacks) + { + if (callback instanceof NameCallback) + { + username = new UsernamePrincipal(((NameCallback)callback).getDefaultName()); + } + else if (callback instanceof PasswordCallback) + { + try + { + _principalDatabase.setPassword(username, (PasswordCallback) callback); + } + catch (AccountNotFoundException e) + { + // very annoyingly the callback handler does not throw anything more appropriate than + // IOException + throw new IOException("Error looking up user " + e); + } + } + else if (callback instanceof AuthorizeCallback) + { + ((AuthorizeCallback)callback).setAuthorized(true); + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + } + + public void initialise(String baseConfigPath, Configuration configuration, + Map<String, PrincipalDatabase> principalDatabases) throws Exception + { + String principalDatabaseName = configuration.getString(baseConfigPath + ".principal-database"); + PrincipalDatabase db = principalDatabases.get(principalDatabaseName); + if (db == null) + { + throw new Exception("Principal database " + principalDatabaseName + " not found. Ensure the name matches " + + "an entry in the configuration file"); + } + _callbackHandler = new ServerCallbackHandler(db); + } + + public CallbackHandler getCallbackHandler() + { + return _callbackHandler; + } + + public Map<String, ?> getProperties() + { + // there are no properties required for the CRAM-MD5 implementation + return null; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java new file mode 100644 index 0000000000..1d425b8399 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth; + +import java.security.Principal; + +/** + * A principal that is just a wrapper for a simple username. + * + */ +public class UsernamePrincipal implements Principal +{ + private String _name; + + public UsernamePrincipal(String name) + { + _name = name; + } + + public String getName() + { + return _name; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java new file mode 100644 index 0000000000..94406237a5 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth.amqplain; + +import org.apache.qpid.server.security.auth.UsernamePasswordInitialiser; + +import javax.security.sasl.SaslServerFactory; + +public class AmqPlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "AMQPLAIN"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return AmqPlainSaslServerFactory.class; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java new file mode 100644 index 0000000000..0f7981abe1 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java @@ -0,0 +1,120 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth.amqplain; + +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.mina.common.ByteBuffer; + +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.auth.callback.*; +import java.io.IOException; + +public class AmqPlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "AMQPLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public AmqPlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + final FieldTable ft = new FieldTable(ByteBuffer.wrap(response), response.length); + String username = (String) ft.get("LOGIN"); + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", username); + // we do not care about the prompt but it throws if null + PasswordCallback passwordCb = new PasswordCallback("prompt", false); + // TODO: should not get pwd as a String but as a char array... + String pwd = (String) ft.get("PASSWORD"); + passwordCb.setPassword(pwd.toCharArray()); + AuthorizeCallback authzCb = new AuthorizeCallback(username, username); + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + _complete = true; + if (authzCb.isAuthorized()) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (AMQFrameDecodingException e) + { + throw new SaslException("Unable to decode response: " + e, e); + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java new file mode 100644 index 0000000000..c4e904f923 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java @@ -0,0 +1,56 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth.amqplain; + +import javax.security.sasl.SaslServerFactory; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import javax.security.auth.callback.CallbackHandler; +import java.util.Map; + +public class AmqPlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (AmqPlainSaslServer.MECHANISM.equals(mechanism)) + { + return new AmqPlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{AmqPlainSaslServer.MECHANISM}; + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java new file mode 100644 index 0000000000..7b055a4f58 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth.plain; + +import org.apache.qpid.server.security.auth.UsernamePasswordInitialiser; + +import javax.security.sasl.SaslServerFactory; + +public class PlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "PLAIN"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return PlainSaslServerFactory.class; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java new file mode 100644 index 0000000000..5a69ed02ba --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java @@ -0,0 +1,141 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth.plain; + +import javax.security.auth.callback.*; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import java.io.IOException; + +public class PlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "PLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public PlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + int authzidNullPosition = findNullPosition(response, 0); + if (authzidNullPosition < 0) + { + throw new SaslException("Invalid PLAIN encoding, authzid null terminator not found"); + } + int authcidNullPosition = findNullPosition(response, authzidNullPosition + 1); + if (authcidNullPosition < 0) + { + throw new SaslException("Invalid PLAIN encoding, authcid null terminator not found"); + } + + // we do not currently support authcid in any meaningful way + String authcid = new String(response, 0, authzidNullPosition, "utf8"); + String authzid = new String(response, authzidNullPosition + 1, authcidNullPosition - 1, "utf8"); + + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", authzid); + // we do not care about the prompt but it throws if null + PasswordCallback passwordCb = new PasswordCallback("prompt", false); + // TODO: should not get pwd as a String but as a char array... + int passwordLen = response.length - authcidNullPosition - 1; + String pwd = new String(response, authcidNullPosition + 1, passwordLen, "utf8"); + passwordCb.setPassword(pwd.toCharArray()); + AuthorizeCallback authzCb = new AuthorizeCallback(authzid, authzid); + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + _complete = true; + if (authzCb.isAuthorized()) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + private int findNullPosition(byte[] response, int startPosition) + { + int position = startPosition; + while (position < response.length) + { + if (response[position] == (byte) 0) + { + return position; + } + position++; + } + return -1; + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java new file mode 100644 index 0000000000..754ecbde78 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java @@ -0,0 +1,56 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.security.auth.plain; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class PlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (PlainSaslServer.MECHANISM.equals(mechanism)) + { + return new PlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{PlainSaslServer.MECHANISM}; + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java b/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java new file mode 100644 index 0000000000..46d46eb4c0 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java @@ -0,0 +1,33 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.state; + +/** + * States used in the AMQ protocol. Used by the finite state machine to determine + * valid responses. + */ +public enum AMQState +{ + CONNECTION_NOT_STARTED, + CONNECTION_NOT_AUTH, + CONNECTION_NOT_TUNED, + CONNECTION_NOT_OPENED, + CONNECTION_OPEN, + CONNECTION_CLOSING, + CONNECTION_CLOSED +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java b/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java new file mode 100644 index 0000000000..54c8bcd9d5 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java @@ -0,0 +1,219 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.state; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.handler.*; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQMethodListener; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * The state manager is responsible for managing the state of the protocol session. + * <p/> + * For each AMQProtocolHandler there is a separate state manager. + * + */ +public class AMQStateManager implements AMQMethodListener +{ + private static final Logger _logger = Logger.getLogger(AMQStateManager.class); + + /** + * The current state + */ + private AMQState _currentState; + + /** + * Maps from an AMQState instance to a Map from Class to StateTransitionHandler. + * The class must be a subclass of AMQFrame. + */ + private final Map<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>> _state2HandlersMap = + new HashMap<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>>(); + + private CopyOnWriteArraySet<StateListener> _stateListeners = new CopyOnWriteArraySet<StateListener>(); + + public AMQStateManager() + { + this(AMQState.CONNECTION_NOT_STARTED, true); + } + + protected AMQStateManager(AMQState initial, boolean register) + { + _currentState = initial; + if (register) + { + registerListeners(); + } + } + + protected void registerListeners() + { + Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> frame2handlerMap = + new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + + // we need to register a map for the null (i.e. all state) handlers otherwise you get + // a stack overflow in the handler searching code when you present it with a frame for which + // no handlers are registered + // + _state2HandlersMap.put(null, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + frame2handlerMap.put(ConnectionStartOkBody.class, ConnectionStartOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + frame2handlerMap.put(ConnectionSecureOkBody.class, ConnectionSecureOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_AUTH, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + frame2handlerMap.put(ConnectionTuneOkBody.class, ConnectionTuneOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + frame2handlerMap.put(ConnectionOpenBody.class, ConnectionOpenMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap); + + // + // ConnectionOpen handlers + // + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + frame2handlerMap.put(ChannelOpenBody.class, ChannelOpenHandler.getInstance()); + frame2handlerMap.put(ChannelCloseBody.class, ChannelCloseHandler.getInstance()); + frame2handlerMap.put(ChannelCloseOkBody.class, ChannelCloseOkHandler.getInstance()); + frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); + frame2handlerMap.put(ExchangeDeclareBody.class, ExchangeDeclareHandler.getInstance()); + frame2handlerMap.put(ExchangeDeleteBody.class, ExchangeDeleteHandler.getInstance()); + frame2handlerMap.put(BasicAckBody.class, BasicAckMethodHandler.getInstance()); + frame2handlerMap.put(BasicRecoverBody.class, BasicRecoverMethodHandler.getInstance()); + frame2handlerMap.put(BasicConsumeBody.class, BasicConsumeMethodHandler.getInstance()); + frame2handlerMap.put(BasicCancelBody.class, BasicCancelMethodHandler.getInstance()); + frame2handlerMap.put(BasicPublishBody.class, BasicPublishMethodHandler.getInstance()); + frame2handlerMap.put(BasicQosBody.class, BasicQosHandler.getInstance()); + frame2handlerMap.put(QueueBindBody.class, QueueBindHandler.getInstance()); + frame2handlerMap.put(QueueDeclareBody.class, QueueDeclareHandler.getInstance()); + frame2handlerMap.put(QueueDeleteBody.class, QueueDeleteHandler.getInstance()); + frame2handlerMap.put(ChannelFlowBody.class, ChannelFlowHandler.getInstance()); + frame2handlerMap.put(TxSelectBody.class, TxSelectHandler.getInstance()); + frame2handlerMap.put(TxCommitBody.class, TxCommitHandler.getInstance()); + frame2handlerMap.put(TxRollbackBody.class, TxRollbackHandler.getInstance()); + + _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + frame2handlerMap.put(ConnectionCloseOkBody.class, ConnectionCloseOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_CLOSING, frame2handlerMap); + + } + + public AMQState getCurrentState() + { + return _currentState; + } + + public void changeState(AMQState newState) throws AMQException + { + _logger.debug("State changing to " + newState + " from old state " + _currentState); + final AMQState oldState = _currentState; + _currentState = newState; + + for (StateListener l : _stateListeners) + { + l.stateChanged(oldState, newState); + } + } + + public void error(AMQException e) + { + _logger.error("State manager received error notification: " + e, e); + for (StateListener l : _stateListeners) + { + l.error(e); + } + } + + public <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt, + AMQProtocolSession protocolSession, + QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry) throws AMQException + { + StateAwareMethodListener<B> handler = findStateTransitionHandler(_currentState, evt.getMethod()); + if (handler != null) + { + handler.methodReceived(this, queueRegistry, exchangeRegistry, protocolSession, evt); + return true; + } + return false; + } + + protected <B extends AMQMethodBody> StateAwareMethodListener<B> findStateTransitionHandler(AMQState currentState, + B frame) + throws IllegalStateTransitionException + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Looking for state transition handler for frame " + frame.getClass()); + } + final Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> + classToHandlerMap = _state2HandlersMap.get(currentState); + + if (classToHandlerMap == null) + { + // if no specialised per state handler is registered look for a + // handler registered for "all" states + return findStateTransitionHandler(null, frame); + } + final StateAwareMethodListener<B> handler = (StateAwareMethodListener<B>) classToHandlerMap.get(frame.getClass()); + if (handler == null) + { + if (currentState == null) + { + _logger.debug("No state transition handler defined for receiving frame " + frame); + return null; + } + else + { + // if no specialised per state handler is registered look for a + // handler registered for "all" states + return findStateTransitionHandler(null, frame); + } + } + else + { + return handler; + } + } + + public void addStateListener(StateListener listener) + { + _logger.debug("Adding state listener"); + _stateListeners.add(listener); + } + + public void removeStateListener(StateListener listener) + { + _stateListeners.remove(listener); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java b/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java new file mode 100644 index 0000000000..c77cb4f833 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.state; + +import org.apache.qpid.AMQException; + +public class IllegalStateTransitionException extends AMQException +{ + private AMQState _originalState; + + private Class _frame; + + public IllegalStateTransitionException(AMQState originalState, Class frame) + { + super("No valid state transition defined for receiving frame " + frame + + " from state " + originalState); + _originalState = originalState; + _frame = frame; + } + + public AMQState getOriginalState() + { + return _originalState; + } + + public Class getFrameClass() + { + return _frame; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java b/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java new file mode 100644 index 0000000000..9776151c28 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.state; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.framing.AMQMethodBody; + +/** + * A frame listener that is informed of the protocol state when invoked and has + * the opportunity to update state. + * + */ +public interface StateAwareMethodListener <B extends AMQMethodBody> +{ + void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent<B> evt) throws AMQException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java b/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java new file mode 100644 index 0000000000..d31e786ef1 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java @@ -0,0 +1,27 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.state; + +import org.apache.qpid.AMQException; + +public interface StateListener +{ + void stateChanged(AMQState oldState, AMQState newState) throws AMQException; + + void error(Throwable t); +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java b/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java new file mode 100644 index 0000000000..0a243fa138 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java @@ -0,0 +1,172 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.store; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.server.queue.QueueRegistry; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A simple message store that stores the messages in a threadsafe structure in memory. + */ +public class MemoryMessageStore implements MessageStore +{ + private static final Logger _log = Logger.getLogger(MemoryMessageStore.class); + + private static final int DEFAULT_HASHTABLE_CAPACITY = 50000; + + private static final String HASHTABLE_CAPACITY_CONFIG = "hashtable-capacity"; + + protected ConcurrentMap<Long, MessageMetaData> _metaDataMap; + + protected ConcurrentMap<Long, List<ContentBody>> _contentBodyMap; + + private final AtomicLong _messageId = new AtomicLong(1); + + public void configure() + { + _log.info("Using capacity " + DEFAULT_HASHTABLE_CAPACITY + " for hash tables"); + _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(DEFAULT_HASHTABLE_CAPACITY); + _contentBodyMap = new ConcurrentHashMap<Long, List<ContentBody>>(DEFAULT_HASHTABLE_CAPACITY); + } + + public void configure(String base, Configuration config) + { + int hashtableCapacity = config.getInt(base + "." + HASHTABLE_CAPACITY_CONFIG, DEFAULT_HASHTABLE_CAPACITY); + _log.info("Using capacity " + hashtableCapacity + " for hash tables"); + _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(hashtableCapacity); + _contentBodyMap = new ConcurrentHashMap<Long, List<ContentBody>>(hashtableCapacity); + } + + public void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception + { + configure(base, config); + } + + public void close() throws Exception + { + if (_metaDataMap != null) + { + _metaDataMap.clear(); + _metaDataMap = null; + } + if (_contentBodyMap != null) + { + _contentBodyMap.clear(); + _contentBodyMap = null; + } + } + + public void removeMessage(long messageId) + { + if (_log.isDebugEnabled()) + { + _log.debug("Removing message with id " + messageId); + } + _metaDataMap.remove(messageId); + _contentBodyMap.remove(messageId); + } + + public void createQueue(AMQQueue queue) throws AMQException + { + // Not required to do anything + } + + public void removeQueue(String name) throws AMQException + { + // Not required to do anything + } + + public void enqueueMessage(String name, long messageId) throws AMQException + { + // Not required to do anything + } + + public void dequeueMessage(String name, long messageId) throws AMQException + { + // Not required to do anything + } + + public void beginTran() throws AMQException + { + // Not required to do anything + } + + public void commitTran() throws AMQException + { + // Not required to do anything + } + + public void abortTran() throws AMQException + { + // Not required to do anything + } + + public boolean inTran() + { + return false; + } + + public List<AMQQueue> createQueues() throws AMQException + { + return null; + } + + public long getNewMessageId() + { + return _messageId.getAndIncrement(); + } + + public void storeContentBodyChunk(long messageId, int index, ContentBody contentBody) throws AMQException + { + List<ContentBody> bodyList = _contentBodyMap.get(messageId); + if (bodyList == null) + { + bodyList = new ArrayList<ContentBody>(); + _contentBodyMap.put(messageId, bodyList); + } + + bodyList.add(index, contentBody); + } + + public void storeMessageMetaData(long messageId, MessageMetaData messageMetaData) throws AMQException + { + _metaDataMap.put(messageId, messageMetaData); + } + + public MessageMetaData getMessageMetaData(long messageId) throws AMQException + { + return _metaDataMap.get(messageId); + } + + public ContentBody getContentBodyChunk(long messageId, int index) throws AMQException + { + List<ContentBody> bodyList = _contentBodyMap.get(messageId); + return bodyList.get(index); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java b/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java new file mode 100644 index 0000000000..c0e8d0465b --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java @@ -0,0 +1,87 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.store; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.server.queue.QueueRegistry; + +import java.util.List; + +public interface MessageStore +{ + /** + * Called after instantiation in order to configure the message store. A particular implementation can define + * whatever parameters it wants. + * @param queueRegistry the registry of queues to be used by this store + * @param base the base element identifier from which all configuration items are relative. For example, if the base + * element is "store", the all elements used by concrete classes will be "store.foo" etc. + * @param config the apache commons configuration object + * @throws Exception if an error occurs that means the store is unable to configure itself + */ + void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception; + + /** + * Called to close and cleanup any resources used by the message store. + * @throws Exception if close fails + */ + void close() throws Exception; + + void removeMessage(long messageId) throws AMQException; + + void createQueue(AMQQueue queue) throws AMQException; + + void removeQueue(String name) throws AMQException; + + void enqueueMessage(String name, long messageId) throws AMQException; + + void dequeueMessage(String name, long messageId) throws AMQException; + + void beginTran() throws AMQException; + + void commitTran() throws AMQException; + + void abortTran() throws AMQException; + + boolean inTran(); + + /** + * Recreate all queues that were persisted, including re-enqueuing of existing messages + * @return + * @throws AMQException + */ + List<AMQQueue> createQueues() throws AMQException; + + /** + * Return a valid, currently unused message id. + * @return a message id + */ + long getNewMessageId(); + + void storeContentBodyChunk(long messageId, int index, ContentBody contentBody) throws AMQException; + + void storeMessageMetaData(long messageId, MessageMetaData messageMetaData) throws AMQException; + + MessageMetaData getMessageMetaData(long messageId) throws AMQException; + + ContentBody getContentBodyChunk(long messageId, int index) throws AMQException; + +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java b/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java new file mode 100644 index 0000000000..6470d876bb --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java @@ -0,0 +1,83 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.transport; + +import org.apache.qpid.configuration.Configured; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.filter.executor.ExecutorExecutor; +import org.apache.mina.util.NewThreadExecutor; + +public class ConnectorConfiguration +{ + public static final String DEFAULT_PORT = "5672"; + + public static final String SSL_PORT = "8672"; + + @Configured(path = "connector.processors", + defaultValue = "4") + public int processors; + + @Configured(path = "connector.port", + defaultValue = DEFAULT_PORT) + public int port; + + @Configured(path = "connector.bind", + defaultValue = "wildcard") + public String bindAddress; + + @Configured(path = "connector.sslport", + defaultValue = SSL_PORT) + public int sslPort; + + @Configured(path = "connector.socketReceiveBuffer", + defaultValue = "32767") + public int socketReceiveBufferSize; + + @Configured(path = "connector.socketWriteBuffer", + defaultValue = "32767") + public int socketWriteBuferSize; + + @Configured(path = "connector.tcpNoDelay", + defaultValue = "true") + public boolean tcpNoDelay; + + @Configured(path = "advanced.filterchain[@enableExecutorPool]", + defaultValue = "false") + public boolean enableExecutorPool; + + @Configured(path = "advanced.enablePooledAllocator", + defaultValue = "false") + public boolean enablePooledAllocator; + + @Configured(path = "advanced.enableDirectBuffers", + defaultValue = "false") + public boolean enableDirectBuffers; + + @Configured(path = "connector.ssl", + defaultValue = "false") + public boolean enableSSL; + + @Configured(path = "connector.nonssl", + defaultValue = "true") + public boolean enableNonSSL; + + public IoAcceptor createAcceptor() + { + return new org.apache.mina.transport.socket.nio.SocketAcceptor(processors, new NewThreadExecutor()); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java b/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java new file mode 100644 index 0000000000..e3d87f808c --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java @@ -0,0 +1,692 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.transport; + +import org.apache.mina.common.*; +import org.apache.mina.util.*; +import org.apache.mina.util.Queue; +import org.apache.mina.util.Stack; + +import java.util.*; + +/** + * A Thread-pooling filter. This filter forwards {@link IoHandler} events + * to its thread pool. + * <p/> + * This is an implementation of + * <a href="http://deuce.doc.wustl.edu/doc/pspdfs/lf.pdf">Leader/Followers + * thread pool</a> by Douglas C. Schmidt et al. + */ +public class ThreadPoolFilter extends IoFilterAdapter +{ + /** + * Default maximum size of thread pool (2G). + */ + public static final int DEFAULT_MAXIMUM_POOL_SIZE = Integer.MAX_VALUE; + + /** + * Default keep-alive time of thread pool (1 min). + */ + public static final int DEFAULT_KEEP_ALIVE_TIME = 60 * 1000; + + /** + * A queue which contains {@link Integer}s which represents reusable + * thread IDs. {@link Worker} first checks this queue and then + * uses {@link #threadId} when no reusable thread ID is available. + */ + private static final Queue threadIdReuseQueue = new Queue(); + private static int threadId = 0; + + private static int acquireThreadId() + { + synchronized (threadIdReuseQueue) + { + Integer id = (Integer) threadIdReuseQueue.pop(); + if (id == null) + { + return ++ threadId; + } + else + { + return id.intValue(); + } + } + } + + private static void releaseThreadId(int id) + { + synchronized (threadIdReuseQueue) + { + threadIdReuseQueue.push(new Integer(id)); + } + } + + private final String threadNamePrefix; + private final Map buffers = new IdentityHashMap(); + private final BlockingQueue unfetchedSessionBuffers = new BlockingQueue(); + private final Set allSessionBuffers = new IdentityHashSet(); + + private Worker leader; + private final Stack followers = new Stack(); + private final Set allWorkers = new IdentityHashSet(); + + private int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE; + private int keepAliveTime = DEFAULT_KEEP_ALIVE_TIME; + + private boolean shuttingDown; + + private int poolSize; + private final Object poolSizeLock = new Object(); + + /** + * Creates a new instance of this filter with default thread pool settings. + */ + public ThreadPoolFilter() + { + this("IoThreadPool"); + } + + /** + * Creates a new instance of this filter with the specified thread name prefix + * and other default settings. + * + * @param threadNamePrefix the prefix of the thread names this pool will create. + */ + public ThreadPoolFilter(String threadNamePrefix) + { + if (threadNamePrefix == null) + { + throw new NullPointerException("threadNamePrefix"); + } + threadNamePrefix = threadNamePrefix.trim(); + if (threadNamePrefix.length() == 0) + { + throw new IllegalArgumentException("threadNamePrefix is empty."); + } + this.threadNamePrefix = threadNamePrefix; + } + + public String getThreadNamePrefix() + { + return threadNamePrefix; + } + + public int getPoolSize() + { + synchronized (poolSizeLock) + { + return poolSize; + } + } + + public int getMaximumPoolSize() + { + return maximumPoolSize; + } + + public int getKeepAliveTime() + { + return keepAliveTime; + } + + public void setMaximumPoolSize(int maximumPoolSize) + { + if (maximumPoolSize <= 0) + { + throw new IllegalArgumentException(); + } + this.maximumPoolSize = maximumPoolSize; + } + + public void setKeepAliveTime(int keepAliveTime) + { + this.keepAliveTime = keepAliveTime; + } + + public void init() + { + shuttingDown = false; + leader = new Worker(); + leader.start(); + leader.lead(); + } + + public void destroy() + { + shuttingDown = true; + int expectedPoolSize = 0; + while (getPoolSize() != expectedPoolSize) + { + List allWorkers; + synchronized (poolSizeLock) + { + allWorkers = new ArrayList(this.allWorkers); + } + + // You may not interrupt the current thread. + if (allWorkers.remove(Thread.currentThread())) + { + expectedPoolSize = 1; + } + + for (Iterator i = allWorkers.iterator(); i.hasNext();) + { + Worker worker = (Worker) i.next(); + while (worker.isAlive()) + { + worker.interrupt(); + try + { + // This timeout will help us from + // infinite lock-up and interrupt workers again. + worker.join(100); + } + catch (InterruptedException e) + { + } + } + } + } + + this.allSessionBuffers.clear(); + this.unfetchedSessionBuffers.clear(); + this.buffers.clear(); + this.followers.clear(); + this.leader = null; + } + + private void increasePoolSize(Worker worker) + { + synchronized (poolSizeLock) + { + poolSize++; + allWorkers.add(worker); + } + } + + private void decreasePoolSize(Worker worker) + { + synchronized (poolSizeLock) + { + poolSize--; + allWorkers.remove(worker); + } + } + + private void fireEvent(NextFilter nextFilter, IoSession session, + EventType type, Object data) + { + final BlockingQueue unfetchedSessionBuffers = this.unfetchedSessionBuffers; + final Set allSessionBuffers = this.allSessionBuffers; + final Event event = new Event(type, nextFilter, data); + + synchronized (unfetchedSessionBuffers) + { + final SessionBuffer buf = getSessionBuffer(session); + final Queue eventQueue = buf.eventQueue; + + synchronized (buf) + { + eventQueue.push(event); + } + + if (!allSessionBuffers.contains(buf)) + { + allSessionBuffers.add(buf); + unfetchedSessionBuffers.push(buf); + } + } + } + + /** + * Implement this method to fetch (or pop) a {@link SessionBuffer} from + * the given <tt>unfetchedSessionBuffers</tt>. The default implementation + * simply pops the buffer from it. You could prioritize the fetch order. + * + * @return A non-null {@link SessionBuffer} + */ + protected SessionBuffer fetchSessionBuffer(Queue unfetchedSessionBuffers) + { + return (SessionBuffer) unfetchedSessionBuffers.pop(); + } + + private SessionBuffer getSessionBuffer(IoSession session) + { + final Map buffers = this.buffers; + SessionBuffer buf = (SessionBuffer) buffers.get(session); + if (buf == null) + { + synchronized (buffers) + { + buf = (SessionBuffer) buffers.get(session); + if (buf == null) + { + buf = new SessionBuffer(session); + buffers.put(session, buf); + } + } + } + return buf; + } + + private void removeSessionBuffer(SessionBuffer buf) + { + final Map buffers = this.buffers; + final IoSession session = buf.session; + synchronized (buffers) + { + buffers.remove(session); + } + } + + protected static class SessionBuffer + { + private final IoSession session; + + private final Queue eventQueue = new Queue(); + + private SessionBuffer(IoSession session) + { + this.session = session; + } + + public IoSession getSession() + { + return session; + } + + public Queue getEventQueue() + { + return eventQueue; + } + } + + private class Worker extends Thread + { + private final int id; + private final Object promotionLock = new Object(); + private boolean dead; + + private Worker() + { + int id = acquireThreadId(); + this.id = id; + this.setName(threadNamePrefix + '-' + id); + increasePoolSize(this); + } + + public boolean lead() + { + final Object promotionLock = this.promotionLock; + synchronized (promotionLock) + { + if (dead) + { + return false; + } + + leader = this; + promotionLock.notify(); + } + + return true; + } + + public void run() + { + for (; ;) + { + if (!waitForPromotion()) + { + break; + } + + SessionBuffer buf = fetchBuffer(); + giveUpLead(); + if (buf == null) + { + break; + } + + processEvents(buf); + follow(); + releaseBuffer(buf); + } + + decreasePoolSize(this); + releaseThreadId(id); + } + + private SessionBuffer fetchBuffer() + { + BlockingQueue unfetchedSessionBuffers = ThreadPoolFilter.this.unfetchedSessionBuffers; + synchronized (unfetchedSessionBuffers) + { + while (!shuttingDown) + { + try + { + unfetchedSessionBuffers.waitForNewItem(); + } + catch (InterruptedException e) + { + continue; + } + + return ThreadPoolFilter.this.fetchSessionBuffer(unfetchedSessionBuffers); + } + } + + return null; + } + + private void processEvents(SessionBuffer buf) + { + final IoSession session = buf.session; + final Queue eventQueue = buf.eventQueue; + for (; ;) + { + Event event; + synchronized (buf) + { + event = (Event) eventQueue.pop(); + if (event == null) + { + break; + } + } + processEvent(event.getNextFilter(), session, + event.getType(), event.getData()); + } + } + + private void follow() + { + final Object promotionLock = this.promotionLock; + final Stack followers = ThreadPoolFilter.this.followers; + synchronized (promotionLock) + { + if (this != leader) + { + synchronized (followers) + { + followers.push(this); + } + } + } + } + + private void releaseBuffer(SessionBuffer buf) + { + final BlockingQueue unfetchedSessionBuffers = ThreadPoolFilter.this.unfetchedSessionBuffers; + final Set allSessionBuffers = ThreadPoolFilter.this.allSessionBuffers; + final Queue eventQueue = buf.eventQueue; + + synchronized (unfetchedSessionBuffers) + { + if (eventQueue.isEmpty()) + { + allSessionBuffers.remove(buf); + removeSessionBuffer(buf); + } + else + { + unfetchedSessionBuffers.push(buf); + } + } + } + + private boolean waitForPromotion() + { + final Object promotionLock = this.promotionLock; + + long startTime = System.currentTimeMillis(); + long currentTime = System.currentTimeMillis(); + + synchronized (promotionLock) + { + while (this != leader && !shuttingDown) + { + // Calculate remaining keep-alive time + int keepAliveTime = getKeepAliveTime(); + if (keepAliveTime > 0) + { + keepAliveTime -= (currentTime - startTime); + } + else + { + keepAliveTime = Integer.MAX_VALUE; + } + + // Break the loop if there's no remaining keep-alive time. + if (keepAliveTime <= 0) + { + break; + } + + // Wait for promotion + try + { + promotionLock.wait(keepAliveTime); + } + catch (InterruptedException e) + { + } + + // Update currentTime for the next iteration + currentTime = System.currentTimeMillis(); + } + + boolean timeToLead = this == leader && !shuttingDown; + + if (!timeToLead) + { + // time to die + synchronized (followers) + { + followers.remove(this); + } + + // Mark as dead explicitly when we've got promotionLock. + dead = true; + } + + return timeToLead; + } + } + + private void giveUpLead() + { + final Stack followers = ThreadPoolFilter.this.followers; + Worker worker; + do + { + synchronized (followers) + { + worker = (Worker) followers.pop(); + } + + if (worker == null) + { + // Increase the number of threads if we + // are not shutting down and we can increase the number. + if (!shuttingDown + && getPoolSize() < getMaximumPoolSize()) + { + worker = new Worker(); + worker.lead(); + worker.start(); + } + + // This loop should end because: + // 1) lead() is called already, + // 2) or it is shutting down and there's no more threads left. + break; + } + } + while (!worker.lead()); + } + } + + protected static class EventType + { + public static final EventType OPENED = new EventType("OPENED"); + + public static final EventType CLOSED = new EventType("CLOSED"); + + public static final EventType READ = new EventType("READ"); + + public static final EventType WRITTEN = new EventType("WRITTEN"); + + public static final EventType RECEIVED = new EventType("RECEIVED"); + + public static final EventType SENT = new EventType("SENT"); + + public static final EventType IDLE = new EventType("IDLE"); + + public static final EventType EXCEPTION = new EventType("EXCEPTION"); + + private final String value; + + private EventType(String value) + { + this.value = value; + } + + public String toString() + { + return value; + } + } + + protected static class Event + { + private final EventType type; + private final NextFilter nextFilter; + private final Object data; + + public Event(EventType type, NextFilter nextFilter, Object data) + { + this.type = type; + this.nextFilter = nextFilter; + this.data = data; + } + + public Object getData() + { + return data; + } + + + public NextFilter getNextFilter() + { + return nextFilter; + } + + + public EventType getType() + { + return type; + } + } + + public void sessionCreated(NextFilter nextFilter, IoSession session) + { + nextFilter.sessionCreated(session); + } + + public void sessionOpened(NextFilter nextFilter, + IoSession session) + { + fireEvent(nextFilter, session, EventType.OPENED, null); + } + + public void sessionClosed(NextFilter nextFilter, + IoSession session) + { + fireEvent(nextFilter, session, EventType.CLOSED, null); + } + + public void sessionIdle(NextFilter nextFilter, + IoSession session, IdleStatus status) + { + fireEvent(nextFilter, session, EventType.IDLE, status); + } + + public void exceptionCaught(NextFilter nextFilter, + IoSession session, Throwable cause) + { + fireEvent(nextFilter, session, EventType.EXCEPTION, cause); + } + + public void messageReceived(NextFilter nextFilter, + IoSession session, Object message) + { + ByteBufferUtil.acquireIfPossible(message); + fireEvent(nextFilter, session, EventType.RECEIVED, message); + } + + public void messageSent(NextFilter nextFilter, + IoSession session, Object message) + { + ByteBufferUtil.acquireIfPossible(message); + fireEvent(nextFilter, session, EventType.SENT, message); + } + + protected void processEvent(NextFilter nextFilter, + IoSession session, EventType type, + Object data) + { + if (type == EventType.RECEIVED) + { + nextFilter.messageReceived(session, data); + ByteBufferUtil.releaseIfPossible(data); + } + else if (type == EventType.SENT) + { + nextFilter.messageSent(session, data); + ByteBufferUtil.releaseIfPossible(data); + } + else if (type == EventType.EXCEPTION) + { + nextFilter.exceptionCaught(session, (Throwable) data); + } + else if (type == EventType.IDLE) + { + nextFilter.sessionIdle(session, (IdleStatus) data); + } + else if (type == EventType.OPENED) + { + nextFilter.sessionOpened(session); + } + else if (type == EventType.CLOSED) + { + nextFilter.sessionClosed(session); + } + } + + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) + { + nextFilter.filterWrite(session, writeRequest); + } + + public void filterClose(NextFilter nextFilter, IoSession session) throws Exception + { + nextFilter.filterClose(session); + } +}
\ No newline at end of file diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/CleanupMessageOperation.java b/java/broker/src/main/java/org/apache/qpid/server/txn/CleanupMessageOperation.java new file mode 100644 index 0000000000..7ff609b750 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/txn/CleanupMessageOperation.java @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.txn; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.NoConsumersException; +import org.apache.qpid.server.RequiredDeliveryException; + +import java.util.List; + +/** + * @author Apache Software Foundation + */ +public class CleanupMessageOperation implements TxnOp +{ + private static final Logger _log = Logger.getLogger(CleanupMessageOperation.class); + + private final AMQMessage _msg; + + private final List<RequiredDeliveryException> _returns; + + public CleanupMessageOperation(AMQMessage msg, List<RequiredDeliveryException> returns) + { + _msg = msg; + _returns = returns; + } + + public void prepare() throws AMQException + { + } + + public void undoPrepare() + { + //don't need to do anything here, if the store's txn failed + //when processing prepare then the message was not stored + //or enqueued on any queues and can be discarded + } + + public void commit() + { + //The routers reference can now be released. This is done + //here to ensure that it happens after the queues that + //enqueue it have incremented their counts (which as a + //memory only operation is done in the commit phase). + try + { + _msg.decrementReference(); + } + catch (AMQException e) + { + _log.error("On commiting transaction, failed to cleanup unused message: " + e, e); + } + try + { + _msg.checkDeliveredToConsumer(); + } + catch (NoConsumersException e) + { + //TODO: store this for delivery after the commit-ok + _returns.add(e); + } + catch (AMQException e) + { + _log.error("On commiting transaction, unable to determine whether delivered to a consumer immediately: " + + e, e); + } + } + + public void rollback() + { + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/DeliverMessageOperation.java b/java/broker/src/main/java/org/apache/qpid/server/txn/DeliverMessageOperation.java new file mode 100644 index 0000000000..059c13c687 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/txn/DeliverMessageOperation.java @@ -0,0 +1,61 @@ +/** + * User: Robert Greig + * Date: 01-Nov-2006 + ****************************************************************************** + * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of + * this program may be photocopied reproduced or translated to another + * program language without prior written consent of JP Morgan Chase Ltd + ******************************************************************************/ +package org.apache.qpid.server.txn; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * @author Robert Greig (robert.j.greig@jpmorgan.com) + */ +public class DeliverMessageOperation implements TxnOp +{ + private static final Logger _logger = Logger.getLogger(DeliverMessageOperation.class); + + private final AMQMessage _msg; + + private final AMQQueue _queue; + + public DeliverMessageOperation(AMQMessage msg, AMQQueue queue) + { + _msg = msg; + _queue = queue; + _msg.incrementReference(); + } + + public void prepare() throws AMQException + { + } + + public void undoPrepare() + { + } + + public void commit() + { + //do the memeory part of the record() + _msg.incrementReference(); + //then process the message + try + { + _queue.process(_msg); + } + catch (AMQException e) + { + //TODO: is there anything else we can do here? I think not... + _logger.error("Error during commit of a queue delivery: " + e, e); + } + } + + public void rollback() + { + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java b/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java new file mode 100644 index 0000000000..df6520e0a8 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java @@ -0,0 +1,149 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.txn; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.ack.TxAck; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.store.MessageStore; + +import java.util.List; + +/** + * A transactional context that only supports local transactions. + */ +public class LocalTransactionalContext implements TransactionalContext +{ + private final TxnBuffer _txnBuffer; + + /** + * We keep hold of the ack operation so that we can consolidate acks, i.e. multiple acks within a txn are + * consolidated into a single operation + */ + private TxAck _ackOp; + + private List<RequiredDeliveryException> _returnMessages; + + private final MessageStore _messageStore; + + private boolean _inTran = false; + + public LocalTransactionalContext(MessageStore messageStore, + TxnBuffer txnBuffer, List<RequiredDeliveryException> returnMessages) + { + _messageStore = messageStore; + _txnBuffer = txnBuffer; + _returnMessages = returnMessages; + _txnBuffer.enlist(new StoreMessageOperation(messageStore)); + } + + public void rollback() throws AMQException + { + _txnBuffer.rollback(); + } + + public void deliver(AMQMessage message, AMQQueue queue) throws AMQException + { + // don't create a transaction unless needed + if (message.isPersistent()) + { + _txnBuffer.containsPersistentChanges(); + } + + // A publication will result in the enlisting of several + // TxnOps. The first is an op that will store the message. + // Following that (and ordering is important), an op will + // be added for every queue onto which the message is + // enqueued. Finally a cleanup op will be added to decrement + // the reference associated with the routing. + + _txnBuffer.enlist(new DeliverMessageOperation(message, queue)); + _txnBuffer.enlist(new CleanupMessageOperation(message, _returnMessages)); + } + + private void checkAck(long deliveryTag, UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException + { + if (!unacknowledgedMessageMap.contains(deliveryTag)) + { + throw new AMQException("Ack with delivery tag " + deliveryTag + " not known for channel"); + } + } + + public void acknowledgeMessage(long deliveryTag, long lastDeliveryTag, boolean multiple, + UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException + { + //check that the tag exists to give early failure + if (!multiple || deliveryTag > 0) + { + checkAck(deliveryTag, unacknowledgedMessageMap); + } + //we use a single txn op for all acks and update this op + //as new acks come in. If this is the first ack in the txn + //we will need to create and enlist the op. + if (_ackOp == null) + { + _ackOp = new TxAck(unacknowledgedMessageMap); + _txnBuffer.enlist(_ackOp); + } + // update the op to include this ack request + if (multiple && deliveryTag == 0) + { + // if have signalled to ack all, that refers only + // to all at this time + _ackOp.update(lastDeliveryTag, multiple); + } + else + { + _ackOp.update(deliveryTag, multiple); + } + } + + public void messageFullyReceived(boolean persistent) throws AMQException + { + // Not required in this transactional context + } + + public void beginTranIfNecessary() throws AMQException + { + if (!_inTran) + { + _messageStore.beginTran(); + _inTran = true; + } + } + + public void commit() throws AMQException + { + if (_ackOp != null) + { + _ackOp.consolidate(); + if (_ackOp.checkPersistent()) + { + _txnBuffer.containsPersistentChanges(); + } + //already enlisted, after commit will reset regardless of outcome + _ackOp = null; + } + + _txnBuffer.commit(); + //TODO: may need to return 'immediate' messages at this point + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java b/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java new file mode 100644 index 0000000000..9c8e7c7002 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java @@ -0,0 +1,171 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.txn; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.ack.UnacknowledgedMessage; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.NoConsumersException; +import org.apache.qpid.server.store.MessageStore; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Apache Software Foundation + */ +public class NonTransactionalContext implements TransactionalContext +{ + private static final Logger _log = Logger.getLogger(NonTransactionalContext.class); + + /** + * Channel is useful for logging + */ + private final AMQChannel _channel; + + /** + * Where to put undeliverable messages + */ + private final List<RequiredDeliveryException> _returnMessages; + + private final MessageStore _messageStore; + + /** + * Whether we are in a transaction + */ + private boolean _inTran; + + public NonTransactionalContext(MessageStore messageStore, AMQChannel channel, + List<RequiredDeliveryException> returnMessages) + { + _channel = channel; + _returnMessages = returnMessages; + _messageStore = messageStore; + } + + public void beginTranIfNecessary() throws AMQException + { + if (!_inTran) + { + _messageStore.beginTran(); + _inTran = true; + } + } + + public void commit() throws AMQException + { + // Does not apply to this context + } + + public void rollback() throws AMQException + { + // Does not apply to this context + } + + public void deliver(AMQMessage message, AMQQueue queue) throws AMQException + { + try + { + message.incrementReference(); + queue.process(message); + //following check implements the functionality + //required by the 'immediate' flag: + message.checkDeliveredToConsumer(); + } + catch (NoConsumersException e) + { + _returnMessages.add(e); + } + } + + public void acknowledgeMessage(final long deliveryTag, long lastDeliveryTag, + boolean multiple, final UnacknowledgedMessageMap unacknowledgedMessageMap) + throws AMQException + { + if (multiple) + { + if (deliveryTag == 0) + { + + //Spec 2.1.6.11 ... If the multiple field is 1, and the delivery tag is zero, + // tells the server to acknowledge all outstanding mesages. + _log.info("Multiple ack on delivery tag 0. ACKing all messages. Current count:" + + unacknowledgedMessageMap.size()); + unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor() + { + public boolean callback(UnacknowledgedMessage message) throws AMQException + { + message.discard(); + return false; + } + + public void visitComplete() + { + unacknowledgedMessageMap.clear(); + } + }); + } + else + { + if (!unacknowledgedMessageMap.contains(deliveryTag)) + { + throw new AMQException("Multiple ack on delivery tag " + deliveryTag + " not known for channel"); + } + + LinkedList<UnacknowledgedMessage> acked = new LinkedList<UnacknowledgedMessage>(); + unacknowledgedMessageMap.drainTo(acked, deliveryTag); + for (UnacknowledgedMessage msg : acked) + { + msg.discard(); + } + } + } + else + { + UnacknowledgedMessage msg; + msg = unacknowledgedMessageMap.remove(deliveryTag); + + if (msg == null) + { + _log.info("Single ack on delivery tag " + deliveryTag + " not known for channel:" + + _channel.getChannelId()); + throw new AMQException("Single ack on delivery tag " + deliveryTag + " not known for channel:" + + _channel.getChannelId()); + } + msg.discard(); + if (_log.isDebugEnabled()) + { + _log.debug("Received non-multiple ack for messaging with delivery tag " + deliveryTag); + } + } + } + + public void messageFullyReceived(boolean persistent) throws AMQException + { + if (persistent) + { + _messageStore.commitTran(); + _inTran = false; + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java b/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java new file mode 100644 index 0000000000..2a3b524060 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java @@ -0,0 +1,45 @@ +/** + * User: Robert Greig + * Date: 01-Nov-2006 + ****************************************************************************** + * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of + * this program may be photocopied reproduced or translated to another + * program language without prior written consent of JP Morgan Chase Ltd + ******************************************************************************/ +package org.apache.qpid.server.txn; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.MessageStore; + +/** + * A transactional operation to store messages in an underlying persistent store. When this operation + * commits it will do everything to ensure that all messages are safely committed to persistent + * storage. + */ +public class StoreMessageOperation implements TxnOp +{ + private final MessageStore _messsageStore; + + public StoreMessageOperation(MessageStore messageStore) + { + _messsageStore = messageStore; + } + + public void prepare() throws AMQException + { + } + + public void undoPrepare() + { + } + + public void commit() throws AMQException + { + _messsageStore.commitTran(); + } + + public void rollback() throws AMQException + { + _messsageStore.abortTran(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java b/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java new file mode 100644 index 0000000000..1ec216cc8b --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java @@ -0,0 +1,33 @@ +/** + * User: Robert Greig + * Date: 01-Nov-2006 + ****************************************************************************** + * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of + * this program may be photocopied reproduced or translated to another + * program language without prior written consent of JP Morgan Chase Ltd + ******************************************************************************/ +package org.apache.qpid.server.txn; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * @author Robert Greig (robert.j.greig@jpmorgan.com) + */ +public interface TransactionalContext +{ + void beginTranIfNecessary() throws AMQException; + + void commit() throws AMQException; + + void rollback() throws AMQException; + + void deliver(AMQMessage message, AMQQueue queue) throws AMQException; + + void acknowledgeMessage(long deliveryTag, long lastDeliveryTag, boolean multiple, + UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException; + + void messageFullyReceived(boolean persistent) throws AMQException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java new file mode 100644 index 0000000000..4e141495c2 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java @@ -0,0 +1,119 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.txn; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.MessageStore; + +import java.util.ArrayList; +import java.util.List; + +/** + * Holds a list of TxnOp instance representing transactional + * operations. + */ +public class TxnBuffer +{ + private boolean _containsPersistentChanges = false; + private final MessageStore _store; + private final List<TxnOp> _ops = new ArrayList<TxnOp>(); + private static final Logger _log = Logger.getLogger(TxnBuffer.class); + + public TxnBuffer(MessageStore store) + { + _store = store; + } + + public void containsPersistentChanges() + { + _containsPersistentChanges = true; + } + + public void commit() throws AMQException + { + if (_containsPersistentChanges) + { + _log.debug("Begin Transaction."); + if (prepare()) + { + _log.debug("Transaction Succeeded"); + for (TxnOp op : _ops) + { + op.commit(); + } + } + else + { + _log.debug("Transaction Failed"); + } + } + else + { + if (prepare()) + { + for (TxnOp op : _ops) + { + op.commit(); + } + } + } + _ops.clear(); + } + + private boolean prepare() + { + for (int i = 0; i < _ops.size(); i++) + { + TxnOp op = _ops.get(i); + try + { + op.prepare(); + } + catch (Exception e) + { + //compensate previously prepared ops + for(int j = 0; j < i; j++) + { + _ops.get(j).undoPrepare(); + } + return false; + } + } + return true; + } + + public void rollback() throws AMQException + { + for (TxnOp op : _ops) + { + op.rollback(); + } + _ops.clear(); + } + + public void enlist(TxnOp op) + { + _ops.add(op); + } + + public void cancel(TxnOp op) + { + _ops.remove(op); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java new file mode 100644 index 0000000000..1a81786460 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java @@ -0,0 +1,51 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.txn; + +import org.apache.qpid.AMQException; + +/** + * This provides the abstraction of an individual operation within a + * transaction. It is used by the TxnBuffer class. + */ +public interface TxnOp +{ + /** + * Do the part of the operation that updates persistent state + */ + public void prepare() throws AMQException; + /** + * Complete the operation started by prepare. Can now update in + * memory state or make netork transfers. + */ + public void commit() throws AMQException; + /** + * This is not the same as rollback. Unfortunately the use of an + * in memory reference count as a locking mechanism and a test for + * whether a message should be deleted means that as things are, + * handling an acknowledgement unavoidably alters both memory and + * persistent state on prepare. This is needed to 'compensate' or + * undo the in-memory change if the peristent update of later ops + * fails. + */ + public void undoPrepare(); + /** + * Rolls back the operation. + */ + public void rollback() throws AMQException; +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java b/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java new file mode 100644 index 0000000000..d2c10cb4d1 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java @@ -0,0 +1,123 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +import java.util.Iterator; + +public class CircularBuffer implements Iterable +{ + private final Object[] _log; + private int _size; + private int _index; + + public CircularBuffer(int size) + { + _log = new Object[size]; + } + + public void add(Object o) + { + _log[_index++] = o; + _size = Math.min(_size+1, _log.length); + if(_index >= _log.length) + { + _index = 0; + } + } + + public Object get(int i) + { + if(i >= _log.length) + { + throw new ArrayIndexOutOfBoundsException(i); + } + return _log[index(i)]; + } + + public int size() { + return _size; + } + + public Iterator iterator() + { + return new Iterator() + { + private int i = 0; + + public boolean hasNext() + { + return i < _size; + } + + public Object next() + { + return get(i++); + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } + + public String toString() + { + StringBuilder s = new StringBuilder(); + boolean first = true; + for(Object o : this) + { + if(!first) + { + s.append(", "); + } + else + { + first = false; + } + s.append(o); + } + return s.toString(); + } + + public void dump() + { + for(Object o : this) + { + System.out.println(o); + } + } + + int index(int i) + { + return _size == _log.length ? (_index + i) % _log.length : i; + } + + public static void main(String[] artgv) + { + String[] items = new String[]{ + "A","B","C","D","E","F","G","H","I","J","K" + }; + CircularBuffer buffer = new CircularBuffer(5); + for(String s : items) + { + buffer.add(s); + System.out.println(buffer); + } + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java b/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java new file mode 100644 index 0000000000..03c4896422 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java @@ -0,0 +1,102 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; + +/** + * Dynamic proxy that records invocations in a fixed size circular buffer, + * dumping details on hitting an exception. + * <p> + * Useful in debugging. + * <p> + */ +public class LoggingProxy implements InvocationHandler +{ + private final Object _target; + private final CircularBuffer _log; + + public LoggingProxy(Object target, int size) + { + _target = target; + _log = new CircularBuffer(size); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + try + { + entered(method, args); + Object result = method.invoke(_target, args); + returned(method, result); + return result; + } + catch(InvocationTargetException e) + { + dump(); + throw e.getTargetException(); + } + } + + void dump() + { + _log.dump(); + } + + CircularBuffer getBuffer() + { + return _log; + } + + private synchronized void entered(Method method, Object[] args) + { + if (args == null) + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() entered"); + } + else + { + _log.add(Thread.currentThread() + ": " + method.getName() + "(" + Arrays.toString(args) + ") entered"); + } + } + + private synchronized void returned(Method method, Object result) + { + if (method.getReturnType() == Void.TYPE) + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() returned"); + } + else + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() returned " + result); + } + } + + public Object getProxy(Class... c) + { + return Proxy.newProxyInstance(_target.getClass().getClassLoader(), c, this); + } + + public int getBufferSize() { + return _log.size(); + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java new file mode 100644 index 0000000000..0fab1f6895 --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java @@ -0,0 +1,106 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.DefaultExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.queue.DefaultQueueRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.NullAuthenticationManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.MapConfiguration; + +import java.util.HashMap; + +public class NullApplicationRegistry extends ApplicationRegistry +{ + private QueueRegistry _queueRegistry; + + private ExchangeRegistry _exchangeRegistry; + + private ExchangeFactory _exchangeFactory; + + private ManagedObjectRegistry _managedObjectRegistry; + + private AuthenticationManager _authenticationManager; + + private MessageStore _messageStore; + + + public NullApplicationRegistry() + { + super(new MapConfiguration(new HashMap())); + } + + public void initialise() throws Exception + { + _managedObjectRegistry = new NoopManagedObjectRegistry(); + _queueRegistry = new DefaultQueueRegistry(); + _exchangeFactory = new DefaultExchangeFactory(); + _exchangeRegistry = new DefaultExchangeRegistry(_exchangeFactory); + _authenticationManager = new NullAuthenticationManager(); + _messageStore = new MemoryMessageStore(); + ((MemoryMessageStore)_messageStore).configure(); + + _configuration.addProperty("heartbeat.delay", 10 * 60); // 10 minutes + } + + public Configuration getConfiguration() + { + return _configuration; + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + public ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + public ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + public ManagedObjectRegistry getManagedObjectRegistry() + { + return _managedObjectRegistry; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } +} + diff --git a/java/broker/src/main/log4j.properties b/java/broker/src/main/log4j.properties new file mode 100644 index 0000000000..3ff6f0b581 --- /dev/null +++ b/java/broker/src/main/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootCategory=${amqj.logging.level}, console + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.Threshold=info +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%t %d %p [%c{4}] %m%n diff --git a/java/broker/src/main/test/build-module.xml b/java/broker/src/main/test/build-module.xml new file mode 100644 index 0000000000..3128b77249 --- /dev/null +++ b/java/broker/src/main/test/build-module.xml @@ -0,0 +1,32 @@ +<!-- + - + - Copyright (c) 2006 The Apache Software Foundation + - + - 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. + - + --> +<project name="Broker Tests" default="build"> + <property name="module.depends" value="broker common"/> + <property name="module.dist" value="false"/> + + <import file="../../module.xml"/> + + <target name="test" depends="build"> + <junit fork="yes" showoutput="true" haltonfailure="yes"> + <test name="org.apache.qpid.server.UnitTests"/> + <formatter type="plain"/> + <classpath refid="module.class.path"/> + </junit> + </target> + +</project> diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/UnitTests.java new file mode 100644 index 0000000000..3372584c07 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/UnitTests.java @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + org.apache.qpid.server.configuration.UnitTests.class, + org.apache.qpid.server.exchange.UnitTests.class, + org.apache.qpid.server.protocol.UnitTests.class, + org.apache.qpid.server.queue.UnitTests.class, + org.apache.qpid.server.store.UnitTests.class, + org.apache.qpid.server.txn.UnitTests.class, + org.apache.qpid.server.util.UnitTests.class + }) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/ack/TxAckTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/ack/TxAckTest.java new file mode 100644 index 0000000000..3f8898069b --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/ack/TxAckTest.java @@ -0,0 +1,187 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.ack; + +import junit.framework.JUnit4TestAdapter; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.junit.Ignore; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.store.TestableMemoryMessageStore; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; + +public class TxAckTest +{ + private Scenario individual; + private Scenario multiple; + private Scenario combined; + + @Before + public void setup() throws Exception + { + //ack only 5th msg + individual = new Scenario(10, Arrays.asList(5l), Arrays.asList(1l, 2l, 3l, 4l, 6l, 7l, 8l, 9l, 10l)); + individual.update(5, false); + + //ack all up to and including 5th msg + multiple = new Scenario(10, Arrays.asList(1l, 2l, 3l, 4l, 5l), Arrays.asList(6l, 7l, 8l, 9l, 10l)); + multiple.update(5, true); + + //leave only 8th and 9th unacked + combined = new Scenario(10, Arrays.asList(1l, 2l, 3l, 4l, 5l, 6l, 7l, 10l), Arrays.asList(8l, 9l)); + combined.update(3, false); + combined.update(5, true); + combined.update(7, true); + combined.update(2, true);//should be ignored + combined.update(1, false);//should be ignored + combined.update(10, false); + } + + @Test + public void prepare() throws AMQException + { + individual.prepare(); + multiple.prepare(); + combined.prepare(); + } + + @Test + public void undoPrepare() throws AMQException + { + individual.undoPrepare(); + multiple.undoPrepare(); + combined.undoPrepare(); + } + + @Test + public void commit() throws AMQException + { + individual.commit(); + multiple.commit(); + combined.commit(); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(TxAckTest.class); + } + + private class Scenario + { + private final LinkedHashMap<Long, UnacknowledgedMessage> _messages = new LinkedHashMap<Long, UnacknowledgedMessage>(); + private final UnacknowledgedMessageMap _map = new UnacknowledgedMessageMapImpl(500); + private final TxAck _op = new TxAck(_map); + private final List<Long> _acked; + private final List<Long> _unacked; + + Scenario(int messageCount, List<Long> acked, List<Long> unacked) throws AMQException + { + for(int i = 0; i < messageCount; i++) + { + long deliveryTag = i + 1; + _messages.put(deliveryTag, new UnacknowledgedMessage(null, new TestMessage(deliveryTag), null, deliveryTag)); + } + _acked = acked; + _unacked = unacked; + } + + void update(long deliverytag, boolean multiple) + { + _op.update(deliverytag, multiple); + } + + private void assertCount(List<Long> tags, int expected) + { + for(long tag : tags) + { + UnacknowledgedMessage u = _messages.get(tag); + assertTrue("Message not found for tag " + tag, u != null); + ((TestMessage) u.message).assertCountEquals(expected); + } + } + + void prepare() throws AMQException + { + _op.consolidate(); + _op.prepare(); + + assertCount(_acked, -1); + assertCount(_unacked, 0); + + } + void undoPrepare() + { + _op.consolidate(); + _op.undoPrepare(); + + assertCount(_acked, 1); + assertCount(_unacked, 0); + } + + void commit() + { + _op.consolidate(); + _op.commit(); + + + //check acked messages are removed from map + HashSet<Long> keys = new HashSet<Long>(_messages.keySet()); + keys.retainAll(_acked); + assertTrue("Expected messages with following tags to have been removed from map: " + keys, keys.isEmpty()); + //check unacked messages are still in map + keys = new HashSet<Long>(_unacked); + keys.removeAll(_messages.keySet()); + assertTrue("Expected messages with following tags to still be in map: " + keys, keys.isEmpty()); + } + } + + private class TestMessage extends AMQMessage + { + private final long _tag; + private int _count; + + TestMessage(long tag) throws AMQException + { + super(null); // new TestableMemoryMessageStore(), null); + _tag = tag; + throw new AMQException("Fix this!"); + } + + public void incrementReference() + { + _count++; + } + + public void decrementReference() + { + _count--; + } + + void assertCountEquals(int expected) + { + assertEquals("Wrong count for message with tag " + _tag, expected, _count); + } + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/ack/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/ack/UnitTests.java new file mode 100644 index 0000000000..3d7db01365 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/ack/UnitTests.java @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.ack; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + TxAckTest.class +}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java b/java/broker/src/main/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java new file mode 100644 index 0000000000..bd78d1c786 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.configuration; + +import junit.framework.JUnit4TestAdapter; +import org.apache.qpid.configuration.PropertyException; +import org.apache.qpid.configuration.PropertyUtils; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +// TODO: This belongs in the "common" module. +public class TestPropertyUtils +{ + @Test + public void testSimpleExpansion() throws PropertyException + { + System.setProperty("banana", "fruity"); + String expandedProperty = PropertyUtils.replaceProperties("${banana}"); + assertEquals(expandedProperty, "fruity"); + } + + @Test + public void testDualExpansion() throws PropertyException + { + System.setProperty("banana", "fruity"); + System.setProperty("concrete", "horrible"); + String expandedProperty = PropertyUtils.replaceProperties("${banana}xyz${concrete}"); + assertEquals(expandedProperty, "fruityxyzhorrible"); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(TestPropertyUtils.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/configuration/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/configuration/UnitTests.java new file mode 100644 index 0000000000..4c70d7c4da --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/configuration/UnitTests.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.configuration; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({TestPropertyUtils.class}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java new file mode 100644 index 0000000000..1e9dd54d85 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java @@ -0,0 +1,232 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.*; + +public class AbstractHeadersExchangeTest +{ + private static final Logger _log = Logger.getLogger(AbstractHeadersExchangeTest.class); + + private final HeadersExchange exchange = new HeadersExchange(); + protected final Set<TestQueue> queues = new HashSet<TestQueue>(); + private int count; + + protected TestQueue bindDefault(String... bindings) throws AMQException + { + return bind("Queue" + (++count), bindings); + } + + protected TestQueue bind(String queueName, String... bindings) throws AMQException + { + return bind(queueName, getHeaders(bindings)); + } + + protected TestQueue bind(String queue, FieldTable bindings) throws AMQException + { + return bind(new TestQueue(queue), bindings); + } + + protected TestQueue bind(TestQueue queue, String... bindings) throws AMQException + { + return bind(queue, getHeaders(bindings)); + } + + protected TestQueue bind(TestQueue queue, FieldTable bindings) throws AMQException + { + queues.add(queue); + exchange.registerQueue(null, queue, bindings); + return queue; + } + + + protected void route(Message m) throws AMQException + { + m.route(exchange); + } + + protected void routeAndTest(Message m, TestQueue... expected) throws AMQException + { + routeAndTest(m, Arrays.asList(expected)); + } + + protected void routeAndTest(Message m, List<TestQueue> expected) throws AMQException + { + route(m); + for (TestQueue q : queues) + { + if (expected.contains(q)) + { + assertTrue("Expected " + m + " to be delivered to " + q, m.isInQueue(q)); + //assert m.isInQueue(q) : "Expected " + m + " to be delivered to " + q; + } + else + { + assertFalse("Did not expect " + m + " to be delivered to " + q, m.isInQueue(q)); + //assert !m.isInQueue(q) : "Did not expect " + m + " to be delivered to " + q; + } + } + } + + static FieldTable getHeaders(String... entries) + { + FieldTable headers = new FieldTable(); + for (String s : entries) + { + String[] parts = s.split("=", 2); + headers.put(parts[0], parts.length > 1 ? parts[1] : ""); + } + return headers; + } + + static BasicPublishBody getPublishRequest(String id) + { + BasicPublishBody request = new BasicPublishBody(); + request.routingKey = id; + return request; + } + + static ContentHeaderBody getContentHeader(FieldTable headers) + { + ContentHeaderBody header = new ContentHeaderBody(); + header.properties = getProperties(headers); + return header; + } + + static BasicContentHeaderProperties getProperties(FieldTable headers) + { + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + properties.setHeaders(headers); + return properties; + } + + static class TestQueue extends AMQQueue + { + final List<HeadersExchangeTest.Message> messages = new ArrayList<HeadersExchangeTest.Message>(); + + public TestQueue(String name) throws AMQException + { + super(name, false, "test", true, ApplicationRegistry.getInstance().getQueueRegistry()); + } + + public void deliver(AMQMessage msg) throws AMQException + { + messages.add(new HeadersExchangeTest.Message(msg)); + } + } + + /** + * Just add some extra utility methods to AMQMessage to aid testing. + */ + static class Message extends AMQMessage + { + private static MessageStore _messageStore = new SkeletonMessageStore(); + + Message(String id, String... headers) throws AMQException + { + this(id, getHeaders(headers)); + } + + Message(String id, FieldTable headers) throws AMQException + { + this(getPublishRequest(id), getContentHeader(headers), null); + } + + private Message(BasicPublishBody publish, ContentHeaderBody header, List<ContentBody> bodies) throws AMQException + { + //super(_messageStore, publish, header, bodies); + super(null); + throw new AMQException("Fix this!!!!"); + } + + private Message(AMQMessage msg) throws AMQException + { + super(msg); + } + + void route(Exchange exchange) throws AMQException + { + exchange.route(this); + } + + boolean isInQueue(TestQueue queue) + { + return queue.messages.contains(this); + } + + public int hashCode() + { + try + { + return getKey().hashCode(); + } + catch (AMQException e) + { + _log.error("Error getting key: " + e, e); + return 0; + } + } + + public boolean equals(Object o) + { + return o instanceof HeadersExchangeTest.Message && equals((HeadersExchangeTest.Message) o); + } + + private boolean equals(HeadersExchangeTest.Message m) + { + try + { + return getKey().equals(m.getKey()); + } + catch (AMQException e) + { + _log.error("Error getting key: " + e, e); + return false; + } + } + + public String toString() + { + try + { + return getKey().toString(); + } + catch (AMQException e) + { + _log.error("Error getting key: " + e, e); + return null; + } + } + + private Object getKey() throws AMQException + { + return getPublishBody().routingKey; + } + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java new file mode 100644 index 0000000000..7e33b1d711 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java @@ -0,0 +1,200 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + + +import java.util.Map; +import java.util.HashMap; + +import junit.framework.JUnit4TestAdapter; + +/** + */ +public class HeadersBindingTest +{ + private Map<String, String> bindHeaders = new HashMap<String, String>(); + private Map<String, String> matchHeaders = new HashMap<String, String>(); + + @Test public void default_1() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void default_2() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void default_3() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Altered value of A"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_1() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_2() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_3() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_4() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + matchHeaders.put("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_5() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Altered value of B"); + matchHeaders.put("C", "Value of C"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_1() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_2() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_3() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_4() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + matchHeaders.put("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_5() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Altered value of B"); + matchHeaders.put("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_6() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Altered value of A"); + matchHeaders.put("B", "Altered value of B"); + matchHeaders.put("C", "Value of C"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(HeadersBindingTest.class); + } + +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java new file mode 100644 index 0000000000..74cb082db7 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java @@ -0,0 +1,181 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.NoConsumersException; +import org.apache.qpid.server.util.TimedRun; +import org.apache.qpid.server.util.AveragedRun; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ContentBody; + +import java.util.List; + +/** + * Want to vary the number of regsitrations, messages and matches and measure + * the corresponding variance in execution time. + * <p/> + * Each registration will contain the 'All' header, even registrations will + * contain the 'Even' header and odd headers will contain the 'Odd' header. + * In additions each regsitration will have a unique value for the 'Specific' + * header as well. + * <p/> + * Messages can then be routed to all registrations, to even- or odd- registrations + * or to a specific registration. + * + */ +public class HeadersExchangePerformanceTest extends AbstractHeadersExchangeTest +{ + private static enum Mode {ALL, ODD_OR_EVEN, SPECIFIC} + + private final TestQueue[] queues; + private final Mode mode; + + public HeadersExchangePerformanceTest(Mode mode, int registrations) throws AMQException + { + this.mode = mode; + queues = new TestQueue[registrations]; + for (int i = 0; i < queues.length; i++) + { + switch(mode) + { + case ALL: + queues[i] = bind(new FastQueue("Queue" + i), "All"); + break; + case ODD_OR_EVEN: + queues[i] = bind(new FastQueue("Queue" + i), "All", oddOrEven(i)); + break; + case SPECIFIC: + queues[i] = bind(new FastQueue("Queue" + i), "All", oddOrEven(i), "Specific"+ i); + break; + } + } + } + + void sendToAll(int count) throws AMQException + { + send(count, "All=True"); + } + + void sendToOdd(int count) throws AMQException + { + send(count, "All=True", "Odd=True"); + } + + void sendToEven(int count) throws AMQException + { + send(count, "All=True", "Even=True"); + } + + void sendToAllSpecifically(int count) throws AMQException + { + for (int i = 0; i < queues.length; i++) + { + sendToSpecific(count, i); + } + } + + void sendToSpecific(int count, int index) throws AMQException + { + send(count, "All=True", oddOrEven(index) + "=True", "Specific=" + index); + } + + private void send(int count, String... headers) throws AMQException + { + for (int i = 0; i < count; i++) + { + route(new Message("Message" + i, headers)); + } + } + + private static String oddOrEven(int i) + { + return (i % 2 == 0 ? "Even" : "Odd"); + } + + static class FastQueue extends TestQueue + { + + public FastQueue(String name) throws AMQException + { + super(name); + } + + public void deliver(BasicPublishBody publishBody, ContentHeaderBody contentHeaderBody, List<ContentBody> contentBodies) throws NoConsumersException + { + //just discard as we are not testing routing functionality here + } + } + + static class Test extends TimedRun + { + private final Mode mode; + private final int registrations; + private final int count; + private HeadersExchangePerformanceTest test; + + Test(Mode mode, int registrations, int count) + { + super(mode + ", registrations=" + registrations + ", count=" + count); + this.mode = mode; + this.registrations = registrations; + this.count = count; + } + + protected void setup() throws Exception + { + test = new HeadersExchangePerformanceTest(mode, registrations); + run(100); //do a warm up run before times start + } + + protected void teardown() throws Exception + { + test = null; + System.gc(); + } + + protected void run() throws Exception + { + run(count); + } + + private void run(int count) throws Exception + { + switch(mode) + { + case ALL: + test.sendToAll(count); + break; + default: + System.out.println("Test for " + mode + " not yet implemented."); + } + } + } + + public static void main(String[] argv) throws Exception + { + int registrations = Integer.parseInt(argv[0]); + int messages = Integer.parseInt(argv[1]); + int iterations = Integer.parseInt(argv[2]); + TimedRun test = new Test(Mode.ALL, registrations, messages); + AveragedRun tests = new AveragedRun(test, iterations); + System.out.println(tests.call()); + } +} + diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java new file mode 100644 index 0000000000..1c594d4374 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java @@ -0,0 +1,81 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import org.junit.Test; +import org.junit.Before; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.util.TestApplicationRegistry; +import junit.framework.JUnit4TestAdapter; + +public class HeadersExchangeTest extends AbstractHeadersExchangeTest +{ + @Before + public void init() throws Exception + { + ApplicationRegistry.initialise(new TestApplicationRegistry()); + } + + @Test + public void simple() throws AMQException + { + TestQueue q1 = bindDefault("F0000"); + TestQueue q2 = bindDefault("F0000=Aardvark"); + TestQueue q3 = bindDefault("F0001"); + TestQueue q4 = bindDefault("F0001=Bear"); + TestQueue q5 = bindDefault("F0000", "F0001"); + TestQueue q6 = bindDefault("F0000=Aardvark", "F0001=Bear"); + TestQueue q7 = bindDefault("F0000", "F0001=Bear"); + TestQueue q8 = bindDefault("F0000=Aardvark", "F0001"); + TestQueue q9 = bindDefault("F0000=Apple", "F0001=Banana"); + TestQueue q10 = bindDefault("F0000=Apple", "F0001"); + + routeAndTest(new Message("Message1", "F0000"), q1); + routeAndTest(new Message("Message2", "F0000=Aardvark"), q1, q2); + routeAndTest(new Message("Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q5, q8); + routeAndTest(new Message("Message4", "F0000", "F0001=Bear"), q1, q3, q4, q5, q7); + routeAndTest(new Message("Message5", "F0000=Aardvark", "F0001=Bear"), + q1, q2, q3, q4, q5, q6, q7, q8); + routeAndTest(new Message("Message6", "F0002")); + } + + @Test + public void any() throws AMQException + { + TestQueue q1 = bindDefault("F0000", "F0001", "X-match=any"); + TestQueue q2 = bindDefault("F0000=Aardvark", "F0001=Bear", "X-match=any"); + TestQueue q3 = bindDefault("F0000", "F0001=Bear", "X-match=any"); + TestQueue q4 = bindDefault("F0000=Aardvark", "F0001", "X-match=any"); + TestQueue q5 = bindDefault("F0000=Apple", "F0001=Banana", "X-match=any"); + TestQueue q6 = bindDefault("F0000=Apple", "F0001", "X-match=any"); + + routeAndTest(new Message("Message1", "F0000"), q1, q3); + routeAndTest(new Message("Message2", "F0000=Aardvark"), q1, q2, q3, q4); + routeAndTest(new Message("Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message4", "F0000", "F0001=Bear"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message5", "F0000=Aardvark", "F0001=Bear"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message6", "F0002")); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(HeadersExchangeTest.class); + } + +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/UnitTests.java new file mode 100644 index 0000000000..a3c6439b67 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/UnitTests.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.exchange; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({HeadersBindingTest.class, HeadersExchangeTest.class}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/protocol/MockIoSession.java b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/MockIoSession.java new file mode 100644 index 0000000000..f985050e9f --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/MockIoSession.java @@ -0,0 +1,293 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import org.apache.mina.common.*; +import org.apache.mina.common.support.DefaultCloseFuture; +import org.apache.mina.common.support.DefaultWriteFuture; + +import java.net.SocketAddress; +import java.util.Set; + +public class MockIoSession implements IoSession +{ + private AMQProtocolSession _protocolSession; + + /** + * Stores the last response written + */ + private Object _lastWrittenObject; + + private boolean _closing; + + public MockIoSession() + { + } + + public Object getLastWrittenObject() + { + return _lastWrittenObject; + } + + public IoService getService() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public IoServiceConfig getServiceConfig() + { + return null; + } + + public IoHandler getHandler() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public IoSessionConfig getConfig() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public IoFilterChain getFilterChain() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public WriteFuture write(Object message) + { + WriteFuture wf = new DefaultWriteFuture(null); + _lastWrittenObject = message; + return wf; + } + + public CloseFuture close() + { + _closing = true; + CloseFuture cf = new DefaultCloseFuture(null); + cf.setClosed(); + return cf; + } + + public Object getAttachment() + { + return _protocolSession; + } + + public Object setAttachment(Object attachment) + { + Object current = _protocolSession; + _protocolSession = (AMQProtocolSession) attachment; + return current; + } + + public Object getAttribute(String key) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public Object setAttribute(String key, Object value) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public Object setAttribute(String key) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public Object removeAttribute(String key) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean containsAttribute(String key) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public Set getAttributeKeys() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public TransportType getTransportType() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isConnected() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isClosing() + { + return _closing; + } + + public CloseFuture getCloseFuture() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public SocketAddress getRemoteAddress() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public SocketAddress getLocalAddress() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public SocketAddress getServiceAddress() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getIdleTime(IdleStatus status) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getIdleTimeInMillis(IdleStatus status) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setIdleTime(IdleStatus status, int idleTime) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public int getWriteTimeout() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getWriteTimeoutInMillis() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setWriteTimeout(int writeTimeout) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public TrafficMask getTrafficMask() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setTrafficMask(TrafficMask trafficMask) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void suspendRead() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void suspendWrite() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void resumeRead() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void resumeWrite() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getReadBytes() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getWrittenBytes() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getReadMessages() + { + return 0L; + } + + public long getWrittenMessages() + { + return 0L; + } + + public long getWrittenWriteRequests() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getScheduledWriteRequests() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getScheduledWriteBytes() + { + return 0; //TODO + } + + public long getCreationTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getLastIoTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getLastReadTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getLastWriteTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isIdle(IdleStatus status) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getIdleCount(IdleStatus status) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getLastIdleTime(IdleStatus status) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/protocol/TestProtocolInitiation.java b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/TestProtocolInitiation.java new file mode 100644 index 0000000000..34e1709a2d --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/TestProtocolInitiation.java @@ -0,0 +1,212 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import junit.framework.Assert; +import junit.framework.JUnit4TestAdapter; +import org.apache.qpid.codec.AMQDecoder; +import org.apache.qpid.codec.AMQEncoder; +import org.apache.qpid.framing.*; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.WriteFuture; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; +import org.apache.mina.filter.codec.support.SimpleProtocolDecoderOutput; +import org.junit.Before; +import org.junit.Test; + +/** + * This test suite tests the handling of protocol initiation frames and related issues. + */ +public class TestProtocolInitiation implements ProtocolVersionList +{ + private AMQPFastProtocolHandler _protocolHandler; + + private MockIoSession _mockIoSession; + + /** + * We need to use the object encoder mechanism so to allow us to retrieve the + * output (a bytebuffer) we define our own encoder output class. The encoder + * writes the encoded data to this class, from where we can retrieve it during + * the test run. + */ + private class TestProtocolEncoderOutput implements ProtocolEncoderOutput + { + public ByteBuffer result; + + public void write(ByteBuffer buf) + { + result = buf; + } + + public void mergeAll() + { + throw new UnsupportedOperationException(); + } + + public WriteFuture flush() + { + throw new UnsupportedOperationException(); + } + } + + private class TestProtocolDecoderOutput implements ProtocolDecoderOutput + { + public Object result; + + public void write(Object buf) + { + result = buf; + } + + public void flush() + { + throw new UnsupportedOperationException(); + } + } + + @Before + public void createCommonObjects() + { + _mockIoSession = new MockIoSession(); + _protocolHandler = new AMQPFastProtocolHandler(null, null); + } + + + /** + * Tests that the AMQDecoder handles invalid protocol classes + * @throws Exception + */ + @Test(expected = AMQProtocolClassException.class) + public void testDecoderValidateProtocolClass() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.protocolClass = 2; + decodePI(pi); + } + + /** + * Tests that the AMQDecoder handles invalid protocol instance numbers + * @throws Exception + */ + @Test(expected = AMQProtocolInstanceException.class) + public void testDecoderValidatesProtocolInstance() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.protocolInstance = 2; + decodePI(pi); + } + + /** + * Tests that the AMQDecoder handles invalid protocol major + * @throws Exception + */ + @Test(expected = AMQProtocolVersionException.class) + public void testDecoderValidatesProtocolMajor() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.protocolMajor = 2; + decodePI(pi); + } + + /** + * Tests that the AMQDecoder handles invalid protocol minor + * @throws Exception + */ + @Test(expected = AMQProtocolVersionException.class) + public void testDecoderValidatesProtocolMinor() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.protocolMinor = 99; + decodePI(pi); + } + + /** + * Tests that the AMQDecoder accepts a valid PI + * @throws Exception + */ + @Test(expected = AMQProtocolHeaderException.class) + public void testDecoderValidatesHeader() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.header = new char[] {'P', 'Q', 'M', 'A' }; + decodePI(pi); + } + + /** + * Test that a valid header is passed by the decoder. + * @throws Exception + */ + @Test + public void testDecoderAcceptsValidHeader() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + decodePI(pi); + } + + /** + * This test checks that an invalid protocol header results in the + * connection being closed. + */ + @Test + public void testInvalidProtocolHeaderClosesConnection() throws Exception + { + AMQProtocolHeaderException pe = new AMQProtocolHeaderException("Test"); + _protocolHandler.exceptionCaught(_mockIoSession, pe); + Assert.assertNotNull(_mockIoSession.getLastWrittenObject()); + Object piResponse = _mockIoSession.getLastWrittenObject(); + Assert.assertEquals(piResponse.getClass(), ProtocolInitiation.class); + ProtocolInitiation pi = (ProtocolInitiation) piResponse; + Assert.assertEquals("Protocol Initiation sent out was not the broker's expected header", pi, + createValidProtocolInitiation()); + Assert.assertTrue("Session has not been closed", _mockIoSession.isClosing()); + } + + private ProtocolInitiation createValidProtocolInitiation() + { + /* Find last protocol version in protocol version list. Make sure last protocol version + listed in the build file (build-module.xml) is the latest version which will be used + here. */ + int i = pv.length - 1; + return new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR]); + } + + /** + * Helper that encodes a protocol initiation and attempts to decode it + * @param pi + * @throws Exception + */ + private void decodePI(ProtocolInitiation pi) throws Exception + { + // we need to do this test at the level of the decoder since we initially only expect PI frames + // so the protocol handler is not set up to know whether it should be expecting a PI frame or + // a different type of frame + AMQDecoder decoder = new AMQDecoder(true); + AMQEncoder encoder = new AMQEncoder(); + TestProtocolEncoderOutput peo = new TestProtocolEncoderOutput(); + encoder.encode(_mockIoSession, pi, peo); + TestProtocolDecoderOutput pdo = new TestProtocolDecoderOutput(); + decoder.decode(_mockIoSession, peo.result, pdo); + ((ProtocolInitiation) pdo.result).checkVersion(this); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(TestProtocolInitiation.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/protocol/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/UnitTests.java new file mode 100644 index 0000000000..09dc76d310 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/UnitTests.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({TestProtocolInitiation.class}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/AckTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/AckTest.java new file mode 100644 index 0000000000..a76dac7263 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/AckTest.java @@ -0,0 +1,268 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import junit.framework.JUnit4TestAdapter; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.ack.UnacknowledgedMessage; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.util.TestApplicationRegistry; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Tests that acknowledgements are handled correctly. + */ +public class AckTest +{ + private static final Logger _log = Logger.getLogger(AckTest.class); + + private SubscriptionImpl _subscription; + + private MockProtocolSession _protocolSession; + + private TestableMemoryMessageStore _messageStore; + + private AMQChannel _channel; + + private SubscriptionSet _subscriptionManager; + + private AMQQueue _queue; + + public AckTest() throws Exception + { + ApplicationRegistry.initialise(new TestApplicationRegistry()); + } + + @Before + public void setup() throws Exception + { + _messageStore = new TestableMemoryMessageStore(); + _channel = new AMQChannel(5, _messageStore, null/*dont need exchange registry*/); + _protocolSession = new MockProtocolSession(_messageStore); + _protocolSession.addChannel(_channel); + _subscriptionManager = new SubscriptionSet(); + _queue = new AMQQueue("myQ", false, "guest", true, new DefaultQueueRegistry(), _subscriptionManager); + } + + private void publishMessages(int count) throws AMQException + { + for (int i = 1; i <= count; i++) + { + BasicPublishBody publishBody = new BasicPublishBody(); + publishBody.routingKey = "rk"; + publishBody.exchange = "someExchange"; + AMQMessage msg = null; //new AMQMessage(_messageStore, publishBody); + msg.setContentHeaderBody(new ContentHeaderBody()); + _subscription.send(msg, _queue); + } + } + + /** + * Tests that the acknowledgements are correctly associated with a channel and + * order is preserved when acks are enabled + */ + @Test @Ignore /* FIXME: broken at the moment */ + public void ackChannelAssociationTest() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + final int msgCount = 10; + publishMessages(msgCount); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == msgCount); + + map.visit(new UnacknowledgedMessageMap.Visitor() + { + private int i = 1; + + public boolean callback(UnacknowledgedMessage message) throws AMQException + { + assertTrue(message.deliveryTag == i++); + assertTrue(message.queue == _queue); + return false; + } + + public void visitComplete() + { + } + }); + + assertTrue(_messageStore.getMessageMetaDataMap().size() == msgCount); + } + + /** + * Tests that in no-ack mode no messages are retained + */ + @Test + public void testNoAckMode() throws AMQException + { + // false arg means no acks expected + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", false); + final int msgCount = 10; + publishMessages(msgCount); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + assertTrue(_messageStore.getMessageMetaDataMap().size() == 0); + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + @Test + public void singleAckReceivedTest() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, false); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == msgCount - 1); + + map.visit(new UnacknowledgedMessageMap.Visitor() + { + private int i = 1; + + public boolean callback(UnacknowledgedMessage message) throws AMQException + { + assertTrue(message.deliveryTag == i); + assertTrue(message.queue == _queue); + // 5 is the delivery tag of the message that *should* be removed + if (++i == 5) + { + ++i; + } + return false; + } + + public void visitComplete() + { + } + }); + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + @Test + public void multiAckReceivedTest() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + + map.visit(new UnacknowledgedMessageMap.Visitor() + { + private int i = 1; + + public boolean callback(UnacknowledgedMessage message) throws AMQException + { + assertTrue(message.deliveryTag == i + 5); + assertTrue(message.queue == _queue); + ++i; + return false; + } + + public void visitComplete() + { + } + }); + } + + /** + * Tests that a multiple acknowledgement is handled correctly. When ack'ing all pending msgs. + * + */ + @Test + public void multiAckAllReceivedTest() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(0, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + + map.visit(new UnacknowledgedMessageMap.Visitor() + { + private int i = 1; + + public boolean callback(UnacknowledgedMessage message) throws AMQException + { + assertTrue(message.deliveryTag == i + 5); + assertTrue(message.queue == _queue); + ++i; + return false; + } + + public void visitComplete() + { + } + }); + } + + + + @Test + public void testPrefetch() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + _channel.setPrefetchCount(5); + final int msgCount = 5; + publishMessages(msgCount); + + // at this point we should have sent out only 5 messages with a further 5 queued + // up in the channel which should be suspended + assertTrue(_subscription.isSuspended()); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + _channel.acknowledgeMessage(5, true); + assertTrue(!_subscription.isSuspended()); + try + { + Thread.sleep(3000); + } + catch (InterruptedException e) + { + _log.error("Error: " + e, e); + } + assertTrue(map.size() == 0); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(AckTest.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/ConcurrencyTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/ConcurrencyTest.java new file mode 100644 index 0000000000..1cf11933fa --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/ConcurrencyTest.java @@ -0,0 +1,261 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import junit.framework.JUnit4TestAdapter; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import org.junit.Test; +import org.junit.Assert; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.handler.OnCurrentThreadExecutor; + +import java.util.*; +import java.util.concurrent.Executor; + +/** + * Tests delivery in the face of concurrent incoming _messages, subscription alterations + * and attempts to asynchronously process queued _messages. + */ +public class ConcurrencyTest extends MessageTestHelper +{ + private final Random random = new Random(); + + private final int numMessages = 1000; + + private final List<TestSubscription> _subscribers = new ArrayList<TestSubscription>(); + private final Set<Subscription> _active = new HashSet<Subscription>(); + private final List<AMQMessage> _messages = new ArrayList<AMQMessage>(); + private int next = 0;//index to next message to send + private final List<AMQMessage> _received = Collections.synchronizedList(new ArrayList<AMQMessage>()); + private final Executor _executor = new OnCurrentThreadExecutor(); + private final List<Thread> _threads = new ArrayList<Thread>(); + + private final SubscriptionSet _subscriptionMgr = new SubscriptionSet(); + private final DeliveryManager _deliveryMgr; + + private boolean isComplete; + private boolean failed; + + public ConcurrencyTest() throws Exception + { + _deliveryMgr = new DeliveryManager(_subscriptionMgr, new AMQQueue("myQ", false, "guest", false, + new DefaultQueueRegistry())); + } + + @Test + public void concurrent1() throws InterruptedException, AMQException + { + initSubscriptions(10); + initMessages(numMessages); + initThreads(1, 4, 4, 4); + run(); + check(); + } + + @Test + public void concurrent2() throws InterruptedException, AMQException + { + initSubscriptions(10); + initMessages(numMessages); + initThreads(4, 2, 2, 2); + run(); + check(); + } + + void check() + { + assertFalse("Failed", failed); + + _deliveryMgr.processAsync(_executor); + + assertEquals("Did not recieve the correct number of messages", _messages.size(), _received.size()); + for(int i = 0; i < _messages.size(); i++) + { + assertEquals("Wrong message at " + i, _messages.get(i), _received.get(i)); + } + } + + void initSubscriptions(int subscriptions) + { + for(int i = 0; i < subscriptions; i++) + { + _subscribers.add(new TestSubscription("Subscriber" + i, _received)); + } + } + + void initMessages(int messages) throws AMQException + { + for(int i = 0; i < messages; i++) + { + _messages.add(message()); + } + } + + void initThreads(int senders, int subscribers, int suspenders, int processors) + { + addThreads(senders, senders == 1 ? new Sender() : new OrderedSender()); + addThreads(subscribers, new Subscriber()); + addThreads(suspenders, new Suspender()); + addThreads(processors, new Processor()); + } + + void addThreads(int count, Runnable runner) + { + for(int i = 0; i < count; i++) + { + _threads.add(new Thread(runner, runner.toString())); + } + } + + void run() throws InterruptedException + { + for(Thread t : _threads) + { + t.start(); + } + + for(Thread t : _threads) + { + t.join(); + } + } + + private void toggle(Subscription s) + { + synchronized (_active) + { + if (_active.contains(s)) + { + _active.remove(s); + Subscription result = _subscriptionMgr.removeSubscriber(s); + Assert.assertTrue("Removed subscription " + result + " but trying to remove subscription " + s, + result != null && result.equals(s)); + } + else + { + _active.add(s); + _subscriptionMgr.addSubscriber(s); + } + } + } + + private AMQMessage nextMessage() + { + synchronized (_messages) + { + if (next < _messages.size()) + { + return _messages.get(next++); + } + else + { + if (_deliveryMgr.getQueueMessageCount() == 0) { + isComplete = true; + } + return null; + } + } + } + + private boolean randomBoolean() + { + return random.nextBoolean(); + } + + private TestSubscription randomSubscriber() + { + return _subscribers.get(random.nextInt(_subscribers.size())); + } + + private class Sender extends Runner + { + void doRun() throws Throwable + { + AMQMessage msg = nextMessage(); + if (msg != null) + { + _deliveryMgr.deliver(toString(), msg); + } + } + } + + private class OrderedSender extends Sender + { + synchronized void doRun() throws Throwable + { + super.doRun(); + } + } + + private class Suspender extends Runner + { + void doRun() throws Throwable + { + randomSubscriber().setSuspended(randomBoolean()); + } + } + + private class Subscriber extends Runner + { + void doRun() throws Throwable + { + toggle(randomSubscriber()); + } + } + + private class Processor extends Runner + { + void doRun() throws Throwable + { + _deliveryMgr.processAsync(_executor); + } + } + + private abstract class Runner implements Runnable + { + public void run() + { + try + { + while (!stop()) + { + doRun(); + } + } + catch (Throwable t) + { + failed = true; + t.printStackTrace(); + } + } + + abstract void doRun() throws Throwable; + + boolean stop() + { + return isComplete || failed; + } + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(ConcurrencyTest.class); + } + +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/DeliveryManagerTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/DeliveryManagerTest.java new file mode 100644 index 0000000000..ef287e079b --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/DeliveryManagerTest.java @@ -0,0 +1,161 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.apache.qpid.server.handler.OnCurrentThreadExecutor; +import org.apache.qpid.AMQException; +import junit.framework.JUnit4TestAdapter; + +public class DeliveryManagerTest extends MessageTestHelper +{ + private final SubscriptionSet _subscriptions = new SubscriptionSet(); + private final DeliveryManager _mgr; + + public DeliveryManagerTest() throws Exception + { + try + { + _mgr = new DeliveryManager(_subscriptions, new AMQQueue("myQ", false, "guest", false, + new DefaultQueueRegistry())); + } + catch(Throwable t) + { + t.printStackTrace(); + throw new AMQException("Could not initialise delivery manager", t); + } + } + + @Test + public void startInQueueingMode() throws AMQException + { + AMQMessage[] messages = new AMQMessage[10]; + for(int i = 0; i < messages.length; i++) + { + messages[i] = message(); + } + int batch = messages.length / 2; + + for(int i = 0; i < batch; i++) + { + _mgr.deliver("Me", messages[i]); + } + + TestSubscription s1 = new TestSubscription("1"); + TestSubscription s2 = new TestSubscription("2"); + _subscriptions.addSubscriber(s1); + _subscriptions.addSubscriber(s2); + + for(int i = batch; i < messages.length; i++) + { + _mgr.deliver("Me", messages[i]); + } + + assertTrue(s1.getMessages().isEmpty()); + assertTrue(s2.getMessages().isEmpty()); + + _mgr.processAsync(new OnCurrentThreadExecutor()); + + assertEquals(messages.length / 2, s1.getMessages().size()); + assertEquals(messages.length / 2, s2.getMessages().size()); + + for(int i = 0; i < messages.length; i++) + { + if(i % 2 == 0) + { + assertTrue(s1.getMessages().get(i / 2) == messages[i]); + } + else + { + assertTrue(s2.getMessages().get(i / 2) == messages[i]); + } + } + } + + @Test + public void startInDirectMode() throws AMQException + { + AMQMessage[] messages = new AMQMessage[10]; + for(int i = 0; i < messages.length; i++) + { + messages[i] = message(); + } + int batch = messages.length / 2; + + TestSubscription s1 = new TestSubscription("1"); + _subscriptions.addSubscriber(s1); + + for(int i = 0; i < batch; i++) + { + _mgr.deliver("Me", messages[i]); + } + + assertEquals(batch, s1.getMessages().size()); + for(int i = 0; i < batch; i++) + { + assertTrue(messages[i] == s1.getMessages().get(i)); + } + s1.getMessages().clear(); + assertEquals(0, s1.getMessages().size()); + + s1.setSuspended(true); + for(int i = batch; i < messages.length; i++) + { + _mgr.deliver("Me", messages[i]); + } + + _mgr.processAsync(new OnCurrentThreadExecutor()); + assertEquals(0, s1.getMessages().size()); + s1.setSuspended(false); + + _mgr.processAsync(new OnCurrentThreadExecutor()); + assertEquals(messages.length - batch, s1.getMessages().size()); + + for(int i = batch; i < messages.length; i++) + { + assertTrue(messages[i] == s1.getMessages().get(i - batch)); + } + + } + + @Test (expected=NoConsumersException.class) + public void noConsumers() throws AMQException + { + AMQMessage msg = message(true); + _mgr.deliver("Me", msg); + msg.checkDeliveredToConsumer(); + } + + @Test (expected=NoConsumersException.class) + public void noActiveConsumers() throws AMQException + { + TestSubscription s = new TestSubscription("A"); + _subscriptions.addSubscriber(s); + s.setSuspended(true); + AMQMessage msg = message(true); + _mgr.deliver("Me", msg); + msg.checkDeliveredToConsumer(); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(DeliveryManagerTest.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/MessageTestHelper.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/MessageTestHelper.java new file mode 100644 index 0000000000..483cbedb58 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/MessageTestHelper.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.util.TestApplicationRegistry; +import org.apache.qpid.AMQException; + +class MessageTestHelper +{ + private final MessageStore _messageStore = new SkeletonMessageStore(); + + MessageTestHelper() throws Exception + { + ApplicationRegistry.initialise(new TestApplicationRegistry()); + } + + AMQMessage message() throws AMQException + { + return message(false); + } + + AMQMessage message(boolean immediate) throws AMQException + { + BasicPublishBody publish = new BasicPublishBody(); + publish.immediate = immediate; + //return new AMQMessage(_messageStore, publish, new ContentHeaderBody(), null); + throw new AMQException("Need to fix this!!!"); + } + +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/MockProtocolSession.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/MockProtocolSession.java new file mode 100644 index 0000000000..f26d6d64b3 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/MockProtocolSession.java @@ -0,0 +1,121 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.store.MessageStore; + +import javax.security.sasl.SaslServer; +import java.util.HashMap; +import java.util.Map; + +/** + * A protocol session that can be used for testing purposes. + */ +public class MockProtocolSession implements AMQProtocolSession +{ + private MessageStore _messageStore; + + private Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>(); + + public MockProtocolSession(MessageStore messageStore) + { + _messageStore = messageStore; + } + + public void dataBlockReceived(AMQDataBlock message) throws Exception + { + } + + public void writeFrame(AMQDataBlock frame) + { + } + + public String getContextKey() + { + return null; + } + + public void setContextKey(String contextKey) + { + } + + public AMQChannel getChannel(int channelId) + { + AMQChannel channel = _channelMap.get(channelId); + if (channel == null) + { + throw new IllegalArgumentException("Invalid channel id: " + channelId); + } + else + { + return channel; + } + } + + public void addChannel(AMQChannel channel) + { + if (channel == null) + { + throw new IllegalArgumentException("Channel must not be null"); + } + else + { + _channelMap.put(channel.getChannelId(), channel); + } + } + + public void closeChannel(int channelId) throws AMQException + { + } + + public void removeChannel(int channelId) + { + _channelMap.remove(channelId); + } + + public void initHeartbeats(int delay) + { + } + + public void closeSession() throws AMQException + { + } + + public Object getKey() + { + return null; + } + + public String getLocalFQDN() + { + return null; + } + + public SaslServer getSaslServer() + { + return null; + } + + public void setSaslServer(SaslServer saslServer) + { + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java new file mode 100644 index 0000000000..8ae8ebae79 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.server.util.AveragedRun; +import org.apache.qpid.server.util.ConcurrentTest; + +public class QueueConcurrentPerfTest extends QueuePerfTest +{ + QueueConcurrentPerfTest(Factory factory, int queueCount, int messages) + { + super(factory, queueCount, messages); + } + + public static void main(String[] argv) throws Exception + { + Factory[] factories = new Factory[]{SYNCHRONIZED, CONCURRENT}; + int iterations = 5; + String label = argv.length > 0 ? argv[0]: null; + System.out.println((label == null ? "" : "Label, ") + "Queue Type, No. of Queues, No. of Operations, Avg Time, Min Time, Max Time"); + //vary number of queues: + for(Factory f : factories) + { + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 100, 10000), iterations), 5)); + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 10000), iterations), 5)); + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 10000, 10000), iterations), 5)); + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 1000), iterations), 5)); + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 100000), iterations), 5)); + } + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueuePerfTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueuePerfTest.java new file mode 100644 index 0000000000..36e4e90f35 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueuePerfTest.java @@ -0,0 +1,255 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.server.util.AveragedRun; +import org.apache.qpid.server.util.TimedRun; +import org.apache.qpid.server.util.RunStats; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class QueuePerfTest extends TimedRun +{ + private final Factory _factory; + private final int _queueCount; + private final int _messages; + private final String _msg = ""; + private List<Queue<String>> _queues; + + QueuePerfTest(Factory factory, int queueCount, int messages) + { + super(factory + ", " + queueCount + ", " + messages); + _factory = factory; + _queueCount = queueCount; + _messages = messages; + } + + protected void setup() throws Exception + { + //init + int count = Integer.getInteger("prepopulate", 0); +// System.err.println("Prepopulating with " + count + " items"); + _queues = new ArrayList<Queue<String>>(_queueCount); + for (int i = 0; i < _queueCount; i++) + { + Queue<String> q = _factory.create(); + for(int j = 0; j < count; ++j) + { + q.add("Item"+ j); + } + _queues.add(q); + } + System.gc(); + } + + protected void teardown() throws Exception + { + System.gc(); + } + + protected void run() throws Exception + { + //dispatch + for (int i = 0; i < _messages; i++) + { + for (Queue<String> q : _queues) + { + q.offer(_msg); + q.poll(); + } + } + } + + static interface Factory + { + Queue<String> create(); + } + + static Factory CONCURRENT = new Factory() + { + public Queue<String> create() + { + return new ConcurrentLinkedQueue<String>(); + } + + public String toString() + { + return "ConcurrentLinkedQueue"; + } + + }; + + static Factory SYNCHRONIZED = new Factory() + { + public Queue<String> create() + { + return new SynchronizedQueue<String>(new LinkedList<String>()); + } + + + public String toString() + { + return "Synchronized LinkedList"; + } + }; + + static Factory PLAIN = new Factory() + { + public Queue<String> create() + { + return new LinkedList<String>(); + } + + public String toString() + { + return "Plain LinkedList"; + } + }; + + static class SynchronizedQueue<E> implements Queue<E> + { + private final Queue<E> queue; + + SynchronizedQueue(Queue<E> queue) + { + this.queue = queue; + } + + public synchronized E element() + { + return queue.element(); + } + + public synchronized boolean offer(E o) + { + return queue.offer(o); + } + + public synchronized E peek() + { + return queue.peek(); + } + + public synchronized E poll() + { + return queue.poll(); + } + + public synchronized E remove() + { + return queue.remove(); + } + + public synchronized int size() + { + return queue.size(); + } + + public synchronized boolean isEmpty() + { + return queue.isEmpty(); + } + + public synchronized boolean contains(Object o) + { + return queue.contains(o); + } + + public synchronized Iterator<E> iterator() + { + return queue.iterator(); + } + + public synchronized Object[] toArray() + { + return queue.toArray(); + } + + public synchronized <T>T[] toArray(T[] a) + { + return queue.toArray(a); + } + + public synchronized boolean add(E o) + { + return queue.add(o); + } + + public synchronized boolean remove(Object o) + { + return queue.remove(o); + } + + public synchronized boolean containsAll(Collection<?> c) + { + return queue.containsAll(c); + } + + public synchronized boolean addAll(Collection<? extends E> c) + { + return queue.addAll(c); + } + + public synchronized boolean removeAll(Collection<?> c) + { + return queue.removeAll(c); + } + + public synchronized boolean retainAll(Collection<?> c) + { + return queue.retainAll(c); + } + + public synchronized void clear() + { + queue.clear(); + } + } + + static void run(String label, AveragedRun test) throws Exception + { + RunStats stats = test.call(); + System.out.println((label == null ? "" : label + ", ") + test + + ", " + stats.getAverage() + ", " + stats.getMax() + ", " + stats.getMin()); + } + + public static void main(String[] argv) throws Exception + { + Factory[] factories = new Factory[]{PLAIN, SYNCHRONIZED, CONCURRENT}; + int iterations = 5; + String label = argv.length > 0 ? argv[0]: null; + System.out.println((label == null ? "" : "Label, ") + "Queue Type, No. of Queues, No. of Operations, Avg Time, Min Time, Max Time"); + //vary number of queues: + + for(Factory f : factories) + { + run(label, new AveragedRun(new QueuePerfTest(f, 100, 10000), iterations)); + run(label, new AveragedRun(new QueuePerfTest(f, 1000, 10000), iterations)); + run(label, new AveragedRun(new QueuePerfTest(f, 10000, 10000), iterations)); + run(label, new AveragedRun(new QueuePerfTest(f, 1000, 1000), iterations)); + run(label, new AveragedRun(new QueuePerfTest(f, 1000, 100000), iterations)); + } + } + +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/SendPerfTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SendPerfTest.java new file mode 100644 index 0000000000..15aceaf037 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SendPerfTest.java @@ -0,0 +1,173 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.AbstractExchange; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.handler.OnCurrentThreadExecutor; +import org.apache.qpid.server.protocol.AMQMinaProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.MockIoSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.util.AveragedRun; +import org.apache.qpid.server.util.TestApplicationRegistry; +import org.apache.qpid.server.util.TimedRun; + +import java.util.ArrayList; +import java.util.List; + +public class SendPerfTest extends TimedRun +{ + private int _messages = 1000; + private int _clients = 10; + private List<AMQQueue> _queues; + + public SendPerfTest(int clients, int messages) + { + super("SendPerfTest, msgs=" + messages + ", clients=" + clients); + _messages = messages; + _clients = clients; + } + + protected void setup() throws Exception + { + _queues = initQueues(_clients); + System.gc(); + } + + protected void teardown() throws Exception + { + System.gc(); + } + + protected void run() throws Exception + { + deliver(_messages, _queues); + } + + //have a dummy AMQProtocolSession that does nothing on the writeFrame() + //set up x number of queues + //create necessary bits and pieces to deliver a message + //deliver y messages to each queue + + public static void main(String[] argv) throws Exception + { + ApplicationRegistry.initialise(new TestApplicationRegistry()); + int clients = Integer.parseInt(argv[0]); + int messages = Integer.parseInt(argv[1]); + int iterations = Integer.parseInt(argv[2]); + AveragedRun test = new AveragedRun(new SendPerfTest(clients, messages), iterations); + test.run(); + } + + /** + * Delivers messages to a number of queues. + * @param count the number of messages to deliver + * @param queues the list of queues + * @throws NoConsumersException + */ + static void deliver(int count, List<AMQQueue> queues) throws AMQException + { + BasicPublishBody publish = new BasicPublishBody(); + publish.exchange = new NullExchange().getName(); + ContentHeaderBody header = new ContentHeaderBody(); + List<ContentBody> body = new ArrayList<ContentBody>(); + MessageStore messageStore = new SkeletonMessageStore(); + body.add(new ContentBody()); + for (int i = 0; i < count; i++) + { + for (AMQQueue q : queues) + { + //q.deliver(new AMQMessage(messageStore, i, publish, header, body)); + //q.process(new AMQMessage(messageStore, i, publish, header, body)); + throw new AMQException("Need to fix this!!"); + } + } + } + + static List<AMQQueue> initQueues(int number) throws AMQException + { + Exchange exchange = new NullExchange(); + List<AMQQueue> queues = new ArrayList<AMQQueue>(number); + for (int i = 0; i < number; i++) + { + AMQQueue q = createQueue("Queue" + (i + 1)); + q.bind("routingKey", exchange); + try + { + q.registerProtocolSession(createSession(), 1, "1", false); + } + catch (Exception e) + { + throw new AMQException("Error creating protocol session: " + e, e); + } + queues.add(q); + } + return queues; + } + + static AMQQueue createQueue(String name) throws AMQException + { + return new AMQQueue(name, false, null, false, ApplicationRegistry.getInstance().getQueueRegistry(), + new OnCurrentThreadExecutor()); + } + + static AMQProtocolSession createSession() throws Exception + { + IApplicationRegistry reg = ApplicationRegistry.getInstance(); + AMQCodecFactory codecFactory = new AMQCodecFactory(true); + AMQMinaProtocolSession result = new AMQMinaProtocolSession(new MockIoSession(), reg.getQueueRegistry(), reg.getExchangeRegistry(), codecFactory); + result.addChannel(new AMQChannel(1, null, null)); + return result; + } + + static class NullExchange extends AbstractExchange + { + public String getName() + { + return "NullExchange"; + } + + protected ExchangeMBean createMBean() + { + return null; + } + + public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + } + + public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException + { + } + + public void route(AMQMessage payload) throws AMQException + { + } + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionManagerTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionManagerTest.java new file mode 100644 index 0000000000..7743db5078 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionManagerTest.java @@ -0,0 +1,105 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import junit.framework.JUnit4TestAdapter; + +public class SubscriptionManagerTest +{ + private final SubscriptionSet mgr = new SubscriptionSet(); + + @Test + public void basicSubscriptionManagement() + { + assertTrue(mgr.isEmpty()); + assertFalse(mgr.hasActiveSubscribers()); + TestSubscription s1 = new TestSubscription("S1"); + mgr.addSubscriber(s1); + assertFalse(mgr.isEmpty()); + assertTrue(mgr.hasActiveSubscribers()); + + TestSubscription s2 = new TestSubscription("S2"); + mgr.addSubscriber(s2); + + s2.setSuspended(true); + assertFalse(mgr.isEmpty()); + assertTrue(mgr.hasActiveSubscribers()); + assertTrue(s2.isSuspended()); + assertFalse(s1.isSuspended()); + + s1.setSuspended(true); + assertFalse(mgr.hasActiveSubscribers()); + + mgr.removeSubscriber(new TestSubscription("S1")); + assertFalse(mgr.isEmpty()); + mgr.removeSubscriber(new TestSubscription("S2")); + assertTrue(mgr.isEmpty()); + } + + @Test + public void roundRobin() + { + TestSubscription a = new TestSubscription("A"); + TestSubscription b = new TestSubscription("B"); + TestSubscription c = new TestSubscription("C"); + TestSubscription d = new TestSubscription("D"); + mgr.addSubscriber(a); + mgr.addSubscriber(b); + mgr.addSubscriber(c); + mgr.addSubscriber(d); + + for (int i = 0; i < 3; i++) + { + assertEquals(a, mgr.nextSubscriber(null)); + assertEquals(b, mgr.nextSubscriber(null)); + assertEquals(c, mgr.nextSubscriber(null)); + assertEquals(d, mgr.nextSubscriber(null)); + } + + c.setSuspended(true); + + for (int i = 0; i < 3; i++) + { + assertEquals(a, mgr.nextSubscriber(null)); + assertEquals(b, mgr.nextSubscriber(null)); + assertEquals(d, mgr.nextSubscriber(null)); + } + + mgr.removeSubscriber(a); + d.setSuspended(true); + c.setSuspended(false); + Subscription e = new TestSubscription("D"); + mgr.addSubscriber(e); + + for (int i = 0; i < 3; i++) + { + assertEquals(b, mgr.nextSubscriber(null)); + assertEquals(c, mgr.nextSubscriber(null)); + assertEquals(e, mgr.nextSubscriber(null)); + } + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(SubscriptionManagerTest.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionSetTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionSetTest.java new file mode 100644 index 0000000000..b6e8f8b44d --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionSetTest.java @@ -0,0 +1,149 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import junit.framework.JUnit4TestAdapter; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Test; + +public class SubscriptionSetTest +{ + /** + * A SubscriptionSet that counts the number of items scanned. + */ + static class TestSubscriptionSet extends SubscriptionSet + { + private int scanned = 0; + + void resetScanned() + { + scanned = 0; + } + + protected void subscriberScanned() + { + ++scanned; + } + + int getScanned() + { + return scanned; + } + } + + final TestSubscription sub1 = new TestSubscription("1"); + final TestSubscription sub2 = new TestSubscription("2"); + final TestSubscription sub3 = new TestSubscription("3"); + + final TestSubscription suspendedSub1 = new TestSubscription("sus1", true); + final TestSubscription suspendedSub2 = new TestSubscription("sus2", true); + final TestSubscription suspendedSub3 = new TestSubscription("sus3", true); + + @Test + public void nextMessage() + { + SubscriptionSet ss = new SubscriptionSet(); + assertNull(ss.nextSubscriber(null)); + assertEquals(0, ss.getCurrentSubscriber()); + + ss.addSubscriber(sub1); + assertEquals(sub1, ss.nextSubscriber(null)); + assertEquals(1, ss.getCurrentSubscriber()); + assertEquals(sub1, ss.nextSubscriber(null)); + assertEquals(1, ss.getCurrentSubscriber()); + + ss.addSubscriber(sub2); + ss.addSubscriber(sub3); + + assertEquals(sub2, ss.nextSubscriber(null)); + assertEquals(2, ss.getCurrentSubscriber()); + + assertEquals(sub3, ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + } + + @Test + public void nextMessageWhenAllSuspended() + { + SubscriptionSet ss = createAllSuspendedSubscriptionSet(); + assertNull(ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + + assertNull(ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + } + + private TestSubscriptionSet createAllSuspendedSubscriptionSet() + { + TestSubscriptionSet ss = new TestSubscriptionSet(); + ss.addSubscriber(suspendedSub1); + ss.addSubscriber(suspendedSub2); + ss.addSubscriber(suspendedSub3); + return ss; + } + + @Test + public void nextMessageAfterRemove() + { + SubscriptionSet ss = new SubscriptionSet(); + ss.addSubscriber(suspendedSub1); + ss.addSubscriber(suspendedSub2); + ss.addSubscriber(sub3); + assertEquals(sub3, ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + + assertEquals(suspendedSub1, ss.removeSubscriber(suspendedSub1)); + + assertEquals(sub3, ss.nextSubscriber(null)); // Current implementation handles OutOfBoundsException here. + assertEquals(2, ss.getCurrentSubscriber()); + } + + @Test + public void nextMessageOverScanning() + { + TestSubscriptionSet ss = new TestSubscriptionSet(); + TestSubscription sub = new TestSubscription("test"); + ss.addSubscriber(suspendedSub1); + ss.addSubscriber(sub); + ss.addSubscriber(suspendedSub3); + assertEquals(sub, ss.nextSubscriber(null)); + assertEquals(2, ss.getCurrentSubscriber()); + assertEquals(2, ss.getScanned()); + + ss.resetScanned(); + sub.setSuspended(true); + assertNull(ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + // Current implementation overscans by one item here. + assertEquals(ss.size() + 1, ss.getScanned()); + } + + @Test + public void nextMessageOverscanWorstCase() { + TestSubscriptionSet ss = createAllSuspendedSubscriptionSet(); + ss.nextSubscriber(null); + // Scans the subscriptions twice. + assertEquals(ss.size() * 2, ss.getScanned()); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(SubscriptionSetTest.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/TestSubscription.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/TestSubscription.java new file mode 100644 index 0000000000..093d7e60f4 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/TestSubscription.java @@ -0,0 +1,84 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import java.util.ArrayList; +import java.util.List; + +public class TestSubscription implements Subscription +{ + private final List<AMQMessage> messages; + private final Object key; + private boolean isSuspended; + + public TestSubscription(Object key) + { + this(key, new ArrayList<AMQMessage>()); + } + + public TestSubscription(final Object key, final boolean isSuspended) + { + this(key); + setSuspended(isSuspended); + } + + TestSubscription(Object key, List<AMQMessage> messages) + { + this.key = key; + this.messages = messages; + } + + List<AMQMessage> getMessages() + { + return messages; + } + + public void send(AMQMessage msg, AMQQueue queue) + { + messages.add(msg); + } + + public void setSuspended(boolean suspended) + { + isSuspended = suspended; + } + + public boolean isSuspended() + { + return isSuspended; + } + + public void queueDeleted(AMQQueue queue) + { + } + + public int hashCode() + { + return key.hashCode(); + } + + public boolean equals(Object o) + { + return o instanceof TestSubscription && ((TestSubscription) o).key.equals(key); + } + + public String toString() + { + return key.toString(); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/UnitTests.java new file mode 100644 index 0000000000..3a86773a15 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/UnitTests.java @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.queue; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + AckTest.class, + DeliveryManagerTest.class, + SubscriptionManagerTest.class, + SubscriptionSetTest.class, + ConcurrencyTest.class} +) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java b/java/broker/src/main/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java new file mode 100644 index 0000000000..981665b4e3 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java @@ -0,0 +1,120 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.store; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.MessageMetaData; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A message store that does nothing. Designed to be used in tests that do not want to use any message store + * functionality. + */ +public class SkeletonMessageStore implements MessageStore +{ + private final AtomicLong _messageId = new AtomicLong(1); + + public void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception + { + } + + public void close() throws Exception + { + } + + public void removeMessage(long messageId) + { + } + + public void createQueue(AMQQueue queue) throws AMQException + { + } + + public void removeQueue(String name) throws AMQException + { + } + + public void enqueueMessage(String name, long messageId) throws AMQException + { + } + + public void dequeueMessage(String name, long messageId) throws AMQException + { + } + + public void beginTran() throws AMQException + { + } + + public boolean inTran() + { + return false; + } + + public void commitTran() throws AMQException + { + } + + public void abortTran() throws AMQException + { + } + + public List<AMQQueue> createQueues() throws AMQException + { + return null; + } + + public long getNewMessageId() + { + return _messageId.getAndIncrement(); + } + + public void storePublishBody(long messageId, BasicPublishBody publishBody) throws AMQException + { + } + + public void storeContentHeader(long messageId, ContentHeaderBody contentHeaderBody) throws AMQException + { + } + + public void storeContentBodyChunk(long messageId, int index, ContentBody contentBody) throws AMQException + { + } + + public void storeMessageMetaData(long messageId, MessageMetaData messageMetaData) throws AMQException + { + } + + public MessageMetaData getMessageMetaData(long messageId) throws AMQException + { + return null; + } + + public ContentBody getContentBodyChunk(long messageId, int index) throws AMQException + { + return null; + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/store/TestReferenceCounting.java b/java/broker/src/main/test/java/org/apache/qpid/server/store/TestReferenceCounting.java new file mode 100644 index 0000000000..fea0c25852 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/store/TestReferenceCounting.java @@ -0,0 +1,73 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.store; + +import junit.framework.JUnit4TestAdapter; +import org.junit.Test; +import org.junit.Assert; +import org.junit.Before; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.AMQException; + +/** + * Tests that reference counting works correctly with AMQMessage and the message store + */ +public class TestReferenceCounting +{ + private TestableMemoryMessageStore _store; + + @Before + public void createCommonObjects() + { + _store = new TestableMemoryMessageStore(); + } + + /** + * Check that when the reference count is decremented the message removes itself from the store + */ + @Test + public void testMessageGetsRemoved() throws AMQException + { + throw new AMQException("Fix this!!!"); + /*AMQMessage message = new AMQMessage(_store, null); + _store.put(message); + Assert.assertTrue(_store.getMessageMap().size() == 1); + message.decrementReference(); + Assert.assertTrue(_store.getMessageMap().size() == 0); + */ + } + + @Test + public void testMessageRemains() throws AMQException + { + throw new AMQException("Fix me!!!"); + /*AMQMessage message = new AMQMessage(_store, null); + _store.put(message); + Assert.assertTrue(_store.getMessageMap().size() == 1); + message.incrementReference(); + message.decrementReference(); + Assert.assertTrue(_store.getMessageMap().size() == 1);*/ + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(TestReferenceCounting.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java b/java/broker/src/main/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java new file mode 100644 index 0000000000..c6ff9ce561 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.store; + +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.server.queue.MessageMetaData; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Adds some extra methods to the memory message store for testing purposes. + */ +public class TestableMemoryMessageStore extends MemoryMessageStore +{ + public TestableMemoryMessageStore() + { + _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(); + _contentBodyMap = new ConcurrentHashMap<Long, List<ContentBody>>(); + } + + public ConcurrentMap<Long, MessageMetaData> getMessageMetaDataMap() + { + return _metaDataMap; + } + + public ConcurrentMap<Long, List<ContentBody>> getContentBodyMap() + { + return _contentBodyMap; + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/store/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/store/UnitTests.java new file mode 100644 index 0000000000..a917d736a2 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/store/UnitTests.java @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.store; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + TestReferenceCounting.class +}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/txn/TxnBufferTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/txn/TxnBufferTest.java new file mode 100644 index 0000000000..2b49752023 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/txn/TxnBufferTest.java @@ -0,0 +1,305 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.txn; + +import junit.framework.JUnit4TestAdapter; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.junit.Ignore; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestableMemoryMessageStore; + +import java.util.LinkedList; + +public class TxnBufferTest +{ + private final LinkedList<MockOp> ops = new LinkedList<MockOp>(); + + @Before + public void setup() throws Exception + { + } + + @Test + public void commit() throws AMQException + { + MockStore store = new MockStore(); + + TxnBuffer buffer = new TxnBuffer(store); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + //check relative ordering + MockOp op = new MockOp().expectPrepare().expectPrepare().expectCommit().expectCommit(); + buffer.enlist(op); + buffer.enlist(op); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + + buffer.commit(); + + validateOps(); + store.validate(); + } + + @Test + public void rollback() throws AMQException + { + MockStore store = new MockStore(); + + TxnBuffer buffer = new TxnBuffer(store); + buffer.enlist(new MockOp().expectRollback()); + buffer.enlist(new MockOp().expectRollback()); + buffer.enlist(new MockOp().expectRollback()); + + buffer.rollback(); + + validateOps(); + store.validate(); + } + + @Test + public void commitWithFailureDuringPrepare() throws AMQException + { + MockStore store = new MockStore(); + store.expectBegin().expectAbort(); + + TxnBuffer buffer = new TxnBuffer(store); + buffer.containsPersistentChanges(); + buffer.enlist(new MockOp().expectPrepare().expectUndoPrepare()); + buffer.enlist(new TxnTester(store)); + buffer.enlist(new MockOp().expectPrepare().expectUndoPrepare()); + buffer.enlist(new FailedPrepare()); + buffer.enlist(new MockOp()); + + buffer.commit(); + validateOps(); + store.validate(); + } + + @Test + public void commitWithPersistance() throws AMQException + { + MockStore store = new MockStore(); + store.expectBegin().expectCommit(); + + TxnBuffer buffer = new TxnBuffer(store); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + buffer.enlist(new TxnTester(store)); + buffer.containsPersistentChanges(); + + buffer.commit(); + validateOps(); + store.validate(); + } + + private void validateOps() + { + for(MockOp op : ops) + { + op.validate(); + } + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(TxnBufferTest.class); + } + + class MockOp implements TxnOp + { + final Object PREPARE = "PREPARE"; + final Object COMMIT = "COMMIT"; + final Object UNDO_PREPARE = "UNDO_PREPARE"; + final Object ROLLBACK = "ROLLBACK"; + + private final LinkedList expected = new LinkedList(); + + MockOp() + { + ops.add(this); + } + + public void prepare() + { + assertEquals(expected.removeLast(), PREPARE); + } + + public void commit() + { + assertEquals(expected.removeLast(), COMMIT); + } + + public void undoPrepare() + { + assertEquals(expected.removeLast(), UNDO_PREPARE); + } + + public void rollback() + { + assertEquals(expected.removeLast(), ROLLBACK); + } + + private MockOp expect(Object optype) + { + expected.addFirst(optype); + return this; + } + + MockOp expectPrepare() + { + return expect(PREPARE); + } + + MockOp expectCommit() + { + return expect(COMMIT); + } + + MockOp expectUndoPrepare() + { + return expect(UNDO_PREPARE); + } + + MockOp expectRollback() + { + return expect(ROLLBACK); + } + + void validate() + { + assertEquals("Expected ops were not all invoked", new LinkedList(), expected); + } + + void clear() + { + expected.clear(); + } + } + + class MockStore extends TestableMemoryMessageStore + { + final Object BEGIN = "BEGIN"; + final Object ABORT = "ABORT"; + final Object COMMIT = "COMMIT"; + + private final LinkedList expected = new LinkedList(); + private boolean inTran; + + public void beginTran() throws AMQException + { + assertEquals(expected.removeLast(), BEGIN); + inTran = true; + } + + public void commitTran() throws AMQException + { + assertEquals(expected.removeLast(), COMMIT); + inTran = false; + } + + public void abortTran() throws AMQException + { + assertEquals(expected.removeLast(), ABORT); + inTran = false; + } + + public boolean inTran() + { + return inTran; + } + + private MockStore expect(Object optype) + { + expected.addFirst(optype); + return this; + } + + MockStore expectBegin() + { + return expect(BEGIN); + } + + MockStore expectCommit() + { + return expect(COMMIT); + } + + MockStore expectAbort() + { + return expect(ABORT); + } + + void clear() + { + expected.clear(); + } + + void validate() + { + assertEquals("Expected ops were not all invoked", new LinkedList(), expected); + } + } + + class NullOp implements TxnOp + { + public void prepare() throws AMQException + { + } + public void commit() + { + } + public void undoPrepare() + { + } + public void rollback() + { + } + } + + class FailedPrepare extends NullOp + { + public void prepare() throws AMQException + { + throw new AMQException("Fail!"); + } + } + + class TxnTester extends NullOp + { + private final MessageStore store; + + TxnTester(MessageStore store) + { + this.store = store; + } + + public void prepare() throws AMQException + { + assertTrue("Expected prepare to be performed under txn", store.inTran()); + } + + public void commit() + { + assertTrue("Expected commit not to be performed under txn", !store.inTran()); + } + } + +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/txn/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/txn/UnitTests.java new file mode 100644 index 0000000000..dd63b260fd --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/txn/UnitTests.java @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.txn; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + TxnBufferTest.class +}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/AveragedRun.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/AveragedRun.java new file mode 100644 index 0000000000..3e4a1edac4 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/AveragedRun.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +import org.apache.qpid.server.util.TimedRun; + +import java.util.concurrent.Callable; +import java.util.Collection; + +public class AveragedRun implements Callable<RunStats> +{ + private final RunStats stats = new RunStats(); + private final TimedRun test; + private final int iterations; + + public AveragedRun(TimedRun test, int iterations) + { + this.test = test; + this.iterations = iterations; + } + + public RunStats call() throws Exception + { + for (int i = 0; i < iterations; i++) + { + stats.record(test.call()); + } + return stats; + } + + public void run() throws Exception + { + System.out.println(test + ": " + call()); + } + + public String toString() + { + return test.toString(); + } + + static void run(Collection<AveragedRun> tests) throws Exception + { + for(AveragedRun test : tests) + { + test.run(); + } + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/ConcurrentTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/ConcurrentTest.java new file mode 100644 index 0000000000..6c5185e254 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/ConcurrentTest.java @@ -0,0 +1,76 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +public class ConcurrentTest extends TimedRun +{ + private final TimedRun _test; + private final Thread[] _threads; + + public ConcurrentTest(TimedRun test, int threads) + { + super(test.toString()); + _test = test; + _threads = new Thread[threads]; + } + + protected void setup() throws Exception + { + _test.setup(); + for(int i = 0; i < _threads.length; i++) + { + _threads[i] = new Thread(new Runner()); + } + } + + protected void teardown() throws Exception + { + _test.teardown(); + } + + protected void run() throws Exception + { + for(Thread t : _threads) + { + t.start(); + } + for(Thread t : _threads) + { + t.join(); + } + } + + private class Runner implements Runnable + { + private Exception error; + + public void run() + { + try + { + _test.run(); + } + catch(Exception e) + { + error = e; + e.printStackTrace(); + } + } + } + +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/LoggingProxyTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/LoggingProxyTest.java new file mode 100644 index 0000000000..15c9e1a59a --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/LoggingProxyTest.java @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +import junit.framework.JUnit4TestAdapter; +import org.junit.Assert; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class LoggingProxyTest +{ + static interface IFoo { + void foo(); + void foo(int i, Collection c); + String bar(); + String bar(String s, List l); + } + + static class Foo implements IFoo { + public void foo() + { + } + + public void foo(int i, Collection c) + { + } + + public String bar() + { + return null; + } + + public String bar(String s, List l) + { + return "ha"; + } + } + + @Test + public void simple() { + LoggingProxy proxy = new LoggingProxy(new Foo(), 20); + IFoo foo = (IFoo)proxy.getProxy(IFoo.class); + foo.foo(); + assertEquals(2, proxy.getBufferSize()); + Assert.assertTrue(proxy.getBuffer().get(0).toString().matches(".*: foo\\(\\) entered$")); + Assert.assertTrue(proxy.getBuffer().get(1).toString().matches(".*: foo\\(\\) returned$")); + + foo.foo(3, Arrays.asList(0, 1, 2)); + assertEquals(4, proxy.getBufferSize()); + Assert.assertTrue(proxy.getBuffer().get(2).toString().matches(".*: foo\\(\\[3, \\[0, 1, 2\\]\\]\\) entered$")); + Assert.assertTrue(proxy.getBuffer().get(3).toString().matches(".*: foo\\(\\) returned$")); + + foo.bar(); + assertEquals(6, proxy.getBufferSize()); + Assert.assertTrue(proxy.getBuffer().get(4).toString().matches(".*: bar\\(\\) entered$")); + Assert.assertTrue(proxy.getBuffer().get(5).toString().matches(".*: bar\\(\\) returned null$")); + + foo.bar("hello", Arrays.asList(1, 2, 3)); + assertEquals(8, proxy.getBufferSize()); + Assert.assertTrue(proxy.getBuffer().get(6).toString().matches(".*: bar\\(\\[hello, \\[1, 2, 3\\]\\]\\) entered$")); + Assert.assertTrue(proxy.getBuffer().get(7).toString().matches(".*: bar\\(\\) returned ha$")); + + proxy.dump(); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(LoggingProxyTest.class); + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/RunStats.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/RunStats.java new file mode 100644 index 0000000000..248622836d --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/RunStats.java @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +public class RunStats +{ + private long min = Long.MAX_VALUE; + private long max; + private long total; + private int count; + + public void record(long time) + { + max = Math.max(time, max); + min = Math.min(time, min); + total += time; + count++; + } + + public long getMin() + { + return min; + } + + public long getMax() + { + return max; + } + + public long getAverage() + { + return total / count; + } + + public String toString() + { + return "avg=" + getAverage() + ", min=" + min + ", max=" + max; + } +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java new file mode 100644 index 0000000000..69939b88e4 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java @@ -0,0 +1,104 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.DefaultExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.queue.DefaultQueueRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.NullAuthenticationManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.MapConfiguration; + +import java.util.HashMap; + +public class TestApplicationRegistry extends ApplicationRegistry +{ + private QueueRegistry _queueRegistry; + + private ExchangeRegistry _exchangeRegistry; + + private ExchangeFactory _exchangeFactory; + + private ManagedObjectRegistry _managedObjectRegistry; + + private AuthenticationManager _authenticationManager; + + private MessageStore _messageStore; + + public TestApplicationRegistry() + { + super(new MapConfiguration(new HashMap())); + } + + public void initialise() throws Exception + { + _managedObjectRegistry = new NoopManagedObjectRegistry(); + _queueRegistry = new DefaultQueueRegistry(); + _exchangeFactory = new DefaultExchangeFactory(); + _exchangeRegistry = new DefaultExchangeRegistry(_exchangeFactory); + _authenticationManager = new NullAuthenticationManager(); + _messageStore = new TestableMemoryMessageStore(); + + _configuration.addProperty("heartbeat.delay", 10 * 60); // 10 minutes + } + + public Configuration getConfiguration() + { + return _configuration; + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + public ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + public ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + public ManagedObjectRegistry getManagedObjectRegistry() + { + return _managedObjectRegistry; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } +} + diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/TimedRun.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/TimedRun.java new file mode 100644 index 0000000000..f779b7fbb6 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/TimedRun.java @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +import java.util.concurrent.Callable; + +public abstract class TimedRun implements Callable<Long> +{ + private final String description; + + public TimedRun(String description) + { + this.description = description; + } + + public Long call() throws Exception + { + setup(); + long start = System.currentTimeMillis(); + run(); + long stop = System.currentTimeMillis(); + teardown(); + return stop - start; + } + + public String toString() + { + return description; + } + + protected void setup() throws Exception{} + protected void teardown() throws Exception{} + protected abstract void run() throws Exception; +} diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/UnitTests.java new file mode 100644 index 0000000000..d6cc471413 --- /dev/null +++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/UnitTests.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.util; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({LoggingProxyTest.class}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/src/main/test/lib/README b/java/broker/src/main/test/lib/README new file mode 100644 index 0000000000..a75673935e --- /dev/null +++ b/java/broker/src/main/test/lib/README @@ -0,0 +1 @@ +Junit copied here momentarily.
diff --git a/java/broker/src/main/test/lib/junit/junit-4.0.jar b/java/broker/src/main/test/lib/junit/junit-4.0.jar Binary files differnew file mode 100644 index 0000000000..b20406924a --- /dev/null +++ b/java/broker/src/main/test/lib/junit/junit-4.0.jar diff --git a/java/broker/src/main/test/lib/junit/junit.jar b/java/broker/src/main/test/lib/junit/junit.jar Binary files differnew file mode 100644 index 0000000000..674d71e89e --- /dev/null +++ b/java/broker/src/main/test/lib/junit/junit.jar |