diff options
| author | Kim van der Riet <kpvdr@apache.org> | 2013-09-20 18:59:30 +0000 |
|---|---|---|
| committer | Kim van der Riet <kpvdr@apache.org> | 2013-09-20 18:59:30 +0000 |
| commit | c70bf3ea28cdf6bafd8571690d3e5c466a0658a2 (patch) | |
| tree | 68b24940e433f3f9c278b054d9ea1622389bd332 /qpid/java/broker-plugins | |
| parent | fcdf1723c7b5cdf0772054a93edb6e7d97c4bb1e (diff) | |
| download | qpid-python-c70bf3ea28cdf6bafd8571690d3e5c466a0658a2.tar.gz | |
QPID-4984: WIP - Merge from trunk r.1525056
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/linearstore@1525101 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java/broker-plugins')
229 files changed, 31739 insertions, 210 deletions
diff --git a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java index 181d693614..cc93c540df 100644 --- a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java +++ b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java @@ -42,7 +42,7 @@ import org.apache.qpid.test.utils.QpidTestCase; * The ruleset is configured directly rather than using an external file by adding rules individually, calling the * {@link RuleSet#grant(Integer, String, Permission, Operation, ObjectType, ObjectProperties)} method. Then, the * access control mechanism is validated by checking whether operations would be authorised by calling the - * {@link RuleSet#check(Principal, Operation, ObjectType, ObjectProperties)} method. + * {@link RuleSet#check(Subject, Operation, ObjectType, ObjectProperties)} method. * * It ensure that permissions can be granted correctly on users directly and on groups. */ @@ -53,9 +53,9 @@ public class RuleSetTest extends QpidTestCase private static final String TEST_USER = "user"; // Common things that are passed to frame constructors - private AMQShortString _queueName = new AMQShortString(this.getClass().getName() + "queue"); - private AMQShortString _exchangeName = new AMQShortString("amq.direct"); - private AMQShortString _exchangeType = new AMQShortString("direct"); + private String _queueName = this.getClass().getName() + "queue"; + private String _exchangeName = "amq.direct"; + private String _exchangeType = "direct"; private Subject _testSubject = TestPrincipalUtils.createTestSubject(TEST_USER); @Override @@ -116,7 +116,7 @@ public class RuleSetTest extends QpidTestCase public void testExchangeCreate() { ObjectProperties properties = new ObjectProperties(_exchangeName); - properties.put(ObjectProperties.Property.TYPE, _exchangeType.asString()); + properties.put(ObjectProperties.Property.TYPE, _exchangeType); assertDenyGrantAllow(_testSubject, Operation.CREATE, ObjectType.EXCHANGE, properties); } diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/build.xml b/qpid/java/broker-plugins/amqp-0-10-protocol/build.xml new file mode 100644 index 0000000000..e3e6fabc87 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/build.xml @@ -0,0 +1,33 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins AMQP 0-10 Protocol" default="build"> + <property name="module.depends" value="common broker" /> + <property name="module.test.depends" value="common/tests broker/tests" /> + + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + <property name="broker-plugins-amqp-0-10-protocol.libs" value="" /> + + <property name="broker.plugin" value="true"/> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks"/> + +</project> diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/CreditCreditManager.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/CreditCreditManager.java new file mode 100644 index 0000000000..cee1a04b17 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/CreditCreditManager.java @@ -0,0 +1,176 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + + +import org.apache.qpid.server.flow.AbstractFlowCreditManager;public class CreditCreditManager extends AbstractFlowCreditManager implements FlowCreditManager_0_10 +{ + private volatile long _bytesCredit; + private volatile long _messageCredit; + + public CreditCreditManager(long bytesCredit, long messageCredit) + { + _bytesCredit = bytesCredit; + _messageCredit = messageCredit; + setSuspended(!hasCredit()); + + } + + + public synchronized void setCreditLimits(final long bytesCredit, final long messageCredit) + { + _bytesCredit = bytesCredit; + _messageCredit = messageCredit; + + setSuspended(!hasCredit()); + + } + + + public long getMessageCredit() + { + return _messageCredit == -1L + ? Long.MAX_VALUE + : _messageCredit; + } + + public long getBytesCredit() + { + return _bytesCredit == -1L + ? Long.MAX_VALUE + : _bytesCredit; + } + + public synchronized void restoreCredit(final long messageCredit, final long bytesCredit) + { + } + + + public synchronized void addCredit(final long messageCredit, final long bytesCredit) + { + boolean notifyIncrease = true; + if(_messageCredit >= 0L && messageCredit > 0L) + { + notifyIncrease = _messageCredit != 0L; + _messageCredit += messageCredit; + } + + + + if(_bytesCredit >= 0L && bytesCredit > 0L) + { + notifyIncrease = notifyIncrease && bytesCredit>0; + _bytesCredit += bytesCredit; + + + + if(notifyIncrease) + { + notifyIncreaseBytesCredit(); + } + } + + + + setSuspended(!hasCredit()); + + } + + public void clearCredit() + { + _bytesCredit = 0l; + _messageCredit = 0l; + setSuspended(true); + } + + + public synchronized boolean hasCredit() + { + // Note !=, if credit is < 0 that indicates infinite credit + return (_bytesCredit != 0L && _messageCredit != 0L); + } + + public synchronized boolean useCreditForMessage(long msgSize) + { + if(_messageCredit >= 0L) + { + if(_messageCredit > 0) + { + if(_bytesCredit < 0L) + { + _messageCredit--; + + return true; + } + else if(msgSize <= _bytesCredit) + { + _messageCredit--; + _bytesCredit -= msgSize; + + return true; + } + else + { + return false; + } + } + else + { + setSuspended(true); + return false; + } + } + else if(_bytesCredit >= 0L) + { + if(msgSize <= _bytesCredit) + { + _bytesCredit -= msgSize; + + return true; + } + else + { + return false; + } + + } + else + { + return true; + } + + } + + public synchronized void stop() + { + if(_bytesCredit > 0) + { + _bytesCredit = 0; + } + if(_messageCredit > 0) + { + _messageCredit = 0; + } + + } + + +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ExplicitAcceptDispositionChangeListener.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ExplicitAcceptDispositionChangeListener.java new file mode 100755 index 0000000000..4b38b8a1a3 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ExplicitAcceptDispositionChangeListener.java @@ -0,0 +1,93 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.queue.QueueEntry; + + +class ExplicitAcceptDispositionChangeListener implements ServerSession.MessageDispositionChangeListener +{ + private static final Logger _logger = Logger.getLogger(ExplicitAcceptDispositionChangeListener.class); + + + private final QueueEntry _entry; + private final Subscription_0_10 _sub; + + public ExplicitAcceptDispositionChangeListener(QueueEntry entry, Subscription_0_10 subscription_0_10) + { + _entry = entry; + _sub = subscription_0_10; + } + + public void onAccept() + { + final Subscription_0_10 subscription = getSubscription(); + if(subscription != null && _entry.isAcquiredBy(_sub)) + { + subscription.getSessionModel().acknowledge(subscription, _entry); + } + else + { + _logger.warn("MessageAccept received for message which has not been acquired (likely client error)"); + } + + } + + public void onRelease(boolean setRedelivered) + { + final Subscription_0_10 subscription = getSubscription(); + if(subscription != null && _entry.isAcquiredBy(_sub)) + { + subscription.release(_entry, setRedelivered); + } + else + { + _logger.warn("MessageRelease received for message which has not been acquired (likely client error)"); + } + } + + public void onReject() + { + final Subscription_0_10 subscription = getSubscription(); + if(subscription != null && _entry.isAcquiredBy(_sub)) + { + subscription.reject(_entry); + } + else + { + _logger.warn("MessageReject received for message which has not been acquired (likely client error)"); + } + + } + + public boolean acquire() + { + return _entry.acquire(getSubscription()); + } + + + private Subscription_0_10 getSubscription() + { + return _sub; + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/FlowCreditManager_0_10.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/FlowCreditManager_0_10.java new file mode 100755 index 0000000000..7f092814da --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/FlowCreditManager_0_10.java @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.server.flow.FlowCreditManager; + +public interface FlowCreditManager_0_10 extends FlowCreditManager +{ + public void addCredit(long count, long bytes); + + void clearCredit(); +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ImplicitAcceptDispositionChangeListener.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ImplicitAcceptDispositionChangeListener.java new file mode 100755 index 0000000000..ce0155b789 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ImplicitAcceptDispositionChangeListener.java @@ -0,0 +1,88 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.queue.QueueEntry; + +class ImplicitAcceptDispositionChangeListener implements ServerSession.MessageDispositionChangeListener +{ + private static final Logger _logger = Logger.getLogger(ImplicitAcceptDispositionChangeListener.class); + + + private final QueueEntry _entry; + private Subscription_0_10 _sub; + + public ImplicitAcceptDispositionChangeListener(QueueEntry entry, Subscription_0_10 subscription_0_10) + { + _entry = entry; + _sub = subscription_0_10; + } + + public void onAccept() + { + _logger.warn("MessageAccept received for message which is using NONE as the accept mode (likely client error)"); + } + + public void onRelease(boolean setRedelivered) + { + if(_entry.isAcquiredBy(_sub)) + { + getSubscription().release(_entry, setRedelivered); + } + else + { + _logger.warn("MessageRelease received for message which has not been acquired (likely client error)"); + } + } + + public void onReject() + { + if(_entry.isAcquiredBy(_sub)) + { + getSubscription().reject(_entry); + } + else + { + _logger.warn("MessageReject received for message which has not been acquired (likely client error)"); + } + + } + + public boolean acquire() + { + boolean acquired = _entry.acquire(getSubscription()); + if(acquired) + { + getSubscription().recordUnacknowledged(_entry); + } + return acquired; + + } + + public Subscription_0_10 getSubscription() + { + return _sub; + } + + +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageAcceptCompletionListener.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageAcceptCompletionListener.java new file mode 100755 index 0000000000..f5f2a8d43f --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageAcceptCompletionListener.java @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.transport.Method; + +public class MessageAcceptCompletionListener implements Method.CompletionListener +{ + private final Subscription_0_10 _sub; + private final QueueEntry _entry; + private final ServerSession _session; + private boolean _restoreCredit; + + public MessageAcceptCompletionListener(Subscription_0_10 sub, ServerSession session, QueueEntry entry, boolean restoreCredit) + { + super(); + _sub = sub; + _entry = entry; + _session = session; + _restoreCredit = restoreCredit; + } + + public void onComplete(Method method) + { + if(_restoreCredit) + { + _sub.restoreCredit(_entry); + } + if(_entry.isAcquiredBy(_sub)) + { + _session.acknowledge(_sub, _entry); + } + + _session.removeDispositionListener(method); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageConverter_v0_10.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageConverter_v0_10.java new file mode 100644 index 0000000000..c6ae0c6e47 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageConverter_v0_10.java @@ -0,0 +1,138 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import java.nio.ByteBuffer; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.plugin.MessageConverter; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.MessageDeliveryPriority; +import org.apache.qpid.transport.MessageProperties; + +public class MessageConverter_v0_10 implements MessageConverter<ServerMessage, MessageTransferMessage> +{ + @Override + public Class<ServerMessage> getInputClass() + { + return ServerMessage.class; + } + + @Override + public Class<MessageTransferMessage> getOutputClass() + { + return MessageTransferMessage.class; + } + + @Override + public MessageTransferMessage convert(ServerMessage serverMsg, VirtualHost vhost) + { + return new MessageTransferMessage(convertToStoredMessage(serverMsg), null); + } + + private StoredMessage<MessageMetaData_0_10> convertToStoredMessage(final ServerMessage serverMsg) + { + final MessageMetaData_0_10 messageMetaData_0_10 = convertMetaData(serverMsg); + + return new StoredMessage<MessageMetaData_0_10>() + { + @Override + public MessageMetaData_0_10 getMetaData() + { + return messageMetaData_0_10; + } + + @Override + public long getMessageNumber() + { + return serverMsg.getMessageNumber(); + } + + @Override + public void addContent(int offsetInMessage, ByteBuffer src) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getContent(int offsetInMessage, ByteBuffer dst) + { + return serverMsg.getContent(dst, offsetInMessage); + } + + @Override + public ByteBuffer getContent(int offsetInMessage, int size) + { + return serverMsg.getContent(offsetInMessage, size); + } + + @Override + public StoreFuture flushToStore() + { + return StoreFuture.IMMEDIATE_FUTURE; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } + + private MessageMetaData_0_10 convertMetaData(ServerMessage serverMsg) + { + DeliveryProperties deliveryProps = new DeliveryProperties(); + MessageProperties messageProps = new MessageProperties(); + + int size = (int) serverMsg.getSize(); + ByteBuffer body = ByteBuffer.allocate(size); + serverMsg.getContent(body, 0); + body.flip(); + + + deliveryProps.setExpiration(serverMsg.getExpiration()); + deliveryProps.setImmediate(serverMsg.isImmediate()); + deliveryProps.setPriority(MessageDeliveryPriority.get(serverMsg.getMessageHeader().getPriority())); + deliveryProps.setRoutingKey(serverMsg.getRoutingKey()); + deliveryProps.setTimestamp(serverMsg.getMessageHeader().getTimestamp()); + + messageProps.setContentEncoding(serverMsg.getMessageHeader().getEncoding()); + messageProps.setContentLength(size); + messageProps.setContentType(serverMsg.getMessageHeader().getMimeType()); + if(serverMsg.getMessageHeader().getCorrelationId() != null) + { + messageProps.setCorrelationId(serverMsg.getMessageHeader().getCorrelationId().getBytes()); + } + + Header header = new Header(deliveryProps, messageProps, null); + return new MessageMetaData_0_10(header, size, serverMsg.getArrivalTime()); + } + + @Override + public String getType() + { + return "Unknown to v0-10"; + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageMetaDataType_0_10.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageMetaDataType_0_10.java new file mode 100644 index 0000000000..90fb443f5b --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageMetaDataType_0_10.java @@ -0,0 +1,67 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import java.nio.ByteBuffer; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.store.StoredMessage; + +public class MessageMetaDataType_0_10 implements MessageMetaDataType<MessageMetaData_0_10> +{ + + public static final int TYPE = 1; + + @Override + public int ordinal() + { + return TYPE; + } + + @Override + public MessageMetaData_0_10 createMetaData(ByteBuffer buf) + { + return MessageMetaData_0_10.FACTORY.createMetaData(buf); + } + + @Override + public ServerMessage<MessageMetaData_0_10> createMessage(StoredMessage<MessageMetaData_0_10> msg) + { + return new MessageTransferMessage(msg, null); + } + + public int hashCode() + { + return ordinal(); + } + + public boolean equals(Object o) + { + return o != null && o.getClass() == getClass(); + } + + @Override + public String getType() + { + return AmqpProtocolVersion.v0_10.toString(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageMetaData_0_10.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageMetaData_0_10.java new file mode 100755 index 0000000000..092ea7c3c9 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageMetaData_0_10.java @@ -0,0 +1,276 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.MessageDeliveryMode; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.MessageTransfer; +import org.apache.qpid.transport.Struct; +import org.apache.qpid.transport.codec.BBDecoder; +import org.apache.qpid.transport.codec.BBEncoder; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class MessageMetaData_0_10 implements StorableMessageMetaData, InboundMessage +{ + private Header _header; + private DeliveryProperties _deliveryProps; + private MessageProperties _messageProps; + private MessageTransferHeader _messageHeader; + private long _arrivalTime; + private int _bodySize; + + private static final int ENCODER_SIZE = 1 << 10; + + public static final MessageMetaDataType.Factory<MessageMetaData_0_10> FACTORY = new MetaDataFactory(); + + private static final MessageMetaDataType_0_10 TYPE = new MessageMetaDataType_0_10(); + + private volatile ByteBuffer _encoded; + private Object _connectionReference; + + + public MessageMetaData_0_10(MessageTransfer xfr) + { + this(xfr.getHeader(), xfr.getBodySize(), System.currentTimeMillis()); + } + + public MessageMetaData_0_10(Header header, int bodySize, long arrivalTime) + { + _header = header; + if(_header != null) + { + _deliveryProps = _header.getDeliveryProperties(); + _messageProps = _header.getMessageProperties(); + } + else + { + _deliveryProps = null; + _messageProps = null; + } + _messageHeader = new MessageTransferHeader(_deliveryProps, _messageProps); + _arrivalTime = arrivalTime; + _bodySize = bodySize; + + } + + + + public MessageMetaDataType getType() + { + return TYPE; + } + + public int getStorableSize() + { + ByteBuffer buf = _encoded; + + if(buf == null) + { + buf = encodeAsBuffer(); + _encoded = buf; + } + + //TODO -- need to add stuff + return buf.limit(); + } + + private ByteBuffer encodeAsBuffer() + { + BBEncoder encoder = new BBEncoder(ENCODER_SIZE); + + encoder.writeInt64(_arrivalTime); + encoder.writeInt32(_bodySize); + int headersLength = 0; + if(_header.getDeliveryProperties() != null) + { + headersLength++; + } + if(_header.getMessageProperties() != null) + { + headersLength++; + } + if(_header.getNonStandardProperties() != null) + { + headersLength += _header.getNonStandardProperties().size(); + } + + encoder.writeInt32(headersLength); + + if(_header.getDeliveryProperties() != null) + { + encoder.writeStruct32(_header.getDeliveryProperties()); + } + if(_header.getMessageProperties() != null) + { + encoder.writeStruct32(_header.getMessageProperties()); + } + if(_header.getNonStandardProperties() != null) + { + + for(Struct header : _header.getNonStandardProperties()) + { + encoder.writeStruct32(header); + } + + } + ByteBuffer buf = encoder.buffer(); + return buf; + } + + public int writeToBuffer(int offsetInMetaData, ByteBuffer dest) + { + ByteBuffer buf = _encoded; + + if(buf == null) + { + buf = encodeAsBuffer(); + _encoded = buf; + } + + buf = buf.duplicate(); + + buf.position(offsetInMetaData); + + if(dest.remaining() < buf.limit()) + { + buf.limit(dest.remaining()); + } + dest.put(buf); + return buf.limit(); + } + + public int getContentSize() + { + return _bodySize; + } + + public boolean isPersistent() + { + return _deliveryProps == null ? false : _deliveryProps.getDeliveryMode() == MessageDeliveryMode.PERSISTENT; + } + + public String getRoutingKey() + { + return _deliveryProps == null ? null : _deliveryProps.getRoutingKey(); + } + + public AMQMessageHeader getMessageHeader() + { + return _messageHeader; + } + + public long getSize() + { + + return _bodySize; + } + + public boolean isImmediate() + { + return _deliveryProps != null && _deliveryProps.getImmediate(); + } + + public long getExpiration() + { + return _deliveryProps == null ? 0L : _deliveryProps.getExpiration(); + } + + public boolean isRedelivered() + { + // The *Message* is never redelivered, only queue entries are... + return false; + } + + public long getArrivalTime() + { + return _arrivalTime; + } + + public Header getHeader() + { + return _header; + } + + public void setConnectionReference(Object connectionReference) + { + _connectionReference = connectionReference; + } + + public Object getConnectionReference() + { + return _connectionReference; + } + + private static class MetaDataFactory implements MessageMetaDataType.Factory<MessageMetaData_0_10> + { + public MessageMetaData_0_10 createMetaData(ByteBuffer buf) + { + BBDecoder decoder = new BBDecoder(); + decoder.init(buf); + + long arrivalTime = decoder.readInt64(); + int bodySize = decoder.readInt32(); + int headerCount = decoder.readInt32(); + + DeliveryProperties deliveryProperties = null; + MessageProperties messageProperties = null; + List<Struct> otherProps = null; + + for(int i = 0 ; i < headerCount; i++) + { + Struct struct = decoder.readStruct32(); + if(struct instanceof DeliveryProperties && deliveryProperties == null) + { + deliveryProperties = (DeliveryProperties) struct; + } + else if(struct instanceof MessageProperties && messageProperties == null) + { + messageProperties = (MessageProperties) struct; + } + else + { + if(otherProps == null) + { + otherProps = new ArrayList<Struct>(); + + } + otherProps.add(struct); + } + } + Header header = new Header(deliveryProperties,messageProperties,otherProps); + + return new MessageMetaData_0_10(header, bodySize, arrivalTime); + + } + } + + +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageTransferHeader.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageTransferHeader.java new file mode 100644 index 0000000000..1b506d9bf8 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageTransferHeader.java @@ -0,0 +1,171 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import java.util.*; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.MessageDeliveryPriority; +import org.apache.qpid.transport.MessageProperties; + +class MessageTransferHeader implements AMQMessageHeader +{ + + + public static final String JMS_TYPE = "x-jms-type"; + + private final DeliveryProperties _deliveryProps; + private final MessageProperties _messageProps; + + public MessageTransferHeader(DeliveryProperties deliveryProps, MessageProperties messageProps) + { + _deliveryProps = deliveryProps; + _messageProps = messageProps; + } + + public String getCorrelationId() + { + if (_messageProps != null && _messageProps.getCorrelationId() != null) + { + return new String(_messageProps.getCorrelationId()); + } + else + { + return null; + } + } + + public long getExpiration() + { + return _deliveryProps == null ? 0L : _deliveryProps.getExpiration(); + } + + public String getUserId() + { + byte[] userIdBytes = _messageProps == null ? null : _messageProps.getUserId(); + return userIdBytes == null ? null : new String(userIdBytes); + } + + public String getAppId() + { + byte[] appIdBytes = _messageProps == null ? null : _messageProps.getAppId(); + return appIdBytes == null ? null : new String(appIdBytes); + } + + public String getMessageId() + { + UUID id = _messageProps == null ? null : _messageProps.getMessageId(); + + return id == null ? null : String.valueOf(id); + } + + public String getMimeType() + { + return _messageProps == null ? null : _messageProps.getContentType(); + } + + public String getEncoding() + { + return _messageProps == null ? null : _messageProps.getContentEncoding(); + } + + public byte getPriority() + { + MessageDeliveryPriority priority = _deliveryProps == null || !_deliveryProps.hasPriority() + ? MessageDeliveryPriority.MEDIUM + : _deliveryProps.getPriority(); + return (byte) priority.getValue(); + } + + public long getTimestamp() + { + return _deliveryProps == null ? 0L : _deliveryProps.getTimestamp(); + } + + public String getType() + { + Object type = getHeader(JMS_TYPE); + return type instanceof String ? (String) type : null; + } + + public String getReplyTo() + { + if (_messageProps != null && _messageProps.getReplyTo() != null) + { + return _messageProps.getReplyTo().toString(); + } + else + { + return null; + } + } + + public String getReplyToExchange() + { + if (_messageProps != null && _messageProps.getReplyTo() != null) + { + return _messageProps.getReplyTo().getExchange(); + } + else + { + return null; + } + } + + public String getReplyToRoutingKey() + { + if (_messageProps != null && _messageProps.getReplyTo() != null) + { + return _messageProps.getReplyTo().getRoutingKey(); + } + else + { + return null; + } + } + + public Object getHeader(String name) + { + Map<String, Object> appHeaders = _messageProps == null ? null : _messageProps.getApplicationHeaders(); + return appHeaders == null ? null : appHeaders.get(name); + } + + public boolean containsHeaders(Set<String> names) + { + Map<String, Object> appHeaders = _messageProps == null ? null : _messageProps.getApplicationHeaders(); + return appHeaders != null && appHeaders.keySet().containsAll(names); + + } + + @Override + public Collection<String> getHeaderNames() + { + Map<String, Object> appHeaders = _messageProps == null ? null : _messageProps.getApplicationHeaders(); + return appHeaders != null ? Collections.unmodifiableCollection(appHeaders.keySet()) : Collections.EMPTY_SET ; + + } + + public boolean containsHeader(String name) + { + Map<String, Object> appHeaders = _messageProps == null ? null : _messageProps.getApplicationHeaders(); + return appHeaders != null && appHeaders.containsKey(name); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageTransferMessage.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageTransferMessage.java new file mode 100644 index 0000000000..e5914d1d4e --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/MessageTransferMessage.java @@ -0,0 +1,131 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.AbstractServerMessageImpl; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.transport.Header; + +import java.nio.ByteBuffer; + + +public class MessageTransferMessage extends AbstractServerMessageImpl<MessageMetaData_0_10> implements InboundMessage +{ + + private Object _connectionRef; + + public MessageTransferMessage(StoredMessage<MessageMetaData_0_10> storeMessage, Object connectionRef) + { + super(storeMessage); + _connectionRef = connectionRef; + } + + private MessageMetaData_0_10 getMetaData() + { + return getStoredMessage().getMetaData(); + } + + public String getRoutingKey() + { + return getMetaData().getRoutingKey(); + } + + public AMQMessageHeader getMessageHeader() + { + return getMetaData().getMessageHeader(); + } + + public boolean isPersistent() + { + return getMetaData().isPersistent(); + } + + + public boolean isRedelivered() + { + // The *Message* is never redelivered, only queue entries are... this is here so that filters + // can run against the message on entry to an exchange + return false; + } + + public long getSize() + { + + return getMetaData().getSize(); + } + + public boolean isImmediate() + { + return getMetaData().isImmediate(); + } + + public long getExpiration() + { + return getMetaData().getExpiration(); + } + + public MessageReference newReference() + { + return new TransferMessageReference(this); + } + + public long getMessageNumber() + { + return getStoredMessage().getMessageNumber(); + } + + public long getArrivalTime() + { + return getMetaData().getArrivalTime(); + } + + public int getContent(ByteBuffer buf, int offset) + { + return getStoredMessage().getContent(offset, buf); + } + + + public ByteBuffer getContent(int offset, int size) + { + return getStoredMessage().getContent(offset,size); + } + + public Header getHeader() + { + return getMetaData().getHeader(); + } + + public ByteBuffer getBody() + { + + return getContent(0, (int)getSize()); + } + + public Object getConnectionReference() + { + return _connectionRef; + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ProtocolEngineCreator_0_10.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ProtocolEngineCreator_0_10.java new file mode 100644 index 0000000000..ab50a33b9b --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ProtocolEngineCreator_0_10.java @@ -0,0 +1,100 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.plugin.ProtocolEngineCreator; +import org.apache.qpid.transport.ConnectionDelegate; +import org.apache.qpid.transport.network.NetworkConnection; + +public class ProtocolEngineCreator_0_10 implements ProtocolEngineCreator +{ + + private static final byte[] AMQP_0_10_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 1, + (byte) 1, + (byte) 0, + (byte) 10 + }; + + + public ProtocolEngineCreator_0_10() + { + } + + public AmqpProtocolVersion getVersion() + { + return AmqpProtocolVersion.v0_10; + } + + + public byte[] getHeaderIdentifier() + { + return AMQP_0_10_HEADER; + } + + public ServerProtocolEngine newProtocolEngine(Broker broker, + NetworkConnection network, + Port port, + Transport transport, + long id) + { + String fqdn = null; + SocketAddress address = network.getLocalAddress(); + if (address instanceof InetSocketAddress) + { + fqdn = ((InetSocketAddress) address).getHostName(); + } + final ConnectionDelegate connDelegate = new ServerConnectionDelegate(broker, + fqdn, broker.getSubjectCreator(address)); + + ServerConnection conn = new ServerConnection(id,broker); + + conn.setConnectionDelegate(connDelegate); + conn.setRemoteAddress(network.getRemoteAddress()); + conn.setLocalAddress(network.getLocalAddress()); + return new ProtocolEngine_0_10( conn, network, port, transport); + } + + + private static ProtocolEngineCreator INSTANCE = new ProtocolEngineCreator_0_10(); + + public static ProtocolEngineCreator getInstance() + { + return INSTANCE; + } + + @Override + public String getType() + { + return getVersion().toString(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ProtocolEngine_0_10.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ProtocolEngine_0_10.java new file mode 100755 index 0000000000..73708d9841 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ProtocolEngine_0_10.java @@ -0,0 +1,200 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.logging.messages.ConnectionMessages; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.v0_10.ServerConnection; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.network.Assembler; +import org.apache.qpid.transport.network.Disassembler; +import org.apache.qpid.transport.network.InputHandler; +import org.apache.qpid.transport.network.NetworkConnection; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; + + +public class ProtocolEngine_0_10 extends InputHandler implements ServerProtocolEngine +{ + public static final int MAX_FRAME_SIZE = 64 * 1024 - 1; + + private NetworkConnection _network; + private long _readBytes; + private long _writtenBytes; + private ServerConnection _connection; + + private long _createTime = System.currentTimeMillis(); + private long _lastReadTime; + private long _lastWriteTime; + + public ProtocolEngine_0_10(ServerConnection conn, + NetworkConnection network, + Port port, + Transport transport) + { + super(new Assembler(conn)); + _connection = conn; + _connection.setPort(port); + _connection.setTransport(transport); + + if(network != null) + { + setNetworkConnection(network); + } + + + } + + public void setNetworkConnection(NetworkConnection network) + { + setNetworkConnection(network, network.getSender()); + } + + public void setNetworkConnection(NetworkConnection network, Sender<ByteBuffer> sender) + { + _network = network; + + _connection.setNetworkConnection(network); + _connection.setSender(new Disassembler(wrapSender(sender), MAX_FRAME_SIZE)); + _connection.setPeerPrincipal(_network.getPeerPrincipal()); + // FIXME Two log messages to maintain compatibility with earlier protocol versions + _connection.getLogActor().message(ConnectionMessages.OPEN(null, null, null, false, false, false)); + _connection.getLogActor().message(ConnectionMessages.OPEN(null, "0-10", null, false, true, false)); + } + + private Sender<ByteBuffer> wrapSender(final Sender<ByteBuffer> sender) + { + return new Sender<ByteBuffer>() + { + @Override + public void setIdleTimeout(int i) + { + sender.setIdleTimeout(i); + + } + + @Override + public void send(ByteBuffer msg) + { + _lastWriteTime = System.currentTimeMillis(); + sender.send(msg); + + } + + @Override + public void flush() + { + sender.flush(); + + } + + @Override + public void close() + { + sender.close(); + + } + }; + } + + @Override + public long getLastReadTime() + { + return _lastReadTime; + } + + @Override + public long getLastWriteTime() + { + return _lastWriteTime; + } + + public SocketAddress getRemoteAddress() + { + return _network.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _network.getLocalAddress(); + } + + public void received(final ByteBuffer buf) + { + _lastReadTime = System.currentTimeMillis(); + super.received(buf); + _connection.receivedComplete(); + } + + public long getReadBytes() + { + return _readBytes; + } + + public long getWrittenBytes() + { + return _writtenBytes; + } + + public void writerIdle() + { + _connection.doHeartbeat(); + } + + public void readerIdle() + { + //Todo + } + + public String getAddress() + { + return getRemoteAddress().toString(); + } + + public String getAuthId() + { + return _connection.getAuthorizedPrincipal() == null ? null : _connection.getAuthorizedPrincipal().getName(); + } + + public boolean isDurable() + { + return false; + } + + @Override + public void closed() + { + super.closed(); + } + + public long getCreateTime() + { + return _createTime; + } + + public long getConnectionId() + { + return _connection.getConnectionId(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerConnection.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerConnection.java new file mode 100644 index 0000000000..0015988ab7 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerConnection.java @@ -0,0 +1,560 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import java.net.SocketAddress; +import java.security.Principal; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import javax.security.auth.Subject; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.AMQPConnectionActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.messages.ConnectionMessages; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.security.AuthorizationHolder; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionCloseCode; +import org.apache.qpid.transport.ExecutionErrorCode; +import org.apache.qpid.transport.ExecutionException; +import org.apache.qpid.transport.Method; +import org.apache.qpid.transport.ProtocolEvent; +import org.apache.qpid.transport.Session; +import org.apache.qpid.transport.network.NetworkConnection; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.CONNECTION_FORMAT; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.SOCKET_FORMAT; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.USER_FORMAT; + +public class ServerConnection extends Connection implements AMQConnectionModel, LogSubject, AuthorizationHolder +{ + private Runnable _onOpenTask; + private AtomicBoolean _logClosed = new AtomicBoolean(false); + private LogActor _actor; + + private Subject _authorizedSubject = null; + private Principal _authorizedPrincipal = null; + private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + private final long _connectionId; + private final Object _reference = new Object(); + private VirtualHost _virtualHost; + private Port _port; + private AtomicLong _lastIoTime = new AtomicLong(); + private boolean _blocking; + private Principal _peerPrincipal; + private NetworkConnection _networkConnection; + private Transport _transport; + private volatile boolean _stopped; + + public ServerConnection(final long connectionId, Broker broker) + { + _connectionId = connectionId; + _actor = new AMQPConnectionActor(this, broker.getRootMessageLogger()); + } + + public Object getReference() + { + return _reference; + } + + @Override + protected void invoke(Method method) + { + super.invoke(method); + } + + @Override + protected void setState(State state) + { + super.setState(state); + + if (state == State.OPEN) + { + if (_onOpenTask != null) + { + _onOpenTask.run(); + } + _actor.message(ConnectionMessages.OPEN(getClientId(), "0-10", getClientVersion(), true, true, true)); + + getVirtualHost().getConnectionRegistry().registerConnection(this); + } + + if (state == State.CLOSE_RCVD || state == State.CLOSED || state == State.CLOSING) + { + if(_virtualHost != null) + { + _virtualHost.getConnectionRegistry().deregisterConnection(this); + } + } + + if (state == State.CLOSED) + { + logClosed(); + } + } + + protected void logClosed() + { + if(_logClosed.compareAndSet(false, true)) + { + CurrentActor.get().message(this, ConnectionMessages.CLOSE()); + } + } + + @Override + public ServerConnectionDelegate getConnectionDelegate() + { + return (ServerConnectionDelegate) super.getConnectionDelegate(); + } + + public void setConnectionDelegate(ServerConnectionDelegate delegate) + { + super.setConnectionDelegate(delegate); + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void setVirtualHost(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + + initialiseStatistics(); + } + + @Override + public String getVirtualHostName() + { + return _virtualHost == null ? null : _virtualHost.getName(); + } + + @Override + public Port getPort() + { + return _port; + } + + public void setPort(Port port) + { + _port = port; + } + + @Override + public Transport getTransport() + { + return _transport; + } + + @Override + public void stop() + { + _stopped = true; + } + + @Override + public boolean isStopped() + { + return _stopped; + } + + public void setTransport(Transport transport) + { + _transport = transport; + } + + public void onOpen(final Runnable task) + { + _onOpenTask = task; + } + + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException + { + ExecutionException ex = new ExecutionException(); + ExecutionErrorCode code = ExecutionErrorCode.INTERNAL_ERROR; + try + { + code = ExecutionErrorCode.get(cause.getCode()); + } + catch (IllegalArgumentException iae) + { + // Ignore, already set to INTERNAL_ERROR + } + ex.setErrorCode(code); + ex.setDescription(message); + ((ServerSession)session).invoke(ex); + + ((ServerSession)session).close(cause, message); + } + + public LogSubject getLogSubject() + { + return (LogSubject) this; + } + + @Override + public void received(ProtocolEvent event) + { + _lastIoTime.set(System.currentTimeMillis()); + if (event.isConnectionControl()) + { + CurrentActor.set(_actor); + } + else + { + ServerSession channel = (ServerSession) getSession(event.getChannel()); + LogActor channelActor = null; + + if (channel != null) + { + channelActor = channel.getLogActor(); + } + + CurrentActor.set(channelActor == null ? _actor : channelActor); + } + + try + { + super.received(event); + } + finally + { + CurrentActor.remove(); + } + } + + public String toLogString() + { + boolean hasVirtualHost = (null != this.getVirtualHost()); + boolean hasClientId = (null != getClientId()); + + if (hasClientId && hasVirtualHost) + { + return "[" + + MessageFormat.format(CONNECTION_FORMAT, + getConnectionId(), + getClientId(), + getRemoteAddressString(), + getVirtualHost().getName()) + + "] "; + } + else if (hasClientId) + { + return "[" + + MessageFormat.format(USER_FORMAT, + getConnectionId(), + getClientId(), + getRemoteAddressString()) + + "] "; + + } + else + { + return "[" + + MessageFormat.format(SOCKET_FORMAT, + getConnectionId(), + getRemoteAddressString()) + + "] "; + } + } + + public LogActor getLogActor() + { + return _actor; + } + + public void close(AMQConstant cause, String message) throws AMQException + { + closeSubscriptions(); + ConnectionCloseCode replyCode = ConnectionCloseCode.NORMAL; + try + { + replyCode = ConnectionCloseCode.get(cause.getCode()); + } + catch (IllegalArgumentException iae) + { + // Ignore + } + close(replyCode, message); + } + + public synchronized void block() + { + if(!_blocking) + { + _blocking = true; + for(AMQSessionModel ssn : getSessionModels()) + { + ssn.block(); + } + } + } + + public synchronized void unblock() + { + if(_blocking) + { + _blocking = false; + for(AMQSessionModel ssn : getSessionModels()) + { + ssn.unblock(); + } + } + } + + @Override + public synchronized void registerSession(final Session ssn) + { + super.registerSession(ssn); + if(_blocking) + { + ((ServerSession)ssn).block(); + } + } + + @Override + public synchronized void removeSession(final Session ssn) + { + super.removeSession(ssn); + } + + public List<AMQSessionModel> getSessionModels() + { + List<AMQSessionModel> sessions = new ArrayList<AMQSessionModel>(); + for (Session ssn : getChannels()) + { + sessions.add((AMQSessionModel) ssn); + } + return sessions; + } + + public void registerMessageDelivered(long messageSize) + { + _messagesDelivered.registerEvent(1L); + _dataDelivered.registerEvent(messageSize); + _virtualHost.registerMessageDelivered(messageSize); + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + _messagesReceived.registerEvent(1L, timestamp); + _dataReceived.registerEvent(messageSize, timestamp); + _virtualHost.registerMessageReceived(messageSize, timestamp); + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return _messagesReceived; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceived; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messagesDelivered; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDelivered; + } + + public void resetStatistics() + { + _messagesDelivered.reset(); + _dataDelivered.reset(); + _messagesReceived.reset(); + _dataReceived.reset(); + } + + public void initialiseStatistics() + { + _messagesDelivered = new StatisticsCounter("messages-delivered-" + getConnectionId()); + _dataDelivered = new StatisticsCounter("data-delivered-" + getConnectionId()); + _messagesReceived = new StatisticsCounter("messages-received-" + getConnectionId()); + _dataReceived = new StatisticsCounter("data-received-" + getConnectionId()); + } + + /** + * @return authorizedSubject + */ + public Subject getAuthorizedSubject() + { + return _authorizedSubject; + } + + /** + * Sets the authorized subject. It also extracts the UsernamePrincipal from the subject + * and caches it for optimisation purposes. + * + * @param authorizedSubject + */ + public void setAuthorizedSubject(final Subject authorizedSubject) + { + if (authorizedSubject == null) + { + _authorizedSubject = null; + _authorizedPrincipal = null; + } + else + { + _authorizedSubject = authorizedSubject; + _authorizedPrincipal = AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(authorizedSubject); + } + } + + public Principal getAuthorizedPrincipal() + { + return _authorizedPrincipal; + } + + public long getConnectionId() + { + return _connectionId; + } + + public boolean isSessionNameUnique(byte[] name) + { + return !super.hasSessionWithName(name); + } + + public String getRemoteAddressString() + { + return String.valueOf(getRemoteAddress()); + } + + public String getUserName() + { + return _authorizedPrincipal.getName(); + } + + @Override + public void closed() + { + closeSubscriptions(); + super.closed(); + } + + private void closeSubscriptions() + { + for (Session ssn : getChannels()) + { + ((ServerSession)ssn).unregisterSubscriptions(); + } + } + + public void receivedComplete() + { + for (Session ssn : getChannels()) + { + ((ServerSession)ssn).receivedComplete(); + } + } + + @Override + public void send(ProtocolEvent event) + { + _lastIoTime.set(System.currentTimeMillis()); + super.send(event); + } + + public long getLastIoTime() + { + return _lastIoTime.longValue(); + } + + + public String getClientId() + { + return getConnectionDelegate().getClientId(); + } + + public String getClientVersion() + { + return getConnectionDelegate().getClientVersion(); + } + + public String getPrincipalAsString() + { + return getAuthorizedPrincipal() == null ? null : getAuthorizedPrincipal().getName(); + } + + public long getSessionCountLimit() + { + return getChannelMax(); + } + + public Principal getPeerPrincipal() + { + return _peerPrincipal; + } + + public void setPeerPrincipal(Principal peerPrincipal) + { + _peerPrincipal = peerPrincipal; + } + + @Override + public void setRemoteAddress(SocketAddress remoteAddress) + { + super.setRemoteAddress(remoteAddress); + } + + @Override + public void setLocalAddress(SocketAddress localAddress) + { + super.setLocalAddress(localAddress); + } + + public void setNetworkConnection(NetworkConnection network) + { + _networkConnection = network; + } + + public NetworkConnection getNetworkConnection() + { + return _networkConnection; + } + + public void doHeartbeat() + { + super.doHeartBeat(); + + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerConnectionDelegate.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerConnectionDelegate.java new file mode 100644 index 0000000000..6634627805 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerConnectionDelegate.java @@ -0,0 +1,350 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.common.ServerPropertyNames; +import org.apache.qpid.properties.ConnectionStartProperties; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.virtualhost.State; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.*; +import org.apache.qpid.transport.network.NetworkConnection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.qpid.transport.Connection.State.CLOSE_RCVD; + +public class ServerConnectionDelegate extends ServerDelegate +{ + private static final Logger LOGGER = LoggerFactory.getLogger(ServerConnectionDelegate.class); + + private final Broker _broker; + private final String _localFQDN; + private int _maxNoOfChannels; + private Map<String,Object> _clientProperties; + private final SubjectCreator _subjectCreator; + + public ServerConnectionDelegate(Broker broker, String localFQDN, SubjectCreator subjectCreator) + { + this(createConnectionProperties(broker), Collections.singletonList((Object)"en_US"), broker, localFQDN, subjectCreator); + } + + private ServerConnectionDelegate(Map<String, Object> properties, + List<Object> locales, + Broker broker, + String localFQDN, + SubjectCreator subjectCreator) + { + super(properties, parseToList(subjectCreator.getMechanisms()), locales); + + _broker = broker; + _localFQDN = localFQDN; + _maxNoOfChannels = (Integer)broker.getAttribute(Broker.CONNECTION_SESSION_COUNT_LIMIT); + _subjectCreator = subjectCreator; + } + + private static List<String> getFeatures(Broker broker) + { + String brokerDisabledFeatures = System.getProperty(BrokerProperties.PROPERTY_DISABLED_FEATURES); + final List<String> features = new ArrayList<String>(); + if (brokerDisabledFeatures == null || !brokerDisabledFeatures.contains(ServerPropertyNames.FEATURE_QPID_JMS_SELECTOR)) + { + features.add(ServerPropertyNames.FEATURE_QPID_JMS_SELECTOR); + } + + return Collections.unmodifiableList(features); + } + + private static Map<String, Object> createConnectionProperties(final Broker broker) + { + final Map<String,Object> map = new HashMap<String,Object>(); + // Federation tag is used by the client to identify the broker instance + map.put(ServerPropertyNames.FEDERATION_TAG, broker.getId().toString()); + final List<String> features = getFeatures(broker); + if (features != null && features.size() > 0) + { + map.put(ServerPropertyNames.QPID_FEATURES, features); + } + + map.put(ServerPropertyNames.PRODUCT, QpidProperties.getProductName()); + map.put(ServerPropertyNames.VERSION, QpidProperties.getReleaseVersion()); + map.put(ServerPropertyNames.QPID_BUILD, QpidProperties.getBuildVersion()); + map.put(ServerPropertyNames.QPID_INSTANCE_NAME, broker.getName()); + + return map; + } + + private static List<Object> parseToList(String mechanisms) + { + List<Object> list = new ArrayList<Object>(); + StringTokenizer tokenizer = new StringTokenizer(mechanisms, " "); + while(tokenizer.hasMoreTokens()) + { + list.add(tokenizer.nextToken()); + } + return list; + } + + public ServerSession getSession(Connection conn, SessionAttach atc) + { + SessionDelegate serverSessionDelegate = new ServerSessionDelegate(); + + ServerSession ssn = new ServerSession(conn, serverSessionDelegate, new Binary(atc.getName()), 0); + + return ssn; + } + + protected SaslServer createSaslServer(Connection conn, String mechanism) throws SaslException + { + return _subjectCreator.createSaslServer(mechanism, _localFQDN, ((ServerConnection) conn).getPeerPrincipal()); + + } + + protected void secure(final SaslServer ss, final Connection conn, final byte[] response) + { + final ServerConnection sconn = (ServerConnection) conn; + final SubjectAuthenticationResult authResult = _subjectCreator.authenticate(ss, response); + + if (AuthenticationStatus.SUCCESS.equals(authResult.getStatus())) + { + tuneAuthorizedConnection(sconn); + sconn.setAuthorizedSubject(authResult.getSubject()); + } + else if (AuthenticationStatus.CONTINUE.equals(authResult.getStatus())) + { + connectionAuthContinue(sconn, authResult.getChallenge()); + } + else + { + connectionAuthFailed(sconn, authResult.getCause()); + } + } + + @Override + public void connectionClose(Connection conn, ConnectionClose close) + { + final ServerConnection sconn = (ServerConnection) conn; + try + { + sconn.logClosed(); + } + finally + { + sconn.closeCode(close); + sconn.setState(CLOSE_RCVD); + sendConnectionCloseOkAndCloseSender(conn); + } + } + + public void connectionOpen(Connection conn, ConnectionOpen open) + { + final ServerConnection sconn = (ServerConnection) conn; + + VirtualHost vhost; + String vhostName; + if(open.hasVirtualHost()) + { + vhostName = open.getVirtualHost(); + } + else + { + vhostName = ""; + } + vhost = _broker.getVirtualHostRegistry().getVirtualHost(vhostName); + + SecurityManager.setThreadSubject(sconn.getAuthorizedSubject()); + + if(vhost != null) + { + sconn.setVirtualHost(vhost); + + if (!vhost.getSecurityManager().accessVirtualhost(vhostName, sconn.getRemoteAddress())) + { + sconn.setState(Connection.State.CLOSING); + sconn.invoke(new ConnectionClose(ConnectionCloseCode.CONNECTION_FORCED, "Permission denied '"+vhostName+"'")); + } + else if (vhost.getState() != State.ACTIVE) + { + sconn.setState(Connection.State.CLOSING); + sconn.invoke(new ConnectionClose(ConnectionCloseCode.CONNECTION_FORCED, "Virtual host '"+vhostName+"' is not active")); + } + else + { + sconn.setState(Connection.State.OPEN); + sconn.invoke(new ConnectionOpenOk(Collections.emptyList())); + } + } + else + { + sconn.setState(Connection.State.CLOSING); + sconn.invoke(new ConnectionClose(ConnectionCloseCode.INVALID_PATH, "Unknown virtualhost '"+vhostName+"'")); + } + + } + + @Override + public void connectionTuneOk(final Connection conn, final ConnectionTuneOk ok) + { + ServerConnection sconn = (ServerConnection) conn; + int okChannelMax = ok.getChannelMax(); + + if (okChannelMax > getChannelMax()) + { + LOGGER.error("Connection '" + sconn.getConnectionId() + "' being severed, " + + "client connectionTuneOk returned a channelMax (" + okChannelMax + + ") above the server's offered limit (" + getChannelMax() +")"); + + //Due to the error we must forcefully close the connection without negotiation + sconn.getSender().close(); + return; + } + + final NetworkConnection networkConnection = sconn.getNetworkConnection(); + + if(ok.hasHeartbeat()) + { + int heartbeat = ok.getHeartbeat(); + if(heartbeat < 0) + { + heartbeat = 0; + } + + networkConnection.setMaxReadIdle(2 * heartbeat); + networkConnection.setMaxWriteIdle(heartbeat); + + } + else + { + networkConnection.setMaxReadIdle(0); + networkConnection.setMaxWriteIdle(0); + } + + setConnectionTuneOkChannelMax(sconn, okChannelMax); + } + + @Override + public int getChannelMax() + { + return _maxNoOfChannels; + } + + protected void setChannelMax(int channelMax) + { + _maxNoOfChannels = channelMax; + } + + @Override public void sessionDetach(Connection conn, SessionDetach dtc) + { + // To ensure a clean detach, we stop any remaining subscriptions. Stop ensures + // that any in-progress delivery (SubFlushRunner/QueueRunner) is completed before the stop + // completes. + stopAllSubscriptions(conn, dtc); + Session ssn = conn.getSession(dtc.getChannel()); + ((ServerSession)ssn).setClose(true); + super.sessionDetach(conn, dtc); + } + + private void stopAllSubscriptions(Connection conn, SessionDetach dtc) + { + final ServerSession ssn = (ServerSession) conn.getSession(dtc.getChannel()); + final Collection<Subscription_0_10> subs = ssn.getSubscriptions(); + for (Subscription_0_10 subscription_0_10 : subs) + { + subscription_0_10.stop(); + } + } + + + @Override + public void sessionAttach(final Connection conn, final SessionAttach atc) + { + final Session ssn; + + if(isSessionNameUnique(atc.getName(), conn)) + { + super.sessionAttach(conn, atc); + } + else + { + ssn = getSession(conn, atc); + ssn.invoke(new SessionDetached(atc.getName(), SessionDetachCode.SESSION_BUSY)); + ssn.closed(); + } + } + + private boolean isSessionNameUnique(final byte[] name, final Connection conn) + { + final ServerConnection sconn = (ServerConnection) conn; + final String userId = sconn.getUserName(); + + final Iterator<AMQConnectionModel> connections = + ((ServerConnection)conn).getVirtualHost().getConnectionRegistry().getConnections().iterator(); + while(connections.hasNext()) + { + final AMQConnectionModel amqConnectionModel = (AMQConnectionModel) connections.next(); + if (userId.equals(amqConnectionModel.getUserName()) && !amqConnectionModel.isSessionNameUnique(name)) + { + return false; + } + } + return true; + } + + @Override + public void connectionStartOk(Connection conn, ConnectionStartOk ok) + { + _clientProperties = ok.getClientProperties(); + super.connectionStartOk(conn, ok); + } + + public Map<String,Object> getClientProperties() + { + return _clientProperties; + } + + public String getClientId() + { + return _clientProperties == null ? null : (String) _clientProperties.get(ConnectionStartProperties.CLIENT_ID_0_10); + } + + public String getClientVersion() + { + return _clientProperties == null ? null : (String) _clientProperties.get(ConnectionStartProperties.VERSION_0_10); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSession.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSession.java new file mode 100644 index 0000000000..abe784cefa --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSession.java @@ -0,0 +1,1000 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import java.security.Principal; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import javax.security.auth.Subject; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.TransactionTimeoutHelper; +import org.apache.qpid.server.TransactionTimeoutHelper.CloseAction; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.messages.ChannelMessages; +import org.apache.qpid.server.logging.subjects.ChannelLogSubject; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.security.AuthorizationHolder; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.txn.AlreadyKnownDtxException; +import org.apache.qpid.server.txn.AsyncAutoCommitTransaction; +import org.apache.qpid.server.txn.DistributedTransaction; +import org.apache.qpid.server.txn.DtxNotSelectedException; +import org.apache.qpid.server.txn.IncorrectDtxStateException; +import org.apache.qpid.server.txn.JoinAndResumeDtxException; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.NotAssociatedDtxException; +import org.apache.qpid.server.txn.RollbackOnlyDtxException; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.txn.SuspendAndFailDtxException; +import org.apache.qpid.server.txn.TimeoutDtxException; +import org.apache.qpid.server.txn.UnknownDtxBranchException; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.CHANNEL_FORMAT; +import static org.apache.qpid.util.Serial.gt; + +public class ServerSession extends Session + implements AuthorizationHolder, + AMQSessionModel, LogSubject, AsyncAutoCommitTransaction.FutureRecorder +{ + private static final Logger _logger = LoggerFactory.getLogger(ServerSession.class); + + private static final String NULL_DESTINTATION = UUID.randomUUID().toString(); + private static final int PRODUCER_CREDIT_TOPUP_THRESHOLD = 1 << 30; + private static final int UNFINISHED_COMMAND_QUEUE_THRESHOLD = 500; + + private final UUID _id = UUID.randomUUID(); + private long _createTime = System.currentTimeMillis(); + private LogActor _actor = GenericActor.getInstance(this); + + private final Set<Object> _blockingEntities = Collections.synchronizedSet(new HashSet<Object>()); + + private final AtomicBoolean _blocking = new AtomicBoolean(false); + private ChannelLogSubject _logSubject; + private final AtomicInteger _outstandingCredit = new AtomicInteger(UNLIMITED_CREDIT); + + public static interface MessageDispositionChangeListener + { + public void onAccept(); + + public void onRelease(boolean setRedelivered); + + public void onReject(); + + public boolean acquire(); + + + } + + public static interface Task + { + public void doTask(ServerSession session); + } + + + private final SortedMap<Integer, MessageDispositionChangeListener> _messageDispositionListenerMap = + new ConcurrentSkipListMap<Integer, MessageDispositionChangeListener>(); + + private ServerTransaction _transaction; + + private final AtomicLong _txnStarts = new AtomicLong(0); + private final AtomicLong _txnCommits = new AtomicLong(0); + private final AtomicLong _txnRejects = new AtomicLong(0); + private final AtomicLong _txnCount = new AtomicLong(0); + + private Map<String, Subscription_0_10> _subscriptions = new ConcurrentHashMap<String, Subscription_0_10>(); + + private final List<Task> _taskList = new CopyOnWriteArrayList<Task>(); + + private final TransactionTimeoutHelper _transactionTimeoutHelper; + + private AtomicReference<LogMessage> _forcedCloseLogMessage = new AtomicReference<LogMessage>(); + + public ServerSession(Connection connection, SessionDelegate delegate, Binary name, long expiry) + { + super(connection, delegate, name, expiry); + _transaction = new AsyncAutoCommitTransaction(this.getMessageStore(),this); + _logSubject = new ChannelLogSubject(this); + + _transactionTimeoutHelper = new TransactionTimeoutHelper(_logSubject, new CloseAction() + { + @Override + public void doTimeoutAction(String reason) throws AMQException + { + getConnectionModel().closeSession(ServerSession.this, AMQConstant.RESOURCE_ERROR, reason); + } + }); + } + + protected void setState(State state) + { + super.setState(state); + + if (state == State.OPEN) + { + _actor.message(ChannelMessages.CREATE()); + if(_blocking.get()) + { + invokeBlock(); + } + } + } + + private void invokeBlock() + { + invoke(new MessageSetFlowMode("", MessageFlowMode.CREDIT)); + invoke(new MessageStop("")); + } + + @Override + protected boolean isFull(int id) + { + return isCommandsFull(id); + } + + public void enqueue(final ServerMessage message, final List<? extends BaseQueue> queues) + { + if(_outstandingCredit.get() != UNLIMITED_CREDIT + && _outstandingCredit.decrementAndGet() == (Integer.MAX_VALUE - PRODUCER_CREDIT_TOPUP_THRESHOLD)) + { + _outstandingCredit.addAndGet(PRODUCER_CREDIT_TOPUP_THRESHOLD); + invoke(new MessageFlow("",MessageCreditUnit.MESSAGE, PRODUCER_CREDIT_TOPUP_THRESHOLD)); + } + getConnectionModel().registerMessageReceived(message.getSize(), message.getArrivalTime()); + PostEnqueueAction postTransactionAction = new PostEnqueueAction(queues, message, isTransactional()) ; + _transaction.enqueue(queues,message, postTransactionAction); + incrementOutstandingTxnsIfNecessary(); + } + + + public void sendMessage(MessageTransfer xfr, + Runnable postIdSettingAction) + { + getConnectionModel().registerMessageDelivered(xfr.getBodySize()); + invoke(xfr, postIdSettingAction); + } + + public void onMessageDispositionChange(MessageTransfer xfr, MessageDispositionChangeListener acceptListener) + { + _messageDispositionListenerMap.put(xfr.getId(), acceptListener); + } + + + private static interface MessageDispositionAction + { + void performAction(MessageDispositionChangeListener listener); + } + + public void accept(RangeSet ranges) + { + dispositionChange(ranges, new MessageDispositionAction() + { + public void performAction(MessageDispositionChangeListener listener) + { + listener.onAccept(); + } + }); + } + + + public void release(RangeSet ranges, final boolean setRedelivered) + { + dispositionChange(ranges, new MessageDispositionAction() + { + public void performAction(MessageDispositionChangeListener listener) + { + listener.onRelease(setRedelivered); + } + }); + } + + public void reject(RangeSet ranges) + { + dispositionChange(ranges, new MessageDispositionAction() + { + public void performAction(MessageDispositionChangeListener listener) + { + listener.onReject(); + } + }); + } + + public RangeSet acquire(RangeSet transfers) + { + RangeSet acquired = RangeSetFactory.createRangeSet(); + + if(!_messageDispositionListenerMap.isEmpty()) + { + Iterator<Integer> unacceptedMessages = _messageDispositionListenerMap.keySet().iterator(); + Iterator<Range> rangeIter = transfers.iterator(); + + if(rangeIter.hasNext()) + { + Range range = rangeIter.next(); + + while(range != null && unacceptedMessages.hasNext()) + { + int next = unacceptedMessages.next(); + while(gt(next, range.getUpper())) + { + if(rangeIter.hasNext()) + { + range = rangeIter.next(); + } + else + { + range = null; + break; + } + } + if(range != null && range.includes(next)) + { + MessageDispositionChangeListener changeListener = _messageDispositionListenerMap.get(next); + if(changeListener != null && changeListener.acquire()) + { + acquired.add(next); + } + } + + + } + + } + + + } + + return acquired; + } + + public void dispositionChange(RangeSet ranges, MessageDispositionAction action) + { + if(ranges != null) + { + + if(ranges.size() == 1) + { + Range r = ranges.getFirst(); + for(int i = r.getLower(); i <= r.getUpper(); i++) + { + MessageDispositionChangeListener changeListener = _messageDispositionListenerMap.remove(i); + if(changeListener != null) + { + action.performAction(changeListener); + } + } + } + else if(!_messageDispositionListenerMap.isEmpty()) + { + Iterator<Integer> unacceptedMessages = _messageDispositionListenerMap.keySet().iterator(); + Iterator<Range> rangeIter = ranges.iterator(); + + if(rangeIter.hasNext()) + { + Range range = rangeIter.next(); + + while(range != null && unacceptedMessages.hasNext()) + { + int next = unacceptedMessages.next(); + while(gt(next, range.getUpper())) + { + if(rangeIter.hasNext()) + { + range = rangeIter.next(); + } + else + { + range = null; + break; + } + } + if(range != null && range.includes(next)) + { + MessageDispositionChangeListener changeListener = _messageDispositionListenerMap.remove(next); + action.performAction(changeListener); + } + + + } + + } + } + } + } + + public void removeDispositionListener(Method method) + { + _messageDispositionListenerMap.remove(method.getId()); + } + + public void onClose() + { + if(_transaction instanceof LocalTransaction) + { + _transaction.rollback(); + } + else if(_transaction instanceof DistributedTransaction) + { + getVirtualHost().getDtxRegistry().endAssociations(this); + } + + for(MessageDispositionChangeListener listener : _messageDispositionListenerMap.values()) + { + listener.onRelease(true); + } + _messageDispositionListenerMap.clear(); + + for (Task task : _taskList) + { + task.doTask(this); + } + + LogMessage operationalLoggingMessage = _forcedCloseLogMessage.get(); + if (operationalLoggingMessage == null) + { + operationalLoggingMessage = ChannelMessages.CLOSE(); + } + CurrentActor.get().message(getLogSubject(), operationalLoggingMessage); + } + + @Override + protected void awaitClose() + { + // Broker shouldn't block awaiting close - thus do override this method to do nothing + } + + public void acknowledge(final Subscription_0_10 sub, final QueueEntry entry) + { + _transaction.dequeue(entry.getQueue(), entry.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + sub.acknowledge(entry); + } + + public void onRollback() + { + // The client has acknowledge the message and therefore have seen it. + // In the event of rollback, the message must be marked as redelivered. + entry.setRedelivered(); + entry.release(); + } + }); + } + + public Collection<Subscription_0_10> getSubscriptions() + { + return _subscriptions.values(); + } + + public void register(String destination, Subscription_0_10 sub) + { + _subscriptions.put(destination == null ? NULL_DESTINTATION : destination, sub); + } + + public Subscription_0_10 getSubscription(String destination) + { + return _subscriptions.get(destination == null ? NULL_DESTINTATION : destination); + } + + public void unregister(Subscription_0_10 sub) + { + _subscriptions.remove(sub.getName()); + try + { + sub.getSendLock(); + AMQQueue queue = sub.getQueue(); + if(queue != null) + { + queue.unregisterSubscription(sub); + } + } + catch (AMQException e) + { + // TODO + _logger.error("Failed to unregister subscription :" + e.getMessage(), e); + } + finally + { + sub.releaseSendLock(); + } + } + + public boolean isTransactional() + { + return _transaction.isTransactional(); + } + + public void selectTx() + { + _transaction = new LocalTransaction(this.getMessageStore()); + _txnStarts.incrementAndGet(); + } + + public void selectDtx() + { + _transaction = new DistributedTransaction(this, getMessageStore(), getVirtualHost()); + + } + + + public void startDtx(Xid xid, boolean join, boolean resume) + throws JoinAndResumeDtxException, + UnknownDtxBranchException, + AlreadyKnownDtxException, + DtxNotSelectedException + { + DistributedTransaction distributedTransaction = assertDtxTransaction(); + distributedTransaction.start(xid, join, resume); + } + + + public void endDtx(Xid xid, boolean fail, boolean suspend) + throws NotAssociatedDtxException, + UnknownDtxBranchException, + DtxNotSelectedException, + SuspendAndFailDtxException, TimeoutDtxException + { + DistributedTransaction distributedTransaction = assertDtxTransaction(); + distributedTransaction.end(xid, fail, suspend); + } + + + public long getTimeoutDtx(Xid xid) + throws UnknownDtxBranchException + { + return getVirtualHost().getDtxRegistry().getTimeout(xid); + } + + + public void setTimeoutDtx(Xid xid, long timeout) + throws UnknownDtxBranchException + { + getVirtualHost().getDtxRegistry().setTimeout(xid, timeout); + } + + + public void prepareDtx(Xid xid) + throws UnknownDtxBranchException, + IncorrectDtxStateException, AMQStoreException, RollbackOnlyDtxException, TimeoutDtxException + { + getVirtualHost().getDtxRegistry().prepare(xid); + } + + public void commitDtx(Xid xid, boolean onePhase) + throws UnknownDtxBranchException, + IncorrectDtxStateException, AMQStoreException, RollbackOnlyDtxException, TimeoutDtxException + { + getVirtualHost().getDtxRegistry().commit(xid, onePhase); + } + + + public void rollbackDtx(Xid xid) + throws UnknownDtxBranchException, + IncorrectDtxStateException, AMQStoreException, TimeoutDtxException + { + getVirtualHost().getDtxRegistry().rollback(xid); + } + + + public void forgetDtx(Xid xid) throws UnknownDtxBranchException, IncorrectDtxStateException + { + getVirtualHost().getDtxRegistry().forget(xid); + } + + public List<Xid> recoverDtx() + { + return getVirtualHost().getDtxRegistry().recover(); + } + + private DistributedTransaction assertDtxTransaction() throws DtxNotSelectedException + { + if(_transaction instanceof DistributedTransaction) + { + return (DistributedTransaction) _transaction; + } + else + { + throw new DtxNotSelectedException(); + } + } + + + public void commit() + { + _transaction.commit(); + + _txnCommits.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + + public void rollback() + { + _transaction.rollback(); + + _txnRejects.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + + + private void incrementOutstandingTxnsIfNecessary() + { + if(isTransactional()) + { + //There can currently only be at most one outstanding transaction + //due to only having LocalTransaction support. Set value to 1 if 0. + _txnCount.compareAndSet(0,1); + } + } + + private void decrementOutstandingTxnsIfNecessary() + { + if(isTransactional()) + { + //There can currently only be at most one outstanding transaction + //due to only having LocalTransaction support. Set value to 0 if 1. + _txnCount.compareAndSet(1,0); + } + } + + public Long getTxnCommits() + { + return _txnCommits.get(); + } + + public Long getTxnRejects() + { + return _txnRejects.get(); + } + + public int getChannelId() + { + return getChannel(); + } + + public Long getTxnCount() + { + return _txnCount.get(); + } + + public Long getTxnStart() + { + return _txnStarts.get(); + } + + public Principal getAuthorizedPrincipal() + { + return getConnection().getAuthorizedPrincipal(); + } + + public Subject getAuthorizedSubject() + { + return getConnection().getAuthorizedSubject(); + } + + public void addSessionCloseTask(Task task) + { + _taskList.add(task); + } + + public void removeSessionCloseTask(Task task) + { + _taskList.remove(task); + } + + public Object getReference() + { + return getConnection().getReference(); + } + + public MessageStore getMessageStore() + { + return getVirtualHost().getMessageStore(); + } + + public VirtualHost getVirtualHost() + { + return getConnection().getVirtualHost(); + } + + public boolean isDurable() + { + return false; + } + + + public long getCreateTime() + { + return _createTime; + } + + @Override + public UUID getId() + { + return _id; + } + + public AMQConnectionModel getConnectionModel() + { + return getConnection(); + } + + public String getClientID() + { + return getConnection().getClientId(); + } + + @Override + public ServerConnection getConnection() + { + return (ServerConnection) super.getConnection(); + } + + public LogActor getLogActor() + { + return _actor; + } + + public LogSubject getLogSubject() + { + return (LogSubject) this; + } + + public void checkTransactionStatus(long openWarn, long openClose, long idleWarn, long idleClose) throws AMQException + { + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, openWarn, openClose, idleWarn, idleClose); + } + + public void block(AMQQueue queue) + { + block(queue, queue.getName()); + } + + public void block() + { + block(this, "** All Queues **"); + } + + + private void block(Object queue, String name) + { + synchronized (_blockingEntities) + { + if(_blockingEntities.add(queue)) + { + + if(_blocking.compareAndSet(false,true)) + { + if(getState() == State.OPEN) + { + invokeBlock(); + } + _actor.message(_logSubject, ChannelMessages.FLOW_ENFORCED(name)); + } + + + } + } + } + + public void unblock(AMQQueue queue) + { + unblock((Object)queue); + } + + public void unblock() + { + unblock(this); + } + + private void unblock(Object queue) + { + synchronized(_blockingEntities) + { + if(_blockingEntities.remove(queue) && _blockingEntities.isEmpty()) + { + if(_blocking.compareAndSet(true,false) && !isClosing()) + { + + _actor.message(_logSubject, ChannelMessages.FLOW_REMOVED()); + MessageFlow mf = new MessageFlow(); + mf.setUnit(MessageCreditUnit.MESSAGE); + mf.setDestination(""); + _outstandingCredit.set(Integer.MAX_VALUE); + mf.setValue(Integer.MAX_VALUE); + invoke(mf); + + + } + } + } + } + + public boolean onSameConnection(InboundMessage inbound) + { + return ((inbound instanceof MessageTransferMessage) + && ((MessageTransferMessage)inbound).getConnectionReference() == getConnection().getReference()) + || ((inbound instanceof MessageMetaData_0_10) + && (((MessageMetaData_0_10)inbound).getConnectionReference())== getConnection().getReference()); + } + + + public String toLogString() + { + long connectionId = super.getConnection() instanceof ServerConnection + ? getConnection().getConnectionId() + : -1; + + String remoteAddress = String.valueOf(getConnection().getRemoteAddress()); + return "[" + + MessageFormat.format(CHANNEL_FORMAT, + connectionId, + getClientID(), + remoteAddress, + getVirtualHost().getName(), + getChannel()) + + "] "; + } + + @Override + public void close(AMQConstant cause, String message) + { + if (cause == null) + { + close(); + } + else + { + close(cause.getCode(), message); + } + } + + void close(int cause, String message) + { + _forcedCloseLogMessage.compareAndSet(null, ChannelMessages.CLOSE_FORCED(cause, message)); + close(); + } + + @Override + public void close() + { + // unregister subscriptions in order to prevent sending of new messages + // to subscriptions with closing session + unregisterSubscriptions(); + super.close(); + } + + void unregisterSubscriptions() + { + final Collection<Subscription_0_10> subscriptions = getSubscriptions(); + for (Subscription_0_10 subscription_0_10 : subscriptions) + { + unregister(subscription_0_10); + } + } + + void stopSubscriptions() + { + final Collection<Subscription_0_10> subscriptions = getSubscriptions(); + for (Subscription_0_10 subscription_0_10 : subscriptions) + { + subscription_0_10.stop(); + } + } + + + public void receivedComplete() + { + final Collection<Subscription_0_10> subscriptions = getSubscriptions(); + for (Subscription_0_10 subscription_0_10 : subscriptions) + { + subscription_0_10.flushCreditState(false); + } + awaitCommandCompletion(); + } + + private class PostEnqueueAction implements ServerTransaction.Action + { + + private List<? extends BaseQueue> _queues; + private ServerMessage _message; + private final boolean _transactional; + + public PostEnqueueAction(List<? extends BaseQueue> queues, ServerMessage message, final boolean transactional) + { + _transactional = transactional; + setState(queues, message); + } + + public void setState(List<? extends BaseQueue> queues, ServerMessage message) + { + _message = message; + _queues = queues; + } + + public void postCommit() + { + MessageReference<?> ref = _message.newReference(); + for(int i = 0; i < _queues.size(); i++) + { + try + { + BaseQueue queue = _queues.get(i); + queue.enqueue(_message, _transactional, null); + if(queue instanceof AMQQueue) + { + ((AMQQueue)queue).checkCapacity(ServerSession.this); + } + + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + } + ref.release(); + } + + public void onRollback() + { + // NO-OP + } + } + + public int getUnacknowledgedMessageCount() + { + return _messageDispositionListenerMap.size(); + } + + public boolean getBlocking() + { + return _blocking.get(); + } + + private final LinkedList<AsyncCommand> _unfinishedCommandsQueue = new LinkedList<AsyncCommand>(); + + public void completeAsyncCommands() + { + AsyncCommand cmd; + while((cmd = _unfinishedCommandsQueue.peek()) != null && cmd.isReadyForCompletion()) + { + cmd.complete(); + _unfinishedCommandsQueue.poll(); + } + while(_unfinishedCommandsQueue.size() > UNFINISHED_COMMAND_QUEUE_THRESHOLD) + { + cmd = _unfinishedCommandsQueue.poll(); + cmd.awaitReadyForCompletion(); + cmd.complete(); + } + } + + + public void awaitCommandCompletion() + { + AsyncCommand cmd; + while((cmd = _unfinishedCommandsQueue.poll()) != null) + { + cmd.awaitReadyForCompletion(); + cmd.complete(); + } + } + + + public Object getAsyncCommandMark() + { + return _unfinishedCommandsQueue.isEmpty() ? null : _unfinishedCommandsQueue.getLast(); + } + + public void recordFuture(final StoreFuture future, final ServerTransaction.Action action) + { + _unfinishedCommandsQueue.add(new AsyncCommand(future, action)); + } + + private static class AsyncCommand + { + private final StoreFuture _future; + private ServerTransaction.Action _action; + + public AsyncCommand(final StoreFuture future, final ServerTransaction.Action action) + { + _future = future; + _action = action; + } + + void awaitReadyForCompletion() + { + _future.waitForCompletion(); + } + + void complete() + { + if(!_future.isComplete()) + { + _future.waitForCompletion(); + } + _action.postCommit(); + _action = null; + } + + boolean isReadyForCompletion() + { + return _future.isComplete(); + } + } + + protected void setClose(boolean close) + { + super.setClose(close); + } + + @Override + public int getConsumerCount() + { + return _subscriptions.values().size(); + } + + @Override + public int compareTo(AMQSessionModel o) + { + return getId().compareTo(o.getId()); + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSessionDelegate.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSessionDelegate.java new file mode 100644 index 0000000000..46626e3e92 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSessionDelegate.java @@ -0,0 +1,1541 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import java.util.LinkedHashMap; +import java.util.UUID; +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.AMQUnknownExchangeType; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeInUseException; +import org.apache.qpid.server.exchange.HeadersExchange; +import org.apache.qpid.server.filter.FilterManager; +import org.apache.qpid.server.filter.FilterManagerFactory; +import org.apache.qpid.server.logging.messages.ExchangeMessages; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueArgumentsConverter; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.txn.AlreadyKnownDtxException; +import org.apache.qpid.server.txn.DtxNotSelectedException; +import org.apache.qpid.server.txn.IncorrectDtxStateException; +import org.apache.qpid.server.txn.JoinAndResumeDtxException; +import org.apache.qpid.server.txn.NotAssociatedDtxException; +import org.apache.qpid.server.txn.RollbackOnlyDtxException; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.txn.SuspendAndFailDtxException; +import org.apache.qpid.server.txn.TimeoutDtxException; +import org.apache.qpid.server.txn.UnknownDtxBranchException; +import org.apache.qpid.server.virtualhost.ExchangeExistsException; +import org.apache.qpid.server.virtualhost.ExchangeIsAlternateException; +import org.apache.qpid.server.virtualhost.RequiredExchangeException; +import org.apache.qpid.server.virtualhost.ReservedExchangeNameException; +import org.apache.qpid.server.virtualhost.UnknownExchangeException; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.plugins.QueueExistsException; +import org.apache.qpid.transport.*; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class ServerSessionDelegate extends SessionDelegate +{ + private static final Logger LOGGER = Logger.getLogger(ServerSessionDelegate.class); + + public ServerSessionDelegate() + { + + } + + @Override + public void command(Session session, Method method) + { + try + { + setThreadSubject(session); + + if(!session.isClosing()) + { + Object asyncCommandMark = ((ServerSession)session).getAsyncCommandMark(); + super.command(session, method, false); + Object newOutstanding = ((ServerSession)session).getAsyncCommandMark(); + if(newOutstanding == null || newOutstanding == asyncCommandMark) + { + session.processed(method); + } + + if(newOutstanding != null) + { + ((ServerSession)session).completeAsyncCommands(); + } + + if (method.isSync()) + { + ((ServerSession)session).awaitCommandCompletion(); + session.flushProcessed(); + } + } + } + catch(RuntimeException e) + { + LOGGER.error("Exception processing command", e); + exception(session, method, ExecutionErrorCode.INTERNAL_ERROR, "Exception processing command: " + e); + } + } + + @Override + public void messageAccept(Session session, MessageAccept method) + { + final ServerSession serverSession = (ServerSession) session; + serverSession.accept(method.getTransfers()); + if(!serverSession.isTransactional()) + { + serverSession.recordFuture(StoreFuture.IMMEDIATE_FUTURE, + new CommandProcessedAction(serverSession, method)); + } + } + + @Override + public void messageReject(Session session, MessageReject method) + { + ((ServerSession)session).reject(method.getTransfers()); + } + + @Override + public void messageRelease(Session session, MessageRelease method) + { + ((ServerSession)session).release(method.getTransfers(), method.getSetRedelivered()); + } + + @Override + public void messageAcquire(Session session, MessageAcquire method) + { + RangeSet acquiredRanges = ((ServerSession)session).acquire(method.getTransfers()); + + Acquired result = new Acquired(acquiredRanges); + + + session.executionResult((int) method.getId(), result); + + + } + + @Override + public void messageResume(Session session, MessageResume method) + { + super.messageResume(session, method); + } + + @Override + public void messageSubscribe(Session session, MessageSubscribe method) + { + /* + TODO - work around broken Python tests + Correct code should read like + if not hasAcceptMode() exception ILLEGAL_ARGUMENT "Accept-mode not supplied" + else if not method.hasAcquireMode() exception ExecutionErrorCode.ILLEGAL_ARGUMENT, "Acquire-mode not supplied" + */ + if(!method.hasAcceptMode()) + { + method.setAcceptMode(MessageAcceptMode.EXPLICIT); + } + if(!method.hasAcquireMode()) + { + method.setAcquireMode(MessageAcquireMode.PRE_ACQUIRED); + + } + + if(!method.hasQueue()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "queue not supplied"); + } + else + { + String destination = method.getDestination(); + + if(((ServerSession)session).getSubscription(destination)!=null) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Subscription already exists with destination '"+destination+"'"); + } + else + { + String queueName = method.getQueue(); + VirtualHost vhost = getVirtualHost(session); + + final AMQQueue queue = vhost.getQueue(queueName); + + if(queue == null) + { + exception(session,method,ExecutionErrorCode.NOT_FOUND, "Queue: " + queueName + " not found"); + } + else if(queue.getAuthorizationHolder() != null && queue.getAuthorizationHolder() != session) + { + exception(session,method,ExecutionErrorCode.RESOURCE_LOCKED, "Exclusive Queue: " + queueName + " owned exclusively by another session"); + } + else if(queue.isExclusive() && queue.getExclusiveOwningSession() != null && queue.getExclusiveOwningSession() != session) + { + exception(session,method,ExecutionErrorCode.RESOURCE_LOCKED, "Exclusive Queue: " + queueName + " owned exclusively by another session"); + } + else + { + if(queue.isExclusive()) + { + ServerSession s = (ServerSession) session; + queue.setExclusiveOwningSession(s); + if(queue.getAuthorizationHolder() == null) + { + queue.setAuthorizationHolder(s); + queue.setExclusiveOwningSession(s); + ((ServerSession) session).addSessionCloseTask(new ServerSession.Task() + { + public void doTask(ServerSession session) + { + if(queue.getAuthorizationHolder() == session) + { + queue.setAuthorizationHolder(null); + queue.setExclusiveOwningSession(null); + } + } + }); + } + } + + FlowCreditManager_0_10 creditManager = new WindowCreditManager(0L,0L); + + FilterManager filterManager = null; + try + { + filterManager = FilterManagerFactory.createManager(method.getArguments()); + } + catch (AMQException amqe) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "Exception Creating FilterManager"); + return; + } + + Subscription_0_10 sub = new Subscription_0_10((ServerSession)session, + destination, + method.getAcceptMode(), + method.getAcquireMode(), + MessageFlowMode.WINDOW, + creditManager, + filterManager, + method.getArguments()); + + ((ServerSession)session).register(destination, sub); + try + { + queue.registerSubscription(sub, method.getExclusive()); + } + catch (AMQQueue.ExistingExclusiveSubscription existing) + { + exception(session, method, ExecutionErrorCode.RESOURCE_LOCKED, "Queue has an exclusive consumer"); + } + catch (AMQQueue.ExistingSubscriptionPreventsExclusive exclusive) + { + exception(session, method, ExecutionErrorCode.RESOURCE_LOCKED, "Queue has an existing consumer - can't subscribe exclusively"); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot subscribe to queue '" + queueName + "' with destination '" + destination); + } + } + } + } + } + + @Override + public void messageTransfer(Session ssn, final MessageTransfer xfr) + { + final Exchange exchange = getExchangeForMessage(ssn, xfr); + + DeliveryProperties delvProps = null; + if(xfr.getHeader() != null && (delvProps = xfr.getHeader().getDeliveryProperties()) != null && delvProps.hasTtl() && !delvProps + .hasExpiration()) + { + delvProps.setExpiration(System.currentTimeMillis() + delvProps.getTtl()); + } + + final MessageMetaData_0_10 messageMetaData = new MessageMetaData_0_10(xfr); + messageMetaData.setConnectionReference(((ServerSession)ssn).getReference()); + + if (!getVirtualHost(ssn).getSecurityManager().authorisePublish(messageMetaData.isImmediate(), messageMetaData.getRoutingKey(), exchange.getName())) + { + ExecutionErrorCode errorCode = ExecutionErrorCode.UNAUTHORIZED_ACCESS; + String description = "Permission denied: exchange-name '" + exchange.getName() + "'"; + exception(ssn, xfr, errorCode, description); + + return; + } + + final Exchange exchangeInUse; + List<? extends BaseQueue> queues = exchange.route(messageMetaData); + if(queues.isEmpty() && exchange.getAlternateExchange() != null) + { + final Exchange alternateExchange = exchange.getAlternateExchange(); + queues = alternateExchange.route(messageMetaData); + if (!queues.isEmpty()) + { + exchangeInUse = alternateExchange; + } + else + { + exchangeInUse = exchange; + } + } + else + { + exchangeInUse = exchange; + } + + final ServerSession serverSession = (ServerSession) ssn; + if(!queues.isEmpty()) + { + final MessageStore store = getVirtualHost(ssn).getMessageStore(); + final StoredMessage<MessageMetaData_0_10> storeMessage = createStoreMessage(xfr, messageMetaData, store); + MessageTransferMessage message = new MessageTransferMessage(storeMessage, serverSession.getReference()); + serverSession.enqueue(message, queues); + storeMessage.flushToStore(); + } + else + { + if((delvProps == null || !delvProps.getDiscardUnroutable()) && xfr.getAcceptMode() == MessageAcceptMode.EXPLICIT) + { + RangeSet rejects = RangeSetFactory.createRangeSet(); + rejects.add(xfr.getId()); + MessageReject reject = new MessageReject(rejects, MessageRejectCode.UNROUTABLE, "Unroutable"); + ssn.invoke(reject); + } + else + { + serverSession.getLogActor().message(ExchangeMessages.DISCARDMSG(exchangeInUse.getName(), messageMetaData.getRoutingKey())); + } + } + + + if(serverSession.isTransactional()) + { + serverSession.processed(xfr); + } + else + { + serverSession.recordFuture(StoreFuture.IMMEDIATE_FUTURE, new CommandProcessedAction(serverSession, xfr)); + } + } + + private StoredMessage<MessageMetaData_0_10> createStoreMessage(final MessageTransfer xfr, + final MessageMetaData_0_10 messageMetaData, final MessageStore store) + { + final StoredMessage<MessageMetaData_0_10> storeMessage = store.addMessage(messageMetaData); + ByteBuffer body = xfr.getBody(); + if(body != null) + { + storeMessage.addContent(0, body); + } + return storeMessage; + } + + @Override + public void messageCancel(Session session, MessageCancel method) + { + String destination = method.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else + { + AMQQueue queue = sub.getQueue(); + ((ServerSession)session).unregister(sub); + if(!queue.isDeleted() && queue.isExclusive() && queue.getConsumerCount() == 0) + { + queue.setAuthorizationHolder(null); + } + } + } + + @Override + public void messageFlush(Session session, MessageFlush method) + { + String destination = method.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else + { + + try + { + sub.flush(); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot flush subscription '" + destination); + } + } + } + + @Override + public void txSelect(Session session, TxSelect method) + { + // TODO - check current tx mode + ((ServerSession)session).selectTx(); + } + + @Override + public void txCommit(Session session, TxCommit method) + { + // TODO - check current tx mode + ((ServerSession)session).commit(); + } + + @Override + public void txRollback(Session session, TxRollback method) + { + // TODO - check current tx mode + ((ServerSession)session).rollback(); + } + + @Override + public void dtxSelect(Session session, DtxSelect method) + { + // TODO - check current tx mode + ((ServerSession)session).selectDtx(); + } + + @Override + public void dtxStart(Session session, DtxStart method) + { + XaResult result = new XaResult(); + result.setStatus(DtxXaStatus.XA_OK); + try + { + ((ServerSession)session).startDtx(method.getXid(), method.getJoin(), method.getResume()); + session.executionResult(method.getId(), result); + } + catch(JoinAndResumeDtxException e) + { + exception(session, method, ExecutionErrorCode.COMMAND_INVALID, e.getMessage()); + } + catch(UnknownDtxBranchException e) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Unknown xid " + method.getXid()); + } + catch(AlreadyKnownDtxException e) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Xid already started an neither join nor " + + "resume set" + method.getXid()); + } + catch(DtxNotSelectedException e) + { + exception(session, method, ExecutionErrorCode.COMMAND_INVALID, e.getMessage()); + } + + } + + @Override + public void dtxEnd(Session session, DtxEnd method) + { + XaResult result = new XaResult(); + result.setStatus(DtxXaStatus.XA_OK); + try + { + try + { + ((ServerSession)session).endDtx(method.getXid(), method.getFail(), method.getSuspend()); + } + catch (TimeoutDtxException e) + { + result.setStatus(DtxXaStatus.XA_RBTIMEOUT); + } + session.executionResult(method.getId(), result); + } + catch(UnknownDtxBranchException e) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_STATE, e.getMessage()); + } + catch(NotAssociatedDtxException e) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_STATE, e.getMessage()); + } + catch(DtxNotSelectedException e) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_STATE, e.getMessage()); + } + catch(SuspendAndFailDtxException e) + { + exception(session, method, ExecutionErrorCode.COMMAND_INVALID, e.getMessage()); + } + + } + + @Override + public void dtxCommit(Session session, DtxCommit method) + { + XaResult result = new XaResult(); + result.setStatus(DtxXaStatus.XA_OK); + try + { + try + { + ((ServerSession)session).commitDtx(method.getXid(), method.getOnePhase()); + } + catch (RollbackOnlyDtxException e) + { + result.setStatus(DtxXaStatus.XA_RBROLLBACK); + } + catch (TimeoutDtxException e) + { + result.setStatus(DtxXaStatus.XA_RBTIMEOUT); + } + session.executionResult(method.getId(), result); + } + catch(UnknownDtxBranchException e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, e.getMessage()); + } + catch(IncorrectDtxStateException e) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_STATE, e.getMessage()); + } + catch(AMQStoreException e) + { + exception(session, method, ExecutionErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public void dtxForget(Session session, DtxForget method) + { + try + { + ((ServerSession)session).forgetDtx(method.getXid()); + } + catch(UnknownDtxBranchException e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, e.getMessage()); + } + catch(IncorrectDtxStateException e) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_STATE, e.getMessage()); + } + + } + + @Override + public void dtxGetTimeout(Session session, DtxGetTimeout method) + { + GetTimeoutResult result = new GetTimeoutResult(); + try + { + result.setTimeout(((ServerSession) session).getTimeoutDtx(method.getXid())); + session.executionResult(method.getId(), result); + } + catch(UnknownDtxBranchException e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, e.getMessage()); + } + } + + @Override + public void dtxPrepare(Session session, DtxPrepare method) + { + XaResult result = new XaResult(); + result.setStatus(DtxXaStatus.XA_OK); + try + { + try + { + ((ServerSession)session).prepareDtx(method.getXid()); + } + catch (RollbackOnlyDtxException e) + { + result.setStatus(DtxXaStatus.XA_RBROLLBACK); + } + catch (TimeoutDtxException e) + { + result.setStatus(DtxXaStatus.XA_RBTIMEOUT); + } + session.executionResult((int) method.getId(), result); + } + catch(UnknownDtxBranchException e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, e.getMessage()); + } + catch(IncorrectDtxStateException e) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_STATE, e.getMessage()); + } + catch(AMQStoreException e) + { + exception(session, method, ExecutionErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public void dtxRecover(Session session, DtxRecover method) + { + RecoverResult result = new RecoverResult(); + List inDoubt = ((ServerSession)session).recoverDtx(); + result.setInDoubt(inDoubt); + session.executionResult(method.getId(), result); + } + + @Override + public void dtxRollback(Session session, DtxRollback method) + { + + XaResult result = new XaResult(); + result.setStatus(DtxXaStatus.XA_OK); + try + { + try + { + ((ServerSession)session).rollbackDtx(method.getXid()); + } + catch (TimeoutDtxException e) + { + result.setStatus(DtxXaStatus.XA_RBTIMEOUT); + } + session.executionResult(method.getId(), result); + } + catch(UnknownDtxBranchException e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, e.getMessage()); + } + catch(IncorrectDtxStateException e) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_STATE, e.getMessage()); + } + catch(AMQStoreException e) + { + exception(session, method, ExecutionErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public void dtxSetTimeout(Session session, DtxSetTimeout method) + { + try + { + ((ServerSession)session).setTimeoutDtx(method.getXid(), method.getTimeout()); + } + catch(UnknownDtxBranchException e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, e.getMessage()); + } + } + + @Override + public void executionSync(final Session ssn, final ExecutionSync sync) + { + ((ServerSession)ssn).awaitCommandCompletion(); + super.executionSync(ssn, sync); + } + + @Override + public void exchangeDeclare(Session session, ExchangeDeclare method) + { + String exchangeName = method.getExchange(); + VirtualHost virtualHost = getVirtualHost(session); + + //we must check for any unsupported arguments present and throw not-implemented + if(method.hasArguments()) + { + Map<String,Object> args = method.getArguments(); + //QPID-3392: currently we don't support any! + if(!args.isEmpty()) + { + exception(session, method, ExecutionErrorCode.NOT_IMPLEMENTED, "Unsupported exchange argument(s) found " + args.keySet().toString()); + return; + } + } + + if(method.getPassive()) + { + Exchange exchange = getExchange(session, exchangeName); + + if(exchange == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "not-found: exchange-name '" + exchangeName + "'"); + } + else + { + if (!exchange.getTypeName().equals(method.getType()) + && (method.getType() != null && method.getType().length() > 0)) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Attempt to redeclare exchange: " + + exchangeName + " of type " + exchange.getTypeName() + " to " + method.getType() + "."); + } + } + } + else + { + + try + { + virtualHost.createExchange(null, + method.getExchange(), + method.getType(), + method.getDurable(), + method.getAutoDelete(), + method.getAlternateExchange()); + } + catch(ReservedExchangeNameException e) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Attempt to declare exchange: " + + exchangeName + " which begins with reserved name or prefix."); + } + catch(UnknownExchangeException e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, + "Unknown alternate exchange " + e.getExchangeName()); + } + catch(AMQUnknownExchangeType e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Unknown Exchange Type: " + method.getType()); + } + catch(ExchangeExistsException e) + { + Exchange exchange = e.getExistingExchange(); + if(!exchange.getTypeName().equals(method.getType())) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, + "Attempt to redeclare exchange: " + exchangeName + + " of type " + exchange.getTypeName() + + " to " + method.getType() +"."); + } + else if(method.hasAlternateExchange() + && (exchange.getAlternateExchange() == null || + !method.getAlternateExchange().equals(exchange.getAlternateExchange().getName()))) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, + "Attempt to change alternate exchange of: " + exchangeName + + " from " + exchange.getAlternateExchange() + + " to " + method.getAlternateExchange() +"."); + } + } + catch (AMQException e) + { + exception(session, method, e, "Cannot declare exchange '" + exchangeName); + } + + + } + + } + + // TODO decouple AMQException and AMQConstant error codes + private void exception(Session session, Method method, AMQException exception, String message) + { + ExecutionErrorCode errorCode = ExecutionErrorCode.INTERNAL_ERROR; + if (exception.getErrorCode() != null) + { + try + { + errorCode = ExecutionErrorCode.get(exception.getErrorCode().getCode()); + } + catch (IllegalArgumentException iae) + { + // ignore, already set to INTERNAL_ERROR + } + } + String description = message + "': " + exception.getMessage(); + + exception(session, method, errorCode, description); + } + + private void exception(Session session, Method method, ExecutionErrorCode errorCode, String description) + { + ExecutionException ex = new ExecutionException(); + ex.setErrorCode(errorCode); + ex.setCommandId(method.getId()); + ex.setDescription(description); + + session.invoke(ex); + + ((ServerSession)session).close(errorCode.getValue(), description); + } + + private Exchange getExchange(Session session, String exchangeName) + { + return getVirtualHost(session).getExchange(exchangeName); + } + + private Exchange getExchangeForMessage(Session ssn, MessageTransfer xfr) + { + VirtualHost virtualHost = getVirtualHost(ssn); + + Exchange exchange; + if(xfr.hasDestination()) + { + exchange = virtualHost.getExchange(xfr.getDestination()); + if(exchange == null) + { + exchange = virtualHost.getDefaultExchange(); + } + } + else + { + exchange = virtualHost.getDefaultExchange(); + } + return exchange; + } + + private VirtualHost getVirtualHost(Session session) + { + ServerConnection conn = getServerConnection(session); + VirtualHost vhost = conn.getVirtualHost(); + return vhost; + } + + private ServerConnection getServerConnection(Session session) + { + ServerConnection conn = (ServerConnection) session.getConnection(); + return conn; + } + + @Override + public void exchangeDelete(Session session, ExchangeDelete method) + { + VirtualHost virtualHost = getVirtualHost(session); + + try + { + if (nameNullOrEmpty(method.getExchange())) + { + exception(session, method, ExecutionErrorCode.INVALID_ARGUMENT, "Delete not allowed for default exchange"); + return; + } + + Exchange exchange = getExchange(session, method.getExchange()); + + if(exchange == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "No such exchange '" + method.getExchange() + "'"); + } + else + { + virtualHost.removeExchange(exchange, !method.getIfUnused()); + } + } + catch (ExchangeInUseException e) + { + exception(session, method, ExecutionErrorCode.PRECONDITION_FAILED, "Exchange in use"); + } + catch (ExchangeIsAlternateException e) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Exchange in use as an alternate exchange"); + } + catch (RequiredExchangeException e) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Exchange '"+method.getExchange()+"' cannot be deleted"); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot delete exchange '" + method.getExchange() ); + } + } + + private boolean nameNullOrEmpty(String name) + { + if(name == null || name.length() == 0) + { + return true; + } + + return false; + } + + private boolean isStandardExchange(Exchange exchange, Collection<ExchangeType<? extends Exchange>> registeredTypes) + { + for(ExchangeType type : registeredTypes) + { + if(type.getDefaultExchangeName().equals( exchange.getName() )) + { + return true; + } + } + return false; + } + + @Override + public void exchangeQuery(Session session, ExchangeQuery method) + { + + ExchangeQueryResult result = new ExchangeQueryResult(); + + Exchange exchange = getExchange(session, method.getName()); + + if(exchange != null) + { + result.setDurable(exchange.isDurable()); + result.setType(exchange.getTypeName()); + result.setNotFound(false); + } + else + { + result.setNotFound(true); + } + + session.executionResult((int) method.getId(), result); + } + + @Override + public void exchangeBind(Session session, ExchangeBind method) + { + + VirtualHost virtualHost = getVirtualHost(session); + + if (!method.hasQueue()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "queue not set"); + } + else if (nameNullOrEmpty(method.getExchange())) + { + exception(session, method, ExecutionErrorCode.INVALID_ARGUMENT, "Bind not allowed for default exchange"); + } + else + { + //TODO - here because of non-compiant python tests + // should raise exception ILLEGAL_ARGUMENT "binding-key not set" + if (!method.hasBindingKey()) + { + method.setBindingKey(method.getQueue()); + } + AMQQueue queue = virtualHost.getQueue(method.getQueue()); + Exchange exchange = virtualHost.getExchange(method.getExchange()); + if(queue == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Queue: '" + method.getQueue() + "' not found"); + } + else if(exchange == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Exchange: '" + method.getExchange() + "' not found"); + } + else if(exchange.getType().equals(HeadersExchange.TYPE) && (!method.hasArguments() || method.getArguments() == null || !method.getArguments().containsKey("x-match"))) + { + exception(session, method, ExecutionErrorCode.INTERNAL_ERROR, "Bindings to an exchange of type " + HeadersExchange.TYPE.getType() + " require an x-match header"); + } + else + { + if (!exchange.isBound(method.getBindingKey(), method.getArguments(), queue)) + { + try + { + exchange.addBinding(method.getBindingKey(), queue, method.getArguments()); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot add binding '" + method.getBindingKey()); + } + } + else + { + // todo + } + } + + + } + + + + } + + @Override + public void exchangeUnbind(Session session, ExchangeUnbind method) + { + VirtualHost virtualHost = getVirtualHost(session); + + if (!method.hasQueue()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "queue not set"); + } + else if (nameNullOrEmpty(method.getExchange())) + { + exception(session, method, ExecutionErrorCode.INVALID_ARGUMENT, "Unbind not allowed for default exchange"); + } + else if (!method.hasBindingKey()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "binding-key not set"); + } + else + { + AMQQueue queue = virtualHost.getQueue(method.getQueue()); + Exchange exchange = virtualHost.getExchange(method.getExchange()); + if(queue == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Queue: '" + method.getQueue() + "' not found"); + } + else if(exchange == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Exchange: '" + method.getExchange() + "' not found"); + } + else + { + try + { + exchange.removeBinding(method.getBindingKey(), queue, null); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot remove binding '" + method.getBindingKey()); + } + } + } + } + + @Override + public void exchangeBound(Session session, ExchangeBound method) + { + + ExchangeBoundResult result = new ExchangeBoundResult(); + VirtualHost virtualHost = getVirtualHost(session); + Exchange exchange; + AMQQueue queue; + if(method.hasExchange()) + { + exchange = virtualHost.getExchange(method.getExchange()); + + if(exchange == null) + { + result.setExchangeNotFound(true); + } + } + else + { + exchange = virtualHost.getDefaultExchange(); + } + + + if(method.hasQueue()) + { + + queue = getQueue(session, method.getQueue()); + if(queue == null) + { + result.setQueueNotFound(true); + } + + + if(exchange != null && queue != null) + { + + boolean queueMatched = exchange.isBound(queue); + + result.setQueueNotMatched(!queueMatched); + + + if(method.hasBindingKey()) + { + + if(queueMatched) + { + final boolean keyMatched = exchange.isBound(method.getBindingKey(), queue); + result.setKeyNotMatched(!keyMatched); + if(method.hasArguments()) + { + if(keyMatched) + { + result.setArgsNotMatched(!exchange.isBound(method.getBindingKey(), method.getArguments(), queue)); + } + else + { + result.setArgsNotMatched(!exchange.isBound(method.getArguments(), queue)); + } + } + } + else + { + boolean keyMatched = exchange.isBound(method.getBindingKey()); + result.setKeyNotMatched(!keyMatched); + if(method.hasArguments()) + { + if(keyMatched) + { + result.setArgsNotMatched(!exchange.isBound(method.getBindingKey(), method.getArguments())); + } + else + { + result.setArgsNotMatched(!exchange.isBound(method.getArguments())); + } + } + } + + } + else if (method.hasArguments()) + { + if(queueMatched) + { + result.setArgsNotMatched(!exchange.isBound(method.getArguments(), queue)); + } + else + { + result.setArgsNotMatched(!exchange.isBound(method.getArguments())); + } + } + + } + else if(exchange != null && method.hasBindingKey()) + { + final boolean keyMatched = exchange.isBound(method.getBindingKey()); + result.setKeyNotMatched(!keyMatched); + + if(method.hasArguments()) + { + if(keyMatched) + { + result.setArgsNotMatched(!exchange.isBound(method.getBindingKey(), method.getArguments())); + } + else + { + result.setArgsNotMatched(!exchange.isBound(method.getArguments())); + } + } + + + } + + } + else if(exchange != null && method.hasBindingKey()) + { + final boolean keyMatched = exchange.isBound(method.getBindingKey()); + result.setKeyNotMatched(!keyMatched); + + if(method.hasArguments()) + { + if(keyMatched) + { + result.setArgsNotMatched(!exchange.isBound(method.getBindingKey(), method.getArguments())); + } + else + { + result.setArgsNotMatched(!exchange.isBound(method.getArguments())); + } + } + + } + else if(exchange != null && method.hasArguments()) + { + result.setArgsNotMatched(!exchange.isBound(method.getArguments())); + } + + + session.executionResult((int) method.getId(), result); + + + } + + private AMQQueue getQueue(Session session, String queue) + { + return getVirtualHost(session).getQueue(queue); + } + + @Override + public void queueDeclare(Session session, final QueueDeclare method) + { + + final VirtualHost virtualHost = getVirtualHost(session); + DurableConfigurationStore store = virtualHost.getDurableConfigurationStore(); + + String queueName = method.getQueue(); + AMQQueue queue; + //TODO: do we need to check that the queue already exists with exactly the same "configuration"? + + final boolean exclusive = method.getExclusive(); + final boolean autoDelete = method.getAutoDelete(); + + if(method.getPassive()) + { + queue = virtualHost.getQueue(queueName); + + if (queue == null) + { + String description = "Queue: " + queueName + " not found on VirtualHost(" + virtualHost + ")."; + ExecutionErrorCode errorCode = ExecutionErrorCode.NOT_FOUND; + + exception(session, method, errorCode, description); + + } + else if (exclusive && (queue.getExclusiveOwningSession() != null && !queue.getExclusiveOwningSession().equals(session))) + { + String description = "Cannot declare queue('" + queueName + "')," + + " as exclusive queue with same name " + + "declared on another session"; + ExecutionErrorCode errorCode = ExecutionErrorCode.RESOURCE_LOCKED; + + exception(session, method, errorCode, description); + + } + } + else + { + + try + { + + String owner = method.getExclusive() ? ((ServerSession)session).getClientID() : null; + final String alternateExchangeName = method.getAlternateExchange(); + + + final Map<String, Object> arguments = QueueArgumentsConverter.convertWireArgsToModel(method.getArguments()); + + if(alternateExchangeName != null && alternateExchangeName.length() != 0) + { + arguments.put(Queue.ALTERNATE_EXCHANGE, alternateExchangeName); + } + + final UUID id = UUIDGenerator.generateQueueUUID(queueName, virtualHost.getName()); + + final boolean deleteOnNoConsumer = !exclusive && autoDelete; + + queue = virtualHost.createQueue(id, queueName, method.getDurable(), owner, + autoDelete, exclusive, deleteOnNoConsumer, + arguments); + + if (autoDelete && exclusive) + { + final AMQQueue q = queue; + final ServerSession.Task deleteQueueTask = new ServerSession.Task() + { + public void doTask(ServerSession session) + { + try + { + virtualHost.removeQueue(q); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot delete '" + method.getQueue()); + } + } + }; + final ServerSession s = (ServerSession) session; + s.addSessionCloseTask(deleteQueueTask); + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) throws AMQException + { + s.removeSessionCloseTask(deleteQueueTask); + } + }); + } + if (exclusive) + { + final AMQQueue q = queue; + final ServerSession.Task removeExclusive = new ServerSession.Task() + { + public void doTask(ServerSession session) + { + q.setAuthorizationHolder(null); + q.setExclusiveOwningSession(null); + } + }; + final ServerSession s = (ServerSession) session; + q.setExclusiveOwningSession(s); + s.addSessionCloseTask(removeExclusive); + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) throws AMQException + { + s.removeSessionCloseTask(removeExclusive); + } + }); + } + } + catch(QueueExistsException qe) + { + queue = qe.getExistingQueue(); + if (exclusive && (queue.getExclusiveOwningSession() != null && !queue.getExclusiveOwningSession().equals(session))) + { + String description = "Cannot declare queue('" + queueName + "')," + + " as exclusive queue with same name " + + "declared on another session"; + ExecutionErrorCode errorCode = ExecutionErrorCode.RESOURCE_LOCKED; + + exception(session, method, errorCode, description); + } + } + catch (AMQException e) + { + exception(session, method, e, "Cannot declare queue '" + queueName); + } + } + } + + /** + * Converts a queue argument into a boolean value. For compatibility with the C++ + * and the clients, accepts with Boolean, String, or Number types. + * @param argValue argument value. + * + * @return true if set + */ + private boolean convertBooleanValue(Object argValue) + { + if(argValue instanceof Boolean && ((Boolean)argValue)) + { + return true; + } + else if (argValue instanceof String && Boolean.parseBoolean((String)argValue)) + { + return true; + } + else if (argValue instanceof Number && ((Number)argValue).intValue() != 0) + { + return true; + } + return false; + } + + @Override + public void queueDelete(Session session, QueueDelete method) + { + String queueName = method.getQueue(); + if(queueName == null || queueName.length()==0) + { + exception(session, method, ExecutionErrorCode.INVALID_ARGUMENT, "No queue name supplied"); + + } + else + { + AMQQueue queue = getQueue(session, queueName); + + + if (queue == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "No queue " + queueName + " found"); + } + else + { + if(queue.getAuthorizationHolder() != null && queue.getAuthorizationHolder() != session) + { + exception(session,method,ExecutionErrorCode.RESOURCE_LOCKED, "Exclusive Queue: " + queueName + " owned exclusively by another session"); + } + else if(queue.isExclusive() && queue.getExclusiveOwningSession() != null && queue.getExclusiveOwningSession() != session) + { + exception(session,method,ExecutionErrorCode.RESOURCE_LOCKED, "Exclusive Queue: " + queueName + " owned exclusively by another session"); + } + else if (method.getIfEmpty() && !queue.isEmpty()) + { + exception(session, method, ExecutionErrorCode.PRECONDITION_FAILED, "Queue " + queueName + " not empty"); + } + else if (method.getIfUnused() && !queue.isUnused()) + { + // TODO - Error code + exception(session, method, ExecutionErrorCode.PRECONDITION_FAILED, "Queue " + queueName + " in use"); + + } + else + { + VirtualHost virtualHost = getVirtualHost(session); + + try + { + virtualHost.removeQueue(queue); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot delete queue '" + queueName); + } + } + } + } + } + + @Override + public void queuePurge(Session session, QueuePurge method) + { + String queueName = method.getQueue(); + if(queueName == null || queueName.length()==0) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "No queue name supplied"); + } + else + { + AMQQueue queue = getQueue(session, queueName); + + if (queue == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "No queue " + queueName + " found"); + } + else + { + try + { + queue.clearQueue(); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot purge queue '" + queueName); + } + } + } + } + + @Override + public void queueQuery(Session session, QueueQuery method) + { + QueueQueryResult result = new QueueQueryResult(); + + AMQQueue queue = getQueue(session, method.getQueue()); + + if(queue != null) + { + result.setQueue(queue.getName()); + result.setDurable(queue.isDurable()); + result.setExclusive(queue.isExclusive()); + result.setAutoDelete(queue.isAutoDelete()); + Map<String, Object> arguments = new LinkedHashMap<String, Object>(); + Collection<String> availableAttrs = queue.getAvailableAttributes(); + + for(String attrName : availableAttrs) + { + arguments.put(attrName, queue.getAttribute(attrName)); + } + result.setArguments(QueueArgumentsConverter.convertModelArgsToWire(arguments)); + result.setMessageCount(queue.getMessageCount()); + result.setSubscriberCount(queue.getConsumerCount()); + + } + + + session.executionResult((int) method.getId(), result); + + } + + @Override + public void messageSetFlowMode(Session session, MessageSetFlowMode sfm) + { + String destination = sfm.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, sfm, ExecutionErrorCode.NOT_FOUND, "not-found: destination '" + destination + "'"); + } + else if(sub.isStopped()) + { + sub.setFlowMode(sfm.getFlowMode()); + } + } + + @Override + public void messageStop(Session session, MessageStop stop) + { + String destination = stop.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, stop, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else + { + sub.stop(); + } + + } + + @Override + public void messageFlow(Session session, MessageFlow flow) + { + String destination = flow.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, flow, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else + { + sub.addCredit(flow.getUnit(), flow.getValue()); + } + + } + + @Override + public void closed(Session session) + { + setThreadSubject(session); + + ServerSession serverSession = (ServerSession)session; + + serverSession.stopSubscriptions(); + serverSession.onClose(); + serverSession.unregisterSubscriptions(); + } + + @Override + public void detached(Session session) + { + closed(session); + } + + private void setThreadSubject(Session session) + { + final ServerConnection scon = (ServerConnection) session.getConnection(); + SecurityManager.setThreadSubject(scon.getAuthorizedSubject()); + } + + private static class CommandProcessedAction implements ServerTransaction.Action + { + private final ServerSession _serverSession; + private final Method _method; + + public CommandProcessedAction(final ServerSession serverSession, final Method xfr) + { + _serverSession = serverSession; + _method = xfr; + } + + public void postCommit() + { + _serverSession.processed(_method); + } + + public void onRollback() + { + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/Subscription_0_10.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/Subscription_0_10.java new file mode 100644 index 0000000000..77b63906cc --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/Subscription_0_10.java @@ -0,0 +1,949 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.filter.FilterManager; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.messages.ChannelMessages; +import org.apache.qpid.server.logging.messages.SubscriptionMessages; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.plugin.MessageConverter; +import org.apache.qpid.server.protocol.MessageConverterRegistry; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.InboundMessageAdapter; +import org.apache.qpid.server.queue.QueueArgumentsConverter; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.MessageAcceptMode; +import org.apache.qpid.transport.MessageAcquireMode; +import org.apache.qpid.transport.MessageCreditUnit; +import org.apache.qpid.transport.MessageFlowMode; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.MessageTransfer; +import org.apache.qpid.transport.Method; +import org.apache.qpid.transport.Option; +import org.apache.qpid.transport.Struct; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.QUEUE_FORMAT; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.SUBSCRIPTION_FORMAT; + +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class Subscription_0_10 implements Subscription, FlowCreditManager.FlowCreditManagerListener, LogSubject +{ + private final long _subscriptionID; + + private final QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this); + + private static final Option[] BATCHED = new Option[] { Option.BATCH }; + + private final Lock _stateChangeLock = new ReentrantLock(); + + private final AtomicReference<State> _state = new AtomicReference<State>(State.ACTIVE); + private volatile AMQQueue.Context _queueContext; + private final AtomicBoolean _deleted = new AtomicBoolean(false); + + + private FlowCreditManager_0_10 _creditManager; + + private StateListener _stateListener = new StateListener() + { + + public void stateChange(Subscription sub, State oldState, State newState) + { + CurrentActor.get().message(SubscriptionMessages.STATE(newState.toString())); + } + }; + private AMQQueue _queue; + private final String _destination; + private boolean _noLocal; + private final FilterManager _filters; + private final MessageAcceptMode _acceptMode; + private final MessageAcquireMode _acquireMode; + private MessageFlowMode _flowMode; + private final ServerSession _session; + private final AtomicBoolean _stopped = new AtomicBoolean(true); + private static final Struct[] EMPTY_STRUCT_ARRAY = new Struct[0]; + + private LogActor _logActor; + private final Map<String, Object> _properties = new ConcurrentHashMap<String, Object>(); + private String _traceExclude; + private String _trace; + private final long _createTime = System.currentTimeMillis(); + private final AtomicLong _deliveredCount = new AtomicLong(0); + private final AtomicLong _deliveredBytes = new AtomicLong(0); + private final AtomicLong _unacknowledgedCount = new AtomicLong(0); + private final AtomicLong _unacknowledgedBytes = new AtomicLong(0); + + private final Map<String, Object> _arguments; + private int _deferredMessageCredit; + private long _deferredSizeCredit; + + + public Subscription_0_10(ServerSession session, String destination, MessageAcceptMode acceptMode, + MessageAcquireMode acquireMode, + MessageFlowMode flowMode, + FlowCreditManager_0_10 creditManager, + FilterManager filters,Map<String, Object> arguments) + { + _subscriptionID = SUB_ID_GENERATOR.getAndIncrement(); + _session = session; + _postIdSettingAction = new AddMessageDispositionListenerAction(session); + _destination = destination; + _acceptMode = acceptMode; + _acquireMode = acquireMode; + _creditManager = creditManager; + _flowMode = flowMode; + _filters = filters; + _creditManager.addStateListener(this); + _arguments = arguments == null ? Collections.<String, Object> emptyMap() : + Collections.<String, Object> unmodifiableMap(arguments); + _state.set(_creditManager.hasCredit() ? State.ACTIVE : State.SUSPENDED); + + } + + public void setNoLocal(boolean noLocal) + { + _noLocal = noLocal; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public QueueEntry.SubscriptionAcquiredState getOwningState() + { + return _owningState; + } + + public void setQueue(AMQQueue queue, boolean exclusive) + { + if(getQueue() != null) + { + throw new IllegalStateException("Attempt to set queue for subscription " + this + " to " + queue + "when already set to " + getQueue()); + } + _queue = queue; + + _traceExclude = (String) queue.getAttribute(Queue.FEDERATION_EXCLUDES); + _trace = (String) queue.getAttribute(Queue.FEDERATION_ID); + String filterLogString = null; + + _logActor = GenericActor.getInstance(this); + if (CurrentActor.get().getRootMessageLogger().isMessageEnabled(_logActor, this, SubscriptionMessages.CREATE_LOG_HIERARCHY)) + { + filterLogString = getFilterLogString(); + CurrentActor.get().message(this, SubscriptionMessages.CREATE(filterLogString, queue.isDurable() && exclusive, + filterLogString.length() > 0)); + } + } + + public String getConsumerName() + { + return _destination; + } + + public boolean isSuspended() + { + return !isActive() || _deleted.get() || _session.isClosing() || _session.getConnectionModel().isStopped(); // TODO check for Session suspension + } + + public boolean hasInterest(QueueEntry entry) + { + + + + //check that the message hasn't been rejected + if (entry.isRejectedBy(getSubscriptionID())) + { + + return false; + } + + if (entry.getMessage() instanceof MessageTransferMessage) + { + if(_noLocal) + { + Object connectionRef = ((MessageTransferMessage)entry.getMessage()).getConnectionReference(); + if (connectionRef != null && connectionRef == _session.getReference()) + { + return false; + } + } + } + else + { + // no interest in messages we can't convert + if(MessageConverterRegistry.getConverter(entry.getMessage().getClass(), MessageTransferMessage.class)==null) + { + return false; + } + } + + + return checkFilters(entry); + + + } + + private boolean checkFilters(QueueEntry entry) + { + return (_filters == null) || _filters.allAllow(entry); + } + + public boolean isClosed() + { + return getState() == State.CLOSED; + } + + public boolean isBrowser() + { + return _acquireMode == MessageAcquireMode.NOT_ACQUIRED; + } + + public boolean seesRequeues() + { + return _acquireMode != MessageAcquireMode.NOT_ACQUIRED || _acceptMode == MessageAcceptMode.EXPLICIT; + } + + public void close() + { + boolean closed = false; + State state = getState(); + + _stateChangeLock.lock(); + try + { + while(!closed && state != State.CLOSED) + { + closed = _state.compareAndSet(state, State.CLOSED); + if(!closed) + { + state = getState(); + } + else + { + _stateListener.stateChange(this,state, State.CLOSED); + } + } + _creditManager.removeListener(this); + CurrentActor.get().message(getLogSubject(), SubscriptionMessages.CLOSE()); + } + finally + { + _stateChangeLock.unlock(); + } + + + + } + + public Long getDelivered() + { + return _deliveredCount.get(); + } + + public void creditStateChanged(boolean hasCredit) + { + + if(hasCredit) + { + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + else + { + // this is a hack to get round the issue of increasing bytes credit + _stateListener.stateChange(this, State.ACTIVE, State.ACTIVE); + } + } + else + { + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + } + } + + + public static class AddMessageDispositionListenerAction implements Runnable + { + private MessageTransfer _xfr; + private ServerSession.MessageDispositionChangeListener _action; + private ServerSession _session; + + public AddMessageDispositionListenerAction(ServerSession session) + { + _session = session; + } + + public void setXfr(MessageTransfer xfr) + { + _xfr = xfr; + } + + public void setAction(ServerSession.MessageDispositionChangeListener action) + { + _action = action; + } + + public void run() + { + if(_action != null) + { + _session.onMessageDispositionChange(_xfr, _action); + } + } + } + + private final AddMessageDispositionListenerAction _postIdSettingAction; + + public void send(final QueueEntry entry, boolean batch) throws AMQException + { + ServerMessage serverMsg = entry.getMessage(); + + + MessageTransfer xfr; + + DeliveryProperties deliveryProps; + MessageProperties messageProps = null; + + MessageTransferMessage msg; + + if(serverMsg instanceof MessageTransferMessage) + { + + msg = (MessageTransferMessage) serverMsg; + + } + else + { + MessageConverter converter = + MessageConverterRegistry.getConverter(serverMsg.getClass(), MessageTransferMessage.class); + + + msg = (MessageTransferMessage) converter.convert(serverMsg, getQueue().getVirtualHost()); + } + DeliveryProperties origDeliveryProps = msg.getHeader() == null ? null : msg.getHeader().getDeliveryProperties(); + messageProps = msg.getHeader() == null ? null : msg.getHeader().getMessageProperties(); + + deliveryProps = new DeliveryProperties(); + if(origDeliveryProps != null) + { + if(origDeliveryProps.hasDeliveryMode()) + { + deliveryProps.setDeliveryMode(origDeliveryProps.getDeliveryMode()); + } + if(origDeliveryProps.hasExchange()) + { + deliveryProps.setExchange(origDeliveryProps.getExchange()); + } + if(origDeliveryProps.hasExpiration()) + { + deliveryProps.setExpiration(origDeliveryProps.getExpiration()); + } + if(origDeliveryProps.hasPriority()) + { + deliveryProps.setPriority(origDeliveryProps.getPriority()); + } + if(origDeliveryProps.hasRoutingKey()) + { + deliveryProps.setRoutingKey(origDeliveryProps.getRoutingKey()); + } + if(origDeliveryProps.hasTimestamp()) + { + deliveryProps.setTimestamp(origDeliveryProps.getTimestamp()); + } + if(origDeliveryProps.hasTtl()) + { + deliveryProps.setTtl(origDeliveryProps.getTtl()); + } + + + } + + deliveryProps.setRedelivered(entry.isRedelivered()); + + if(_trace != null && messageProps == null) + { + messageProps = new MessageProperties(); + } + + Header header = new Header(deliveryProps, messageProps, msg.getHeader() == null ? null : msg.getHeader().getNonStandardProperties()); + + + xfr = batch ? new MessageTransfer(_destination,_acceptMode,_acquireMode,header,msg.getBody(), BATCHED) + : new MessageTransfer(_destination,_acceptMode,_acquireMode,header,msg.getBody()); + + boolean excludeDueToFederation = false; + + if(_trace != null) + { + if(!messageProps.hasApplicationHeaders()) + { + messageProps.setApplicationHeaders(new HashMap<String,Object>()); + } + Map<String,Object> appHeaders = messageProps.getApplicationHeaders(); + String trace = (String) appHeaders.get("x-qpid.trace"); + if(trace == null) + { + trace = _trace; + } + else + { + if(_traceExclude != null) + { + excludeDueToFederation = Arrays.asList(trace.split(",")).contains(_traceExclude); + } + trace+=","+_trace; + } + appHeaders.put("x-qpid.trace",trace); + } + + if(!excludeDueToFederation) + { + if(_acceptMode == MessageAcceptMode.NONE && _acquireMode != MessageAcquireMode.PRE_ACQUIRED) + { + xfr.setCompletionListener(new MessageAcceptCompletionListener(this, _session, entry, _flowMode == MessageFlowMode.WINDOW)); + } + else if(_flowMode == MessageFlowMode.WINDOW) + { + xfr.setCompletionListener(new Method.CompletionListener() + { + public void onComplete(Method method) + { + deferredAddCredit(1, entry.getSize()); + } + }); + } + + + _postIdSettingAction.setXfr(xfr); + if(_acceptMode == MessageAcceptMode.EXPLICIT) + { + _postIdSettingAction.setAction(new ExplicitAcceptDispositionChangeListener(entry, this)); + } + else if(_acquireMode != MessageAcquireMode.PRE_ACQUIRED) + { + _postIdSettingAction.setAction(new ImplicitAcceptDispositionChangeListener(entry, this)); + } + else + { + _postIdSettingAction.setAction(null); + } + + + _session.sendMessage(xfr, _postIdSettingAction); + entry.incrementDeliveryCount(); + _deliveredCount.incrementAndGet(); + _deliveredBytes.addAndGet(entry.getSize()); + if(_acceptMode == MessageAcceptMode.NONE && _acquireMode == MessageAcquireMode.PRE_ACQUIRED) + { + forceDequeue(entry, false); + } + else if(_acquireMode == MessageAcquireMode.PRE_ACQUIRED) + { + recordUnacknowledged(entry); + } + } + else + { + forceDequeue(entry, _flowMode == MessageFlowMode.WINDOW); + + } + } + + void recordUnacknowledged(QueueEntry entry) + { + _unacknowledgedCount.incrementAndGet(); + _unacknowledgedBytes.addAndGet(entry.getSize()); + } + + private void deferredAddCredit(final int deferredMessageCredit, final long deferredSizeCredit) + { + _deferredMessageCredit += deferredMessageCredit; + _deferredSizeCredit += deferredSizeCredit; + + } + + public void flushCreditState(boolean strict) + { + if(strict || !isSuspended() || _deferredMessageCredit >= 200 + || !(_creditManager instanceof WindowCreditManager) + || ((WindowCreditManager)_creditManager).getMessageCreditLimit() < 400 ) + { + _creditManager.restoreCredit(_deferredMessageCredit, _deferredSizeCredit); + _deferredMessageCredit = 0; + _deferredSizeCredit = 0l; + } + } + + private void forceDequeue(final QueueEntry entry, final boolean restoreCredit) + { + AutoCommitTransaction dequeueTxn = new AutoCommitTransaction(getQueue().getVirtualHost().getMessageStore()); + dequeueTxn.dequeue(entry.getQueue(), entry.getMessage(), + new ServerTransaction.Action() + { + public void postCommit() + { + if (restoreCredit) + { + restoreCredit(entry); + } + entry.discard(); + } + + public void onRollback() + { + + } + }); + } + + void reject(final QueueEntry entry) + { + entry.setRedelivered(); + entry.routeToAlternate(); + if(entry.isAcquiredBy(this)) + { + entry.discard(); + } + } + + void release(final QueueEntry entry, final boolean setRedelivered) + { + if (setRedelivered) + { + entry.setRedelivered(); + } + + if (getSessionModel().isClosing() || !setRedelivered) + { + entry.decrementDeliveryCount(); + } + + if (isMaxDeliveryLimitReached(entry)) + { + sendToDLQOrDiscard(entry); + } + else + { + entry.release(); + } + } + + protected void sendToDLQOrDiscard(QueueEntry entry) + { + final Exchange alternateExchange = entry.getQueue().getAlternateExchange(); + final LogActor logActor = CurrentActor.get(); + final ServerMessage msg = entry.getMessage(); + if (alternateExchange != null) + { + final InboundMessage m = new InboundMessageAdapter(entry); + + final List<? extends BaseQueue> destinationQueues = alternateExchange.route(m); + + if (destinationQueues == null || destinationQueues.isEmpty()) + { + entry.discard(); + + logActor.message( ChannelMessages.DISCARDMSG_NOROUTE(msg.getMessageNumber(), alternateExchange.getName())); + } + else + { + entry.routeToAlternate(); + + //output operational logging for each delivery post commit + for (final BaseQueue destinationQueue : destinationQueues) + { + logActor.message( ChannelMessages.DEADLETTERMSG(msg.getMessageNumber(), destinationQueue.getName())); + } + } + } + else + { + entry.discard(); + logActor.message(ChannelMessages.DISCARDMSG_NOALTEXCH(msg.getMessageNumber(), entry.getQueue().getName(), msg.getRoutingKey())); + } + } + + private boolean isMaxDeliveryLimitReached(QueueEntry entry) + { + final int maxDeliveryLimit = entry.getQueue().getMaximumDeliveryCount(); + return (maxDeliveryLimit > 0 && entry.getDeliveryCount() >= maxDeliveryLimit); + } + + public void queueDeleted(AMQQueue queue) + { + _deleted.set(true); + } + + public boolean wouldSuspend(QueueEntry entry) + { + return !_creditManager.useCreditForMessage(entry.getMessage().getSize()); + } + + public boolean trySendLock() + { + return _stateChangeLock.tryLock(); + } + + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public void restoreCredit(QueueEntry queueEntry) + { + _creditManager.restoreCredit(1, queueEntry.getSize()); + } + + public void onDequeue(QueueEntry queueEntry) + { + // no-op for 0-10, credit restored by completing command. + } + + public void releaseQueueEntry(QueueEntry queueEntry) + { + // no-op for 0-10, credit restored by completing command. + } + + public void setStateListener(StateListener listener) + { + _stateListener = listener; + } + + public State getState() + { + return _state.get(); + } + + public AMQQueue.Context getQueueContext() + { + return _queueContext; + } + + public void setQueueContext(AMQQueue.Context queueContext) + { + _queueContext = queueContext; + } + + public boolean isActive() + { + return getState() == State.ACTIVE; + } + + public void set(String key, Object value) + { + _properties.put(key, value); + } + + public Object get(String key) + { + return _properties.get(key); + } + + + public FlowCreditManager_0_10 getCreditManager() + { + return _creditManager; + } + + + public void stop() + { + try + { + getSendLock(); + + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + _stopped.set(true); + FlowCreditManager_0_10 creditManager = getCreditManager(); + creditManager.clearCredit(); + } + finally + { + releaseSendLock(); + } + } + + public void addCredit(MessageCreditUnit unit, long value) + { + FlowCreditManager_0_10 creditManager = getCreditManager(); + + switch (unit) + { + case MESSAGE: + + creditManager.addCredit(value, 0L); + break; + case BYTE: + creditManager.addCredit(0l, value); + break; + } + + _stopped.set(false); + + if(creditManager.hasCredit()) + { + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + } + + } + + public void setFlowMode(MessageFlowMode flowMode) + { + + + _creditManager.removeListener(this); + + switch(flowMode) + { + case CREDIT: + _creditManager = new CreditCreditManager(0l,0l); + break; + case WINDOW: + _creditManager = new WindowCreditManager(0l,0l); + break; + default: + throw new RuntimeException("Unknown message flow mode: " + flowMode); + } + _flowMode = flowMode; + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + + _creditManager.addStateListener(this); + + } + + public boolean isStopped() + { + return _stopped.get(); + } + + public boolean acquires() + { + return _acquireMode == MessageAcquireMode.PRE_ACQUIRED; + } + + public void acknowledge(QueueEntry entry) + { + // TODO Fix Store Context / cleanup + if(entry.isAcquiredBy(this)) + { + _unacknowledgedBytes.addAndGet(-entry.getSize()); + _unacknowledgedCount.decrementAndGet(); + entry.discard(); + } + } + + public void flush() throws AMQException + { + flushCreditState(true); + _queue.flushSubscription(this); + stop(); + } + + public long getSubscriptionID() + { + return _subscriptionID; + } + + public LogActor getLogActor() + { + return _logActor; + } + + public boolean isTransient() + { + return false; + } + + public ServerSession getSessionModel() + { + return _session; + } + + public boolean isBrowsing() + { + return _acquireMode == MessageAcquireMode.NOT_ACQUIRED; + } + + public boolean isExclusive() + { + return getQueue().hasExclusiveSubscriber(); + } + + public boolean isDurable() + { + return false; + } + + + public boolean isExplicitAcknowledge() + { + return _acceptMode == MessageAcceptMode.EXPLICIT; + } + + public String getCreditMode() + { + return _flowMode.toString(); + } + + public String getName() + { + return _destination; + } + + public Map<String, Object> getArguments() + { + return _arguments; + } + + public boolean isSessionTransactional() + { + return _session.isTransactional(); + } + + public void queueEmpty() + { + } + + public long getCreateTime() + { + return _createTime; + } + + public String toLogString() + { + String queueInfo = MessageFormat.format(QUEUE_FORMAT, _queue.getVirtualHost().getName(), + _queue.getName()); + String result = "[" + MessageFormat.format(SUBSCRIPTION_FORMAT, getSubscriptionID()) + "(" + // queueString is "vh(/{0})/qu({1}) " so need to trim + + queueInfo.substring(0, queueInfo.length() - 1) + ")" + "] "; + return result; + } + + private String getFilterLogString() + { + StringBuilder filterLogString = new StringBuilder(); + String delimiter = ", "; + boolean hasEntries = false; + if (_filters != null && _filters.hasFilters()) + { + filterLogString.append(_filters.toString()); + hasEntries = true; + } + + if (isBrowser()) + { + if (hasEntries) + { + filterLogString.append(delimiter); + } + filterLogString.append("Browser"); + hasEntries = true; + } + + if (isDurable()) + { + if (hasEntries) + { + filterLogString.append(delimiter); + } + filterLogString.append("Durable"); + hasEntries = true; + } + + return filterLogString.toString(); + } + + public LogSubject getLogSubject() + { + return (LogSubject) this; + } + + + public void flushBatched() + { + _session.getConnection().flush(); + } + + public long getBytesOut() + { + return _deliveredBytes.longValue(); + } + + public long getMessagesOut() + { + return _deliveredCount.longValue(); + } + + public long getUnacknowledgedBytes() + { + return _unacknowledgedBytes.longValue(); + } + + public long getUnacknowledgedMessages() + { + return _unacknowledgedCount.longValue(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/TransferMessageReference.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/TransferMessageReference.java new file mode 100644 index 0000000000..0c04f22232 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/TransferMessageReference.java @@ -0,0 +1,41 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.server.message.MessageReference; + +public class TransferMessageReference extends MessageReference<MessageTransferMessage> +{ + public TransferMessageReference(MessageTransferMessage message) + { + super(message); + } + + protected void onReference(MessageTransferMessage message) + { + message.incrementReference(); + } + + protected void onRelease(MessageTransferMessage message) + { + message.decrementReference(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/WindowCreditManager.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/WindowCreditManager.java new file mode 100644 index 0000000000..8e48741b91 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/WindowCreditManager.java @@ -0,0 +1,207 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.flow.AbstractFlowCreditManager; + +public class WindowCreditManager extends AbstractFlowCreditManager implements FlowCreditManager_0_10 +{ + private static final Logger LOGGER = Logger.getLogger(WindowCreditManager.class); + + private volatile long _bytesCreditLimit; + private volatile long _messageCreditLimit; + + private volatile long _bytesUsed; + private volatile long _messageUsed; + + public WindowCreditManager() + { + this(0L, 0L); + } + + public WindowCreditManager(long bytesCreditLimit, long messageCreditLimit) + { + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + setSuspended(!hasCredit()); + + } + + public long getBytesCreditLimit() + { + return _bytesCreditLimit; + } + + public long getMessageCreditLimit() + { + return _messageCreditLimit; + } + + public synchronized void setCreditLimits(final long bytesCreditLimit, final long messageCreditLimit) + { + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + + setSuspended(!hasCredit()); + + } + + + public long getMessageCredit() + { + return _messageCreditLimit == -1L + ? Long.MAX_VALUE + : _messageUsed < _messageCreditLimit ? _messageCreditLimit - _messageUsed : 0L; + } + + public long getBytesCredit() + { + return _bytesCreditLimit == -1L + ? Long.MAX_VALUE + : _bytesUsed < _bytesCreditLimit ? _bytesCreditLimit - _bytesUsed : 0L; + } + + public synchronized void restoreCredit(final long messageCredit, final long bytesCredit) + { + _messageUsed -= messageCredit; + if(_messageUsed < 0L) + { + LOGGER.error("Message credit used value was negative: "+ _messageUsed); + _messageUsed = 0; + } + + boolean notifyIncrease = true; + + if(_messageCreditLimit > 0L) + { + notifyIncrease = (_messageUsed != _messageCreditLimit); + } + + _bytesUsed -= bytesCredit; + if(_bytesUsed < 0L) + { + LOGGER.error("Bytes credit used value was negative: "+ _messageUsed); + _bytesUsed = 0; + } + + if(_bytesCreditLimit > 0L) + { + notifyIncrease = notifyIncrease && bytesCredit>0; + + if(notifyIncrease) + { + notifyIncreaseBytesCredit(); + } + } + + setSuspended(!hasCredit()); + } + + + + public synchronized boolean hasCredit() + { + return (_bytesCreditLimit < 0L || _bytesCreditLimit > _bytesUsed) + && (_messageCreditLimit < 0L || _messageCreditLimit > _messageUsed); + } + + public synchronized boolean useCreditForMessage(final long msgSize) + { + if(_messageCreditLimit >= 0L) + { + if(_messageUsed < _messageCreditLimit) + { + if(_bytesCreditLimit < 0L) + { + _messageUsed++; + + return true; + } + else if(_bytesUsed + msgSize <= _bytesCreditLimit) + { + _messageUsed++; + _bytesUsed += msgSize; + + return true; + } + else + { + return false; + } + } + else + { + setSuspended(true); + return false; + } + } + else if(_bytesCreditLimit >= 0L) + { + if(_bytesUsed + msgSize <= _bytesCreditLimit) + { + _bytesUsed += msgSize; + + return true; + } + else + { + return false; + } + + } + else + { + return true; + } + + } + + + public synchronized void addCredit(long count, long bytes) + { + if(bytes > 0) + { + _bytesCreditLimit += bytes; + } + else if(bytes == -1) + { + _bytesCreditLimit = -1; + } + + + if(count > 0) + { + _messageCreditLimit += count; + } + else if(count == -1) + { + _messageCreditLimit = -1; + } + } + + public void clearCredit() + { + _bytesCreditLimit = 0l; + _messageCreditLimit = 0l; + setSuspended(true); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter new file mode 100644 index 0000000000..995b0fabdc --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.v0_10.MessageConverter_v0_10 diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType new file mode 100644 index 0000000000..36118fe053 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.v0_10.MessageMetaDataType_0_10 diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator new file mode 100644 index 0000000000..3d94e26671 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.v0_10.ProtocolEngineCreator_0_10 diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/test/java/org/apache/qpid/server/protocol/v0_10/ServerSessionTest.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/test/java/org/apache/qpid/server/protocol/v0_10/ServerSessionTest.java new file mode 100644 index 0000000000..421adb33a8 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/test/java/org/apache/qpid/server/protocol/v0_10/ServerSessionTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.transport.Binary; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ServerSessionTest extends QpidTestCase +{ + + private VirtualHost _virtualHost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _virtualHost = BrokerTestHelper.createVirtualHost(getName()); + GenericActor.setDefaultMessageLogger(CurrentActor.get().getRootMessageLogger()); + } + + @Override + public void tearDown() throws Exception + { + try + { + if (_virtualHost != null) + { + _virtualHost.close(); + } + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + public void testCompareTo() throws Exception + { + final Broker broker = mock(Broker.class); + when(broker.getRootMessageLogger()).thenReturn(mock(RootMessageLogger.class)); + ServerConnection connection = new ServerConnection(1, broker); + connection.setVirtualHost(_virtualHost); + ServerSession session1 = new ServerSession(connection, new ServerSessionDelegate(), + new Binary(getName().getBytes()), 0); + + // create a session with the same name but on a different connection + ServerConnection connection2 = new ServerConnection(2, broker); + connection2.setVirtualHost(_virtualHost); + ServerSession session2 = new ServerSession(connection2, new ServerSessionDelegate(), + new Binary(getName().getBytes()), 0); + + assertFalse("Unexpected compare result", session1.compareTo(session2) == 0); + assertEquals("Unexpected compare result", 0, session1.compareTo(session1)); + } + + +} diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/test/java/org/apache/qpid/server/protocol/v0_10/WindowCreditManagerTest.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/test/java/org/apache/qpid/server/protocol/v0_10/WindowCreditManagerTest.java new file mode 100644 index 0000000000..1c4a694be6 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/test/java/org/apache/qpid/server/protocol/v0_10/WindowCreditManagerTest.java @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_10; + +import org.apache.qpid.server.protocol.v0_10.WindowCreditManager; +import org.apache.qpid.test.utils.QpidTestCase; + +public class WindowCreditManagerTest extends QpidTestCase +{ + private WindowCreditManager _creditManager; + + protected void setUp() throws Exception + { + super.setUp(); + _creditManager = new WindowCreditManager(); + } + + /** + * Tests that after the credit limit is cleared (e.g. from a message.stop command), credit is + * restored (e.g. from completed MessageTransfer) without increasing the available credit, and + * more credit is added, that the 'used' count is correct and the proper values for bytes + * and message credit are returned along with appropriate 'hasCredit' results (QPID-3592). + */ + public void testRestoreCreditDecrementsUsedCountAfterCreditClear() + { + assertEquals("unexpected credit value", 0, _creditManager.getMessageCredit()); + assertEquals("unexpected credit value", 0, _creditManager.getBytesCredit()); + + //give some message credit + _creditManager.addCredit(1, 0); + assertFalse("Manager should not 'haveCredit' due to having 0 bytes credit", _creditManager.hasCredit()); + assertEquals("unexpected credit value", 1, _creditManager.getMessageCredit()); + assertEquals("unexpected credit value", 0, _creditManager.getBytesCredit()); + + //give some bytes credit + _creditManager.addCredit(0, 1); + assertTrue("Manager should 'haveCredit'", _creditManager.hasCredit()); + assertEquals("unexpected credit value", 1, _creditManager.getMessageCredit()); + assertEquals("unexpected credit value", 1, _creditManager.getBytesCredit()); + + //use all the credit + _creditManager.useCreditForMessage(1); + assertEquals("unexpected credit value", 0, _creditManager.getBytesCredit()); + assertEquals("unexpected credit value", 0, _creditManager.getMessageCredit()); + assertFalse("Manager should not 'haveCredit'", _creditManager.hasCredit()); + + //clear credit out (eg from a message.stop command) + _creditManager.clearCredit(); + assertEquals("unexpected credit value", 0, _creditManager.getBytesCredit()); + assertEquals("unexpected credit value", 0, _creditManager.getMessageCredit()); + assertFalse("Manager should not 'haveCredit'", _creditManager.hasCredit()); + + //restore credit (e.g the original message transfer command got completed) + //this should not increase credit, because it is now limited to 0 + _creditManager.restoreCredit(1, 1); + assertEquals("unexpected credit value", 0, _creditManager.getBytesCredit()); + assertEquals("unexpected credit value", 0, _creditManager.getMessageCredit()); + assertFalse("Manager should not 'haveCredit'", _creditManager.hasCredit()); + + //give more credit to open the window again + _creditManager.addCredit(1, 1); + assertEquals("unexpected credit value", 1, _creditManager.getBytesCredit()); + assertEquals("unexpected credit value", 1, _creditManager.getMessageCredit()); + assertTrue("Manager should 'haveCredit'", _creditManager.hasCredit()); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/build.xml b/qpid/java/broker-plugins/amqp-0-8-protocol/build.xml new file mode 100644 index 0000000000..45086b6242 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/build.xml @@ -0,0 +1,33 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins AMQP 0-8 Protocol" default="build"> + <property name="module.depends" value="common broker" /> + <property name="module.test.depends" value="common/tests broker/tests" /> + + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + <property name="broker-plugins-amqp-0-8-protocol.libs" value="" /> + + <property name="broker.plugin" value="true"/> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks"/> + +</project> diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQChannel.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQChannel.java new file mode 100644 index 0000000000..a603807f87 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQChannel.java @@ -0,0 +1,1633 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.TransactionTimeoutHelper; +import org.apache.qpid.server.TransactionTimeoutHelper.CloseAction; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.flow.Pre0_10CreditManager; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.AMQPChannelActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ChannelMessages; +import org.apache.qpid.server.logging.messages.ExchangeMessages; +import org.apache.qpid.server.logging.subjects.ChannelLogSubject; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.protocol.v0_8.output.ProtocolOutputConverter; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.InboundMessageAdapter; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.RecordDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.AsyncAutoCommitTransaction; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.LocalTransaction.ActivityTimeAccessor; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.TransportException; + +public class AMQChannel implements AMQSessionModel, AsyncAutoCommitTransaction.FutureRecorder +{ + public static final int DEFAULT_PREFETCH = 4096; + + private static final Logger _logger = Logger.getLogger(AMQChannel.class); + + //TODO use Broker property to configure message authorization requirements + private boolean _messageAuthorizationRequired = Boolean.getBoolean(BrokerProperties.PROPERTY_MSG_AUTH); + + private final int _channelId; + + + private final Pre0_10CreditManager _creditManager = new Pre0_10CreditManager(0l,0l); + + /** + * 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 long _deliveryTag = 0; + + /** A channel has a default queue (the last declared) that is used when no queue name is explicitly 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 IncomingMessage _currentMessage; + + /** Maps from consumer tag to subscription instance. Allows us to unsubscribe from a queue. */ + private final Map<AMQShortString, Subscription> _tag2SubscriptionMap = new HashMap<AMQShortString, Subscription>(); + + private final MessageStore _messageStore; + + private final LinkedList<AsyncCommand> _unfinishedCommandsQueue = new LinkedList<AsyncCommand>(); + + private UnacknowledgedMessageMap _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(DEFAULT_PREFETCH); + + // Set of messages being acknowledged in the current transaction + private SortedSet<QueueEntry> _acknowledgedMessages = new TreeSet<QueueEntry>(); + + private final AtomicBoolean _suspended = new AtomicBoolean(false); + + private ServerTransaction _transaction; + + private final AtomicLong _txnStarts = new AtomicLong(0); + private final AtomicLong _txnCommits = new AtomicLong(0); + private final AtomicLong _txnRejects = new AtomicLong(0); + private final AtomicLong _txnCount = new AtomicLong(0); + + private final AMQProtocolSession _session; + private AtomicBoolean _closing = new AtomicBoolean(false); + + private final Set<Object> _blockingEntities = Collections.synchronizedSet(new HashSet<Object>()); + + private final AtomicBoolean _blocking = new AtomicBoolean(false); + + + private LogActor _actor; + private LogSubject _logSubject; + private volatile boolean _rollingBack; + + private static final Runnable NULL_TASK = new Runnable() { public void run() {} }; + private List<QueueEntry> _resendList = new ArrayList<QueueEntry>(); + private static final + AMQShortString IMMEDIATE_DELIVERY_REPLY_TEXT = new AMQShortString("Immediate delivery is not possible."); + private long _createTime = System.currentTimeMillis(); + + private final ClientDeliveryMethod _clientDeliveryMethod; + + private final TransactionTimeoutHelper _transactionTimeoutHelper; + private final UUID _id = UUID.randomUUID(); + + public AMQChannel(AMQProtocolSession session, int channelId, MessageStore messageStore) + throws AMQException + { + _session = session; + _channelId = channelId; + + _actor = new AMQPChannelActor(this, session.getLogActor().getRootMessageLogger()); + _logSubject = new ChannelLogSubject(this); + _actor.message(ChannelMessages.CREATE()); + + _messageStore = messageStore; + + // by default the session is non-transactional + _transaction = new AsyncAutoCommitTransaction(_messageStore, this); + + _clientDeliveryMethod = session.createDeliveryMethod(_channelId); + + _transactionTimeoutHelper = new TransactionTimeoutHelper(_logSubject, new CloseAction() + { + @Override + public void doTimeoutAction(String reason) throws AMQException + { + closeConnection(reason); + } + }); + } + + /** Sets this channel to be part of a local transaction */ + public void setLocalTransactional() + { + _transaction = new LocalTransaction(_messageStore, new ActivityTimeAccessor() + { + @Override + public long getActivityTime() + { + return _session.getLastReceivedTime(); + } + }); + _txnStarts.incrementAndGet(); + } + + public boolean isTransactional() + { + return _transaction.isTransactional(); + } + + public void receivedComplete() + { + sync(); + } + + private void incrementOutstandingTxnsIfNecessary() + { + if(isTransactional()) + { + //There can currently only be at most one outstanding transaction + //due to only having LocalTransaction support. Set value to 1 if 0. + _txnCount.compareAndSet(0,1); + } + } + + private void decrementOutstandingTxnsIfNecessary() + { + if(isTransactional()) + { + //There can currently only be at most one outstanding transaction + //due to only having LocalTransaction support. Set value to 0 if 1. + _txnCount.compareAndSet(1,0); + } + } + + public Long getTxnCommits() + { + return _txnCommits.get(); + } + + public Long getTxnRejects() + { + return _txnRejects.get(); + } + + public Long getTxnCount() + { + return _txnCount.get(); + } + + public Long getTxnStart() + { + return _txnStarts.get(); + } + + public int getChannelId() + { + return _channelId; + } + + public void setPublishFrame(MessagePublishInfo info, final Exchange e) throws AMQSecurityException + { + String routingKey = info.getRoutingKey() == null ? null : info.getRoutingKey().asString(); + SecurityManager securityManager = getVirtualHost().getSecurityManager(); + if (!securityManager.authorisePublish(info.isImmediate(), routingKey, e.getName())) + { + throw new AMQSecurityException("Permission denied: " + e.getName()); + } + _currentMessage = new IncomingMessage(info, getProtocolSession().getReference()); + _currentMessage.setExchange(e); + } + + public void publishContentHeader(ContentHeaderBody contentHeaderBody) + throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content header without previously receiving a BasicPublish frame"); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Content header received on channel " + _channelId); + } + + _currentMessage.setContentHeaderBody(contentHeaderBody); + + _currentMessage.setExpiration(); + + _currentMessage.headersReceived(getProtocolSession().getLastReceivedTime()); + + _currentMessage.route(); + + deliverCurrentMessageIfComplete(); + } + } + + private void deliverCurrentMessageIfComplete() + throws AMQException + { + // check and deliver if header says body length is zero + if (_currentMessage.allContentReceived()) + { + try + { + final List<? extends BaseQueue> destinationQueues = _currentMessage.getDestinationQueues(); + + if(!checkMessageUserId(_currentMessage.getContentHeader())) + { + _transaction.addPostTransactionAction(new WriteReturnAction(AMQConstant.ACCESS_REFUSED, "Access Refused", _currentMessage)); + } + else + { + if(destinationQueues == null || destinationQueues.isEmpty()) + { + handleUnroutableMessage(); + } + else + { + final StoredMessage<MessageMetaData> handle = _messageStore.addMessage(_currentMessage.getMessageMetaData()); + _currentMessage.setStoredMessage(handle); + int bodyCount = _currentMessage.getBodyCount(); + if(bodyCount > 0) + { + long bodyLengthReceived = 0; + for(int i = 0 ; i < bodyCount ; i++) + { + ContentChunk contentChunk = _currentMessage.getContentChunk(i); + handle.addContent((int)bodyLengthReceived, ByteBuffer.wrap(contentChunk.getData())); + bodyLengthReceived += contentChunk.getSize(); + } + } + + _transaction.addPostTransactionAction(new ServerTransaction.Action() + { + public void postCommit() + { + } + + public void onRollback() + { + handle.remove(); + } + }); + + _transaction.enqueue(destinationQueues, _currentMessage, new MessageDeliveryAction(_currentMessage, destinationQueues)); + incrementOutstandingTxnsIfNecessary(); + _currentMessage.getStoredMessage().flushToStore(); + } + } + } + finally + { + long bodySize = _currentMessage.getSize(); + long timestamp = ((BasicContentHeaderProperties) _currentMessage.getContentHeader().getProperties()).getTimestamp(); + _session.registerMessageReceived(bodySize, timestamp); + _currentMessage = null; + } + } + + } + + /** + * Either throws a {@link AMQConnectionException} or returns the message + * + * Pre-requisite: the current message is judged to have no destination queues. + * + * @throws AMQConnectionException if the message is mandatoryclose-on-no-route + * @see AMQProtocolSession#isCloseWhenNoRoute() + */ + private void handleUnroutableMessage() throws AMQConnectionException + { + boolean mandatory = _currentMessage.isMandatory(); + String description = currentMessageDescription(); + boolean closeOnNoRoute = _session.isCloseWhenNoRoute(); + + if(_logger.isDebugEnabled()) + { + _logger.debug(String.format( + "Unroutable message %s, mandatory=%s, transactionalSession=%s, closeOnNoRoute=%s", + description, mandatory, isTransactional(), closeOnNoRoute)); + } + + if (mandatory && isTransactional() && _session.isCloseWhenNoRoute()) + { + throw new AMQConnectionException( + AMQConstant.NO_ROUTE, + "No route for message " + currentMessageDescription(), + 0, 0, // default class and method ids + getProtocolSession().getProtocolVersion().getMajorVersion(), + getProtocolSession().getProtocolVersion().getMinorVersion(), + (Throwable) null); + } + + if (mandatory || _currentMessage.isImmediate()) + { + _transaction.addPostTransactionAction(new WriteReturnAction(AMQConstant.NO_ROUTE, "No Route for message " + currentMessageDescription(), _currentMessage)); + } + else + { + _actor.message(ExchangeMessages.DISCARDMSG(_currentMessage.getExchange().asString(), _currentMessage.getRoutingKey())); + } + } + + private String currentMessageDescription() + { + if(_currentMessage == null || !_currentMessage.allContentReceived()) + { + throw new IllegalStateException("Cannot create message description for message: " + _currentMessage); + } + + return String.format( + "[Exchange: %s, Routing key: %s]", + _currentMessage.getExchange(), + _currentMessage.getRoutingKey()); + } + + public void publishContentBody(ContentBody contentBody) throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content body without previously receiving a JmsPublishBody"); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug(debugIdentity() + " content body received on channel " + _channelId); + } + + try + { + final ContentChunk contentChunk = + _session.getMethodRegistry().getProtocolVersionMethodConverter().convertToContentChunk(contentBody); + + _currentMessage.addContentBodyFrame(contentChunk); + + deliverCurrentMessageIfComplete(); + } + 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; + } + catch (RuntimeException 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; + } + } + + public long getNextDeliveryTag() + { + return ++_deliveryTag; + } + + public int getNextConsumerTag() + { + return ++_consumerTag; + } + + + public Subscription getSubscription(AMQShortString subscription) + { + return _tag2SubscriptionMap.get(subscription); + } + + /** + * 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 acks Are acks enabled for this subscriber + * @param filters Filters to apply to this subscriber + * + * @param noLocal Flag stopping own messages being received. + * @param exclusive Flag requesting exclusive access to the queue + * @return the consumer tag. This is returned to the subscriber and used in subsequent unsubscribe requests + * + * @throws AMQException if something goes wrong + */ + public AMQShortString subscribeToQueue(AMQShortString tag, AMQQueue queue, boolean acks, + FieldTable filters, boolean noLocal, boolean exclusive) throws AMQException + { + if (tag == null) + { + tag = new AMQShortString("sgen_" + getNextConsumerTag()); + } + + if (_tag2SubscriptionMap.containsKey(tag)) + { + throw new AMQException("Consumer already exists with same tag: " + tag); + } + + Subscription subscription = + SubscriptionFactoryImpl.INSTANCE.createSubscription(_channelId, _session, tag, acks, filters, noLocal, _creditManager); + + + // So to keep things straight we put before the call and catch all exceptions from the register and tidy up. + // We add before we register as the Async Delivery process may AutoClose the subscriber + // so calling _cT2QM.remove before we have done put which was after the register succeeded. + // So to keep things straight we put before the call and catch all exceptions from the register and tidy up. + + _tag2SubscriptionMap.put(tag, subscription); + + try + { + queue.registerSubscription(subscription, exclusive); + } + catch (AMQException e) + { + _tag2SubscriptionMap.remove(tag); + throw e; + } + catch (RuntimeException e) + { + _tag2SubscriptionMap.remove(tag); + throw e; + } + return tag; + } + + /** + * Unsubscribe a consumer from a queue. + * @param consumerTag + * @return true if the consumerTag had a mapped queue that could be unregistered. + * @throws AMQException + */ + public boolean unsubscribeConsumer(AMQShortString consumerTag) throws AMQException + { + + Subscription sub = _tag2SubscriptionMap.remove(consumerTag); + if (sub != null) + { + try + { + sub.getSendLock(); + sub.getQueue().unregisterSubscription(sub); + } + finally + { + sub.releaseSendLock(); + } + return true; + } + else + { + _logger.warn("Attempt to unsubscribe consumer with tag '"+consumerTag+"' which is not registered."); + } + return false; + } + + /** + * Called from the protocol session to close this channel and clean up. T + * + * @throws AMQException if there is an error during closure + */ + @Override + public void close() throws AMQException + { + close(null, null); + } + + public void close(AMQConstant cause, String message) throws AMQException + { + if(!_closing.compareAndSet(false, true)) + { + //Channel is already closing + return; + } + + LogMessage operationalLogMessage = cause == null ? + ChannelMessages.CLOSE() : + ChannelMessages.CLOSE_FORCED(cause.getCode(), message); + CurrentActor.get().message(_logSubject, operationalLogMessage); + + unsubscribeAllConsumers(); + _transaction.rollback(); + + try + { + requeue(); + } + catch (AMQException e) + { + _logger.error("Caught AMQException whilst attempting to requeue:" + e); + } + catch (TransportException e) + { + _logger.error("Caught TransportException whilst attempting to requeue:" + e); + } + } + + private void unsubscribeAllConsumers() throws AMQException + { + if (_logger.isInfoEnabled()) + { + if (!_tag2SubscriptionMap.isEmpty()) + { + _logger.info("Unsubscribing all consumers on channel " + toString()); + } + else + { + _logger.info("No consumers to unsubscribe on channel " + toString()); + } + } + + for (Map.Entry<AMQShortString, Subscription> me : _tag2SubscriptionMap.entrySet()) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Unsubscribing consumer '" + me.getKey() + "' on channel " + toString()); + } + + Subscription sub = me.getValue(); + + try + { + sub.getSendLock(); + sub.getQueue().unregisterSubscription(sub); + } + finally + { + sub.releaseSendLock(); + } + + } + + _tag2SubscriptionMap.clear(); + } + + /** + * Add a message to the channel-based list of unacknowledged messages + * + * @param entry the record of the message on the queue that was delivered + * @param deliveryTag the delivery tag used when delivering the message (see protocol spec for description of the + * delivery tag) + * @param subscription The consumer that is to acknowledge this message. + */ + public void addUnacknowledgedMessage(QueueEntry entry, long deliveryTag, Subscription subscription) + { + if (_logger.isDebugEnabled()) + { + if (entry.getQueue() == null) + { + _logger.debug("Adding unacked message with a null queue:" + entry); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug(debugIdentity() + " Adding unacked message(" + entry.getMessage().toString() + " DT:" + deliveryTag + + ") with a queue(" + entry.getQueue() + ") for " + subscription); + } + } + } + + _unacknowledgedMessageMap.add(deliveryTag, entry); + + } + + private final String id = "(" + System.identityHashCode(this) + ")"; + + public String debugIdentity() + { + return _channelId + id; + } + + /** + * Called to attempt re-delivery 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<QueueEntry> messagesToBeDelivered = _unacknowledgedMessageMap.cancelAllMessages(); + + if (!messagesToBeDelivered.isEmpty()) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Requeuing " + messagesToBeDelivered.size() + " unacked messages. for " + toString()); + } + + } + + for (QueueEntry unacked : messagesToBeDelivered) + { + if (!unacked.isQueueDeleted()) + { + // Mark message redelivered + unacked.setRedelivered(); + + // Ensure message is released for redelivery + unacked.release(); + + } + else + { + unacked.discard(); + } + } + + } + + /** + * Requeue a single message + * + * @param deliveryTag The message to requeue + * + * @throws AMQException If something goes wrong. + */ + public void requeue(long deliveryTag) throws AMQException + { + QueueEntry unacked = _unacknowledgedMessageMap.remove(deliveryTag); + + if (unacked != null) + { + // Mark message redelivered + unacked.setRedelivered(); + + // Ensure message is released for redelivery + if (!unacked.isQueueDeleted()) + { + + // Ensure message is released for redelivery + unacked.release(); + + } + else + { + _logger.warn(System.identityHashCode(this) + " Requested requeue of message(" + unacked + + "):" + deliveryTag + " but no queue defined and no DeadLetter queue so DROPPING message."); + + unacked.discard(); + } + } + else + { + _logger.warn("Requested requeue of message:" + deliveryTag + " but no such delivery tag exists." + + _unacknowledgedMessageMap.size()); + + } + + } + + public boolean isMaxDeliveryCountEnabled(final long deliveryTag) + { + final QueueEntry queueEntry = _unacknowledgedMessageMap.get(deliveryTag); + if (queueEntry != null) + { + final int maximumDeliveryCount = queueEntry.getQueue().getMaximumDeliveryCount(); + return maximumDeliveryCount > 0; + } + + return false; + } + + public boolean isDeliveredTooManyTimes(final long deliveryTag) + { + final QueueEntry queueEntry = _unacknowledgedMessageMap.get(deliveryTag); + if (queueEntry != null) + { + final int maximumDeliveryCount = queueEntry.getQueue().getMaximumDeliveryCount(); + final int numDeliveries = queueEntry.getDeliveryCount(); + return maximumDeliveryCount != 0 && numDeliveries >= maximumDeliveryCount; + } + + return false; + } + + /** + * Called to resend all outstanding unacknowledged messages to this same channel. + * + * @param requeue Are the messages to be requeued or dropped. + * + * @throws AMQException When something goes wrong. + */ + public void resend(final boolean requeue) throws AMQException + { + + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("unacked map Size:" + _unacknowledgedMessageMap.size()); + } + + // Process the Unacked-Map. + // Marking messages who still have a consumer for to be resent + // and those that don't to be requeued. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, + msgToRequeue, + msgToResend, + requeue, + _messageStore)); + + + // Process Messages to Resend + if (_logger.isDebugEnabled()) + { + if (!msgToResend.isEmpty()) + { + _logger.debug("Preparing (" + msgToResend.size() + ") message to resend."); + } + else + { + _logger.debug("No message to resend."); + } + } + + for (Map.Entry<Long, QueueEntry> entry : msgToResend.entrySet()) + { + QueueEntry message = entry.getValue(); + long deliveryTag = entry.getKey(); + + //Amend the delivery counter as the client hasn't seen these messages yet. + message.decrementDeliveryCount(); + + AMQQueue queue = message.getQueue(); + + // Without any details from the client about what has been processed we have to mark + // all messages in the unacked map as redelivered. + message.setRedelivered(); + + Subscription sub = message.getDeliveredSubscription(); + + if (sub != null) + { + + if(!queue.resend(message,sub)) + { + msgToRequeue.put(deliveryTag, message); + } + } + else + { + + if (_logger.isInfoEnabled()) + { + _logger.info("DeliveredSubscription not recorded so just requeueing(" + message.toString() + + ")to prevent loss"); + } + // move this message to requeue + msgToRequeue.put(deliveryTag, message); + } + } // for all messages + // } else !isSuspend + + if (_logger.isInfoEnabled()) + { + if (!msgToRequeue.isEmpty()) + { + _logger.info("Preparing (" + msgToRequeue.size() + ") message to requeue to."); + } + } + + // Process Messages to Requeue at the front of the queue + for (Map.Entry<Long, QueueEntry> entry : msgToRequeue.entrySet()) + { + QueueEntry message = entry.getValue(); + long deliveryTag = entry.getKey(); + + //Amend the delivery counter as the client hasn't seen these messages yet. + message.decrementDeliveryCount(); + + _unacknowledgedMessageMap.remove(deliveryTag); + + message.setRedelivered(); + message.release(); + + } + } + + + /** + * 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 + { + Collection<QueueEntry> ackedMessages = getAckedMessages(deliveryTag, multiple); + _transaction.dequeue(ackedMessages, new MessageAcknowledgeAction(ackedMessages)); + } + + private Collection<QueueEntry> getAckedMessages(long deliveryTag, boolean multiple) + { + + return _unacknowledgedMessageMap.acknowledge(deliveryTag, multiple); + + } + + /** + * Used only for testing purposes. + * + * @return the map of unacknowledged messages + */ + public UnacknowledgedMessageMap getUnacknowledgedMessageMap() + { + return _unacknowledgedMessageMap; + } + + /** + * Called from the ChannelFlowHandler to suspend this Channel + * @param suspended boolean, should this Channel be suspended + */ + public void setSuspended(boolean suspended) + { + boolean wasSuspended = _suspended.getAndSet(suspended); + if (wasSuspended != suspended) + { + // Log Flow Started before we start the subscriptions + if (!suspended) + { + _actor.message(_logSubject, ChannelMessages.FLOW("Started")); + } + + + // This section takes two different approaches to perform to perform + // the same function. Ensuring that the Subscription has taken note + // of the change in Channel State + + // Here we have become unsuspended and so we ask each the queue to + // perform an Async delivery for each of the subscriptions in this + // Channel. The alternative would be to ensure that the subscription + // had received the change in suspension state. That way the logic + // behind decieding to start an async delivery was located with the + // Subscription. + if (wasSuspended) + { + // may need to deliver queued messages + for (Subscription s : _tag2SubscriptionMap.values()) + { + s.getQueue().deliverAsync(s); + } + } + + + // Here we have become suspended so we need to ensure that each of + // the Subscriptions has noticed this change so that we can be sure + // they are not still sending messages. Again the code here is a + // very simplistic approach to ensure that the change of suspension + // has been noticed by each of the Subscriptions. Unlike the above + // case we don't actually need to do anything else. + if (!wasSuspended) + { + // may need to deliver queued messages + for (Subscription s : _tag2SubscriptionMap.values()) + { + try + { + s.getSendLock(); + } + finally + { + s.releaseSendLock(); + } + } + } + + + // Log Suspension only after we have confirmed all suspensions are + // stopped. + if (suspended) + { + _actor.message(_logSubject, ChannelMessages.FLOW("Stopped")); + } + + } + } + + public boolean isSuspended() + { + return _suspended.get() || _closing.get() || _session.isClosing(); + } + + public void commit() throws AMQException + { + commit(null, false); + } + + + public void commit(final Runnable immediateAction, boolean async) throws AMQException + { + + if (!isTransactional()) + { + throw new AMQException("Fatal error: commit called on non-transactional channel"); + } + + if(async && _transaction instanceof LocalTransaction) + { + + ((LocalTransaction)_transaction).commitAsync(new Runnable() + { + @Override + public void run() + { + immediateAction.run(); + _txnCommits.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + }); + } + else + { + _transaction.commit(immediateAction); + + _txnCommits.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + } + + public void rollback() throws AMQException + { + rollback(NULL_TASK); + } + + public void rollback(Runnable postRollbackTask) throws AMQException + { + if (!isTransactional()) + { + throw new AMQException("Fatal error: commit called on non-transactional channel"); + } + + // stop all subscriptions + _rollingBack = true; + boolean requiresSuspend = _suspended.compareAndSet(false,true); + + // ensure all subscriptions have seen the change to the channel state + for(Subscription sub : _tag2SubscriptionMap.values()) + { + sub.getSendLock(); + sub.releaseSendLock(); + } + + try + { + _transaction.rollback(); + } + finally + { + _rollingBack = false; + + _txnRejects.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + + postRollbackTask.run(); + + for(QueueEntry entry : _resendList) + { + Subscription sub = entry.getDeliveredSubscription(); + if(sub == null || sub.isClosed()) + { + entry.release(); + } + else + { + sub.getQueue().resend(entry, sub); + } + } + _resendList.clear(); + + if(requiresSuspend) + { + _suspended.set(false); + for(Subscription sub : _tag2SubscriptionMap.values()) + { + sub.getQueue().deliverAsync(sub); + } + + } + } + + public String toString() + { + return "["+_session.toString()+":"+_channelId+"]"; + } + + public void setDefaultQueue(AMQQueue queue) + { + _defaultQueue = queue; + } + + public AMQQueue getDefaultQueue() + { + return _defaultQueue; + } + + + public boolean isClosing() + { + return _closing.get(); + } + + public AMQProtocolSession getProtocolSession() + { + return _session; + } + + public FlowCreditManager getCreditManager() + { + return _creditManager; + } + + public void setCredit(final long prefetchSize, final int prefetchCount) + { + _actor.message(ChannelMessages.PREFETCH_SIZE(prefetchSize, prefetchCount)); + _creditManager.setCreditLimits(prefetchSize, prefetchCount); + } + + public MessageStore getMessageStore() + { + return _messageStore; + } + + public ClientDeliveryMethod getClientDeliveryMethod() + { + return _clientDeliveryMethod; + } + + private final RecordDeliveryMethod _recordDeliveryMethod = new RecordDeliveryMethod() + { + + public void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag) + { + addUnacknowledgedMessage(entry, deliveryTag, sub); + } + }; + + public RecordDeliveryMethod getRecordDeliveryMethod() + { + return _recordDeliveryMethod; + } + + + private AMQMessage createAMQMessage(IncomingMessage incomingMessage) + throws AMQException + { + + AMQMessage message = new AMQMessage(incomingMessage.getStoredMessage()); + + message.setExpiration(incomingMessage.getExpiration()); + message.setConnectionIdentifier(_session.getReference()); + return message; + } + + private boolean checkMessageUserId(ContentHeaderBody header) + { + AMQShortString userID = + header.getProperties() instanceof BasicContentHeaderProperties + ? ((BasicContentHeaderProperties) header.getProperties()).getUserId() + : null; + + return (!_messageAuthorizationRequired || _session.getAuthorizedPrincipal().getName().equals(userID == null? "" : userID.toString())); + + } + + @Override + public UUID getId() + { + return _id; + } + + public AMQConnectionModel getConnectionModel() + { + return _session; + } + + public String getClientID() + { + return String.valueOf(_session.getContextKey()); + } + + public LogSubject getLogSubject() + { + return _logSubject; + } + + @Override + public int compareTo(AMQSessionModel o) + { + return getId().compareTo(o.getId()); + } + + private class MessageDeliveryAction implements ServerTransaction.Action + { + private IncomingMessage _incommingMessage; + private List<? extends BaseQueue> _destinationQueues; + + public MessageDeliveryAction(IncomingMessage currentMessage, + List<? extends BaseQueue> destinationQueues) + { + _incommingMessage = currentMessage; + _destinationQueues = destinationQueues; + } + + public void postCommit() + { + try + { + final boolean immediate = _incommingMessage.isImmediate(); + + final AMQMessage amqMessage = createAMQMessage(_incommingMessage); + MessageReference ref = amqMessage.newReference(); + + for(int i = 0; i < _destinationQueues.size(); i++) + { + BaseQueue queue = _destinationQueues.get(i); + + BaseQueue.PostEnqueueAction action; + + if(immediate) + { + action = new ImmediateAction(queue); + } + else + { + action = null; + } + + queue.enqueue(amqMessage, isTransactional(), action); + + if(queue instanceof AMQQueue) + { + ((AMQQueue)queue).checkCapacity(AMQChannel.this); + } + + } + + _incommingMessage.getStoredMessage().flushToStore(); + ref.release(); + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + } + + public void onRollback() + { + // Maybe keep track of entries that were created and then delete them here in case of failure + // to in memory enqueue + } + + private class ImmediateAction implements BaseQueue.PostEnqueueAction + { + private final BaseQueue _queue; + + public ImmediateAction(BaseQueue queue) + { + _queue = queue; + } + + public void onEnqueue(QueueEntry entry) + { + if (!entry.getDeliveredToConsumer() && entry.acquire()) + { + + + ServerTransaction txn = new LocalTransaction(_messageStore); + Collection<QueueEntry> entries = new ArrayList<QueueEntry>(1); + entries.add(entry); + final AMQMessage message = (AMQMessage) entry.getMessage(); + txn.dequeue(_queue, entry.getMessage(), + new MessageAcknowledgeAction(entries) + { + @Override + public void postCommit() + { + try + { + final + ProtocolOutputConverter outputConverter = + _session.getProtocolOutputConverter(); + + outputConverter.writeReturn(message.getMessagePublishInfo(), + message.getContentHeaderBody(), + message, + _channelId, + AMQConstant.NO_CONSUMERS.getCode(), + IMMEDIATE_DELIVERY_REPLY_TEXT); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + super.postCommit(); + } + } + ); + txn.commit(); + + + } + + } + } + } + + private class MessageAcknowledgeAction implements ServerTransaction.Action + { + private final Collection<QueueEntry> _ackedMessages; + + public MessageAcknowledgeAction(Collection<QueueEntry> ackedMessages) + { + _ackedMessages = ackedMessages; + } + + public void postCommit() + { + try + { + for(QueueEntry entry : _ackedMessages) + { + entry.discard(); + } + } + finally + { + _acknowledgedMessages.clear(); + } + + } + + public void onRollback() + { + // explicit rollbacks resend the message after the rollback-ok is sent + if(_rollingBack) + { + _resendList.addAll(_ackedMessages); + } + else + { + try + { + for(QueueEntry entry : _ackedMessages) + { + entry.release(); + } + } + finally + { + _acknowledgedMessages.clear(); + } + } + + } + } + + private class WriteReturnAction implements ServerTransaction.Action + { + private final AMQConstant _errorCode; + private final IncomingMessage _message; + private final String _description; + + public WriteReturnAction(AMQConstant errorCode, + String description, + IncomingMessage message) + { + _errorCode = errorCode; + _message = message; + _description = description; + } + + public void postCommit() + { + try + { + _session.getProtocolOutputConverter().writeReturn(_message.getMessagePublishInfo(), + _message.getContentHeader(), + _message, + _channelId, + _errorCode.getCode(), + AMQShortString.validValueOf(_description)); + } + catch (AMQException e) + { + //TODO + throw new RuntimeException(e); + } + + } + + public void onRollback() + { + } + } + + + public LogActor getLogActor() + { + return _actor; + } + + public synchronized void block() + { + if(_blockingEntities.add(this)) + { + if(_blocking.compareAndSet(false,true)) + { + _actor.message(_logSubject, ChannelMessages.FLOW_ENFORCED("** All Queues **")); + flow(false); + } + } + } + + public synchronized void unblock() + { + if(_blockingEntities.remove(this)) + { + if(_blockingEntities.isEmpty() && _blocking.compareAndSet(true,false)) + { + _actor.message(_logSubject, ChannelMessages.FLOW_REMOVED()); + + flow(true); + } + } + } + + public synchronized void block(AMQQueue queue) + { + if(_blockingEntities.add(queue)) + { + + if(_blocking.compareAndSet(false,true)) + { + _actor.message(_logSubject, ChannelMessages.FLOW_ENFORCED(queue.getName())); + flow(false); + } + } + } + + public synchronized void unblock(AMQQueue queue) + { + if(_blockingEntities.remove(queue)) + { + if(_blockingEntities.isEmpty() && _blocking.compareAndSet(true,false) && !isClosing()) + { + _actor.message(_logSubject, ChannelMessages.FLOW_REMOVED()); + + flow(true); + } + } + } + + public boolean onSameConnection(InboundMessage inbound) + { + if(inbound instanceof IncomingMessage) + { + IncomingMessage incoming = (IncomingMessage) inbound; + return getProtocolSession().getReference() == incoming.getConnectionReference(); + } + return false; + } + + public int getUnacknowledgedMessageCount() + { + return getUnacknowledgedMessageMap().size(); + } + + private void flow(boolean flow) + { + MethodRegistry methodRegistry = _session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createChannelFlowBody(flow); + _session.writeFrame(responseBody.generateFrame(_channelId)); + } + + @Override + public boolean getBlocking() + { + return _blocking.get(); + } + + public VirtualHost getVirtualHost() + { + return getProtocolSession().getVirtualHost(); + } + + public void checkTransactionStatus(long openWarn, long openClose, long idleWarn, long idleClose) throws AMQException + { + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, openWarn, openClose, idleWarn, idleClose); + } + + /** + * Typically called from the HouseKeepingThread instead of the main receiver thread, + * therefore uses a lock to close the connection in a thread-safe manner. + */ + private void closeConnection(String reason) throws AMQException + { + Lock receivedLock = _session.getReceivedLock(); + receivedLock.lock(); + try + { + _session.close(AMQConstant.RESOURCE_ERROR, reason); + } + finally + { + receivedLock.unlock(); + } + } + + public void deadLetter(long deliveryTag) throws AMQException + { + final UnacknowledgedMessageMap unackedMap = getUnacknowledgedMessageMap(); + final QueueEntry rejectedQueueEntry = unackedMap.get(deliveryTag); + + if (rejectedQueueEntry == null) + { + _logger.warn("No message found, unable to DLQ delivery tag: " + deliveryTag); + return; + } + else + { + final ServerMessage msg = rejectedQueueEntry.getMessage(); + + final AMQQueue queue = rejectedQueueEntry.getQueue(); + + final Exchange altExchange = queue.getAlternateExchange(); + unackedMap.remove(deliveryTag); + + if (altExchange == null) + { + _logger.debug("No alternate exchange configured for queue, must discard the message as unable to DLQ: delivery tag: " + deliveryTag); + _actor.message(_logSubject, ChannelMessages.DISCARDMSG_NOALTEXCH(msg.getMessageNumber(), queue.getName(), msg.getRoutingKey())); + rejectedQueueEntry.discard(); + return; + } + + final InboundMessage m = new InboundMessageAdapter(rejectedQueueEntry); + + final List<? extends BaseQueue> destinationQueues = altExchange.route(m); + + if (destinationQueues == null || destinationQueues.isEmpty()) + { + _logger.debug("Routing process provided no queues to enqueue the message on, must discard message as unable to DLQ: delivery tag: " + deliveryTag); + _actor.message(_logSubject, ChannelMessages.DISCARDMSG_NOROUTE(msg.getMessageNumber(), altExchange.getName())); + rejectedQueueEntry.discard(); + return; + } + + rejectedQueueEntry.routeToAlternate(); + + //output operational logging for each delivery post commit + for (final BaseQueue destinationQueue : destinationQueues) + { + _actor.message(_logSubject, ChannelMessages.DEADLETTERMSG(msg.getMessageNumber(), destinationQueue.getName())); + } + + } + } + + public void recordFuture(final StoreFuture future, final ServerTransaction.Action action) + { + _unfinishedCommandsQueue.add(new AsyncCommand(future, action)); + } + + public void sync() + { + if(_logger.isDebugEnabled()) + { + _logger.debug("sync() called on channel " + debugIdentity()); + } + + AsyncCommand cmd; + while((cmd = _unfinishedCommandsQueue.poll()) != null) + { + cmd.awaitReadyForCompletion(); + cmd.complete(); + } + if(_transaction instanceof LocalTransaction) + { + ((LocalTransaction)_transaction).sync(); + } + } + + private static class AsyncCommand + { + private final StoreFuture _future; + private ServerTransaction.Action _action; + + public AsyncCommand(final StoreFuture future, final ServerTransaction.Action action) + { + _future = future; + _action = action; + } + + void awaitReadyForCompletion() + { + _future.waitForCompletion(); + } + + void complete() + { + if(!_future.isComplete()) + { + _future.waitForCompletion(); + } + _action.postCommit(); + _action = null; + } + } + + @Override + public int getConsumerCount() + { + return _tag2SubscriptionMap.size(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQMessage.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQMessage.java new file mode 100644 index 0000000000..416a4da183 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQMessage.java @@ -0,0 +1,251 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.AbstractServerMessageImpl; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.StoredMessage; + +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; + +/** + * A deliverable message. + */ +public class AMQMessage extends AbstractServerMessageImpl<MessageMetaData> +{ + /** Used for debugging purposes. */ + private static final Logger _log = Logger.getLogger(AMQMessage.class); + + /** Flag to indicate that this message requires 'immediate' delivery. */ + + private static final byte IMMEDIATE = 0x01; + + /** + * Flag to indicate whether this message has been delivered to a consumer. Used in implementing return functionality + * for messages published with the 'immediate' flag. + */ + + private static final byte DELIVERED_TO_CONSUMER = 0x02; + + private byte _flags = 0; + + private long _expiration; + + private final long _size; + + private Object _connectionIdentifier; + private static final byte IMMEDIATE_AND_DELIVERED = (byte) (IMMEDIATE | DELIVERED_TO_CONSUMER); + + public AMQMessage(StoredMessage<MessageMetaData> handle) + { + this(handle, null); + } + + public AMQMessage(StoredMessage<MessageMetaData> handle, WeakReference<AMQChannel> channelRef) + { + super(handle); + + + final MessageMetaData metaData = handle.getMetaData(); + _size = metaData.getContentSize(); + final MessagePublishInfo messagePublishInfo = metaData.getMessagePublishInfo(); + + if(messagePublishInfo.isImmediate()) + { + _flags |= IMMEDIATE; + } + } + + public void setExpiration(final long expiration) + { + + _expiration = expiration; + + } + + public MessageMetaData getMessageMetaData() + { + return getStoredMessage().getMetaData(); + } + + public ContentHeaderBody getContentHeaderBody() + { + return getMessageMetaData().getContentHeaderBody(); + } + + public Long getMessageId() + { + return getStoredMessage().getMessageNumber(); + } + + /** + * Called selectors to determin if the message has already been sent + * + * @return _deliveredToConsumer + */ + public boolean getDeliveredToConsumer() + { + return (_flags & DELIVERED_TO_CONSUMER) != 0; + } + + public String getRoutingKey() + { + MessageMetaData messageMetaData = getMessageMetaData(); + if (messageMetaData != null) + { + AMQShortString routingKey = messageMetaData.getMessagePublishInfo().getRoutingKey(); + if (routingKey != null) + { + return routingKey.asString(); + } + } + return null; + } + + public AMQMessageHeader getMessageHeader() + { + return getMessageMetaData().getMessageHeader(); + } + + public boolean isPersistent() + { + return getMessageMetaData().isPersistent(); + } + + /** + * Called to enforce the 'immediate' flag. + * + * @returns true if the message is marked for immediate delivery but has not been marked as delivered + * to a consumer + */ + public boolean immediateAndNotDelivered() + { + + return (_flags & IMMEDIATE_AND_DELIVERED) == IMMEDIATE; + + } + + public MessagePublishInfo getMessagePublishInfo() + { + return getMessageMetaData().getMessagePublishInfo(); + } + + public long getArrivalTime() + { + return getMessageMetaData().getArrivalTime(); + } + + /** + * Checks to see if the message has expired. If it has the message is dequeued. + * + * @param queue The queue to check the expiration against. (Currently not used) + * + * @return true if the message has expire + * + * @throws AMQException + */ + public boolean expired(AMQQueue queue) throws AMQException + { + + if (_expiration != 0L) + { + long now = System.currentTimeMillis(); + + return (now > _expiration); + } + + return false; + } + + /** + * Called when this message is delivered to a consumer. (used to implement the 'immediate' flag functionality). + * And for selector efficiency. + */ + public void setDeliveredToConsumer() + { + _flags |= DELIVERED_TO_CONSUMER; + } + + public long getSize() + { + return _size; + + } + + public boolean isImmediate() + { + return (_flags & IMMEDIATE) == IMMEDIATE; + } + + public long getExpiration() + { + return _expiration; + } + + public MessageReference newReference() + { + return new AMQMessageReference(this); + } + + public long getMessageNumber() + { + return getStoredMessage().getMessageNumber(); + } + + + public Object getConnectionIdentifier() + { + return _connectionIdentifier; + + } + + public void setConnectionIdentifier(final Object connectionIdentifier) + { + _connectionIdentifier = connectionIdentifier; + } + + + public String toString() + { + return "Message[" + debugIdentity() + "]: " + getMessageId() + "; ref count: " + getReferenceCount(); + } + + public int getContent(ByteBuffer buf, int offset) + { + return getStoredMessage().getContent(offset, buf); + } + + + public ByteBuffer getContent(int offset, int size) + { + return getStoredMessage().getContent(offset, size); + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQMessageReference.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQMessageReference.java new file mode 100644 index 0000000000..3adc9f70cd --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQMessageReference.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.server.message.MessageReference; + +public class AMQMessageReference extends MessageReference<AMQMessage> +{ + + + public AMQMessageReference(AMQMessage message) + { + super(message); + } + + protected void onReference(AMQMessage message) + { + message.incrementReference(); + } + + protected void onRelease(AMQMessage message) + { + message.decrementReference(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQNoMethodHandlerException.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQNoMethodHandlerException.java new file mode 100644 index 0000000000..8faf1a7c65 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQNoMethodHandlerException.java @@ -0,0 +1,46 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol.v0_8;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+
+/**
+ * AMQNoMethodHandlerException represents the case where no method handler exists to handle an AQMP method.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to handle an AMQP method.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Missing method handler. Unlikely to ever happen, and if it does its a coding error. Consider replacing with a
+ * Runtime.
+ */
+public class AMQNoMethodHandlerException extends AMQException
+{
+ public AMQNoMethodHandlerException(AMQMethodEvent<AMQMethodBody> evt)
+ {
+ super("AMQMethodEvent " + evt + " was not processed by any listener on Broker.");
+ }
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolEngine.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolEngine.java new file mode 100644 index 0000000000..dcf8d1fd47 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolEngine.java @@ -0,0 +1,1705 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import javax.security.auth.Subject; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.common.ServerPropertyNames; +import org.apache.qpid.framing.AMQBody; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQProtocolHeaderException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; +import org.apache.qpid.framing.HeartbeatBody; +import org.apache.qpid.framing.MethodDispatcher; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.ProtocolInitiation; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.properties.ConnectionStartProperties; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.protocol.AMQMethodListener; +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.protocol.v0_8.handler.ServerMethodDispatcherImpl; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.AMQPConnectionActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.server.logging.messages.ConnectionMessages; +import org.apache.qpid.server.logging.subjects.ConnectionLogSubject; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.v0_8.output.ProtocolOutputConverter; +import org.apache.qpid.server.protocol.v0_8.output.ProtocolOutputConverterRegistry; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.protocol.v0_8.state.AMQState; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.TransportException; +import org.apache.qpid.transport.network.NetworkConnection; +import org.apache.qpid.util.BytesDataOutput; + +public class AMQProtocolEngine implements ServerProtocolEngine, AMQProtocolSession +{ + private static final Logger _logger = Logger.getLogger(AMQProtocolEngine.class); + + // to save boxing the channelId and looking up in a map... cache in an array the low numbered + // channels. This value must be of the form 2^x - 1. + private static final int CHANNEL_CACHE_SIZE = 0xff; + private static final int REUSABLE_BYTE_BUFFER_CAPACITY = 65 * 1024; + private final Port _port; + + private AMQShortString _contextKey; + + private String _clientVersion = null; + + private VirtualHost _virtualHost; + + private final Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>(); + + private final AMQChannel[] _cachedChannels = new AMQChannel[CHANNEL_CACHE_SIZE + 1]; + + /** + * The channels that the latest call to {@link #received(ByteBuffer)} applied to. + * Used so we know which channels we need to call {@link AMQChannel#receivedComplete()} + * on after handling the frames. + * + * Thread-safety: guarded by {@link #_receivedLock}. + */ + private final Set<AMQChannel> _channelsForCurrentMessage = new HashSet<AMQChannel>(); + + private final CopyOnWriteArraySet<AMQMethodListener> _frameListeners = new CopyOnWriteArraySet<AMQMethodListener>(); + + private final AMQStateManager _stateManager; + + private AMQCodecFactory _codecFactory; + + private SaslServer _saslServer; + + private Object _lastReceived; + + private Object _lastSent; + + private volatile boolean _closed; + + // maximum number of channels this session should have + private long _maxNoOfChannels; + + /* AMQP Version for this session */ + private ProtocolVersion _protocolVersion = ProtocolVersion.getLatestSupportedVersion(); + private MethodRegistry _methodRegistry = MethodRegistry.getMethodRegistry(_protocolVersion); + private FieldTable _clientProperties; + private final List<Task> _taskList = new CopyOnWriteArrayList<Task>(); + + private Map<Integer, Long> _closingChannelsList = new ConcurrentHashMap<Integer, Long>(); + private ProtocolOutputConverter _protocolOutputConverter; + private Subject _authorizedSubject; + private MethodDispatcher _dispatcher; + + private final long _connectionID; + private Object _reference = new Object(); + + private AMQPConnectionActor _actor; + private LogSubject _logSubject; + + private long _lastIoTime; + + private long _writtenBytes; + private long _readBytes; + + + private long _maxFrameSize; + private final AtomicBoolean _closing = new AtomicBoolean(false); + private long _createTime = System.currentTimeMillis(); + + private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + + private NetworkConnection _network; + private Sender<ByteBuffer> _sender; + + private volatile boolean _deferFlush; + private long _lastReceivedTime; + private boolean _blocking; + + private final ReentrantLock _receivedLock; + private AtomicLong _lastWriteTime = new AtomicLong(System.currentTimeMillis()); + private final Broker _broker; + private final Transport _transport; + + private volatile boolean _closeWhenNoRoute; + private volatile boolean _stopped; + + public AMQProtocolEngine(Broker broker, + NetworkConnection network, + final long connectionId, + Port port, + Transport transport) + { + _broker = broker; + _port = port; + _transport = transport; + _maxNoOfChannels = (Integer)broker.getAttribute(Broker.CONNECTION_SESSION_COUNT_LIMIT); + _receivedLock = new ReentrantLock(); + _stateManager = new AMQStateManager(broker, this); + _codecFactory = new AMQCodecFactory(true, this); + + setNetworkConnection(network); + _connectionID = connectionId; + + _actor = new AMQPConnectionActor(this, _broker.getRootMessageLogger()); + + _logSubject = new ConnectionLogSubject(this); + + _actor.message(ConnectionMessages.OPEN(null, null, null, false, false, false)); + + _closeWhenNoRoute = (Boolean)_broker.getAttribute(Broker.CONNECTION_CLOSE_WHEN_NO_ROUTE); + + initialiseStatistics(); + + } + + public void setNetworkConnection(NetworkConnection network) + { + setNetworkConnection(network, network.getSender()); + } + + public void setNetworkConnection(NetworkConnection network, Sender<ByteBuffer> sender) + { + _network = network; + _sender = sender; + } + + public long getSessionID() + { + return _connectionID; + } + + public LogActor getLogActor() + { + return _actor; + } + + public void setMaxFrameSize(long frameMax) + { + _maxFrameSize = frameMax; + } + + public long getMaxFrameSize() + { + return _maxFrameSize; + } + + public boolean isClosing() + { + return _closing.get(); + } + + public synchronized void flushBatched() + { + _sender.flush(); + } + + + public ClientDeliveryMethod createDeliveryMethod(int channelId) + { + return new WriteDeliverMethod(channelId); + } + + public void received(final ByteBuffer msg) + { + final long arrivalTime = System.currentTimeMillis(); + _lastReceivedTime = arrivalTime; + _lastIoTime = arrivalTime; + + _receivedLock.lock(); + try + { + final ArrayList<AMQDataBlock> dataBlocks = _codecFactory.getDecoder().decodeBuffer(msg); + final int len = dataBlocks.size(); + for (int i = 0; i < len; i++) + { + AMQDataBlock dataBlock = dataBlocks.get(i); + try + { + dataBlockReceived(dataBlock); + } + catch(AMQConnectionException e) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Caught AMQConnectionException but will simply stop processing data blocks - the connection should already be closed.", e); + } + break; + } + catch (Exception e) + { + _logger.error("Unexpected exception when processing datablock", e); + closeProtocolSession(); + break; + } + } + receivedComplete(); + } + catch (Exception e) + { + _logger.error("Unexpected exception when processing datablocks", e); + closeProtocolSession(); + } + finally + { + _receivedLock.unlock(); + } + } + + private void receivedComplete() throws AMQException + { + Exception exception = null; + for (AMQChannel channel : _channelsForCurrentMessage) + { + try + { + channel.receivedComplete(); + } + catch(Exception exceptionForThisChannel) + { + if(exception == null) + { + exception = exceptionForThisChannel; + } + _logger.error("Error informing channel that receiving is complete. Channel: " + channel, exceptionForThisChannel); + } + } + + _channelsForCurrentMessage.clear(); + + if(exception != null) + { + throw new AMQException( + AMQConstant.INTERNAL_ERROR, + "Error informing channel that receiving is complete: " + exception.getMessage(), + exception); + } + } + + /** + * Process the data block. + * If the message is for a channel it is added to {@link #_channelsForCurrentMessage}. + * + * @throws an AMQConnectionException if unable to process the data block. In this case, + * the connection is already closed by the time the exception is thrown. If any other + * type of exception is thrown, the connection is not already closed. + */ + private void dataBlockReceived(AMQDataBlock message) throws Exception + { + _lastReceived = message; + if (message instanceof ProtocolInitiation) + { + protocolInitiationReceived((ProtocolInitiation) message); + + } + else if (message instanceof AMQFrame) + { + AMQFrame frame = (AMQFrame) message; + frameReceived(frame); + + } + else + { + throw new AMQException("Unknown message type: " + message.getClass().getName() + ": " + message); + } + } + + /** + * Handle the supplied frame. + * Adds this frame's channel to {@link #_channelsForCurrentMessage}. + * + * @throws an AMQConnectionException if unable to process the data block. In this case, + * the connection is already closed by the time the exception is thrown. If any other + * type of exception is thrown, the connection is not already closed. + */ + private void frameReceived(AMQFrame frame) throws AMQException + { + int channelId = frame.getChannel(); + AMQChannel amqChannel = _channelMap.get(channelId); + if(amqChannel != null) + { + // The _receivedLock is already aquired in the caller + // It is safe to add channel + _channelsForCurrentMessage.add(amqChannel); + } + else + { + // Not an error. The frame is probably a channel Open for this channel id, which + // does not require asynchronous work therefore its absence from + // _channelsForCurrentMessage is ok. + } + + AMQBody body = frame.getBodyFrame(); + + //Look up the Channel's Actor and set that as the current actor + // If that is not available then we can use the ConnectionActor + // that is associated with this AMQMPSession. + LogActor channelActor = null; + if (amqChannel != null) + { + channelActor = amqChannel.getLogActor(); + } + CurrentActor.set(channelActor == null ? _actor : channelActor); + + try + { + long startTime = 0; + String frameToString = null; + if (_logger.isDebugEnabled()) + { + startTime = System.currentTimeMillis(); + frameToString = frame.toString(); + _logger.debug("RECV: " + frame); + } + + // Check that this channel is not closing + if (channelAwaitingClosure(channelId)) + { + if ((frame.getBodyFrame() instanceof ChannelCloseOkBody)) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Channel[" + channelId + "] awaiting closure - processing close-ok"); + } + } + else + { + // The channel has been told to close, we don't process any more frames until + // it's closed. + return; + } + } + + try + { + body.handle(channelId, this); + } + catch(AMQConnectionException e) + { + _logger.info(e.getMessage() + " whilst processing frame: " + body); + closeConnection(channelId, e); + throw e; + } + catch (AMQException e) + { + closeChannel(channelId, e.getErrorCode() == null ? AMQConstant.INTERNAL_ERROR : e.getErrorCode(), e.getMessage()); + throw e; + } + catch (TransportException e) + { + closeChannel(channelId, AMQConstant.CHANNEL_ERROR, e.getMessage()); + throw e; + } + + if(_logger.isDebugEnabled()) + { + _logger.debug("Frame handled in " + (System.currentTimeMillis() - startTime) + " ms. Frame: " + frameToString); + } + } + finally + { + CurrentActor.remove(); + } + } + + private synchronized void protocolInitiationReceived(ProtocolInitiation pi) + { + // this ensures the codec never checks for a PI message again + (_codecFactory.getDecoder()).setExpectProtocolInitiation(false); + try + { + // Log incomming protocol negotiation request + _actor.message(ConnectionMessages.OPEN(null, pi.getProtocolMajor() + "-" + pi.getProtocolMinor(), null, false, true, false)); + + ProtocolVersion pv = pi.checkVersion(); // Fails if not correct + + // This sets the protocol version (and hence framing classes) for this session. + setProtocolVersion(pv); + + String mechanisms = _broker.getSubjectCreator(getLocalAddress()).getMechanisms(); + + String locales = "en_US"; + + + FieldTable serverProperties = FieldTableFactory.newFieldTable(); + + serverProperties.setString(ServerPropertyNames.PRODUCT, + QpidProperties.getProductName()); + serverProperties.setString(ServerPropertyNames.VERSION, + QpidProperties.getReleaseVersion()); + serverProperties.setString(ServerPropertyNames.QPID_BUILD, + QpidProperties.getBuildVersion()); + serverProperties.setString(ServerPropertyNames.QPID_INSTANCE_NAME, + _broker.getName()); + serverProperties.setString(ConnectionStartProperties.QPID_CLOSE_WHEN_NO_ROUTE, + String.valueOf(_closeWhenNoRoute)); + + AMQMethodBody responseBody = getMethodRegistry().createConnectionStartBody((short) getProtocolMajorVersion(), + (short) pv.getActualMinorVersion(), + serverProperties, + mechanisms.getBytes(), + locales.getBytes()); + _sender.send(asByteBuffer(responseBody.generateFrame(0))); + _sender.flush(); + + } + catch (AMQException e) + { + _logger.info("Received unsupported protocol initiation for protocol version: " + getProtocolVersion()); + + _sender.send(asByteBuffer(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion()))); + _sender.flush(); + } + } + + + private final byte[] _reusableBytes = new byte[REUSABLE_BYTE_BUFFER_CAPACITY]; + private final ByteBuffer _reusableByteBuffer = ByteBuffer.wrap(_reusableBytes); + private final BytesDataOutput _reusableDataOutput = new BytesDataOutput(_reusableBytes); + + private ByteBuffer asByteBuffer(AMQDataBlock block) + { + final int size = (int) block.getSize(); + + final byte[] data; + + + if(size > REUSABLE_BYTE_BUFFER_CAPACITY) + { + data= new byte[size]; + } + else + { + + data = _reusableBytes; + } + _reusableDataOutput.setBuffer(data); + + try + { + block.writePayload(_reusableDataOutput); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + + final ByteBuffer buf; + + if(size <= REUSABLE_BYTE_BUFFER_CAPACITY) + { + buf = _reusableByteBuffer; + buf.position(0); + } + else + { + buf = ByteBuffer.wrap(data); + } + buf.limit(_reusableDataOutput.length()); + + return buf; + } + + public void methodFrameReceived(int channelId, AMQMethodBody methodBody) + { + final AMQMethodEvent<AMQMethodBody> evt = new AMQMethodEvent<AMQMethodBody>(channelId, methodBody); + + try + { + try + { + boolean wasAnyoneInterested = _stateManager.methodReceived(evt); + + if (!_frameListeners.isEmpty()) + { + for (AMQMethodListener listener : _frameListeners) + { + wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested; + } + } + + if (!wasAnyoneInterested) + { + throw new AMQNoMethodHandlerException(evt); + } + } + catch (AMQChannelException e) + { + if (getChannel(channelId) != null) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Closing channel due to: " + e.getMessage()); + } + + writeFrame(e.getCloseFrame(channelId)); + closeChannel(channelId, e.getErrorCode() == null ? AMQConstant.INTERNAL_ERROR : e.getErrorCode(), e.getMessage()); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("ChannelException occured on non-existent channel:" + e.getMessage()); + } + + if (_logger.isInfoEnabled()) + { + _logger.info("Closing connection due to: " + e.getMessage()); + } + + AMQConnectionException ce = + evt.getMethod().getConnectionException(AMQConstant.CHANNEL_ERROR, + AMQConstant.CHANNEL_ERROR.getName().toString()); + + _logger.info(e.getMessage() + " whilst processing:" + methodBody); + closeConnection(channelId, ce); + } + } + catch (AMQConnectionException e) + { + _logger.info(e.getMessage() + " whilst processing:" + methodBody); + closeConnection(channelId, e); + } + catch (AMQSecurityException e) + { + AMQConnectionException ce = evt.getMethod().getConnectionException(AMQConstant.ACCESS_REFUSED, e.getMessage()); + _logger.info(e.getMessage() + " whilst processing:" + methodBody); + closeConnection(channelId, ce); + } + } + catch (Exception e) + { + for (AMQMethodListener listener : _frameListeners) + { + listener.error(e); + } + + _logger.error("Unexpected exception while processing frame. Closing connection.", e); + + closeProtocolSession(); + } + } + + public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException + { + + AMQChannel channel = getAndAssertChannel(channelId); + + channel.publishContentHeader(body); + + } + + public void contentBodyReceived(int channelId, ContentBody body) throws AMQException + { + AMQChannel channel = getAndAssertChannel(channelId); + + channel.publishContentBody(body); + } + + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) + { + // NO - OP + } + + /** + * Convenience method that writes a frame to the protocol session. Equivalent to calling + * getProtocolSession().write(). + * + * @param frame the frame to write + */ + public synchronized void writeFrame(AMQDataBlock frame) + { + + final ByteBuffer buf = asByteBuffer(frame); + _writtenBytes += buf.remaining(); + + if(_logger.isDebugEnabled()) + { + _logger.debug("SEND: " + frame); + } + + _sender.send(buf); + final long time = System.currentTimeMillis(); + _lastIoTime = time; + _lastWriteTime.set(time); + + if(!_deferFlush) + { + _sender.flush(); + } + } + + public AMQShortString getContextKey() + { + return _contextKey; + } + + public void setContextKey(AMQShortString contextKey) + { + _contextKey = contextKey; + } + + public List<AMQChannel> getChannels() + { + synchronized (_channelMap) + { + return new ArrayList<AMQChannel>(_channelMap.values()); + } + } + + public AMQChannel getAndAssertChannel(int channelId) throws AMQException + { + AMQChannel channel = getChannel(channelId); + if (channel == null) + { + throw new AMQException(AMQConstant.NOT_FOUND, "Channel not found with id:" + channelId); + } + + return channel; + } + + public AMQChannel getChannel(int channelId) + { + final AMQChannel channel = + ((channelId & CHANNEL_CACHE_SIZE) == channelId) ? _cachedChannels[channelId] : _channelMap.get(channelId); + if ((channel == null) || channel.isClosing()) + { + return null; + } + else + { + return channel; + } + } + + public boolean channelAwaitingClosure(int channelId) + { + return !_closingChannelsList.isEmpty() && _closingChannelsList.containsKey(channelId); + } + + public void addChannel(AMQChannel channel) throws AMQException + { + if (_closed) + { + throw new AMQException("Session is closed"); + } + + final int channelId = channel.getChannelId(); + + if (_closingChannelsList.containsKey(channelId)) + { + throw new AMQException("Session is marked awaiting channel close"); + } + + if (_channelMap.size() == _maxNoOfChannels) + { + String errorMessage = + toString() + ": maximum number of channels has been reached (" + _maxNoOfChannels + + "); can't create channel"; + _logger.error(errorMessage); + throw new AMQException(AMQConstant.NOT_ALLOWED, errorMessage); + } + else + { + synchronized (_channelMap) + { + _channelMap.put(channel.getChannelId(), channel); + + if(_blocking) + { + channel.block(); + } + } + } + + if (((channelId & CHANNEL_CACHE_SIZE) == channelId)) + { + _cachedChannels[channelId] = channel; + } + } + + public Long getMaximumNumberOfChannels() + { + return _maxNoOfChannels; + } + + public void setMaximumNumberOfChannels(Long value) + { + _maxNoOfChannels = value; + } + + public void commitTransactions(AMQChannel channel) throws AMQException + { + if ((channel != null) && channel.isTransactional()) + { + channel.commit(); + } + } + + public void rollbackTransactions(AMQChannel channel) throws AMQException + { + if ((channel != null) && channel.isTransactional()) + { + channel.rollback(); + } + } + + /** + * 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 + */ + @Override + public void closeChannel(int channelId) throws AMQException + { + closeChannel(channelId, null, null); + } + + public void closeChannel(int channelId, AMQConstant cause, String message) throws AMQException + { + final AMQChannel channel = getChannel(channelId); + if (channel == null) + { + throw new IllegalArgumentException("Unknown channel id"); + } + else + { + try + { + channel.close(cause, message); + markChannelAwaitingCloseOk(channelId); + } + finally + { + removeChannel(channelId); + } + } + } + + public void closeChannelOk(int channelId) + { + // todo QPID-847 - This is called from two lcoations ChannelCloseHandler and ChannelCloseOkHandler. + // When it is the CC_OK_Handler then it makes sence to remove the channel else we will leak memory. + // We do it from the Close Handler as we are sending the OK back to the client. + // While this is AMQP spec compliant. The Java client in the event of an IllegalArgumentException + // will send a close-ok.. Where we should call removeChannel. + // However, due to the poor exception handling on the client. The client-user will be notified of the + // InvalidArgument and if they then decide to close the session/connection then the there will be time + // for that to occur i.e. a new close method be sent before the exeption handling can mark the session closed. + + _closingChannelsList.remove(channelId); + } + + private void markChannelAwaitingCloseOk(int channelId) + { + _closingChannelsList.put(channelId, System.currentTimeMillis()); + } + + /** + * In our current implementation this is used by the clustering code. + * + * @param channelId The channel to remove + */ + public void removeChannel(int channelId) + { + synchronized (_channelMap) + { + _channelMap.remove(channelId); + + if ((channelId & CHANNEL_CACHE_SIZE) == channelId) + { + _cachedChannels[channelId] = null; + } + } + } + + /** + * Initialise heartbeats on the session. + * + * @param delay delay in seconds (not ms) + */ + public void initHeartbeats(int delay) + { + if (delay > 0) + { + _network.setMaxWriteIdle(delay); + _network.setMaxReadIdle(BrokerProperties.HEARTBEAT_TIMEOUT_FACTOR * delay); + } + else + { + _network.setMaxWriteIdle(0); + _network.setMaxReadIdle(0); + } + } + + /** + * 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 : getChannels()) + { + channel.close(); + } + synchronized (_channelMap) + { + _channelMap.clear(); + } + for (int i = 0; i <= CHANNEL_CACHE_SIZE; i++) + { + _cachedChannels[i] = null; + } + } + + /** This must be called when the session is _closed in order to free up any resources managed by the session. */ + @Override + public void closeSession() throws AMQException + { + if(_closing.compareAndSet(false,true)) + { + // force sync of outstanding async work + _receivedLock.lock(); + try + { + receivedComplete(); + } + finally + { + _receivedLock.unlock(); + } + + // REMOVE THIS SHOULD NOT BE HERE. + if (CurrentActor.get() == null) + { + CurrentActor.set(_actor); + } + if (!_closed) + { + if (_virtualHost != null) + { + _virtualHost.getConnectionRegistry().deregisterConnection(this); + } + + closeAllChannels(); + + for (Task task : _taskList) + { + task.doTask(this); + } + + synchronized(this) + { + _closed = true; + notifyAll(); + } + CurrentActor.get().message(_logSubject, ConnectionMessages.CLOSE()); + } + } + else + { + synchronized(this) + { + + boolean lockHeld = _receivedLock.isHeldByCurrentThread(); + + while(!_closed) + { + try + { + if(lockHeld) + { + _receivedLock.unlock(); + } + wait(1000); + } + catch (InterruptedException e) + { + + } + finally + { + if(lockHeld) + { + _receivedLock.lock(); + } + } + } + } + } + } + + private void closeConnection(int channelId, AMQConnectionException e) throws AMQException + { + try + { + if (_logger.isInfoEnabled()) + { + _logger.info("Closing connection due to: " + e); + } + + markChannelAwaitingCloseOk(channelId); + closeSession(); + } + finally + { + try + { + _stateManager.changeState(AMQState.CONNECTION_CLOSING); + writeFrame(e.getCloseFrame(channelId)); + } + finally + { + closeProtocolSession(); + } + } + + + } + + @Override + public void closeProtocolSession() + { + _network.close(); + + try + { + _stateManager.changeState(AMQState.CONNECTION_CLOSED); + } + catch (AMQException e) + { + _logger.info(e.getMessage()); + } + catch (TransportException e) + { + _logger.info(e.getMessage()); + } + } + + public String toString() + { + return getRemoteAddress() + "(" + (getAuthorizedPrincipal() == null ? "?" : getAuthorizedPrincipal().getName() + ")"); + } + + public String dump() + { + return this + " last_sent=" + _lastSent + " last_received=" + _lastReceived; + } + + /** @return an object that can be used to identity */ + public Object getKey() + { + return 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 = _network.getLocalAddress(); + if (address instanceof InetSocketAddress) + { + return ((InetSocketAddress) address).getHostName(); + } + else + { + throw new IllegalArgumentException("Unsupported socket address class: " + address); + } + } + + public SaslServer getSaslServer() + { + return _saslServer; + } + + public void setSaslServer(SaslServer saslServer) + { + _saslServer = saslServer; + } + + public void setClientProperties(FieldTable clientProperties) + { + _clientProperties = clientProperties; + if (_clientProperties != null) + { + String closeWhenNoRoute = _clientProperties.getString(ConnectionStartProperties.QPID_CLOSE_WHEN_NO_ROUTE); + if (closeWhenNoRoute != null) + { + _closeWhenNoRoute = Boolean.parseBoolean(closeWhenNoRoute); + if(_logger.isDebugEnabled()) + { + _logger.debug("Client set closeWhenNoRoute=" + _closeWhenNoRoute + " for protocol engine " + this); + } + } + + _clientVersion = _clientProperties.getString(ConnectionStartProperties.VERSION_0_8); + + if (_clientProperties.getString(ConnectionStartProperties.CLIENT_ID_0_8) != null) + { + String clientID = _clientProperties.getString(ConnectionStartProperties.CLIENT_ID_0_8); + setContextKey(new AMQShortString(clientID)); + + // Log the Opening of the connection for this client + _actor.message(ConnectionMessages.OPEN(clientID, _protocolVersion.toString(), _clientVersion, true, true, true)); + } + } + } + + private void setProtocolVersion(ProtocolVersion pv) + { + _protocolVersion = pv; + _methodRegistry = MethodRegistry.getMethodRegistry(_protocolVersion); + _protocolOutputConverter = ProtocolOutputConverterRegistry.getConverter(this); + _dispatcher = ServerMethodDispatcherImpl.createMethodDispatcher(_stateManager, _protocolVersion); + } + + public byte getProtocolMajorVersion() + { + return _protocolVersion.getMajorVersion(); + } + + public ProtocolVersion getProtocolVersion() + { + return _protocolVersion; + } + + public byte getProtocolMinorVersion() + { + return _protocolVersion.getMinorVersion(); + } + + public boolean isProtocolVersion(byte major, byte minor) + { + return (getProtocolMajorVersion() == major) && (getProtocolMinorVersion() == minor); + } + + public MethodRegistry getRegistry() + { + return getMethodRegistry(); + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void setVirtualHost(VirtualHost virtualHost) throws AMQException + { + _virtualHost = virtualHost; + + _virtualHost.getConnectionRegistry().registerConnection(this); + + } + + public void addSessionCloseTask(Task task) + { + _taskList.add(task); + } + + public void removeSessionCloseTask(Task task) + { + _taskList.remove(task); + } + + public ProtocolOutputConverter getProtocolOutputConverter() + { + return _protocolOutputConverter; + } + + public void setAuthorizedSubject(final Subject authorizedSubject) + { + if (authorizedSubject == null) + { + throw new IllegalArgumentException("authorizedSubject cannot be null"); + } + _authorizedSubject = authorizedSubject; + } + + public Subject getAuthorizedSubject() + { + return _authorizedSubject; + } + + public Principal getAuthorizedPrincipal() + { + return _authorizedSubject == null ? null : AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(_authorizedSubject); + } + + public SocketAddress getRemoteAddress() + { + return _network.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _network.getLocalAddress(); + } + + public Principal getPeerPrincipal() + { + return _network.getPeerPrincipal(); + } + + public MethodRegistry getMethodRegistry() + { + return _methodRegistry; + } + + public MethodDispatcher getMethodDispatcher() + { + return _dispatcher; + } + + public void closed() + { + try + { + try + { + closeSession(); + } + finally + { + closeProtocolSession(); + } + } + catch (AMQException e) + { + _logger.error("Could not close protocol engine", e); + } + catch (TransportException e) + { + _logger.error("Could not close protocol engine", e); + } + } + + public void readerIdle() + { + // TODO - enforce disconnect on lack of inbound data + } + + public synchronized void writerIdle() + { + writeFrame(HeartbeatBody.FRAME); + } + + public void exception(Throwable throwable) + { + if (throwable instanceof AMQProtocolHeaderException) + { + writeFrame(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion())); + _sender.close(); + + _logger.error("Error in protocol initiation " + this + ":" + getRemoteAddress() + " :" + throwable.getMessage(), throwable); + } + else if (throwable instanceof IOException) + { + _logger.error("IOException caught in" + this + ", session closed implictly: " + throwable); + } + else + { + _logger.error("Exception caught in" + this + ", closing session explictly: " + throwable, throwable); + + + MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(getProtocolVersion()); + ConnectionCloseBody closeBody = methodRegistry.createConnectionCloseBody(200,new AMQShortString(throwable.getMessage()),0,0); + + writeFrame(closeBody.generateFrame(0)); + + _sender.close(); + } + } + + public void init() + { + // Do nothing + } + + public void setSender(Sender<ByteBuffer> sender) + { + // Do nothing + } + + public long getReadBytes() + { + return _readBytes; + } + + public long getWrittenBytes() + { + return _writtenBytes; + } + + public long getLastIoTime() + { + return _lastIoTime; + } + + @Override + public Port getPort() + { + return _port; + } + + @Override + public Transport getTransport() + { + return _transport; + } + + @Override + public void stop() + { + _stopped = true; + } + + @Override + public boolean isStopped() + { + return _stopped; + } + + @Override + public String getVirtualHostName() + { + return _virtualHost == null ? null : _virtualHost.getName(); + } + + public long getLastReceivedTime() + { + return _lastReceivedTime; + } + + public String getClientVersion() + { + return _clientVersion; + } + + public String getPrincipalAsString() + { + return getAuthId(); + } + + public long getSessionCountLimit() + { + return getMaximumNumberOfChannels(); + } + + public Boolean isIncoming() + { + return true; + } + + public Boolean isSystemConnection() + { + return false; + } + + public Boolean isFederationLink() + { + return false; + } + + public String getAuthId() + { + return getAuthorizedPrincipal() == null ? null : getAuthorizedPrincipal().getName(); + } + + public Integer getRemotePID() + { + return null; + } + + public String getRemoteProcessName() + { + return null; + } + + public Integer getRemoteParentPID() + { + return null; + } + + public boolean isDurable() + { + return false; + } + + public long getConnectionId() + { + return getSessionID(); + } + + public String getAddress() + { + return String.valueOf(getRemoteAddress()); + } + + public long getCreateTime() + { + return _createTime; + } + + public Boolean isShadow() + { + return false; + } + + public void mgmtClose() + { + MethodRegistry methodRegistry = getMethodRegistry(); + ConnectionCloseBody responseBody = + methodRegistry.createConnectionCloseBody( + AMQConstant.REPLY_SUCCESS.getCode(), + new AMQShortString("The connection was closed using the broker's management interface."), + 0,0); + + // This seems ugly but because we use closeConnection in both normal + // broker operation and as part of the management interface it cannot + // be avoided. The Current Actor will be null when this method is + // called via the QMF management interface. As such we need to set one. + boolean removeActor = false; + if (CurrentActor.get() == null) + { + removeActor = true; + CurrentActor.set(new ManagementActor(_actor.getRootMessageLogger())); + } + + try + { + writeFrame(responseBody.generateFrame(0)); + + try + { + + closeSession(); + } + catch (AMQException ex) + { + throw new RuntimeException(ex); + } + } + finally + { + if (removeActor) + { + CurrentActor.remove(); + } + } + } + + public void mgmtCloseChannel(int channelId) + { + MethodRegistry methodRegistry = getMethodRegistry(); + ChannelCloseBody responseBody = + methodRegistry.createChannelCloseBody( + AMQConstant.REPLY_SUCCESS.getCode(), + new AMQShortString("The channel was closed using the broker's management interface."), + 0,0); + + // This seems ugly but because we use AMQChannel.close() in both normal + // broker operation and as part of the management interface it cannot + // be avoided. The Current Actor will be null when this method is + // called via the QMF management interface. As such we need to set one. + boolean removeActor = false; + if (CurrentActor.get() == null) + { + removeActor = true; + CurrentActor.set(new ManagementActor(_actor.getRootMessageLogger())); + } + + try + { + writeFrame(responseBody.generateFrame(channelId)); + + try + { + closeChannel(channelId); + } + catch (AMQException ex) + { + throw new RuntimeException(ex); + } + } + finally + { + if (removeActor) + { + CurrentActor.remove(); + } + } + } + + public String getClientID() + { + return getContextKey().toString(); + } + + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException + { + int channelId = ((AMQChannel)session).getChannelId(); + closeChannel(channelId, cause, message); + + MethodRegistry methodRegistry = getMethodRegistry(); + ChannelCloseBody responseBody = + methodRegistry.createChannelCloseBody( + cause.getCode(), + new AMQShortString(message), + 0,0); + + writeFrame(responseBody.generateFrame(channelId)); + } + + public void close(AMQConstant cause, String message) throws AMQException + { + closeConnection(0, new AMQConnectionException(cause, message, 0, 0, + getProtocolOutputConverter().getProtocolMajorVersion(), + getProtocolOutputConverter().getProtocolMinorVersion(), + (Throwable) null)); + } + + public void block() + { + synchronized (_channelMap) + { + if(!_blocking) + { + _blocking = true; + for(AMQChannel channel : _channelMap.values()) + { + channel.block(); + } + } + } + } + + public void unblock() + { + synchronized (_channelMap) + { + if(_blocking) + { + _blocking = false; + for(AMQChannel channel : _channelMap.values()) + { + channel.unblock(); + } + } + } + } + + public boolean isClosed() + { + return _closed; + } + + public List<AMQSessionModel> getSessionModels() + { + return new ArrayList<AMQSessionModel>(getChannels()); + } + + public LogSubject getLogSubject() + { + return _logSubject; + } + + public void registerMessageDelivered(long messageSize) + { + _messagesDelivered.registerEvent(1L); + _dataDelivered.registerEvent(messageSize); + _virtualHost.registerMessageDelivered(messageSize); + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + _messagesReceived.registerEvent(1L, timestamp); + _dataReceived.registerEvent(messageSize, timestamp); + _virtualHost.registerMessageReceived(messageSize, timestamp); + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return _messagesReceived; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceived; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messagesDelivered; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDelivered; + } + + public void resetStatistics() + { + _messagesDelivered.reset(); + _dataDelivered.reset(); + _messagesReceived.reset(); + _dataReceived.reset(); + } + + public void initialiseStatistics() + { + _messagesDelivered = new StatisticsCounter("messages-delivered-" + getSessionID()); + _dataDelivered = new StatisticsCounter("data-delivered-" + getSessionID()); + _messagesReceived = new StatisticsCounter("messages-received-" + getSessionID()); + _dataReceived = new StatisticsCounter("data-received-" + getSessionID()); + } + + public boolean isSessionNameUnique(byte[] name) + { + // 0-8/0-9/0-9-1 sessions don't have names + return true; + } + + public String getRemoteAddressString() + { + return String.valueOf(getRemoteAddress()); + } + + public String getClientId() + { + return String.valueOf(getContextKey()); + } + + public void setDeferFlush(boolean deferFlush) + { + _deferFlush = deferFlush; + } + + public String getUserName() + { + return getAuthorizedPrincipal().getName(); + } + + public final class WriteDeliverMethod + implements ClientDeliveryMethod + { + private final int _channelId; + + public WriteDeliverMethod(int channelId) + { + _channelId = channelId; + } + + public void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) + throws AMQException + { + registerMessageDelivered(entry.getMessage().getSize()); + _protocolOutputConverter.writeDeliver(entry, _channelId, deliveryTag, ((SubscriptionImpl)sub).getConsumerTag()); + entry.incrementDeliveryCount(); + } + + } + + public Object getReference() + { + return _reference; + } + + public Lock getReceivedLock() + { + return _receivedLock; + } + + @Override + public long getLastReadTime() + { + return _lastReceivedTime; + } + + @Override + public long getLastWriteTime() + { + return _lastWriteTime.get(); + } + + @Override + public boolean isCloseWhenNoRoute() + { + return _closeWhenNoRoute; + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolSession.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolSession.java new file mode 100644 index 0000000000..559ab3468e --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolSession.java @@ -0,0 +1,231 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import java.net.SocketAddress; +import java.security.Principal; +import java.util.List; +import java.util.concurrent.locks.Lock; + +import javax.security.auth.Subject; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.MethodDispatcher; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.v0_8.output.ProtocolOutputConverter; +import org.apache.qpid.server.security.AuthorizationHolder; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.virtualhost.VirtualHost; + + +public interface AMQProtocolSession extends AMQVersionAwareProtocolSession, AuthorizationHolder, AMQConnectionModel +{ + long getSessionID(); + + LogActor getLogActor(); + + void setMaxFrameSize(long frameMax); + + long getMaxFrameSize(); + + boolean isClosing(); + + void flushBatched(); + + void setDeferFlush(boolean defer); + + ClientDeliveryMethod createDeliveryMethod(int channelId); + + long getLastReceivedTime(); + + /** + * Return the local socket address for the connection + * + * @return the socket address + */ + SocketAddress getLocalAddress(); + + public static interface Task + { + public void doTask(AMQProtocolSession session) throws AMQException; + } + + /** + * Get the context key associated with this session. Context key is described in the AMQ protocol specification (RFC + * 6). + * + * @return the context key + */ + AMQShortString 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(AMQShortString 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); + + /** + * 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) throws AMQException; + + /** + * 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; + + void closeChannel(int channelId, AMQConstant cause, String message) throws AMQException; + + /** + * Markes the specific channel as closed. This will release the lock for that channel id so a new channel can be + * created on that id. + * + * @param channelId id of the channel to close + */ + void closeChannelOk(int channelId); + + /** + * Check to see if this chanel is closing + * + * @param channelId id to check + * @return boolean with state of channel awaiting closure + */ + boolean channelAwaitingClosure(int channelId); + + /** + * 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; + + void closeProtocolSession(); + + /** @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); + + void setClientProperties(FieldTable clientProperties); + + Object getReference(); + + VirtualHost getVirtualHost(); + + void setVirtualHost(VirtualHost virtualHost) throws AMQException; + + void addSessionCloseTask(Task task); + + void removeSessionCloseTask(Task task); + + public ProtocolOutputConverter getProtocolOutputConverter(); + + void setAuthorizedSubject(Subject authorizedSubject); + + public java.net.SocketAddress getRemoteAddress(); + + public MethodRegistry getMethodRegistry(); + + public MethodDispatcher getMethodDispatcher(); + + String getClientVersion(); + + long getLastIoTime(); + + long getWrittenBytes(); + + Long getMaximumNumberOfChannels(); + + void setMaximumNumberOfChannels(Long value); + + void commitTransactions(AMQChannel channel) throws AMQException; + + void rollbackTransactions(AMQChannel channel) throws AMQException; + + List<AMQChannel> getChannels(); + + void mgmtCloseChannel(int channelId); + + public Principal getPeerPrincipal(); + + Lock getReceivedLock(); + + /** + * Used for 0-8/0-9/0-9-1 connections to choose to close + * the connection when a transactional session receives a 'mandatory' message which + * can't be routed rather than returning the message. + */ + boolean isCloseWhenNoRoute(); +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ContentHeaderBodyAdapter.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ContentHeaderBodyAdapter.java new file mode 100644 index 0000000000..f5c43003a4 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ContentHeaderBodyAdapter.java @@ -0,0 +1,146 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import java.util.Collection; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; + +import java.util.Set; +import org.apache.qpid.server.message.AMQMessageHeader; + +public class ContentHeaderBodyAdapter implements AMQMessageHeader +{ + private final ContentHeaderBody _contentHeaderBody; + + public ContentHeaderBodyAdapter(ContentHeaderBody contentHeaderBody) + { + _contentHeaderBody = contentHeaderBody; + } + + private BasicContentHeaderProperties getProperties() + { + return (BasicContentHeaderProperties) _contentHeaderBody.getProperties(); + } + + public String getCorrelationId() + { + return getProperties().getCorrelationIdAsString(); + } + + public long getExpiration() + { + return getProperties().getExpiration(); + } + + public String getUserId() + { + return getProperties().getUserIdAsString(); + } + + public String getAppId() + { + return getProperties().getAppIdAsString(); + } + + public String getMessageId() + { + return getProperties().getMessageIdAsString(); + } + + public String getMimeType() + { + return getProperties().getContentTypeAsString(); + } + + public String getEncoding() + { + return getProperties().getEncodingAsString(); + } + + public byte getPriority() + { + return getProperties().getPriority(); + } + + public long getTimestamp() + { + return getProperties().getTimestamp(); + } + + public String getType() + { + return getProperties().getTypeAsString(); + } + + public String getReplyTo() + { + return getProperties().getReplyToAsString(); + } + + public String getReplyToExchange() + { + // TODO + return getReplyTo(); + } + + public String getReplyToRoutingKey() + { + // TODO + return getReplyTo(); + + } + + public Object getHeader(String name) + { + FieldTable ft = getProperties().getHeaders(); + return ft.get(name); + } + + public boolean containsHeaders(Set<String> names) + { + FieldTable ft = getProperties().getHeaders(); + for(String name : names) + { + if(!ft.containsKey(name)) + { + return false; + } + } + return true; + } + + @Override + public Collection<String> getHeaderNames() + { + FieldTable ft = getProperties().getHeaders(); + return ft.keys(); + } + + public boolean containsHeader(String name) + { + FieldTable ft = getProperties().getHeaders(); + return ft.containsKey(name); + } + + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ExtractResendAndRequeue.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ExtractResendAndRequeue.java new file mode 100644 index 0000000000..5e416b52ca --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ExtractResendAndRequeue.java @@ -0,0 +1,133 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; + +import java.util.Map; + +public class ExtractResendAndRequeue implements UnacknowledgedMessageMap.Visitor +{ + private static final Logger _log = Logger.getLogger(ExtractResendAndRequeue.class); + + private final Map<Long, QueueEntry> _msgToRequeue; + private final Map<Long, QueueEntry> _msgToResend; + private final boolean _requeueIfUnabletoResend; + private final UnacknowledgedMessageMap _unacknowledgedMessageMap; + private final MessageStore _transactionLog; + + public ExtractResendAndRequeue(UnacknowledgedMessageMap unacknowledgedMessageMap, + Map<Long, QueueEntry> msgToRequeue, + Map<Long, QueueEntry> msgToResend, + boolean requeueIfUnabletoResend, + MessageStore txnLog) + { + _unacknowledgedMessageMap = unacknowledgedMessageMap; + _msgToRequeue = msgToRequeue; + _msgToResend = msgToResend; + _requeueIfUnabletoResend = requeueIfUnabletoResend; + _transactionLog = txnLog; + } + + public boolean callback(final long deliveryTag, QueueEntry message) throws AMQException + { + + message.setRedelivered(); + final Subscription subscription = message.getDeliveredSubscription(); + if (subscription != null) + { + // Consumer exists + if (!subscription.isClosed()) + { + _msgToResend.put(deliveryTag, message); + } + else // consumer has gone + { + _msgToRequeue.put(deliveryTag, message); + } + } + else + { + // Message has no consumer tag, so was "delivered" to a GET + // or consumer no longer registered + // cannot resend, so re-queue. + if (!message.isQueueDeleted()) + { + if (_requeueIfUnabletoResend) + { + _msgToRequeue.put(deliveryTag, message); + } + else + { + + dequeueEntry(message); + _log.info("No DeadLetter Queue and requeue not requested so dropping message:" + message); + } + } + else + { + dequeueEntry(message); + _log.warn("Message.queue is null and no DeadLetter Queue so dropping message:" + message); + } + } + + // false means continue processing + return false; + } + + + private void dequeueEntry(final QueueEntry node) + { + ServerTransaction txn = new AutoCommitTransaction(_transactionLog); + dequeueEntry(node, txn); + } + + private void dequeueEntry(final QueueEntry node, ServerTransaction txn) + { + txn.dequeue(node.getQueue(), node.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + node.discard(); + } + + public void onRollback() + { + + } + }); + } + + public void visitComplete() + { + _unacknowledgedMessageMap.clear(); + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/IncomingMessage.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/IncomingMessage.java new file mode 100644 index 0000000000..5267651a66 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/IncomingMessage.java @@ -0,0 +1,284 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.MessageContentSource; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.Filterable; +import org.apache.qpid.server.store.StoredMessage; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class IncomingMessage implements Filterable, InboundMessage, EnqueableMessage, MessageContentSource +{ + + /** Used for debugging purposes. */ + private static final Logger _logger = Logger.getLogger(IncomingMessage.class); + + private final MessagePublishInfo _messagePublishInfo; + private ContentHeaderBody _contentHeaderBody; + + + /** + * Keeps a track of how many bytes we have received in body frames + */ + private long _bodyLengthReceived = 0; + + /** + * 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<? extends BaseQueue> _destinationQueues; + + private long _expiration; + + private Exchange _exchange; + + private List<ContentChunk> _contentChunks = new ArrayList<ContentChunk>(); + + // we keep both the original meta data object and the store reference to it just in case the + // store would otherwise flow it to disk + + private MessageMetaData _messageMetaData; + + private StoredMessage<MessageMetaData> _storedMessageHandle; + private Object _connectionReference; + + + public IncomingMessage( + final MessagePublishInfo info + ) + { + this(info, null); + } + + public IncomingMessage(MessagePublishInfo info, Object reference) + { + _messagePublishInfo = info; + _connectionReference = reference; + } + + public void setContentHeaderBody(final ContentHeaderBody contentHeaderBody) throws AMQException + { + _contentHeaderBody = contentHeaderBody; + } + + public void setExpiration() + { + _expiration = ((BasicContentHeaderProperties) _contentHeaderBody.getProperties()).getExpiration(); + } + + public MessageMetaData headersReceived(long currentTime) + { + _messageMetaData = new MessageMetaData(_messagePublishInfo, _contentHeaderBody, 0, currentTime); + return _messageMetaData; + } + + + public List<? extends BaseQueue> getDestinationQueues() + { + return _destinationQueues; + } + + public void addContentBodyFrame(final ContentChunk contentChunk) throws AMQException + { + _bodyLengthReceived += contentChunk.getSize(); + _contentChunks.add(contentChunk); + } + + public boolean allContentReceived() + { + return (_bodyLengthReceived == getContentHeader().getBodySize()); + } + + public AMQShortString getExchange() + { + return _messagePublishInfo.getExchange(); + } + + public String getRoutingKey() + { + return _messagePublishInfo.getRoutingKey() == null ? null : _messagePublishInfo.getRoutingKey().toString(); + } + + public String getBinding() + { + return _messagePublishInfo.getRoutingKey() == null ? null : _messagePublishInfo.getRoutingKey().toString(); + } + + + public boolean isMandatory() + { + return _messagePublishInfo.isMandatory(); + } + + + public boolean isImmediate() + { + return _messagePublishInfo.isImmediate(); + } + + public ContentHeaderBody getContentHeader() + { + return _contentHeaderBody; + } + + + public AMQMessageHeader getMessageHeader() + { + return _messageMetaData.getMessageHeader(); + } + + public boolean isPersistent() + { + return getContentHeader().getProperties() instanceof BasicContentHeaderProperties && + ((BasicContentHeaderProperties) getContentHeader().getProperties()).getDeliveryMode() == + BasicContentHeaderProperties.PERSISTENT; + } + + public boolean isRedelivered() + { + return false; + } + + + public long getSize() + { + return getContentHeader().getBodySize(); + } + + public long getMessageNumber() + { + return _storedMessageHandle.getMessageNumber(); + } + + public void setExchange(final Exchange e) + { + _exchange = e; + } + + public void route() + { + enqueue(_exchange.route(this)); + + } + + public void enqueue(final List<? extends BaseQueue> queues) + { + _destinationQueues = queues; + } + + public MessagePublishInfo getMessagePublishInfo() + { + return _messagePublishInfo; + } + + public long getExpiration() + { + return _expiration; + } + + public int getBodyCount() throws AMQException + { + return _contentChunks.size(); + } + + public ContentChunk getContentChunk(int index) + { + return _contentChunks.get(index); + } + + + public int getContent(ByteBuffer buf, int offset) + { + int pos = 0; + int written = 0; + for(ContentChunk cb : _contentChunks) + { + ByteBuffer data = ByteBuffer.wrap(cb.getData()); + if(offset+written >= pos && offset < pos + data.limit()) + { + ByteBuffer src = data.duplicate(); + src.position(offset+written - pos); + src = src.slice(); + + if(buf.remaining() < src.limit()) + { + src.limit(buf.remaining()); + } + int count = src.limit(); + buf.put(src); + written += count; + if(buf.remaining() == 0) + { + break; + } + } + pos+=data.limit(); + } + return written; + + } + + + public ByteBuffer getContent(int offset, int size) + { + ByteBuffer buf = ByteBuffer.allocate(size); + getContent(buf,offset); + buf.flip(); + return buf; + } + + public void setStoredMessage(StoredMessage<MessageMetaData> storedMessageHandle) + { + _storedMessageHandle = storedMessageHandle; + } + + public StoredMessage<MessageMetaData> getStoredMessage() + { + return _storedMessageHandle; + } + + public Object getConnectionReference() + { + return _connectionReference; + } + + public MessageMetaData getMessageMetaData() + { + return _messageMetaData; + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/MessageMetaData.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/MessageMetaData.java new file mode 100644 index 0000000000..4cc590d8cc --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/MessageMetaData.java @@ -0,0 +1,349 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import java.util.Collection; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.EncodingUtils; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.server.util.ByteBufferOutputStream; +import org.apache.qpid.util.ByteBufferInputStream; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Set; + +/** + * 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 implements StorableMessageMetaData +{ + private MessagePublishInfo _messagePublishInfo; + + private ContentHeaderBody _contentHeaderBody; + + private int _contentChunkCount; + + private long _arrivalTime; + private static final byte MANDATORY_FLAG = 1; + private static final byte IMMEDIATE_FLAG = 2; + public static final MessageMetaDataType.Factory<MessageMetaData> FACTORY = new MetaDataFactory(); + private static final MessageMetaDataType_0_8 TYPE = new MessageMetaDataType_0_8(); + + public MessageMetaData(MessagePublishInfo publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount) + { + this(publishBody,contentHeaderBody, contentChunkCount, System.currentTimeMillis()); + } + + public MessageMetaData(MessagePublishInfo publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount, long arrivalTime) + { + _contentHeaderBody = contentHeaderBody; + _messagePublishInfo = publishBody; + _contentChunkCount = contentChunkCount; + _arrivalTime = arrivalTime; + } + + 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 MessagePublishInfo getMessagePublishInfo() + { + return _messagePublishInfo; + } + + public void setMessagePublishInfo(MessagePublishInfo messagePublishInfo) + { + _messagePublishInfo = messagePublishInfo; + } + + public long getArrivalTime() + { + return _arrivalTime; + } + + public void setArrivalTime(long arrivalTime) + { + _arrivalTime = arrivalTime; + } + + public MessageMetaDataType getType() + { + return TYPE; + } + + public int getStorableSize() + { + int size = _contentHeaderBody.getSize(); + size += 4; + size += EncodingUtils.encodedShortStringLength(_messagePublishInfo.getExchange()); + size += EncodingUtils.encodedShortStringLength(_messagePublishInfo.getRoutingKey()); + size += 1; // flags for immediate/mandatory + size += EncodingUtils.encodedLongLength(); + + return size; + } + + + public int writeToBuffer(int offset, ByteBuffer dest) + { + int oldPosition = dest.position(); + try + { + + DataOutputStream dataOutputStream = new DataOutputStream(new ByteBufferOutputStream(dest)); + EncodingUtils.writeInteger(dataOutputStream, _contentHeaderBody.getSize()); + _contentHeaderBody.writePayload(dataOutputStream); + EncodingUtils.writeShortStringBytes(dataOutputStream, _messagePublishInfo.getExchange()); + EncodingUtils.writeShortStringBytes(dataOutputStream, _messagePublishInfo.getRoutingKey()); + byte flags = 0; + if(_messagePublishInfo.isMandatory()) + { + flags |= MANDATORY_FLAG; + } + if(_messagePublishInfo.isImmediate()) + { + flags |= IMMEDIATE_FLAG; + } + dest.put(flags); + dest.putLong(_arrivalTime); + + } + catch (IOException e) + { + // This shouldn't happen as we are not actually using anything that can throw an IO Exception + throw new RuntimeException(e); + } + + return dest.position()-oldPosition; + } + + public int getContentSize() + { + return (int) _contentHeaderBody.getBodySize(); + } + + public boolean isPersistent() + { + BasicContentHeaderProperties properties = (BasicContentHeaderProperties) (_contentHeaderBody.getProperties()); + return properties.getDeliveryMode() == BasicContentHeaderProperties.PERSISTENT; + } + + private static class MetaDataFactory implements MessageMetaDataType.Factory + { + + + public MessageMetaData createMetaData(ByteBuffer buf) + { + try + { + ByteBufferInputStream bbis = new ByteBufferInputStream(buf); + DataInputStream dais = new DataInputStream(bbis); + int size = EncodingUtils.readInteger(dais); + ContentHeaderBody chb = ContentHeaderBody.createFromBuffer(dais, size); + final AMQShortString exchange = EncodingUtils.readAMQShortString(dais); + final AMQShortString routingKey = EncodingUtils.readAMQShortString(dais); + + final byte flags = EncodingUtils.readByte(dais); + long arrivalTime = EncodingUtils.readLong(dais); + + MessagePublishInfo publishBody = + new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return exchange; + } + + public void setExchange(AMQShortString exchange) + { + } + + public boolean isImmediate() + { + return (flags & IMMEDIATE_FLAG) != 0; + } + + public boolean isMandatory() + { + return (flags & MANDATORY_FLAG) != 0; + } + + public AMQShortString getRoutingKey() + { + return routingKey; + } + }; + return new MessageMetaData(publishBody, chb, 0, arrivalTime); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + + } + }; + + public AMQMessageHeader getMessageHeader() + { + return new MessageHeaderAdapter(); + } + + private final class MessageHeaderAdapter implements AMQMessageHeader + { + private BasicContentHeaderProperties getProperties() + { + return (BasicContentHeaderProperties) getContentHeaderBody().getProperties(); + } + + public String getUserId() + { + return getProperties().getUserIdAsString(); + } + + public String getAppId() + { + return getProperties().getAppIdAsString(); + } + + public String getCorrelationId() + { + return getProperties().getCorrelationIdAsString(); + } + + public long getExpiration() + { + return getProperties().getExpiration(); + } + + public String getMessageId() + { + return getProperties().getMessageIdAsString(); + } + + public String getMimeType() + { + return getProperties().getContentTypeAsString(); + } + + public String getEncoding() + { + return getProperties().getEncodingAsString(); + } + + public byte getPriority() + { + return getProperties().getPriority(); + } + + public long getTimestamp() + { + return getProperties().getTimestamp(); + } + + public String getType() + { + return getProperties().getTypeAsString(); + } + + public String getReplyTo() + { + return getProperties().getReplyToAsString(); + } + + public String getReplyToExchange() + { + // TODO + return getReplyTo(); + } + + public String getReplyToRoutingKey() + { + // TODO + return getReplyTo(); + } + + public Object getHeader(String name) + { + FieldTable ft = getProperties().getHeaders(); + return ft.get(name); + } + + public boolean containsHeaders(Set<String> names) + { + FieldTable ft = getProperties().getHeaders(); + for(String name : names) + { + if(!ft.containsKey(name)) + { + return false; + } + } + return true; + } + + @Override + public Collection<String> getHeaderNames() + { + return getProperties().getHeaders().keys(); + } + + public boolean containsHeader(String name) + { + FieldTable ft = getProperties().getHeaders(); + return ft.containsKey(name); + } + + + + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/MessageMetaDataType_0_8.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/MessageMetaDataType_0_8.java new file mode 100644 index 0000000000..9b50127ec7 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/MessageMetaDataType_0_8.java @@ -0,0 +1,67 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import java.nio.ByteBuffer; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.store.StoredMessage; + +public class MessageMetaDataType_0_8 implements MessageMetaDataType<MessageMetaData> +{ + + public static final int TYPE = 0; + + @Override + public int ordinal() + { + return TYPE; + } + + @Override + public MessageMetaData createMetaData(ByteBuffer buf) + { + return MessageMetaData.FACTORY.createMetaData(buf); + } + + @Override + public ServerMessage<MessageMetaData> createMessage(StoredMessage<MessageMetaData> msg) + { + return new AMQMessage(msg); + } + + public int hashCode() + { + return ordinal(); + } + + public boolean equals(Object o) + { + return o != null && o.getClass() == getClass(); + } + + @Override + public String getType() + { + return AmqpProtocolVersion.v0_8.toString(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_8.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_8.java new file mode 100644 index 0000000000..5ee56508d7 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_8.java @@ -0,0 +1,80 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.plugin.ProtocolEngineCreator; +import org.apache.qpid.transport.network.NetworkConnection; + +public class ProtocolEngineCreator_0_8 implements ProtocolEngineCreator +{ + private static final byte[] AMQP_0_8_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 1, + (byte) 1, + (byte) 8, + (byte) 0 + }; + + + public ProtocolEngineCreator_0_8() + { + } + + public AmqpProtocolVersion getVersion() + { + return AmqpProtocolVersion.v0_8; + } + + public byte[] getHeaderIdentifier() + { + return AMQP_0_8_HEADER; + } + + public ServerProtocolEngine newProtocolEngine(Broker broker, + NetworkConnection network, + Port port, + Transport transport, + long id) + { + return new AMQProtocolEngine(broker, network, id, port, transport); + } + + private static ProtocolEngineCreator INSTANCE = new ProtocolEngineCreator_0_8(); + + public static ProtocolEngineCreator getInstance() + { + return INSTANCE; + } + + @Override + public String getType() + { + return getVersion().toString(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_9.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_9.java new file mode 100644 index 0000000000..2a29348261 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_9.java @@ -0,0 +1,80 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.plugin.ProtocolEngineCreator; +import org.apache.qpid.transport.network.NetworkConnection; + +public class ProtocolEngineCreator_0_9 implements ProtocolEngineCreator +{ + private static final byte[] AMQP_0_9_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 1, + (byte) 1, + (byte) 0, + (byte) 9 + }; + + public ProtocolEngineCreator_0_9() + { + } + + public AmqpProtocolVersion getVersion() + { + return AmqpProtocolVersion.v0_9; + } + + + public byte[] getHeaderIdentifier() + { + return AMQP_0_9_HEADER; + } + + public ServerProtocolEngine newProtocolEngine(Broker broker, + NetworkConnection network, + Port port, + Transport transport, + long id) + { + return new AMQProtocolEngine(broker, network, id, port, transport); + } + + private static ProtocolEngineCreator INSTANCE = new ProtocolEngineCreator_0_9(); + + public static ProtocolEngineCreator getInstance() + { + return INSTANCE; + } + + @Override + public String getType() + { + return getVersion().toString(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_9_1.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_9_1.java new file mode 100644 index 0000000000..dad6bef032 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/ProtocolEngineCreator_0_9_1.java @@ -0,0 +1,82 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.plugin.ProtocolEngineCreator; +import org.apache.qpid.transport.network.NetworkConnection; + +public class ProtocolEngineCreator_0_9_1 implements ProtocolEngineCreator +{ + + private static final byte[] AMQP_0_9_1_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 0, + (byte) 0, + (byte) 9, + (byte) 1 + }; + + public ProtocolEngineCreator_0_9_1() + { + } + + public AmqpProtocolVersion getVersion() + { + return AmqpProtocolVersion.v0_9_1; + } + + + public byte[] getHeaderIdentifier() + { + return AMQP_0_9_1_HEADER; + } + + public ServerProtocolEngine newProtocolEngine(Broker broker, + NetworkConnection network, + Port port, + Transport transport, + long id) + { + return new AMQProtocolEngine(broker, network, id, port, transport); + } + + + private static ProtocolEngineCreator INSTANCE = new ProtocolEngineCreator_0_9_1(); + + public static ProtocolEngineCreator getInstance() + { + return INSTANCE; + } + + @Override + public String getType() + { + return getVersion().toString(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactory.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactory.java new file mode 100644 index 0000000000..6646dc0cc2 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactory.java @@ -0,0 +1,68 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.RecordDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; + +/** + * 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, + AMQShortString consumerTag, + boolean acks, + FieldTable filters, + boolean noLocal, FlowCreditManager creditManager) throws AMQException; + + + Subscription createSubscription(AMQChannel channel, + AMQProtocolSession protocolSession, + AMQShortString consumerTag, + boolean acks, + FieldTable filters, + boolean noLocal, + FlowCreditManager creditManager, + ClientDeliveryMethod clientMethod, + RecordDeliveryMethod recordMethod) throws AMQException; + + + Subscription createBasicGetNoAckSubscription(AMQChannel channel, + AMQProtocolSession session, + AMQShortString consumerTag, + FieldTable filters, + boolean noLocal, + FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) throws AMQException; + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactoryImpl.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactoryImpl.java new file mode 100644 index 0000000000..93b51a0567 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactoryImpl.java @@ -0,0 +1,109 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.RecordDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; + +public class SubscriptionFactoryImpl implements SubscriptionFactory +{ + + public Subscription createSubscription(int channelId, AMQProtocolSession protocolSession, + AMQShortString consumerTag, boolean acks, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager) throws AMQException + { + AMQChannel channel = protocolSession.getChannel(channelId); + if (channel == null) + { + throw new AMQException(AMQConstant.NOT_FOUND, "channel :" + channelId + " not found in protocol session"); + } + ClientDeliveryMethod clientMethod = channel.getClientDeliveryMethod(); + RecordDeliveryMethod recordMethod = channel.getRecordDeliveryMethod(); + + + return createSubscription(channel, protocolSession, consumerTag, acks, filters, + noLocal, + creditManager, + clientMethod, + recordMethod + ); + } + + public Subscription createSubscription(final AMQChannel channel, + final AMQProtocolSession protocolSession, + final AMQShortString consumerTag, + final boolean acks, + final FieldTable filters, + final boolean noLocal, + final FlowCreditManager creditManager, + final ClientDeliveryMethod clientMethod, + final RecordDeliveryMethod recordMethod + ) + throws AMQException + { + boolean isBrowser; + + if (filters != null) + { + Boolean isBrowserObj = (Boolean) filters.get(AMQPFilterTypes.NO_CONSUME.getValue()); + isBrowser = (isBrowserObj != null) && isBrowserObj.booleanValue(); + } + else + { + isBrowser = false; + } + + if(isBrowser) + { + return new SubscriptionImpl.BrowserSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + else if(acks) + { + return new SubscriptionImpl.AckSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + else + { + return new SubscriptionImpl.NoAckSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + } + + public SubscriptionImpl.GetNoAckSubscription createBasicGetNoAckSubscription(final AMQChannel channel, + final AMQProtocolSession session, + final AMQShortString consumerTag, + final FieldTable filters, + final boolean noLocal, + final FlowCreditManager creditManager, + final ClientDeliveryMethod deliveryMethod, + final RecordDeliveryMethod recordMethod) throws AMQException + { + return new SubscriptionImpl.GetNoAckSubscription(channel, session, null, null, false, creditManager, deliveryMethod, recordMethod); + } + + public static final SubscriptionFactoryImpl INSTANCE = new SubscriptionFactoryImpl(); + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionImpl.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionImpl.java new file mode 100644 index 0000000000..d1d86fe478 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/SubscriptionImpl.java @@ -0,0 +1,855 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.filter.FilterManager; +import org.apache.qpid.server.filter.FilterManagerFactory; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.SubscriptionActor; +import org.apache.qpid.server.logging.messages.SubscriptionMessages; +import org.apache.qpid.server.logging.subjects.SubscriptionLogSubject; +import org.apache.qpid.server.protocol.MessageConverterRegistry; +import org.apache.qpid.server.protocol.v0_8.output.ProtocolOutputConverter; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.RecordDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 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 abstract class SubscriptionImpl implements Subscription, FlowCreditManager.FlowCreditManagerListener +{ + + private StateListener _stateListener = new StateListener() + { + + public void stateChange(Subscription sub, State oldState, State newState) + { + + } + }; + + + private final AtomicReference<State> _state = new AtomicReference<State>(State.ACTIVE); + private volatile AMQQueue.Context _queueContext; + + private final ClientDeliveryMethod _deliveryMethod; + private final RecordDeliveryMethod _recordMethod; + + private final QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this); + + private final Map<String, Object> _properties = new ConcurrentHashMap<String, Object>(); + + private final Lock _stateChangeLock; + + private final long _subscriptionID; + private LogSubject _logSubject; + private LogActor _logActor; + private final AtomicLong _deliveredCount = new AtomicLong(0); + private final AtomicLong _deliveredBytes = new AtomicLong(0); + + private final AtomicLong _unacknowledgedCount = new AtomicLong(0); + private final AtomicLong _unacknowledgedBytes = new AtomicLong(0); + + private long _createTime = System.currentTimeMillis(); + + + static final class BrowserSubscription extends SubscriptionImpl + { + public BrowserSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return true; + } + + /** + * 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 entry + * @param batch + * @throws AMQException + */ + @Override + public void send(QueueEntry entry, boolean batch) throws AMQException + { + // We don't decrement the reference here as we don't want to consume the message + // but we do want to send it to the client. + + synchronized (getChannel()) + { + long deliveryTag = getChannel().getNextDeliveryTag(); + sendToClient(entry, deliveryTag); + } + + } + + @Override + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + } + + public static class NoAckSubscription extends SubscriptionImpl + { + private volatile AutoCommitTransaction _txn; + + public NoAckSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return false; + } + + @Override + public boolean isExplicitAcknowledge() + { + return false; + } + + /** + * 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 entry The message to send + * @param batch + * @throws AMQException + */ + @Override + public void send(QueueEntry entry, boolean batch) throws AMQException + { + // 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(_txn == null) + { + _txn = new AutoCommitTransaction(getQueue().getVirtualHost().getMessageStore()); + } + _txn.dequeue(getQueue(), entry.getMessage(), NOOP); + + entry.dequeue(); + + synchronized (getChannel()) + { + getChannel().getProtocolSession().setDeferFlush(batch); + long deliveryTag = getChannel().getNextDeliveryTag(); + + sendToClient(entry, deliveryTag); + + } + entry.dispose(); + + + } + + @Override + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + private static final ServerTransaction.Action NOOP = + new ServerTransaction.Action() + { + @Override + public void postCommit() + { + } + + @Override + public void onRollback() + { + } + }; + } + + /** + * NoAck Subscription for use with BasicGet method. + */ + public static final class GetNoAckSubscription extends SubscriptionImpl.NoAckSubscription + { + public GetNoAckSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + public boolean isTransient() + { + return true; + } + + public boolean wouldSuspend(QueueEntry msg) + { + return !getCreditManager().useCreditForMessage(msg.getMessage().getSize()); + } + + } + + static final class AckSubscription extends SubscriptionImpl + { + public AckSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return false; + } + + + /** + * 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 entry The message to send + * @param batch + * @throws AMQException + */ + @Override + public void send(QueueEntry entry, boolean batch) throws AMQException + { + + + synchronized (getChannel()) + { + getChannel().getProtocolSession().setDeferFlush(batch); + long deliveryTag = getChannel().getNextDeliveryTag(); + + addUnacknowledgedMessage(entry); + recordMessageDelivery(entry, deliveryTag); + sendToClient(entry, deliveryTag); + + + } + } + + + + } + + + private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class); + + private final AMQChannel _channel; + + private final AMQShortString _consumerTag; + + + private boolean _noLocal; + + private final FlowCreditManager _creditManager; + + private FilterManager _filters; + + private final Boolean _autoClose; + + private AMQQueue _queue; + private final AtomicBoolean _deleted = new AtomicBoolean(false); + + + + + public SubscriptionImpl(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable arguments, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + _subscriptionID = SUB_ID_GENERATOR.getAndIncrement(); + _channel = channel; + _consumerTag = consumerTag; + + _creditManager = creditManager; + creditManager.addStateListener(this); + + _noLocal = noLocal; + + + _filters = FilterManagerFactory.createManager(FieldTable.convertToMap(arguments)); + + _deliveryMethod = deliveryMethod; + _recordMethod = recordMethod; + + + _stateChangeLock = new ReentrantLock(); + + + if (arguments != null) + { + Object autoClose = arguments.get(AMQPFilterTypes.AUTO_CLOSE.getValue()); + if (autoClose != null) + { + _autoClose = (Boolean) autoClose; + } + else + { + _autoClose = false; + } + } + else + { + _autoClose = false; + } + + } + + public AMQSessionModel getSessionModel() + { + return _channel; + } + + public Long getDelivered() + { + return _deliveredCount.get(); + } + + public synchronized void setQueue(AMQQueue queue, boolean exclusive) + { + if(getQueue() != null) + { + throw new IllegalStateException("Attempt to set queue for subscription " + this + " to " + queue + "when already set to " + getQueue()); + } + _queue = queue; + + _logSubject = new SubscriptionLogSubject(this); + _logActor = new SubscriptionActor(CurrentActor.get().getRootMessageLogger(), this); + + if (CurrentActor.get().getRootMessageLogger(). + isMessageEnabled(CurrentActor.get(), _logSubject, SubscriptionMessages.CREATE_LOG_HIERARCHY)) + { + // Get the string value of the filters + String filterLogString = null; + if (_filters != null && _filters.hasFilters()) + { + filterLogString = _filters.toString(); + } + + if (isAutoClose()) + { + if (filterLogString == null) + { + filterLogString = ""; + } + else + { + filterLogString += ","; + } + filterLogString += "AutoClose"; + } + + if (isBrowser()) + { + // We do not need to check for null here as all Browsers are AutoClose + filterLogString +=",Browser"; + } + + CurrentActor.get(). + message(_logSubject, + SubscriptionMessages.CREATE(filterLogString, + queue.isDurable() && exclusive, + filterLogString != null)); + } + } + + public String toString() + { + String subscriber = "[channel=" + _channel + + ", consumerTag=" + _consumerTag + + ", session=" + getProtocolSession().getKey() ; + + return subscriber + "]"; + } + + /** + * 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 entry + * @param batch + * @throws AMQException + */ + abstract public void send(QueueEntry entry, boolean batch) throws AMQException; + + + public boolean isSuspended() + { + return !isActive() || _channel.isSuspended() || _deleted.get() || _channel.getConnectionModel().isStopped(); + } + + /** + * Callback indicating that a queue has been deleted. + * + * @param queue The queue to delete + */ + public void queueDeleted(AMQQueue queue) + { + _deleted.set(true); + } + + public boolean hasInterest(QueueEntry entry) + { + //check that the message hasn't been rejected + if (entry.isRejectedBy(getSubscriptionID())) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Subscription:" + this + " rejected message:" + entry); + } + } + + if(entry.getMessage() instanceof AMQMessage) + { + if (_noLocal) + { + AMQMessage message = (AMQMessage) entry.getMessage(); + + final Object publisherReference = message.getConnectionIdentifier(); + + // We don't want local messages so check to see if message is one we sent + Object localReference = getProtocolSession().getReference(); + + if(publisherReference != null && publisherReference.equals(localReference)) + { + return false; + } + } + } + else + { + // No interest in messages we can't convert to AMQMessage + if(MessageConverterRegistry.getConverter(entry.getMessage().getClass(), AMQMessage.class)==null) + { + return false; + } + } + + + if (_logger.isDebugEnabled()) + { + _logger.debug("(" + this + ") checking filters for message (" + entry); + } + return checkFilters(entry); + + } + + private boolean checkFilters(QueueEntry msg) + { + return (_filters == null) || _filters.allAllow(msg); + } + + public boolean isAutoClose() + { + return _autoClose; + } + + public FlowCreditManager getCreditManager() + { + return _creditManager; + } + + + public void close() + { + boolean closed = false; + State state = getState(); + + _stateChangeLock.lock(); + try + { + while(!closed && state != State.CLOSED) + { + closed = _state.compareAndSet(state, State.CLOSED); + if(!closed) + { + state = getState(); + } + else + { + _stateListener.stateChange(this,state, State.CLOSED); + } + } + _creditManager.removeListener(this); + } + finally + { + _stateChangeLock.unlock(); + } + //Log Subscription closed + CurrentActor.get().message(_logSubject, SubscriptionMessages.CLOSE()); + } + + public boolean isClosed() + { + return getState() == State.CLOSED; + } + + + public boolean wouldSuspend(QueueEntry msg) + { + return !_creditManager.useCreditForMessage(msg.getMessage().getSize()); + } + + public boolean trySendLock() + { + return _stateChangeLock.tryLock(); + } + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public AMQChannel getChannel() + { + return _channel; + } + + public AMQShortString getConsumerTag() + { + return _consumerTag; + } + + public String getConsumerName() + { + return _consumerTag == null ? null : _consumerTag.asString(); + } + + public long getSubscriptionID() + { + return _subscriptionID; + } + + public AMQProtocolSession getProtocolSession() + { + return _channel.getProtocolSession(); + } + + public LogActor getLogActor() + { + return _logActor; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public void onDequeue(final QueueEntry queueEntry) + { + restoreCredit(queueEntry); + } + + public void releaseQueueEntry(final QueueEntry queueEntry) + { + restoreCredit(queueEntry); + } + + public void restoreCredit(final QueueEntry queueEntry) + { + _creditManager.restoreCredit(1, queueEntry.getSize()); + } + + public void creditStateChanged(boolean hasCredit) + { + + if(hasCredit) + { + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + else + { + // this is a hack to get round the issue of increasing bytes credit + _stateListener.stateChange(this, State.ACTIVE, State.ACTIVE); + } + } + else + { + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + } + CurrentActor.get().message(_logSubject,SubscriptionMessages.STATE(_state.get().toString())); + } + + public State getState() + { + return _state.get(); + } + + + public void setStateListener(final StateListener listener) + { + _stateListener = listener; + } + + + public AMQQueue.Context getQueueContext() + { + return _queueContext; + } + + public void setQueueContext(AMQQueue.Context context) + { + _queueContext = context; + } + + + protected void sendToClient(final QueueEntry entry, final long deliveryTag) + throws AMQException + { + _deliveryMethod.deliverToClient(this,entry,deliveryTag); + _deliveredCount.incrementAndGet(); + _deliveredBytes.addAndGet(entry.getSize()); + } + + + protected void recordMessageDelivery(final QueueEntry entry, final long deliveryTag) + { + _recordMethod.recordMessageDelivery(this,entry,deliveryTag); + } + + + public boolean isActive() + { + return getState() == State.ACTIVE; + } + + public QueueEntry.SubscriptionAcquiredState getOwningState() + { + return _owningState; + } + + public void confirmAutoClose() + { + ProtocolOutputConverter converter = getChannel().getProtocolSession().getProtocolOutputConverter(); + converter.confirmConsumerAutoClose(getChannel().getChannelId(), getConsumerTag()); + } + + public boolean acquires() + { + return !isBrowser(); + } + + public boolean seesRequeues() + { + return !isBrowser(); + } + + public boolean isTransient() + { + return false; + } + + public void set(String key, Object value) + { + _properties.put(key, value); + } + + public Object get(String key) + { + return _properties.get(key); + } + + + public void setNoLocal(boolean noLocal) + { + _noLocal = noLocal; + } + + abstract boolean isBrowser(); + + public String getCreditMode() + { + return "WINDOW"; + } + + public boolean isBrowsing() + { + return isBrowser(); + } + + public boolean isExplicitAcknowledge() + { + return true; + } + + public boolean isDurable() + { + return false; + } + + public boolean isExclusive() + { + return getQueue().hasExclusiveSubscriber(); + } + + public String getName() + { + return String.valueOf(_consumerTag); + } + + public Map<String, Object> getArguments() + { + return null; + } + + public boolean isSessionTransactional() + { + return _channel.isTransactional(); + } + + public long getCreateTime() + { + return _createTime; + } + + public void queueEmpty() throws AMQException + { + if (isAutoClose()) + { + _queue.unregisterSubscription(this); + + confirmAutoClose(); + } + } + + public void flushBatched() + { + _channel.getProtocolSession().setDeferFlush(false); + + _channel.getProtocolSession().flushBatched(); + } + + public long getBytesOut() + { + return _deliveredBytes.longValue(); + } + + public long getMessagesOut() + { + return _deliveredCount.longValue(); + } + + + protected void addUnacknowledgedMessage(QueueEntry entry) + { + final long size = entry.getSize(); + _unacknowledgedBytes.addAndGet(size); + _unacknowledgedCount.incrementAndGet(); + entry.addStateChangeListener(new QueueEntry.StateChangeListener() + { + public void stateChanged(QueueEntry entry, QueueEntry.State oldState, QueueEntry.State newState) + { + if(oldState.equals(QueueEntry.State.ACQUIRED) && !newState.equals(QueueEntry.State.ACQUIRED)) + { + _unacknowledgedBytes.addAndGet(-size); + _unacknowledgedCount.decrementAndGet(); + entry.removeStateChangeListener(this); + } + } + }); + } + + public long getUnacknowledgedBytes() + { + return _unacknowledgedBytes.longValue(); + } + + public long getUnacknowledgedMessages() + { + return _unacknowledgedCount.longValue(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/UnacknowledgedMessageMap.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/UnacknowledgedMessageMap.java new file mode 100644 index 0000000000..1d41bcdcf4 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/UnacknowledgedMessageMap.java @@ -0,0 +1,69 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.QueueEntry; + +import java.util.Collection; +import java.util.Set; + + +public interface UnacknowledgedMessageMap +{ + public interface Visitor + { + /** + * @param deliveryTag + *@param message the message being iterated over @return true to stop iteration, false to continue + * @throws AMQException + */ + boolean callback(final long deliveryTag, QueueEntry message) throws AMQException; + + void visitComplete(); + } + + void visit(Visitor visitor) throws AMQException; + + void add(long deliveryTag, QueueEntry message); + + QueueEntry remove(long deliveryTag); + + Collection<QueueEntry> cancelAllMessages(); + + int size(); + + void clear(); + + QueueEntry get(long deliveryTag); + + /** + * Get the set of delivery tags that are outstanding. + * + * @return a set of delivery tags + */ + Set<Long> getDeliveryTags(); + + Collection<QueueEntry> acknowledge(long deliveryTag, boolean multiple); + +} + + diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/UnacknowledgedMessageMapImpl.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/UnacknowledgedMessageMapImpl.java new file mode 100644 index 0000000000..17b2c7b985 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/UnacknowledgedMessageMapImpl.java @@ -0,0 +1,183 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.QueueEntry; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +public class UnacknowledgedMessageMapImpl implements UnacknowledgedMessageMap +{ + private final Object _lock = new Object(); + + private long _unackedSize; + + private Map<Long, QueueEntry> _map; + + private long _lastDeliveryTag; + + private final int _prefetchLimit; + + public UnacknowledgedMessageMapImpl(int prefetchLimit) + { + _prefetchLimit = prefetchLimit; + _map = new LinkedHashMap<Long, QueueEntry>(prefetchLimit); + } + + public void collect(long deliveryTag, boolean multiple, Map<Long, QueueEntry> msgs) + { + if (multiple) + { + collect(deliveryTag, msgs); + } + else + { + final QueueEntry entry = get(deliveryTag); + if(entry != null) + { + msgs.put(deliveryTag, entry); + } + } + + } + + public void remove(Map<Long,QueueEntry> msgs) + { + synchronized (_lock) + { + for (Long deliveryTag : msgs.keySet()) + { + remove(deliveryTag); + } + } + } + + public QueueEntry remove(long deliveryTag) + { + synchronized (_lock) + { + + QueueEntry message = _map.remove(deliveryTag); + if(message != null) + { + _unackedSize -= message.getMessage().getSize(); + + } + + return message; + } + } + + public void visit(Visitor visitor) throws AMQException + { + synchronized (_lock) + { + Set<Map.Entry<Long, QueueEntry>> currentEntries = _map.entrySet(); + for (Map.Entry<Long, QueueEntry> entry : currentEntries) + { + visitor.callback(entry.getKey().longValue(), entry.getValue()); + } + visitor.visitComplete(); + } + } + + public void add(long deliveryTag, QueueEntry message) + { + synchronized (_lock) + { + _map.put(deliveryTag, message); + _unackedSize += message.getMessage().getSize(); + _lastDeliveryTag = deliveryTag; + } + } + + public Collection<QueueEntry> cancelAllMessages() + { + synchronized (_lock) + { + Collection<QueueEntry> currentEntries = _map.values(); + _map = new LinkedHashMap<Long, QueueEntry>(_prefetchLimit); + _unackedSize = 0l; + return currentEntries; + } + } + + public int size() + { + synchronized (_lock) + { + return _map.size(); + } + } + + public void clear() + { + synchronized (_lock) + { + _map.clear(); + _unackedSize = 0l; + } + } + + public QueueEntry get(long key) + { + synchronized (_lock) + { + return _map.get(key); + } + } + + public Set<Long> getDeliveryTags() + { + synchronized (_lock) + { + return _map.keySet(); + } + } + + public Collection<QueueEntry> acknowledge(long deliveryTag, boolean multiple) + { + Map<Long, QueueEntry> ackedMessageMap = new LinkedHashMap<Long,QueueEntry>(); + collect(deliveryTag, multiple, ackedMessageMap); + remove(ackedMessageMap); + return ackedMessageMap.values(); + } + + private void collect(long key, Map<Long, QueueEntry> msgs) + { + synchronized (_lock) + { + for (Map.Entry<Long, QueueEntry> entry : _map.entrySet()) + { + msgs.put(entry.getKey(),entry.getValue()); + if (entry.getKey() == key) + { + break; + } + } + } + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/AccessRequestHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/AccessRequestHandler.java new file mode 100644 index 0000000000..ae07d60c4e --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/AccessRequestHandler.java @@ -0,0 +1,85 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol.v0_8.handler;
+
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AccessRequestBody;
+import org.apache.qpid.framing.AccessRequestOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.protocol.v0_8.AMQChannel;
+import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession;
+import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager;
+import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener;
+
+/**
+ * @author Apache Software Foundation
+ *
+ *
+ */
+public class AccessRequestHandler implements StateAwareMethodListener<AccessRequestBody>
+{
+ private static final AccessRequestHandler _instance = new AccessRequestHandler();
+
+
+ public static AccessRequestHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private AccessRequestHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AccessRequestBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ final AMQChannel channel = session.getChannel(channelId);
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+
+ // We don't implement access control class, but to keep clients happy that expect it
+ // always use the "0" ticket.
+ AccessRequestOkBody response;
+ if(methodRegistry instanceof MethodRegistry_0_9)
+ {
+ response = ((MethodRegistry_0_9)methodRegistry).createAccessRequestOkBody(0);
+ }
+ else if(methodRegistry instanceof MethodRegistry_8_0)
+ {
+ response = ((MethodRegistry_8_0)methodRegistry).createAccessRequestOkBody(0);
+ }
+ else
+ {
+ throw new AMQException(AMQConstant.COMMAND_INVALID, "AccessRequest not present in AMQP versions other than 0-8, 0-9");
+ }
+
+ channel.sync();
+ session.writeFrame(response.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicAckMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicAckMethodHandler.java new file mode 100644 index 0000000000..f623d27e87 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicAckMethodHandler.java @@ -0,0 +1,67 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicAckBody; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +public class BasicAckMethodHandler implements StateAwareMethodListener<BasicAckBody> +{ + private static final Logger _log = Logger.getLogger(BasicAckMethodHandler.class); + + private static final BasicAckMethodHandler _instance = new BasicAckMethodHandler(); + + public static BasicAckMethodHandler getInstance() + { + return _instance; + } + + private BasicAckMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicAckBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolSession = stateManager.getProtocolSession(); + + + if (_log.isDebugEnabled()) + { + _log.debug("Ack(Tag:" + body.getDeliveryTag() + ":Mult:" + body.getMultiple() + ") received on channel " + channelId); + } + + final AMQChannel channel = protocolSession.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + // this method throws an AMQException if the delivery tag is not known + channel.acknowledgeMessage(body.getDeliveryTag(), body.getMultiple()); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicCancelMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicCancelMethodHandler.java new file mode 100644 index 0000000000..5a6a7bdc18 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicCancelMethodHandler.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicCancelBody; +import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +public class BasicCancelMethodHandler implements StateAwareMethodListener<BasicCancelBody> +{ + private static final Logger _log = Logger.getLogger(BasicCancelMethodHandler.class); + + private static final BasicCancelMethodHandler _instance = new BasicCancelMethodHandler(); + + public static BasicCancelMethodHandler getInstance() + { + return _instance; + } + + private BasicCancelMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicCancelBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + final AMQChannel channel = session.getChannel(channelId); + + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + if (_log.isDebugEnabled()) + { + _log.debug("BasicCancel: for:" + body.getConsumerTag() + + " nowait:" + body.getNowait()); + } + + channel.unsubscribeConsumer(body.getConsumerTag()); + if (!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + BasicCancelOkBody cancelOkBody = methodRegistry.createBasicCancelOkBody(body.getConsumerTag()); + channel.sync(); + session.writeFrame(cancelOkBody.generateFrame(channelId)); + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicConsumeMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicConsumeMethodHandler.java new file mode 100644 index 0000000000..836de44f4e --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicConsumeMethodHandler.java @@ -0,0 +1,177 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicConsumeBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BasicConsumeMethodHandler implements StateAwareMethodListener<BasicConsumeBody> +{ + private static final Logger _logger = Logger.getLogger(BasicConsumeMethodHandler.class); + + private static final BasicConsumeMethodHandler _instance = new BasicConsumeMethodHandler(); + + public static BasicConsumeMethodHandler getInstance() + { + return _instance; + } + + private BasicConsumeMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicConsumeBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + + AMQChannel channel = protocolConnection.getChannel(channelId); + VirtualHost vHost = protocolConnection.getVirtualHost(); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + else + { + channel.sync(); + if (_logger.isDebugEnabled()) + { + _logger.debug("BasicConsume: from '" + body.getQueue() + + "' for:" + body.getConsumerTag() + + " nowait:" + body.getNowait() + + " args:" + body.getArguments()); + } + + AMQQueue queue = body.getQueue() == null ? channel.getDefaultQueue() : vHost.getQueue(body.getQueue().intern().toString()); + + if (queue == null) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("No queue for '" + body.getQueue() + "'"); + } + if (body.getQueue() != null) + { + String msg = "No such queue, '" + body.getQueue() + "'"; + throw body.getChannelException(AMQConstant.NOT_FOUND, msg); + } + else + { + String msg = "No queue name provided, no default queue defined."; + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, msg); + } + } + else + { + final AMQShortString consumerTagName; + + if (queue.isExclusive() && !queue.isDurable()) + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + if (session == null || session.getConnectionModel() != protocolConnection) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getName() + " is exclusive, but not created on this Connection."); + } + } + + if (body.getConsumerTag() != null) + { + consumerTagName = body.getConsumerTag().intern(false); + } + else + { + consumerTagName = null; + } + + try + { + if(consumerTagName == null || channel.getSubscription(consumerTagName) == null) + { + + AMQShortString consumerTag = channel.subscribeToQueue(consumerTagName, queue, !body.getNoAck(), + body.getArguments(), body.getNoLocal(), body.getExclusive()); + if (!body.getNowait()) + { + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createBasicConsumeOkBody(consumerTag); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + } + } + else + { + AMQShortString msg = new AMQShortString("Non-unique consumer tag, '" + body.getConsumerTag() + "'"); + + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), // replyCode + msg, // replytext + body.getClazz(), + body.getMethod()); + protocolConnection.writeFrame(responseBody.generateFrame(0)); + } + + } + catch (org.apache.qpid.AMQInvalidArgumentException ise) + { + _logger.debug("Closing connection due to invalid selector"); + + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createChannelCloseBody(AMQConstant.ARGUMENT_INVALID.getCode(), + new AMQShortString(ise.getMessage()), + body.getClazz(), + body.getMethod()); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + + } + catch (AMQQueue.ExistingExclusiveSubscription e) + { + throw body.getChannelException(AMQConstant.ACCESS_REFUSED, + "Cannot subscribe to queue " + + queue.getName() + + " as it already has an existing exclusive consumer"); + } + catch (AMQQueue.ExistingSubscriptionPreventsExclusive e) + { + throw body.getChannelException(AMQConstant.ACCESS_REFUSED, + "Cannot subscribe to queue " + + queue.getName() + + " exclusively as it already has a consumer"); + } + + } + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicGetMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicGetMethodHandler.java new file mode 100644 index 0000000000..5238a41e49 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicGetMethodHandler.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ + +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicGetBody; +import org.apache.qpid.framing.BasicGetEmptyBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.flow.MessageOnlyCreditManager; +import org.apache.qpid.server.protocol.v0_8.AMQMessage; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.RecordDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.protocol.v0_8.SubscriptionFactoryImpl; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BasicGetMethodHandler implements StateAwareMethodListener<BasicGetBody> +{ + private static final Logger _log = Logger.getLogger(BasicGetMethodHandler.class); + + private static final BasicGetMethodHandler _instance = new BasicGetMethodHandler(); + + public static BasicGetMethodHandler getInstance() + { + return _instance; + } + + private BasicGetMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicGetBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + + + VirtualHost vHost = protocolConnection.getVirtualHost(); + + AMQChannel channel = protocolConnection.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + else + { + channel.sync(); + AMQQueue queue = body.getQueue() == null ? channel.getDefaultQueue() : vHost.getQueue(body.getQueue().toString()); + if (queue == null) + { + _log.info("No queue for '" + body.getQueue() + "'"); + if(body.getQueue()!=null) + { + throw body.getConnectionException(AMQConstant.NOT_FOUND, + "No such queue, '" + body.getQueue()+ "'"); + } + else + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "No queue name provided, no default queue defined."); + } + } + else + { + if (queue.isExclusive()) + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + if (session == null || session.getConnectionModel() != protocolConnection) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue is exclusive, but not created on this Connection."); + } + } + + if (!performGet(queue,protocolConnection, channel, !body.getNoAck())) + { + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + // TODO - set clusterId + BasicGetEmptyBody responseBody = methodRegistry.createBasicGetEmptyBody(null); + + + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + } + } + } + } + + public static boolean performGet(final AMQQueue queue, + final AMQProtocolSession session, + final AMQChannel channel, + final boolean acks) + throws AMQException + { + + final FlowCreditManager singleMessageCredit = new MessageOnlyCreditManager(1L); + + final ClientDeliveryMethod getDeliveryMethod = new ClientDeliveryMethod() + { + + public void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) + throws AMQException + { + singleMessageCredit.useCreditForMessage(entry.getMessage().getSize()); + if(entry.getMessage() instanceof AMQMessage) + { + session.getProtocolOutputConverter().writeGetOk(entry, channel.getChannelId(), + deliveryTag, queue.getMessageCount()); + entry.incrementDeliveryCount(); + } + else + { + //TODO Convert AMQP 0-10 message + throw new AMQException(AMQConstant.NOT_IMPLEMENTED, "Not implemented conversion of 0-10 message", null); + } + + } + }; + final RecordDeliveryMethod getRecordMethod = new RecordDeliveryMethod() + { + + public void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag) + { + channel.addUnacknowledgedMessage(entry, deliveryTag, null); + } + }; + + Subscription sub; + if(acks) + { + sub = SubscriptionFactoryImpl.INSTANCE.createSubscription(channel, session, null, acks, null, false, singleMessageCredit, getDeliveryMethod, getRecordMethod); + } + else + { + sub = SubscriptionFactoryImpl.INSTANCE.createBasicGetNoAckSubscription(channel, session, null, null, false, singleMessageCredit, getDeliveryMethod, getRecordMethod); + } + + queue.registerSubscription(sub,false); + queue.flushSubscription(sub); + queue.unregisterSubscription(sub); + return(!singleMessageCredit.hasCredit()); + + + } + + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicPublishMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicPublishMethodHandler.java new file mode 100644 index 0000000000..497e97db3e --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicPublishMethodHandler.java @@ -0,0 +1,97 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BasicPublishMethodHandler implements StateAwareMethodListener<BasicPublishBody> +{ + private static final Logger _logger = Logger.getLogger(BasicPublishMethodHandler.class); + + private static final BasicPublishMethodHandler _instance = new BasicPublishMethodHandler(); + + + public static BasicPublishMethodHandler getInstance() + { + return _instance; + } + + private BasicPublishMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicPublishBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Publish received on channel " + channelId); + } + + AMQShortString exchangeName = body.getExchange(); + // TODO: check the delivery tag field details - is it unique across the broker or per subscriber? + if (exchangeName == null) + { + exchangeName = AMQShortString.valueOf(ExchangeDefaults.DEFAULT_EXCHANGE_NAME); + } + + VirtualHost vHost = session.getVirtualHost(); + Exchange exch = vHost.getExchange(exchangeName.toString()); + // if the exchange does not exist we raise a channel exception + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Unknown exchange name"); + } + 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 = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + MessagePublishInfo info = session.getMethodRegistry().getProtocolVersionMethodConverter().convertToInfo(body); + info.setExchange(exchangeName); + channel.setPublishFrame(info, exch); + } + } + +} + + + diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicQosHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicQosHandler.java new file mode 100644 index 0000000000..e4a6636a74 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicQosHandler.java @@ -0,0 +1,58 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.BasicQosBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +public class BasicQosHandler implements StateAwareMethodListener<BasicQosBody> +{ + private static final BasicQosHandler _instance = new BasicQosHandler(); + + public static BasicQosHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, BasicQosBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + channel.sync(); + channel.setCredit(body.getPrefetchSize(), body.getPrefetchCount()); + + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createBasicQosOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRecoverMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRecoverMethodHandler.java new file mode 100644 index 0000000000..0a79466b35 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRecoverMethodHandler.java @@ -0,0 +1,73 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.BasicRecoverBody; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +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, BasicRecoverBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + _logger.debug("Recover received on protocol session " + session + " and channel " + channelId); + AMQChannel channel = session.getChannel(channelId); + + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.resend(body.getRequeue()); + + // Qpid 0-8 hacks a synchronous -ok onto recover. + // In Qpid 0-9 we create a separate sync-recover, sync-recover-ok pair to be "more" compliant + if(session.getProtocolVersion().equals(ProtocolVersion.v8_0)) + { + MethodRegistry_8_0 methodRegistry = (MethodRegistry_8_0) session.getMethodRegistry(); + AMQMethodBody recoverOk = methodRegistry.createBasicRecoverOkBody(); + channel.sync(); + session.writeFrame(recoverOk.generateFrame(channelId)); + + } + + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRecoverSyncMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRecoverSyncMethodHandler.java new file mode 100644 index 0000000000..b54e1c7dcf --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRecoverSyncMethodHandler.java @@ -0,0 +1,81 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol.v0_8.handler;
+
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.BasicRecoverSyncBody;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.framing.amqp_0_91.MethodRegistry_0_91;
+import org.apache.qpid.server.protocol.v0_8.AMQChannel;
+import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession;
+import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager;
+import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener;
+
+public class BasicRecoverSyncMethodHandler implements StateAwareMethodListener<BasicRecoverSyncBody>
+{
+ private static final Logger _logger = Logger.getLogger(BasicRecoverSyncMethodHandler.class);
+
+ private static final BasicRecoverSyncMethodHandler _instance = new BasicRecoverSyncMethodHandler();
+
+ public static BasicRecoverSyncMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ _logger.debug("Recover received on protocol session " + session + " and channel " + channelId);
+ AMQChannel channel = session.getChannel(channelId);
+
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+ channel.sync();
+ channel.resend(body.getRequeue());
+
+ // Qpid 0-8 hacks a synchronous -ok onto recover.
+ // In Qpid 0-9 we create a separate sync-recover, sync-recover-ok pair to be "more" compliant
+ if(session.getProtocolVersion().equals(ProtocolVersion.v0_9))
+ {
+ MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) session.getMethodRegistry();
+ AMQMethodBody recoverOk = methodRegistry.createBasicRecoverSyncOkBody();
+ session.writeFrame(recoverOk.generateFrame(channelId));
+
+ }
+ else if(session.getProtocolVersion().equals(ProtocolVersion.v0_91))
+ {
+ MethodRegistry_0_91 methodRegistry = (MethodRegistry_0_91) session.getMethodRegistry();
+ AMQMethodBody recoverOk = methodRegistry.createBasicRecoverSyncOkBody();
+ session.writeFrame(recoverOk.generateFrame(channelId));
+
+ }
+
+ }
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRejectMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRejectMethodHandler.java new file mode 100644 index 0000000000..0cfdff3338 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/BasicRejectMethodHandler.java @@ -0,0 +1,139 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicRejectBody; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +public class BasicRejectMethodHandler implements StateAwareMethodListener<BasicRejectBody> +{ + private static final Logger _logger = Logger.getLogger(BasicRejectMethodHandler.class); + + private static BasicRejectMethodHandler _instance = new BasicRejectMethodHandler(); + + public static BasicRejectMethodHandler getInstance() + { + return _instance; + } + + private BasicRejectMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicRejectBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Rejecting:" + body.getDeliveryTag() + + ": Requeue:" + body.getRequeue() + + " on channel:" + channel.debugIdentity()); + } + + long deliveryTag = body.getDeliveryTag(); + + QueueEntry message = channel.getUnacknowledgedMessageMap().get(deliveryTag); + + if (message == null) + { + _logger.warn("Dropping reject request as message is null for tag:" + deliveryTag); + } + else + { + if (message.isQueueDeleted()) + { + _logger.warn("Message's Queue has already been purged, dropping message"); + message = channel.getUnacknowledgedMessageMap().remove(deliveryTag); + if(message != null) + { + message.discard(); + } + return; + } + + if (message.getMessage() == null) + { + _logger.warn("Message has already been purged, unable to Reject."); + return; + } + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Rejecting: DT:" + deliveryTag + "-" + message.getMessage() + + ": Requeue:" + body.getRequeue() + + " on channel:" + channel.debugIdentity()); + } + + message.reject(); + + if (body.getRequeue()) + { + channel.requeue(deliveryTag); + + //this requeue represents a message rejected from the pre-dispatch queue + //therefore we need to amend the delivery counter. + message.decrementDeliveryCount(); + } + else + { + final boolean maxDeliveryCountEnabled = channel.isMaxDeliveryCountEnabled(deliveryTag); + _logger.debug("maxDeliveryCountEnabled: " + maxDeliveryCountEnabled + " deliveryTag " + deliveryTag); + if (maxDeliveryCountEnabled) + { + final boolean deliveredTooManyTimes = channel.isDeliveredTooManyTimes(deliveryTag); + _logger.debug("deliveredTooManyTimes: " + deliveredTooManyTimes + " deliveryTag " + deliveryTag); + if (deliveredTooManyTimes) + { + channel.deadLetter(body.getDeliveryTag()); + } + else + { + //this requeue represents a message rejected because of a recover/rollback that we + //are not ready to DLQ. We rely on the reject command to resend from the unacked map + //and therefore need to increment the delivery counter so we cancel out the effect + //of the AMQChannel#resend() decrement. + message.incrementDeliveryCount(); + } + } + else + { + channel.deadLetter(body.getDeliveryTag()); + } + } + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelCloseHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelCloseHandler.java new file mode 100644 index 0000000000..e96d098618 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelCloseHandler.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.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, ChannelCloseBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isInfoEnabled()) + { + _logger.info("Received channel close for id " + channelId + " citing class " + body.getClassId() + + " and method " + body.getMethodId()); + } + + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getConnectionException(AMQConstant.CHANNEL_ERROR, "Trying to close unknown channel"); + } + channel.sync(); + session.closeChannel(channelId); + // Client requested closure so we don't wait for ok we send it + stateManager.getProtocolSession().closeChannelOk(channelId); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + ChannelCloseOkBody responseBody = methodRegistry.createChannelCloseOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelCloseOkHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelCloseOkHandler.java new file mode 100644 index 0000000000..2a220ff78d --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelCloseOkHandler.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +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, ChannelCloseOkBody body, int channelId) throws AMQException + { + + _logger.info("Received channel-close-ok for channel-id " + channelId); + + // Let the Protocol Session know the channel is now closed. + stateManager.getProtocolSession().closeChannelOk(channelId); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelFlowHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelFlowHandler.java new file mode 100644 index 0000000000..cc1677c93e --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelFlowHandler.java @@ -0,0 +1,68 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.ChannelFlowBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +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, ChannelFlowBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + channel.sync(); + channel.setSuspended(!body.getActive()); + _logger.debug("Channel.Flow for channel " + channelId + ", active=" + body.getActive()); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createChannelFlowOkBody(body.getActive()); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelOpenHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelOpenHandler.java new file mode 100644 index 0000000000..442c912032 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ChannelOpenHandler.java @@ -0,0 +1,142 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelOpenBody; +import org.apache.qpid.framing.ChannelOpenOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; +import org.apache.qpid.framing.amqp_0_91.MethodRegistry_0_91; +import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.UUID; + +public class ChannelOpenHandler implements StateAwareMethodListener<ChannelOpenBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelOpenHandler.class); + + private static ChannelOpenHandler _instance = new ChannelOpenHandler(); + + public static ChannelOpenHandler getInstance() + { + return _instance; + } + + private ChannelOpenHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelOpenBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + + // Protect the broker against out of order frame request. + if (virtualHost == null) + { + throw new AMQException(AMQConstant.COMMAND_INVALID, "Virtualhost has not yet been set. ConnectionOpen has not been called.", null); + } + _logger.info("Connecting to: " + virtualHost.getName()); + + final AMQChannel channel = new AMQChannel(session,channelId, virtualHost.getMessageStore()); + + session.addChannel(channel); + + ChannelOpenOkBody response; + + ProtocolVersion pv = session.getProtocolVersion(); + + if(pv.equals(ProtocolVersion.v8_0)) + { + MethodRegistry_8_0 methodRegistry = (MethodRegistry_8_0) MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0); + response = methodRegistry.createChannelOpenOkBody(); + + } + else if(pv.equals(ProtocolVersion.v0_9)) + { + MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9); + UUID uuid = UUID.randomUUID(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + DataOutputStream dataOut = new DataOutputStream(output); + try + { + dataOut.writeLong(uuid.getMostSignificantBits()); + dataOut.writeLong(uuid.getLeastSignificantBits()); + dataOut.flush(); + dataOut.close(); + } + catch (IOException e) + { + // This *really* shouldn't happen as we're not doing any I/O + throw new RuntimeException("I/O exception when writing to byte array", e); + } + + // should really associate this channelId to the session + byte[] channelName = output.toByteArray(); + + response = methodRegistry.createChannelOpenOkBody(channelName); + } + else if(pv.equals(ProtocolVersion.v0_91)) + { + MethodRegistry_0_91 methodRegistry = (MethodRegistry_0_91) MethodRegistry.getMethodRegistry(ProtocolVersion.v0_91); + UUID uuid = UUID.randomUUID(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + DataOutputStream dataOut = new DataOutputStream(output); + try + { + dataOut.writeLong(uuid.getMostSignificantBits()); + dataOut.writeLong(uuid.getLeastSignificantBits()); + dataOut.flush(); + dataOut.close(); + } + catch (IOException e) + { + // This *really* shouldn't happen as we're not doing any I/O + throw new RuntimeException("I/O exception when writing to byte array", e); + } + + // should really associate this channelId to the session + byte[] channelName = output.toByteArray(); + + response = methodRegistry.createChannelOpenOkBody(channelName); + } + else + { + throw new AMQException(AMQConstant.INTERNAL_ERROR, "Got channel open for protocol version not catered for: " + pv, null); + } + + + session.writeFrame(response.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionCloseMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionCloseMethodHandler.java new file mode 100644 index 0000000000..60f9c1d495 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionCloseMethodHandler.java @@ -0,0 +1,72 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +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, ConnectionCloseBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + if (_logger.isInfoEnabled()) + { + _logger.info("ConnectionClose received with reply code/reply text " + body.getReplyCode() + "/" + + body.getReplyText() + " for " + session); + } + try + { + session.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + ConnectionCloseOkBody responseBody = methodRegistry.createConnectionCloseOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + session.closeProtocolSession(); + + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionCloseOkMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionCloseOkMethodHandler.java new file mode 100644 index 0000000000..fe46b6c0cd --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionCloseOkMethodHandler.java @@ -0,0 +1,63 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQState; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +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, ConnectionCloseOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + //todo should this not do more than just log the method? + _logger.info("Received Connection-close-ok"); + + try + { + stateManager.changeState(AMQState.CONNECTION_CLOSED); + session.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionOpenMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionOpenMethodHandler.java new file mode 100644 index 0000000000..62b13baac2 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionOpenMethodHandler.java @@ -0,0 +1,107 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ConnectionOpenBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQState; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.State; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ConnectionOpenMethodHandler implements StateAwareMethodListener<ConnectionOpenBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionOpenMethodHandler.class); + + private static ConnectionOpenMethodHandler _instance = new ConnectionOpenMethodHandler(); + + public static ConnectionOpenMethodHandler getInstance() + { + return _instance; + } + + private ConnectionOpenMethodHandler() + { + } + + private static AMQShortString generateClientID() + { + return new AMQShortString(Long.toString(System.currentTimeMillis())); + } + + public void methodReceived(AMQStateManager stateManager, ConnectionOpenBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + //ignore leading '/' + String virtualHostName; + if ((body.getVirtualHost() != null) && body.getVirtualHost().charAt(0) == '/') + { + virtualHostName = new StringBuilder(body.getVirtualHost().subSequence(1, body.getVirtualHost().length())).toString(); + } + else + { + virtualHostName = body.getVirtualHost() == null ? null : String.valueOf(body.getVirtualHost()); + } + + VirtualHost virtualHost = stateManager.getVirtualHostRegistry().getVirtualHost(virtualHostName); + + if (virtualHost == null) + { + throw body.getConnectionException(AMQConstant.NOT_FOUND, "Unknown virtual host: '" + virtualHostName + "'"); + } + else + { + // Check virtualhost access + if (!virtualHost.getSecurityManager().accessVirtualhost(virtualHostName, session.getRemoteAddress())) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied: '" + virtualHost.getName() + "'"); + } + else if (virtualHost.getState() != State.ACTIVE) + { + throw body.getConnectionException(AMQConstant.CONNECTION_FORCED, "Virtual host '" + virtualHost.getName() + "' is not active"); + } + + session.setVirtualHost(virtualHost); + + // See Spec (0.8.2). Section 3.1.2 Virtual Hosts + if (session.getContextKey() == null) + { + session.setContextKey(generateClientID()); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createConnectionOpenOkBody(body.getVirtualHost()); + + stateManager.changeState(AMQState.CONNECTION_OPEN); + + session.writeFrame(responseBody.generateFrame(channelId)); + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionSecureOkMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionSecureOkMethodHandler.java new file mode 100644 index 0000000000..d319f080d2 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionSecureOkMethodHandler.java @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.framing.ConnectionSecureOkBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.protocol.v0_8.state.AMQState; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +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, ConnectionSecureOkBody body, int channelId) throws AMQException + { + Broker broker = stateManager.getBroker(); + AMQProtocolSession session = stateManager.getProtocolSession(); + + SubjectCreator subjectCreator = stateManager.getSubjectCreator(); + + SaslServer ss = session.getSaslServer(); + if (ss == null) + { + throw new AMQException("No SASL context set up in session"); + } + MethodRegistry methodRegistry = session.getMethodRegistry(); + SubjectAuthenticationResult authResult = subjectCreator.authenticate(ss, body.getResponse()); + switch (authResult.getStatus()) + { + case ERROR: + Exception cause = authResult.getCause(); + + _logger.info("Authentication failed:" + (cause == null ? "" : cause.getMessage())); + + // This should be abstracted + stateManager.changeState(AMQState.CONNECTION_CLOSING); + + ConnectionCloseBody connectionCloseBody = + methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), + AMQConstant.NOT_ALLOWED.getName(), + body.getClazz(), + body.getMethod()); + + session.writeFrame(connectionCloseBody.generateFrame(0)); + disposeSaslServer(session); + break; + case SUCCESS: + if (_logger.isInfoEnabled()) + { + _logger.info("Connected as: " + authResult.getSubject()); + } + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + + ConnectionTuneBody tuneBody = + methodRegistry.createConnectionTuneBody((Integer)broker.getAttribute(Broker.CONNECTION_SESSION_COUNT_LIMIT), + BrokerProperties.FRAME_SIZE, + (Integer)broker.getAttribute(Broker.CONNECTION_HEART_BEAT_DELAY)); + session.writeFrame(tuneBody.generateFrame(0)); + session.setAuthorizedSubject(authResult.getSubject()); + disposeSaslServer(session); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + + ConnectionSecureBody secureBody = methodRegistry.createConnectionSecureBody(authResult.getChallenge()); + session.writeFrame(secureBody.generateFrame(0)); + } + } + + 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/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionStartOkMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionStartOkMethodHandler.java new file mode 100644 index 0000000000..9350327346 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionStartOkMethodHandler.java @@ -0,0 +1,154 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.framing.ConnectionStartOkBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.protocol.v0_8.state.AMQState; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.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(); + + public static ConnectionStartOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionStartOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionStartOkBody body, int channelId) throws AMQException + { + Broker broker = stateManager.getBroker(); + AMQProtocolSession session = stateManager.getProtocolSession(); + + _logger.info("SASL Mechanism selected: " + body.getMechanism()); + _logger.info("Locale selected: " + body.getLocale()); + + SubjectCreator subjectCreator = stateManager.getSubjectCreator(); + SaslServer ss = null; + try + { + ss = subjectCreator.createSaslServer(String.valueOf(body.getMechanism()), session.getLocalFQDN(), session.getPeerPrincipal()); + + if (ss == null) + { + throw body.getConnectionException(AMQConstant.RESOURCE_ERROR, "Unable to create SASL Server:" + body.getMechanism()); + } + + session.setSaslServer(ss); + + final SubjectAuthenticationResult authResult = subjectCreator.authenticate(ss, body.getResponse()); + //save clientProperties + session.setClientProperties(body.getClientProperties()); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + + switch (authResult.getStatus()) + { + case ERROR: + Exception cause = authResult.getCause(); + + _logger.info("Authentication failed:" + (cause == null ? "" : cause.getMessage())); + + stateManager.changeState(AMQState.CONNECTION_CLOSING); + + ConnectionCloseBody closeBody = + methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), // replyCode + AMQConstant.NOT_ALLOWED.getName(), + body.getClazz(), + body.getMethod()); + + session.writeFrame(closeBody.generateFrame(0)); + disposeSaslServer(session); + break; + + case SUCCESS: + if (_logger.isInfoEnabled()) + { + _logger.info("Connected as: " + authResult.getSubject()); + } + session.setAuthorizedSubject(authResult.getSubject()); + + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + + ConnectionTuneBody tuneBody = methodRegistry.createConnectionTuneBody((Integer)broker.getAttribute(Broker.CONNECTION_SESSION_COUNT_LIMIT), + BrokerProperties.FRAME_SIZE, + (Integer)broker.getAttribute(Broker.CONNECTION_HEART_BEAT_DELAY)); + session.writeFrame(tuneBody.generateFrame(0)); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + + ConnectionSecureBody secureBody = methodRegistry.createConnectionSecureBody(authResult.getChallenge()); + session.writeFrame(secureBody.generateFrame(0)); + } + } + catch (SaslException e) + { + disposeSaslServer(session); + 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); + } + } + } + +} + + + diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionTuneOkMethodHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionTuneOkMethodHandler.java new file mode 100644 index 0000000000..5fddab6576 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ConnectionTuneOkMethodHandler.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionTuneOkBody; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQState; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.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, ConnectionTuneOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isDebugEnabled()) + { + _logger.debug(body); + } + stateManager.changeState(AMQState.CONNECTION_NOT_OPENED); + session.initHeartbeats(body.getHeartbeat()); + session.setMaxFrameSize(body.getFrameMax()); + + long maxChannelNumber = body.getChannelMax(); + //0 means no implied limit, except that forced by protocol limitations (0xFFFF) + session.setMaximumNumberOfChannels( maxChannelNumber == 0 ? 0xFFFFL : maxChannelNumber); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeBoundHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeBoundHandler.java new file mode 100644 index 0000000000..0535236f94 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeBoundHandler.java @@ -0,0 +1,192 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ExchangeBoundBody; +import org.apache.qpid.framing.ExchangeBoundOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * @author Apache Software Foundation + * + * + */ +public class ExchangeBoundHandler implements StateAwareMethodListener<ExchangeBoundBody> +{ + private static final ExchangeBoundHandler _instance = new ExchangeBoundHandler(); + + public static final int OK = 0; + + public static final int EXCHANGE_NOT_FOUND = 1; + + public static final int QUEUE_NOT_FOUND = 2; + + public static final int NO_BINDINGS = 3; + + public static final int QUEUE_NOT_BOUND = 4; + + public static final int NO_QUEUE_BOUND_WITH_RK = 5; + + public static final int SPECIFIC_QUEUE_NOT_BOUND_WITH_RK = 6; + + public static ExchangeBoundHandler getInstance() + { + return _instance; + } + + private ExchangeBoundHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ExchangeBoundBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + MethodRegistry methodRegistry = session.getMethodRegistry(); + + final AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + channel.sync(); + + + AMQShortString exchangeName = body.getExchange(); + AMQShortString queueName = body.getQueue(); + AMQShortString routingKey = body.getRoutingKey(); + if (exchangeName == null) + { + throw new AMQException("Exchange exchange must not be null"); + } + Exchange exchange = virtualHost.getExchange(exchangeName.toString()); + ExchangeBoundOkBody response; + if (exchange == null) + { + + + response = methodRegistry.createExchangeBoundOkBody(EXCHANGE_NOT_FOUND, + new AMQShortString("Exchange " + exchangeName + " not found")); + } + else if (routingKey == null) + { + if (queueName == null) + { + if (exchange.hasBindings()) + { + response = methodRegistry.createExchangeBoundOkBody(OK, null); + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(NO_BINDINGS, // replyCode + null); // replyText + } + } + else + { + + AMQQueue queue = virtualHost.getQueue(queueName.toString()); + if (queue == null) + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_FOUND, // replyCode + new AMQShortString("Queue " + queueName + " not found")); // replyText + } + else + { + if (exchange.isBound(queue)) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_BOUND, // replyCode + new AMQShortString("Queue " + queueName + " not bound to exchange " + exchangeName)); // replyText + } + } + } + } + else if (queueName != null) + { + AMQQueue queue = virtualHost.getQueue(queueName.toString()); + if (queue == null) + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_FOUND, // replyCode + new AMQShortString("Queue " + queueName + " not found")); // replyText + } + else + { + String bindingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().asString(); + if (exchange.isBound(bindingKey, queue)) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + String message = "Queue " + queueName + " not bound with routing key " + + body.getRoutingKey() + " to exchange " + exchangeName; + + if(message.length()>255) + { + message = message.substring(0,254); + } + response = methodRegistry.createExchangeBoundOkBody(SPECIFIC_QUEUE_NOT_BOUND_WITH_RK, // replyCode + new AMQShortString(message)); // replyText + } + } + } + else + { + if (exchange.isBound(body.getRoutingKey() == null ? "" : body.getRoutingKey().asString())) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(NO_QUEUE_BOUND_WITH_RK, // replyCode + new AMQShortString("No queue bound with routing key " + body.getRoutingKey() + + " to exchange " + exchangeName)); // replyText + } + } + session.writeFrame(response.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeDeclareHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeDeclareHandler.java new file mode 100644 index 0000000000..154c38a4bf --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeDeclareHandler.java @@ -0,0 +1,138 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQUnknownExchangeType; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ExchangeDeclareBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.ExchangeExistsException; +import org.apache.qpid.server.virtualhost.ReservedExchangeNameException; +import org.apache.qpid.server.virtualhost.VirtualHost; + +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 ExchangeDeclareHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ExchangeDeclareBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + final AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + final AMQShortString exchangeName = body.getExchange(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Request to declare exchange of type " + body.getType() + " with name " + exchangeName); + } + + Exchange exchange; + + if (body.getPassive()) + { + exchange = virtualHost.getExchange(exchangeName == null ? null : exchangeName.toString()); + if(exchange == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Unknown exchange: " + exchangeName); + } + else if (!(body.getType() == null || body.getType().length() ==0) && !exchange.getTypeName().equals(body.getType().asString())) + { + + throw new AMQConnectionException(AMQConstant.NOT_ALLOWED, "Attempt to redeclare exchange: " + + exchangeName + " of type " + exchange.getTypeName() + + " to " + body.getType() +".",body.getClazz(), body.getMethod(),body.getMajor(),body.getMinor(),null); + } + + } + else + { + try + { + exchange = virtualHost.createExchange(null, + exchangeName == null ? null : exchangeName.intern().toString(), + body.getType() == null ? null : body.getType().intern().toString(), + body.getDurable(), + body.getAutoDelete(), + null); + + } + catch(ReservedExchangeNameException e) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Attempt to declare exchange: " + exchangeName + + " which begins with reserved prefix."); + + } + catch(ExchangeExistsException e) + { + exchange = e.getExistingExchange(); + if(!new AMQShortString(exchange.getTypeName()).equals(body.getType())) + { + throw new AMQConnectionException(AMQConstant.NOT_ALLOWED, "Attempt to redeclare exchange: " + + exchangeName + " of type " + + exchange.getTypeName() + + " to " + body.getType() +".", + body.getClazz(), body.getMethod(), + body.getMajor(), body.getMinor(),null); + } + } + catch(AMQUnknownExchangeType e) + { + throw body.getConnectionException(AMQConstant.COMMAND_INVALID, "Unknown exchange: " + exchangeName,e); + } + } + + + if(!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createExchangeDeclareOkBody(); + channel.sync(); + session.writeFrame(responseBody.generateFrame(channelId)); + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeDeleteHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeDeleteHandler.java new file mode 100644 index 0000000000..75f749fe9a --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ExchangeDeleteHandler.java @@ -0,0 +1,92 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ExchangeDeleteBody; +import org.apache.qpid.framing.ExchangeDeleteOkBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeInUseException; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.ExchangeIsAlternateException; +import org.apache.qpid.server.virtualhost.RequiredExchangeException; +import org.apache.qpid.server.virtualhost.VirtualHost; + +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, ExchangeDeleteBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + final AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + channel.sync(); + try + { + final String exchangeName = body.getExchange() == null ? null : body.getExchange().toString(); + + final Exchange exchange = virtualHost.getExchange(exchangeName); + if(exchange == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "No such exchange: " + body.getExchange()); + } + + virtualHost.removeExchange(exchange, !body.getIfUnused()); + + ExchangeDeleteOkBody responseBody = session.getMethodRegistry().createExchangeDeleteOkBody(); + + session.writeFrame(responseBody.generateFrame(channelId)); + } + catch (ExchangeInUseException e) + { + throw body.getChannelException(AMQConstant.IN_USE, "Exchange in use"); + // TODO: sort out consistent channel close mechanism that does all clean up etc. + } + + catch (ExchangeIsAlternateException e) + { + throw body.getChannelException(AMQConstant.NOT_ALLOWED, "Exchange in use as an alternate exchange"); + + } + catch (RequiredExchangeException e) + { + throw body.getChannelException(AMQConstant.NOT_ALLOWED, "Exchange '"+body.getExchange()+"' cannot be deleted"); + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/OnCurrentThreadExecutor.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/OnCurrentThreadExecutor.java new file mode 100644 index 0000000000..6ff511ea30 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/OnCurrentThreadExecutor.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.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/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueBindHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueBindHandler.java new file mode 100644 index 0000000000..d95d59f75f --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueBindHandler.java @@ -0,0 +1,164 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.QueueBindBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Map; + +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, QueueBindBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + VirtualHost virtualHost = protocolConnection.getVirtualHost(); + AMQChannel channel = protocolConnection.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + final AMQQueue queue; + final AMQShortString routingKey; + + final AMQShortString queueName = body.getQueue(); + + if (queueName == null) + { + + queue = channel.getDefaultQueue(); + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "No default queue defined on channel and queue was null"); + } + + if (body.getRoutingKey() == null) + { + routingKey = AMQShortString.valueOf(queue.getName()); + } + else + { + routingKey = body.getRoutingKey().intern(); + } + } + else + { + queue = virtualHost.getQueue(queueName.toString()); + routingKey = body.getRoutingKey() == null ? AMQShortString.EMPTY_STRING : body.getRoutingKey().intern(); + } + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + queueName + " does not exist."); + } + final String exchangeName = body.getExchange() == null ? null : body.getExchange().toString(); + final Exchange exch = virtualHost.getExchange(exchangeName); + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Exchange " + exchangeName + " does not exist."); + } + + + try + { + if (queue.isExclusive() && !queue.isDurable()) + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + if (session == null || session.getConnectionModel() != protocolConnection) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getName() + " is exclusive, but not created on this Connection."); + } + } + + Map<String,Object> arguments = FieldTable.convertToMap(body.getArguments()); + String bindingKey = String.valueOf(routingKey); + + if (!exch.isBound(bindingKey, arguments, queue)) + { + + if(!exch.addBinding(bindingKey, queue, arguments) && TopicExchange.TYPE.equals(exch.getType())) + { + Binding oldBinding = exch.getBinding(bindingKey, queue, arguments); + + Map<String, Object> oldArgs = oldBinding.getArguments(); + if((oldArgs == null && !arguments.isEmpty()) || (oldArgs != null && !oldArgs.equals(arguments))) + { + exch.replaceBinding(oldBinding.getId(), bindingKey, queue, arguments); + } + } + } + } + catch (AMQException e) + { + throw body.getChannelException(AMQConstant.CHANNEL_ERROR, e.toString()); + } + + if (_log.isInfoEnabled()) + { + _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + routingKey); + } + if (!body.getNowait()) + { + channel.sync(); + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueueBindOkBody(); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueDeclareHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueDeclareHandler.java new file mode 100644 index 0000000000..3fdce83c2a --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueDeclareHandler.java @@ -0,0 +1,259 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.QueueDeclareBody; +import org.apache.qpid.framing.QueueDeclareOkBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueArgumentsConverter; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.store.DurableConfigurationStoreHelper; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Map; +import java.util.UUID; +import org.apache.qpid.server.virtualhost.plugins.QueueExistsException; + +public class QueueDeclareHandler implements StateAwareMethodListener<QueueDeclareBody> +{ + private static final Logger _logger = Logger.getLogger(QueueDeclareHandler.class); + + private static final QueueDeclareHandler _instance = new QueueDeclareHandler(); + + public static QueueDeclareHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, QueueDeclareBody body, int channelId) throws AMQException + { + final AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + final AMQSessionModel session = protocolConnection.getChannel(channelId); + VirtualHost virtualHost = protocolConnection.getVirtualHost(); + + final AMQShortString queueName; + + // if we aren't given a queue name, we create one which we return to the client + if ((body.getQueue() == null) || (body.getQueue().length() == 0)) + { + queueName = createName(); + } + else + { + queueName = body.getQueue().intern(); + } + + AMQQueue queue; + + //TODO: do we need to check that the queue already exists with exactly the same "configuration"? + + AMQChannel channel = protocolConnection.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + if(body.getPassive()) + { + queue = virtualHost.getQueue(queueName.toString()); + if (queue == null) + { + String msg = "Queue: " + queueName + " not found on VirtualHost(" + virtualHost + ")."; + throw body.getChannelException(AMQConstant.NOT_FOUND, msg); + } + else + { + AMQSessionModel owningSession = queue.getExclusiveOwningSession(); + if (queue.isExclusive() && !queue.isDurable() + && (owningSession == null || owningSession.getConnectionModel() != protocolConnection)) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getName() + " is exclusive, but not created on this Connection."); + } + + //set this as the default queue on the channel: + channel.setDefaultQueue(queue); + } + } + else + { + + try + { + + queue = createQueue(queueName, body, virtualHost, protocolConnection); + queue.setAuthorizationHolder(protocolConnection); + + if (body.getExclusive()) + { + queue.setExclusiveOwningSession(protocolConnection.getChannel(channelId)); + queue.setAuthorizationHolder(protocolConnection); + + if(!body.getDurable()) + { + final AMQQueue q = queue; + final AMQProtocolSession.Task sessionCloseTask = new AMQProtocolSession.Task() + { + public void doTask(AMQProtocolSession session) throws AMQException + { + q.setExclusiveOwningSession(null); + } + }; + protocolConnection.addSessionCloseTask(sessionCloseTask); + queue.addQueueDeleteTask(new AMQQueue.Task() { + public void doTask(AMQQueue queue) throws AMQException + { + protocolConnection.removeSessionCloseTask(sessionCloseTask); + } + }); + } + } + + } + catch(QueueExistsException qe) + { + + queue = qe.getExistingQueue(); + AMQSessionModel owningSession = queue.getExclusiveOwningSession(); + + if (queue.isExclusive() && !queue.isDurable() && (owningSession == null || owningSession.getConnectionModel() != protocolConnection)) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getName() + " is exclusive, but not created on this Connection."); + } + else if(queue.isExclusive() != body.getExclusive()) + { + + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, + "Cannot re-declare queue '" + queue.getName() + "' with different exclusivity (was: " + + queue.isExclusive() + " requested " + body.getExclusive() + ")"); + } + else if (body.getExclusive() && !(queue.isDurable() ? String.valueOf(queue.getOwner()).equals(session.getClientID()) : (owningSession == null || owningSession.getConnectionModel() == protocolConnection))) + { + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, "Cannot declare queue('" + queueName + "'), " + + "as exclusive queue with same name " + + "declared on another client ID('" + + queue.getOwner() + "') your clientID('" + session.getClientID() + "')"); + + } + else if(queue.isAutoDelete() != body.getAutoDelete()) + { + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, + "Cannot re-declare queue '" + queue.getName() + "' with different auto-delete (was: " + + queue.isAutoDelete() + " requested " + body.getAutoDelete() + ")"); + } + else if(queue.isDurable() != body.getDurable()) + { + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, + "Cannot re-declare queue '" + queue.getName() + "' with different durability (was: " + + queue.isDurable() + " requested " + body.getDurable() + ")"); + } + + } + + //set this as the default queue on the channel: + channel.setDefaultQueue(queue); + } + + if (!body.getNowait()) + { + channel.sync(); + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + QueueDeclareOkBody responseBody = + methodRegistry.createQueueDeclareOkBody(queueName, + queue.getMessageCount(), + queue.getConsumerCount()); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + _logger.info("Queue " + queueName + " declared successfully"); + } + } + + protected AMQShortString createName() + { + return new AMQShortString("tmp_" + UUID.randomUUID()); + } + + protected AMQQueue createQueue(final AMQShortString queueName, + QueueDeclareBody body, + final VirtualHost virtualHost, + final AMQProtocolSession session) + throws AMQException + { + + final boolean durable = body.getDurable(); + final boolean autoDelete = body.getAutoDelete(); + final boolean exclusive = body.getExclusive(); + + String owner = exclusive ? AMQShortString.toString(session.getContextKey()) : null; + + Map<String, Object> arguments = + QueueArgumentsConverter.convertWireArgsToModel(FieldTable.convertToMap(body.getArguments())); + String queueNameString = AMQShortString.toString(queueName); + final UUID id = UUIDGenerator.generateQueueUUID(queueNameString, virtualHost.getName()); + + final AMQQueue queue = virtualHost.createQueue(id, queueNameString, durable, owner, autoDelete, + exclusive, autoDelete, arguments); + + if (exclusive && !durable) + { + final AMQProtocolSession.Task deleteQueueTask = + new AMQProtocolSession.Task() + { + public void doTask(AMQProtocolSession session) throws AMQException + { + if (virtualHost.getQueue(queueName.toString()) == queue) + { + virtualHost.removeQueue(queue); + } + } + }; + + session.addSessionCloseTask(deleteQueueTask); + + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) + { + session.removeSessionCloseTask(deleteQueueTask); + } + }); + } + + return queue; + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueDeleteHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueDeleteHandler.java new file mode 100644 index 0000000000..d3c196a789 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueDeleteHandler.java @@ -0,0 +1,122 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.QueueDeleteBody; +import org.apache.qpid.framing.QueueDeleteOkBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.DurableConfigurationStoreHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class QueueDeleteHandler implements StateAwareMethodListener<QueueDeleteBody> +{ + private static final QueueDeleteHandler _instance = new QueueDeleteHandler(); + + public static QueueDeleteHandler getInstance() + { + return _instance; + } + + private final boolean _failIfNotFound; + + public QueueDeleteHandler() + { + this(true); + } + + public QueueDeleteHandler(boolean failIfNotFound) + { + _failIfNotFound = failIfNotFound; + + } + + public void methodReceived(AMQStateManager stateManager, QueueDeleteBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + VirtualHost virtualHost = protocolConnection.getVirtualHost(); + DurableConfigurationStore store = virtualHost.getDurableConfigurationStore(); + + + AMQChannel channel = protocolConnection.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + channel.sync(); + AMQQueue queue; + if (body.getQueue() == null) + { + + //get the default queue on the channel: + queue = channel.getDefaultQueue(); + } + else + { + queue = virtualHost.getQueue(body.getQueue().toString()); + } + + if (queue == null) + { + if (_failIfNotFound) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + } + else + { + if (body.getIfEmpty() && !queue.isEmpty()) + { + throw body.getChannelException(AMQConstant.IN_USE, "Queue: " + body.getQueue() + " is not empty."); + } + else if (body.getIfUnused() && !queue.isUnused()) + { + // TODO - Error code + throw body.getChannelException(AMQConstant.IN_USE, "Queue: " + body.getQueue() + " is still used."); + } + else + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + if (queue.isExclusive() && !queue.isDurable() && (session == null || session.getConnectionModel() != protocolConnection)) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getName() + " is exclusive, but not created on this Connection."); + } + + int purged = virtualHost.removeQueue(queue); + + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + QueueDeleteOkBody responseBody = methodRegistry.createQueueDeleteOkBody(purged); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + } + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueuePurgeHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueuePurgeHandler.java new file mode 100644 index 0000000000..ff845d3c16 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueuePurgeHandler.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ + +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.QueuePurgeBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class QueuePurgeHandler implements StateAwareMethodListener<QueuePurgeBody> +{ + private static final QueuePurgeHandler _instance = new QueuePurgeHandler(); + + public static QueuePurgeHandler getInstance() + { + return _instance; + } + + private final boolean _failIfNotFound; + + public QueuePurgeHandler() + { + this(true); + } + + public QueuePurgeHandler(boolean failIfNotFound) + { + _failIfNotFound = failIfNotFound; + } + + public void methodReceived(AMQStateManager stateManager, QueuePurgeBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + VirtualHost virtualHost = protocolConnection.getVirtualHost(); + + AMQChannel channel = protocolConnection.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + AMQQueue queue; + if(body.getQueue() == null) + { + + //get the default queue on the channel: + queue = channel.getDefaultQueue(); + + if(queue == null) + { + if(_failIfNotFound) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED,"No queue specified."); + } + } + } + else + { + queue = virtualHost.getQueue(body.getQueue().toString()); + } + + if(queue == null) + { + if(_failIfNotFound) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + } + else + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + + if (queue.isExclusive() && (session == null || session.getConnectionModel() != protocolConnection)) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue is exclusive, but not created on this Connection."); + } + + long purged = queue.clearQueue(); + + + if(!body.getNowait()) + { + channel.sync(); + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueuePurgeOkBody(purged); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + } + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueUnbindHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueUnbindHandler.java new file mode 100644 index 0000000000..d568e0f581 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/QueueUnbindHandler.java @@ -0,0 +1,135 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.QueueUnbindBody; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; +import org.apache.qpid.framing.amqp_0_91.MethodRegistry_0_91; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class QueueUnbindHandler implements StateAwareMethodListener<QueueUnbindBody> +{ + private static final Logger _log = Logger.getLogger(QueueUnbindHandler.class); + + private static final QueueUnbindHandler _instance = new QueueUnbindHandler(); + + public static QueueUnbindHandler getInstance() + { + return _instance; + } + + private QueueUnbindHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueUnbindBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + + final AMQQueue queue; + final AMQShortString routingKey; + + + AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + if (body.getQueue() == null) + { + + queue = channel.getDefaultQueue(); + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "No default queue defined on channel and queue was null"); + } + + routingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().intern(false); + + } + else + { + queue = virtualHost.getQueue(body.getQueue().toString()); + routingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().intern(false); + } + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + final Exchange exch = virtualHost.getExchange(body.getExchange() == null ? null : body.getExchange().toString()); + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Exchange " + body.getExchange() + " does not exist."); + } + + if(exch.getBinding(String.valueOf(routingKey), queue, FieldTable.convertToMap(body.getArguments())) == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND,"No such binding"); + } + else + { + exch.removeBinding(String.valueOf(routingKey), queue, FieldTable.convertToMap(body.getArguments())); + } + + + if (_log.isInfoEnabled()) + { + _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + routingKey); + } + + final MethodRegistry registry = session.getMethodRegistry(); + final AMQMethodBody responseBody; + if (registry instanceof MethodRegistry_0_9) + { + responseBody = ((MethodRegistry_0_9)registry).createQueueUnbindOkBody(); + } + else if (registry instanceof MethodRegistry_0_91) + { + responseBody = ((MethodRegistry_0_91)registry).createQueueUnbindOkBody(); + } + else + { + // 0-8 does not support QueueUnbind + throw new AMQException(AMQConstant.COMMAND_INVALID, "QueueUnbind not present in AMQP version: " + session.getProtocolVersion(), null); + } + channel.sync(); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl.java new file mode 100644 index 0000000000..43e97c0cb6 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl.java @@ -0,0 +1,574 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol.v0_8.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ServerMethodDispatcherImpl implements MethodDispatcher
+{
+ private final AMQStateManager _stateManager;
+
+ private static interface DispatcherFactory
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager);
+ }
+
+ private static final Map<ProtocolVersion, DispatcherFactory> _dispatcherFactories =
+ new HashMap<ProtocolVersion, DispatcherFactory>();
+
+
+ static
+ {
+ _dispatcherFactories.put(ProtocolVersion.v8_0,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_8_0(stateManager);
+ }
+ });
+
+ _dispatcherFactories.put(ProtocolVersion.v0_9,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_0_9(stateManager);
+ }
+ });
+ _dispatcherFactories.put(ProtocolVersion.v0_91,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_0_91(stateManager);
+ }
+ });
+
+ }
+
+
+ private static final AccessRequestHandler _accessRequestHandler = AccessRequestHandler.getInstance();
+ private static final ChannelCloseHandler _channelCloseHandler = ChannelCloseHandler.getInstance();
+ private static final ChannelOpenHandler _channelOpenHandler = ChannelOpenHandler.getInstance();
+ private static final ChannelCloseOkHandler _channelCloseOkHandler = ChannelCloseOkHandler.getInstance();
+ private static final ConnectionCloseMethodHandler _connectionCloseMethodHandler = ConnectionCloseMethodHandler.getInstance();
+ private static final ConnectionCloseOkMethodHandler _connectionCloseOkMethodHandler = ConnectionCloseOkMethodHandler.getInstance();
+ private static final ConnectionOpenMethodHandler _connectionOpenMethodHandler = ConnectionOpenMethodHandler.getInstance();
+ private static final ConnectionTuneOkMethodHandler _connectionTuneOkMethodHandler = ConnectionTuneOkMethodHandler.getInstance();
+ private static final ConnectionSecureOkMethodHandler _connectionSecureOkMethodHandler = ConnectionSecureOkMethodHandler.getInstance();
+ private static final ConnectionStartOkMethodHandler _connectionStartOkMethodHandler = ConnectionStartOkMethodHandler.getInstance();
+ private static final ExchangeDeclareHandler _exchangeDeclareHandler = ExchangeDeclareHandler.getInstance();
+ private static final ExchangeDeleteHandler _exchangeDeleteHandler = ExchangeDeleteHandler.getInstance();
+ private static final ExchangeBoundHandler _exchangeBoundHandler = ExchangeBoundHandler.getInstance();
+ private static final BasicAckMethodHandler _basicAckMethodHandler = BasicAckMethodHandler.getInstance();
+ private static final BasicRecoverMethodHandler _basicRecoverMethodHandler = BasicRecoverMethodHandler.getInstance();
+ private static final BasicConsumeMethodHandler _basicConsumeMethodHandler = BasicConsumeMethodHandler.getInstance();
+ private static final BasicGetMethodHandler _basicGetMethodHandler = BasicGetMethodHandler.getInstance();
+ private static final BasicCancelMethodHandler _basicCancelMethodHandler = BasicCancelMethodHandler.getInstance();
+ private static final BasicPublishMethodHandler _basicPublishMethodHandler = BasicPublishMethodHandler.getInstance();
+ private static final BasicQosHandler _basicQosHandler = BasicQosHandler.getInstance();
+ private static final QueueBindHandler _queueBindHandler = QueueBindHandler.getInstance();
+ private static final QueueDeclareHandler _queueDeclareHandler = QueueDeclareHandler.getInstance();
+ private static final QueueDeleteHandler _queueDeleteHandler = QueueDeleteHandler.getInstance();
+ private static final QueuePurgeHandler _queuePurgeHandler = QueuePurgeHandler.getInstance();
+ private static final ChannelFlowHandler _channelFlowHandler = ChannelFlowHandler.getInstance();
+ private static final TxSelectHandler _txSelectHandler = TxSelectHandler.getInstance();
+ private static final TxCommitHandler _txCommitHandler = TxCommitHandler.getInstance();
+ private static final TxRollbackHandler _txRollbackHandler = TxRollbackHandler.getInstance();
+ private static final BasicRejectMethodHandler _basicRejectMethodHandler = BasicRejectMethodHandler.getInstance();
+
+
+
+ public static MethodDispatcher createMethodDispatcher(AMQStateManager stateManager, ProtocolVersion protocolVersion)
+ {
+ return _dispatcherFactories.get(protocolVersion).createMethodDispatcher(stateManager);
+ }
+
+
+ public ServerMethodDispatcherImpl(AMQStateManager stateManager)
+ {
+ _stateManager = stateManager;
+ }
+
+
+ protected AMQStateManager getStateManager()
+ {
+ return _stateManager;
+ }
+
+
+
+ public boolean dispatchAccessRequest(AccessRequestBody body, int channelId) throws AMQException
+ {
+ _accessRequestHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicAck(BasicAckBody body, int channelId) throws AMQException
+ {
+ _basicAckMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicCancel(BasicCancelBody body, int channelId) throws AMQException
+ {
+ _basicCancelMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicConsume(BasicConsumeBody body, int channelId) throws AMQException
+ {
+ _basicConsumeMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicGet(BasicGetBody body, int channelId) throws AMQException
+ {
+ _basicGetMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicPublish(BasicPublishBody body, int channelId) throws AMQException
+ {
+ _basicPublishMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicQos(BasicQosBody body, int channelId) throws AMQException
+ {
+ _basicQosHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecover(BasicRecoverBody body, int channelId) throws AMQException
+ {
+ _basicRecoverMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicReject(BasicRejectBody body, int channelId) throws AMQException
+ {
+ _basicRejectMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelOpen(ChannelOpenBody body, int channelId) throws AMQException
+ {
+ _channelOpenHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchAccessRequestOk(AccessRequestOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicCancelOk(BasicCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicConsumeOk(BasicConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicDeliver(BasicDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicGetEmpty(BasicGetEmptyBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicGetOk(BasicGetOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicQosOk(BasicQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicReturn(BasicReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelClose(ChannelCloseBody body, int channelId) throws AMQException
+ {
+ _channelCloseHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchChannelCloseOk(ChannelCloseOkBody body, int channelId) throws AMQException
+ {
+ _channelCloseOkHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchChannelFlow(ChannelFlowBody body, int channelId) throws AMQException
+ {
+ _channelFlowHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelFlowOk(ChannelFlowOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOpenOk(ChannelOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+
+ public boolean dispatchConnectionOpen(ConnectionOpenBody body, int channelId) throws AMQException
+ {
+ _connectionOpenMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchConnectionClose(ConnectionCloseBody body, int channelId) throws AMQException
+ {
+ _connectionCloseMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchConnectionCloseOk(ConnectionCloseOkBody body, int channelId) throws AMQException
+ {
+ _connectionCloseOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionOpenOk(ConnectionOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionRedirect(ConnectionRedirectBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionSecure(ConnectionSecureBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionStart(ConnectionStartBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionTune(ConnectionTuneBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchDtxSelectOk(DtxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchDtxStartOk(DtxStartOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeBoundOk(ExchangeBoundOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeDeclareOk(ExchangeDeclareOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeDeleteOk(ExchangeDeleteOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileCancelOk(FileCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileConsumeOk(FileConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileDeliver(FileDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileOpen(FileOpenBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileOpenOk(FileOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileQosOk(FileQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileReturn(FileReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileStage(FileStageBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueBindOk(QueueBindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueDeclareOk(QueueDeclareOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueDeleteOk(QueueDeleteOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueuePurgeOk(QueuePurgeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamCancelOk(StreamCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamConsumeOk(StreamConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamDeliver(StreamDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamQosOk(StreamQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamReturn(StreamReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxCommitOk(TxCommitOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxRollbackOk(TxRollbackOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxSelectOk(TxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+
+ public boolean dispatchConnectionSecureOk(ConnectionSecureOkBody body, int channelId) throws AMQException
+ {
+ _connectionSecureOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionStartOk(ConnectionStartOkBody body, int channelId) throws AMQException
+ {
+ _connectionStartOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionTuneOk(ConnectionTuneOkBody body, int channelId) throws AMQException
+ {
+ _connectionTuneOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchDtxSelect(DtxSelectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchDtxStart(DtxStartBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchExchangeBound(ExchangeBoundBody body, int channelId) throws AMQException
+ {
+ _exchangeBoundHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDeclare(ExchangeDeclareBody body, int channelId) throws AMQException
+ {
+ _exchangeDeclareHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDelete(ExchangeDeleteBody body, int channelId) throws AMQException
+ {
+ _exchangeDeleteHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchFileAck(FileAckBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileCancel(FileCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileConsume(FileConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFilePublish(FilePublishBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileQos(FileQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileReject(FileRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueBind(QueueBindBody body, int channelId) throws AMQException
+ {
+ _queueBindHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDeclare(QueueDeclareBody body, int channelId) throws AMQException
+ {
+ _queueDeclareHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDelete(QueueDeleteBody body, int channelId) throws AMQException
+ {
+ _queueDeleteHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueuePurge(QueuePurgeBody body, int channelId) throws AMQException
+ {
+ _queuePurgeHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchStreamCancel(StreamCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamConsume(StreamConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamPublish(StreamPublishBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamQos(StreamQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTunnelRequest(TunnelRequestBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTxCommit(TxCommitBody body, int channelId) throws AMQException
+ {
+ _txCommitHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchTxRollback(TxRollbackBody body, int channelId) throws AMQException
+ {
+ _txRollbackHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchTxSelect(TxSelectBody body, int channelId) throws AMQException
+ {
+ _txSelectHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+
+
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_0_9.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_0_9.java new file mode 100644 index 0000000000..1ee6d732c2 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_0_9.java @@ -0,0 +1,164 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol.v0_8.handler;
+
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_0_9.MethodDispatcher_0_9;
+import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager;
+
+
+
+public class ServerMethodDispatcherImpl_0_9
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_0_9
+
+{
+
+ private static final BasicRecoverSyncMethodHandler _basicRecoverSyncMethodHandler =
+ BasicRecoverSyncMethodHandler.getInstance();
+ private static final QueueUnbindHandler _queueUnbindHandler =
+ QueueUnbindHandler.getInstance();
+
+
+ public ServerMethodDispatcherImpl_0_9(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverSync(BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ _basicRecoverSyncMethodHandler.methodReceived(getStateManager(), body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecoverSyncOk(BasicRecoverSyncOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOk(ChannelOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPing(ChannelPingBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPong(ChannelPongBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelResume(ChannelResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageAppend(MessageAppendBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCancel(MessageCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCheckpoint(MessageCheckpointBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageClose(MessageCloseBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageConsume(MessageConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageEmpty(MessageEmptyBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageGet(MessageGetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOffset(MessageOffsetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOk(MessageOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOpen(MessageOpenBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageQos(MessageQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageRecover(MessageRecoverBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageReject(MessageRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageResume(MessageResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageTransfer(MessageTransferBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueUnbind(QueueUnbindBody body, int channelId) throws AMQException
+ {
+ _queueUnbindHandler.methodReceived(getStateManager(),body,channelId);
+ return true;
+ }
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_0_91.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_0_91.java new file mode 100644 index 0000000000..b11b9cff2b --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_0_91.java @@ -0,0 +1,168 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol.v0_8.handler;
+
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_0_91.MethodDispatcher_0_91;
+import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager;
+
+
+public class ServerMethodDispatcherImpl_0_91
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_0_91
+
+{
+
+ private static final BasicRecoverSyncMethodHandler _basicRecoverSyncMethodHandler =
+ BasicRecoverSyncMethodHandler.getInstance();
+ private static final QueueUnbindHandler _queueUnbindHandler =
+ QueueUnbindHandler.getInstance();
+
+
+ public ServerMethodDispatcherImpl_0_91(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverSync(BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ _basicRecoverSyncMethodHandler.methodReceived(getStateManager(), body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecoverSyncOk(BasicRecoverSyncOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOk(ChannelOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPing(ChannelPingBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPong(ChannelPongBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelResume(ChannelResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageAppend(MessageAppendBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCancel(MessageCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCheckpoint(MessageCheckpointBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageClose(MessageCloseBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageConsume(MessageConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageEmpty(MessageEmptyBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageGet(MessageGetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOffset(MessageOffsetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOk(MessageOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOpen(MessageOpenBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageQos(MessageQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageRecover(MessageRecoverBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageReject(MessageRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageResume(MessageResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageTransfer(MessageTransferBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueUnbind(QueueUnbindBody body, int channelId) throws AMQException
+ {
+ _queueUnbindHandler.methodReceived(getStateManager(),body,channelId);
+ return true;
+ }
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_8_0.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_8_0.java new file mode 100644 index 0000000000..f05219712f --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/ServerMethodDispatcherImpl_8_0.java @@ -0,0 +1,95 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol.v0_8.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicRecoverOkBody;
+import org.apache.qpid.framing.ChannelAlertBody;
+import org.apache.qpid.framing.TestContentBody;
+import org.apache.qpid.framing.TestContentOkBody;
+import org.apache.qpid.framing.TestIntegerBody;
+import org.apache.qpid.framing.TestIntegerOkBody;
+import org.apache.qpid.framing.TestStringBody;
+import org.apache.qpid.framing.TestStringOkBody;
+import org.apache.qpid.framing.TestTableBody;
+import org.apache.qpid.framing.TestTableOkBody;
+import org.apache.qpid.framing.amqp_8_0.MethodDispatcher_8_0;
+import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager;
+
+public class ServerMethodDispatcherImpl_8_0
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_8_0
+{
+ public ServerMethodDispatcherImpl_8_0(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelAlert(ChannelAlertBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTestContent(TestContentBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestContentOk(TestContentOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestInteger(TestIntegerBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestIntegerOk(TestIntegerOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestString(TestStringBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestStringOk(TestStringOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTable(TestTableBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTableOk(TestTableOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxCommitHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxCommitHandler.java new file mode 100644 index 0000000000..b257030a59 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxCommitHandler.java @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.TxCommitBody; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +public class TxCommitHandler implements StateAwareMethodListener<TxCommitBody> +{ + private static final Logger _log = Logger.getLogger(TxCommitHandler.class); + + private static TxCommitHandler _instance = new TxCommitHandler(); + + public static TxCommitHandler getInstance() + { + return _instance; + } + + private TxCommitHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, TxCommitBody body, final int channelId) throws AMQException + { + final AMQProtocolSession session = stateManager.getProtocolSession(); + + try + { + if (_log.isDebugEnabled()) + { + _log.debug("Commit received on channel " + channelId); + } + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + channel.commit(new Runnable() + { + + @Override + public void run() + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createTxCommitOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + } + }, true); + + + + } + catch (AMQException e) + { + throw body.getChannelException(e.getErrorCode(), "Failed to commit: " + e.getMessage()); + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxRollbackHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxRollbackHandler.java new file mode 100644 index 0000000000..19d0da007b --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxRollbackHandler.java @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.TxRollbackBody; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.state.StateAwareMethodListener; + +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, TxRollbackBody body, final int channelId) throws AMQException + { + final AMQProtocolSession session = stateManager.getProtocolSession(); + + try + { + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + + + final MethodRegistry methodRegistry = session.getMethodRegistry(); + final AMQMethodBody responseBody = methodRegistry.createTxRollbackOkBody(); + + Runnable task = new Runnable() + { + + public void run() + { + session.writeFrame(responseBody.generateFrame(channelId)); + } + }; + + channel.rollback(task); + + //Now resend all the unacknowledged messages back to the original subscribers. + //(Must be done after the TxnRollback-ok response). + // Why, are we not allowed to send messages back to client before the ok method? + channel.resend(false); + + } + catch (AMQException e) + { + throw body.getChannelException(e.getErrorCode(), "Failed to rollback: " + e.getMessage()); + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxSelectHandler.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxSelectHandler.java new file mode 100644 index 0000000000..a43e1ebdab --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/TxSelectHandler.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.TxSelectBody; +import org.apache.qpid.framing.TxSelectOkBody; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.protocol.v0_8.state.AMQStateManager; +import org.apache.qpid.server.protocol.v0_8.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, TxSelectBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.setLocalTransactional(); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + TxSelectOkBody responseBody = methodRegistry.createTxSelectOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/UnexpectedMethodException.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/UnexpectedMethodException.java new file mode 100644 index 0000000000..32a9f768ec --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/handler/UnexpectedMethodException.java @@ -0,0 +1,36 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol.v0_8.handler;
+
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+
+public class UnexpectedMethodException extends AMQException
+{
+
+ private static final long serialVersionUID = -255921574946294892L;
+
+ public UnexpectedMethodException(AMQMethodBody body)
+ {
+ super("Unexpected method recevied: " + body.getClass().getName());
+ }
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverter.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverter.java new file mode 100644 index 0000000000..48e42ce5a3 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverter.java @@ -0,0 +1,60 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.protocol.v0_8.output;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.message.MessageContentSource;
+import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueEntry;
+
+public interface ProtocolOutputConverter
+{
+ void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag);
+
+ interface Factory
+ {
+ ProtocolOutputConverter newInstance(AMQProtocolSession session);
+ }
+
+ void writeDeliver(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException;
+
+ void writeGetOk(QueueEntry message, int channelId, long deliveryTag, int queueSize) throws AMQException;
+
+ byte getProtocolMinorVersion();
+
+ byte getProtocolMajorVersion();
+
+ void writeReturn(MessagePublishInfo messagePublishInfo, ContentHeaderBody header, MessageContentSource msgContent, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException;
+
+ void writeFrame(AMQDataBlock block);
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverterImpl.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverterImpl.java new file mode 100644 index 0000000000..dd5e13e56a --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverterImpl.java @@ -0,0 +1,431 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.output; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQBody; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.framing.BasicGetOkBody; +import org.apache.qpid.framing.BasicReturnBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.plugin.MessageConverter; +import org.apache.qpid.server.protocol.MessageConverterRegistry; +import org.apache.qpid.server.protocol.v0_8.AMQMessage; +import org.apache.qpid.server.message.MessageContentSource; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueEntry; + +import java.io.DataOutput; +import java.io.IOException; +import java.nio.ByteBuffer; + +class ProtocolOutputConverterImpl implements ProtocolOutputConverter +{ + private static final int BASIC_CLASS_ID = 60; + + private final MethodRegistry _methodRegistry; + private final AMQProtocolSession _protocolSession; + + ProtocolOutputConverterImpl(AMQProtocolSession session, MethodRegistry methodRegistry) + { + _protocolSession = session; + _methodRegistry = methodRegistry; + } + + + public AMQProtocolSession getProtocolSession() + { + return _protocolSession; + } + + public void writeDeliver(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag) + throws AMQException + { + AMQMessage msg = convertToAMQMessage(entry); + AMQBody deliverBody = createEncodedDeliverBody(msg, entry.isRedelivered(), deliveryTag, consumerTag); + writeMessageDelivery(msg, channelId, deliverBody); + } + + private AMQMessage convertToAMQMessage(QueueEntry entry) + { + ServerMessage serverMessage = entry.getMessage(); + if(serverMessage instanceof AMQMessage) + { + return (AMQMessage) serverMessage; + } + else + { + return getMessageConverter(serverMessage).convert(serverMessage, entry.getQueue().getVirtualHost()); + } + } + + private <M extends ServerMessage> MessageConverter<M, AMQMessage> getMessageConverter(M message) + { + Class<M> clazz = (Class<M>) message.getClass(); + return MessageConverterRegistry.getConverter(clazz, AMQMessage.class); + } + + private void writeMessageDelivery(AMQMessage message, int channelId, AMQBody deliverBody) + throws AMQException + { + writeMessageDelivery(message, message.getContentHeaderBody(), channelId, deliverBody); + } + + private void writeMessageDelivery(MessageContentSource message, ContentHeaderBody contentHeaderBody, int channelId, AMQBody deliverBody) + throws AMQException + { + + + int bodySize = (int) message.getSize(); + + if(bodySize == 0) + { + SmallCompositeAMQBodyBlock compositeBlock = new SmallCompositeAMQBodyBlock(channelId, deliverBody, + contentHeaderBody); + + writeFrame(compositeBlock); + } + else + { + int maxBodySize = (int) getProtocolSession().getMaxFrameSize() - AMQFrame.getFrameOverhead(); + + + int capacity = bodySize > maxBodySize ? maxBodySize : bodySize; + + int writtenSize = capacity; + + AMQBody firstContentBody = new MessageContentSourceBody(message,0,capacity); + + CompositeAMQBodyBlock + compositeBlock = new CompositeAMQBodyBlock(channelId, deliverBody, contentHeaderBody, firstContentBody); + writeFrame(compositeBlock); + + while(writtenSize < bodySize) + { + capacity = bodySize - writtenSize > maxBodySize ? maxBodySize : bodySize - writtenSize; + MessageContentSourceBody body = new MessageContentSourceBody(message, writtenSize, capacity); + writtenSize += capacity; + + writeFrame(new AMQFrame(channelId, body)); + } + } + } + + private class MessageContentSourceBody implements AMQBody + { + public static final byte TYPE = 3; + private int _length; + private MessageContentSource _message; + private int _offset; + + public MessageContentSourceBody(MessageContentSource message, int offset, int length) + { + _message = message; + _offset = offset; + _length = length; + } + + public byte getFrameType() + { + return TYPE; + } + + public int getSize() + { + return _length; + } + + public void writePayload(DataOutput buffer) throws IOException + { + ByteBuffer buf = _message.getContent(_offset, _length); + + if(buf.hasArray()) + { + buffer.write(buf.array(), buf.arrayOffset()+buf.position(), buf.remaining()); + } + else + { + + byte[] data = new byte[_length]; + + buf.get(data); + + buffer.write(data); + } + } + + public void handle(int channelId, AMQVersionAwareProtocolSession amqProtocolSession) throws AMQException + { + throw new UnsupportedOperationException(); + } + } + + public void writeGetOk(QueueEntry entry, int channelId, long deliveryTag, int queueSize) throws AMQException + { + AMQBody deliver = createEncodedGetOkBody(entry, deliveryTag, queueSize); + writeMessageDelivery(convertToAMQMessage(entry), channelId, deliver); + } + + + private AMQBody createEncodedDeliverBody(AMQMessage message, + boolean isRedelivered, + final long deliveryTag, + final AMQShortString consumerTag) + throws AMQException + { + + final AMQShortString exchangeName; + final AMQShortString routingKey; + + final MessagePublishInfo pb = message.getMessagePublishInfo(); + exchangeName = pb.getExchange(); + routingKey = pb.getRoutingKey(); + + final AMQBody returnBlock = new EncodedDeliveryBody(deliveryTag, routingKey, exchangeName, consumerTag, isRedelivered); + return returnBlock; + } + + private class EncodedDeliveryBody implements AMQBody + { + private final long _deliveryTag; + private final AMQShortString _routingKey; + private final AMQShortString _exchangeName; + private final AMQShortString _consumerTag; + private final boolean _isRedelivered; + private AMQBody _underlyingBody; + + private EncodedDeliveryBody(long deliveryTag, AMQShortString routingKey, AMQShortString exchangeName, AMQShortString consumerTag, boolean isRedelivered) + { + _deliveryTag = deliveryTag; + _routingKey = routingKey; + _exchangeName = exchangeName; + _consumerTag = consumerTag; + _isRedelivered = isRedelivered; + } + + public AMQBody createAMQBody() + { + return _methodRegistry.createBasicDeliverBody(_consumerTag, + _deliveryTag, + _isRedelivered, + _exchangeName, + _routingKey); + } + + public byte getFrameType() + { + return AMQMethodBody.TYPE; + } + + public int getSize() + { + if(_underlyingBody == null) + { + _underlyingBody = createAMQBody(); + } + return _underlyingBody.getSize(); + } + + public void writePayload(DataOutput buffer) throws IOException + { + if(_underlyingBody == null) + { + _underlyingBody = createAMQBody(); + } + _underlyingBody.writePayload(buffer); + } + + public void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession) + throws AMQException + { + throw new AMQException("This block should never be dispatched!"); + } + + @Override + public String toString() + { + return "[" + getClass().getSimpleName() + " underlyingBody: " + String.valueOf(_underlyingBody) + "]"; + } + } + + private AMQBody createEncodedGetOkBody(QueueEntry entry, long deliveryTag, int queueSize) + throws AMQException + { + final AMQShortString exchangeName; + final AMQShortString routingKey; + + final AMQMessage message = convertToAMQMessage(entry); + final MessagePublishInfo pb = message.getMessagePublishInfo(); + exchangeName = pb.getExchange(); + routingKey = pb.getRoutingKey(); + + final boolean isRedelivered = entry.isRedelivered(); + + BasicGetOkBody getOkBody = + _methodRegistry.createBasicGetOkBody(deliveryTag, + isRedelivered, + exchangeName, + routingKey, + queueSize); + + return getOkBody; + } + + public byte getProtocolMinorVersion() + { + return _protocolSession.getProtocolMinorVersion(); + } + + public byte getProtocolMajorVersion() + { + return getProtocolSession().getProtocolMajorVersion(); + } + + private AMQBody createEncodedReturnFrame(MessagePublishInfo messagePublishInfo, + int replyCode, + AMQShortString replyText) throws AMQException + { + + BasicReturnBody basicReturnBody = + _methodRegistry.createBasicReturnBody(replyCode, + replyText, + messagePublishInfo.getExchange(), + messagePublishInfo.getRoutingKey()); + + + return basicReturnBody; + } + + public void writeReturn(MessagePublishInfo messagePublishInfo, ContentHeaderBody header, MessageContentSource message, int channelId, int replyCode, AMQShortString replyText) + throws AMQException + { + + AMQBody returnFrame = createEncodedReturnFrame(messagePublishInfo, replyCode, replyText); + + writeMessageDelivery(message, header, channelId, returnFrame); + } + + + public void writeFrame(AMQDataBlock block) + { + getProtocolSession().writeFrame(block); + } + + + public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag) + { + + BasicCancelOkBody basicCancelOkBody = _methodRegistry.createBasicCancelOkBody(consumerTag); + writeFrame(basicCancelOkBody.generateFrame(channelId)); + + } + + + public static final class CompositeAMQBodyBlock extends AMQDataBlock + { + public static final int OVERHEAD = 3 * AMQFrame.getFrameOverhead(); + + private final AMQBody _methodBody; + private final AMQBody _headerBody; + private final AMQBody _contentBody; + private final int _channel; + + + public CompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody, AMQBody contentBody) + { + _channel = channel; + _methodBody = methodBody; + _headerBody = headerBody; + _contentBody = contentBody; + } + + public long getSize() + { + return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() + _contentBody.getSize(); + } + + public void writePayload(DataOutput buffer) throws IOException + { + AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody, _contentBody); + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("[").append(getClass().getSimpleName()) + .append(" methodBody=").append(_methodBody) + .append(", headerBody=").append(_headerBody) + .append(", contentBody=").append(_contentBody) + .append(", channel=").append(_channel).append("]"); + return builder.toString(); + } + + } + + public static final class SmallCompositeAMQBodyBlock extends AMQDataBlock + { + public static final int OVERHEAD = 2 * AMQFrame.getFrameOverhead(); + + private final AMQBody _methodBody; + private final AMQBody _headerBody; + private final int _channel; + + + public SmallCompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody) + { + _channel = channel; + _methodBody = methodBody; + _headerBody = headerBody; + + } + + public long getSize() + { + return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() ; + } + + public void writePayload(DataOutput buffer) throws IOException + { + AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody); + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()) + .append("methodBody=").append(_methodBody) + .append(", headerBody=").append(_headerBody) + .append(", channel=").append(_channel).append("]"); + return builder.toString(); + } + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverterRegistry.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverterRegistry.java new file mode 100644 index 0000000000..d4332b37ee --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/output/ProtocolOutputConverterRegistry.java @@ -0,0 +1,90 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.protocol.v0_8.output;
+
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.server.protocol.v0_8.output.ProtocolOutputConverter.Factory;
+import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ProtocolOutputConverterRegistry
+{
+
+ private static final Map<ProtocolVersion, Factory> _registry =
+ new HashMap<ProtocolVersion, Factory>();
+
+
+ static
+ {
+ register(ProtocolVersion.v8_0);
+ register(ProtocolVersion.v0_9);
+ register(ProtocolVersion.v0_91);
+ }
+
+ private ProtocolOutputConverterRegistry()
+ {
+ }
+
+ private static void register(ProtocolVersion version)
+ {
+
+ _registry.put(version,new ConverterFactory(version));
+ }
+
+
+ public static ProtocolOutputConverter getConverter(AMQProtocolSession session)
+ {
+ return _registry.get(session.getProtocolVersion()).newInstance(session);
+ }
+
+ private static class ConverterFactory implements Factory
+ {
+ private ProtocolVersion _protocolVersion;
+ private MethodRegistry _methodRegistry;
+ private int _classId;
+
+ public ConverterFactory(ProtocolVersion pv)
+ {
+ _protocolVersion = pv;
+
+ }
+
+ public synchronized ProtocolOutputConverter newInstance(AMQProtocolSession session)
+ {
+ if(_methodRegistry == null)
+ {
+
+ _methodRegistry = MethodRegistry.getMethodRegistry(_protocolVersion);
+
+ }
+ return new ProtocolOutputConverterImpl(session, _methodRegistry);
+ }
+ }
+}
diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/AMQState.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/AMQState.java new file mode 100644 index 0000000000..ee97d5fa87 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/AMQState.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.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/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/AMQStateManager.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/AMQStateManager.java new file mode 100644 index 0000000000..0555bba98b --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/AMQStateManager.java @@ -0,0 +1,162 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.state; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.framing.ChannelOpenBody; +import org.apache.qpid.framing.MethodDispatcher; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.protocol.AMQMethodListener; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolSession; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +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); + + private final Broker _broker; + private final AMQProtocolSession _protocolSession; + /** The current state */ + private AMQState _currentState; + + private CopyOnWriteArraySet<StateListener> _stateListeners = new CopyOnWriteArraySet<StateListener>(); + + public AMQStateManager(Broker broker, AMQProtocolSession protocolSession) + { + _broker = broker; + _protocolSession = protocolSession; + _currentState = AMQState.CONNECTION_NOT_STARTED; + + } + + /** + * Get the Broker instance + * + * @return the Broker + */ + public Broker getBroker() + { + return _broker; + } + + 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(Exception e) + { + _logger.error("State manager received error notification[Current State:" + _currentState + "]: " + e, e); + for (StateListener l : _stateListeners) + { + l.error(e); + } + } + + public <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws AMQException + { + MethodDispatcher dispatcher = _protocolSession.getMethodDispatcher(); + + final int channelId = evt.getChannelId(); + B body = evt.getMethod(); + + if(channelId != 0 && _protocolSession.getChannel(channelId)== null) + { + + if(! ((body instanceof ChannelOpenBody) + || (body instanceof ChannelCloseOkBody) + || (body instanceof ChannelCloseBody))) + { + throw body.getConnectionException(AMQConstant.CHANNEL_ERROR, "channel is closed won't process:" + body); + } + + } + + return body.execute(dispatcher, channelId); + + } + + private <B extends AMQMethodBody> void checkChannel(AMQMethodEvent<B> evt, AMQProtocolSession protocolSession) + throws AMQException + { + if ((evt.getChannelId() != 0) && !(evt.getMethod() instanceof ChannelOpenBody) + && (protocolSession.getChannel(evt.getChannelId()) == null) + && !protocolSession.channelAwaitingClosure(evt.getChannelId())) + { + throw evt.getMethod().getChannelNotFoundException(evt.getChannelId()); + } + } + + public void addStateListener(StateListener listener) + { + _logger.debug("Adding state listener"); + _stateListeners.add(listener); + } + + public void removeStateListener(StateListener listener) + { + _stateListeners.remove(listener); + } + + public VirtualHostRegistry getVirtualHostRegistry() + { + return _broker.getVirtualHostRegistry(); + } + + public AMQProtocolSession getProtocolSession() + { + SecurityManager.setThreadSubject(_protocolSession.getAuthorizedSubject()); + return _protocolSession; + } + + + public SubjectCreator getSubjectCreator() + { + return _broker.getSubjectCreator(getProtocolSession().getLocalAddress()); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/IllegalStateTransitionException.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/IllegalStateTransitionException.java new file mode 100644 index 0000000000..f61553f8a2 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/IllegalStateTransitionException.java @@ -0,0 +1,52 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.state; + +import org.apache.qpid.AMQException; + +/** + * @todo Not an AMQP exception as no status code. + * + * @todo Not used! Delete. + */ +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/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/StateAwareMethodListener.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/StateAwareMethodListener.java new file mode 100644 index 0000000000..63ab23919d --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/StateAwareMethodListener.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.state; + +import org.apache.qpid.AMQException; +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, B evt, int channelId) throws AMQException; +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/StateListener.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/StateListener.java new file mode 100644 index 0000000000..e065ae0d42 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/java/org/apache/qpid/server/protocol/v0_8/state/StateListener.java @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8.state; + +import org.apache.qpid.AMQException; + +public interface StateListener +{ + void stateChanged(AMQState oldState, AMQState newState) throws AMQException; + + void error(Throwable t); +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType new file mode 100644 index 0000000000..43ad3adf13 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.v0_8.MessageMetaDataType_0_8 diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator new file mode 100644 index 0000000000..57ca615a04 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.v0_8.ProtocolEngineCreator_0_8 +org.apache.qpid.server.protocol.v0_8.ProtocolEngineCreator_0_9 +org.apache.qpid.server.protocol.v0_8.ProtocolEngineCreator_0_9_1 diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AMQChannelTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AMQChannelTest.java new file mode 100644 index 0000000000..b358c7c5c5 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AMQChannelTest.java @@ -0,0 +1,141 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.message.MessageContentSource; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +public class AMQChannelTest extends QpidTestCase +{ + private VirtualHost _virtualHost; + private AMQProtocolSession _protocolSession; + private Map<Integer,String> _replies; + private Broker _broker; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _virtualHost = BrokerTestHelper.createVirtualHost(getTestName()); + _broker = BrokerTestHelper.createBrokerMock(); + _protocolSession = new InternalTestProtocolSession(_virtualHost, _broker) + { + @Override + public void writeReturn(MessagePublishInfo messagePublishInfo, + ContentHeaderBody header, + MessageContentSource msgContent, + int channelId, + int replyCode, + AMQShortString replyText) throws AMQException + { + _replies.put(replyCode, replyText.asString()); + } + }; + _replies = new HashMap<Integer, String>(); + } + + @Override + public void tearDown() throws Exception + { + try + { + _virtualHost.close(); + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + public void testCompareTo() throws Exception + { + AMQChannel channel1 = new AMQChannel(_protocolSession, 1, _virtualHost.getMessageStore()); + + // create a channel with the same channelId but on a different session + AMQChannel channel2 = new AMQChannel(new InternalTestProtocolSession(_virtualHost, _broker), 1, _virtualHost.getMessageStore()); + assertFalse("Unexpected compare result", channel1.compareTo(channel2) == 0); + assertEquals("Unexpected compare result", 0, channel1.compareTo(channel1)); + } + + public void testPublishContentHeaderWhenMessageAuthorizationFails() throws Exception + { + setTestSystemProperty(BrokerProperties.PROPERTY_MSG_AUTH, "true"); + AMQChannel channel = new AMQChannel(_protocolSession, 1, _virtualHost.getMessageStore()); + channel.setLocalTransactional(); + + MessagePublishInfo info = mock(MessagePublishInfo.class); + Exchange e = mock(Exchange.class); + ContentHeaderBody contentHeaderBody= mock(ContentHeaderBody.class); + BasicContentHeaderProperties properties = mock(BasicContentHeaderProperties.class); + + when(contentHeaderBody.getProperties()).thenReturn(properties); + when(info.getExchange()).thenReturn(new AMQShortString("test")); + when(properties.getUserId()).thenReturn(new AMQShortString(_protocolSession.getAuthorizedPrincipal().getName() + "_incorrect")); + + channel.setPublishFrame(info, e); + channel.publishContentHeader(contentHeaderBody); + channel.commit(); + + assertEquals("Unexpected number of replies", 1, _replies.size()); + assertEquals("Message authorization passed", "Access Refused", _replies.get(403)); + } + + public void testPublishContentHeaderWhenMessageAuthorizationPasses() throws Exception + { + setTestSystemProperty(BrokerProperties.PROPERTY_MSG_AUTH, "true"); + AMQChannel channel = new AMQChannel(_protocolSession, 1, _virtualHost.getMessageStore()); + channel.setLocalTransactional(); + + MessagePublishInfo info = mock(MessagePublishInfo.class); + Exchange e = mock(Exchange.class); + ContentHeaderBody contentHeaderBody= mock(ContentHeaderBody.class); + BasicContentHeaderProperties properties = mock(BasicContentHeaderProperties.class); + + when(contentHeaderBody.getProperties()).thenReturn(properties); + when(info.getExchange()).thenReturn(new AMQShortString("test")); + when(properties.getUserId()).thenReturn(new AMQShortString(_protocolSession.getAuthorizedPrincipal().getName())); + + channel.setPublishFrame(info, e); + channel.publishContentHeader(contentHeaderBody); + channel.commit(); + + assertEquals("Unexpected number of replies", 0, _replies.size()); + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolEngineTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolEngineTest.java new file mode 100644 index 0000000000..f5e58cfd02 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AMQProtocolEngineTest.java @@ -0,0 +1,72 @@ +package org.apache.qpid.server.protocol.v0_8; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.properties.ConnectionStartProperties; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.transport.network.NetworkConnection; + +public class AMQProtocolEngineTest extends QpidTestCase +{ + private Broker _broker; + private Port _port; + private NetworkConnection _network; + private Transport _transport; + + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _broker = BrokerTestHelper.createBrokerMock(); + when(_broker.getAttribute(Broker.CONNECTION_CLOSE_WHEN_NO_ROUTE)).thenReturn(true); + + _port = mock(Port.class); + _network = mock(NetworkConnection.class); + _transport = Transport.TCP; + } + + public void tearDown() throws Exception + { + try + { + super.tearDown(); + } + finally + { + BrokerTestHelper.tearDown(); + } + } + + public void testSetClientPropertiesForNoRouteProvidedAsString() + { + AMQProtocolEngine engine = new AMQProtocolEngine(_broker, _network, 0, _port, _transport); + assertTrue("Unexpected closeWhenNoRoute before client properties set", engine.isCloseWhenNoRoute()); + + Map<String, Object> clientProperties = new HashMap<String, Object>(); + clientProperties.put(ConnectionStartProperties.QPID_CLOSE_WHEN_NO_ROUTE, Boolean.FALSE.toString()); + engine.setClientProperties(FieldTable.convertToFieldTable(clientProperties)); + + assertFalse("Unexpected closeWhenNoRoute after client properties set", engine.isCloseWhenNoRoute()); + } + + public void testSetClientPropertiesForNoRouteProvidedAsBoolean() + { + AMQProtocolEngine engine = new AMQProtocolEngine(_broker, _network, 0, _port, _transport); + assertTrue("Unexpected closeWhenNoRoute before client properties set", engine.isCloseWhenNoRoute()); + + Map<String, Object> clientProperties = new HashMap<String, Object>(); + clientProperties.put(ConnectionStartProperties.QPID_CLOSE_WHEN_NO_ROUTE, Boolean.FALSE); + engine.setClientProperties(FieldTable.convertToFieldTable(clientProperties)); + + assertFalse("Unexpected closeWhenNoRoute after client properties set", engine.isCloseWhenNoRoute()); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AckTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AckTest.java new file mode 100644 index 0000000000..4ab64ca100 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AckTest.java @@ -0,0 +1,436 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.flow.LimitlessCreditManager; +import org.apache.qpid.server.flow.Pre0_10CreditManager; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.ArrayList; +import java.util.Set; + +/** + * Tests that acknowledgements are handled correctly. + */ +public class AckTest extends QpidTestCase +{ + private Subscription _subscription; + + private AMQProtocolSession _protocolSession; + + private TestableMemoryMessageStore _messageStore; + + private AMQChannel _channel; + + private AMQQueue _queue; + + private static final AMQShortString DEFAULT_CONSUMER_TAG = new AMQShortString("conTag"); + private VirtualHost _virtualHost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _channel = BrokerTestHelper_0_8.createChannel(5); + _protocolSession = _channel.getProtocolSession(); + _virtualHost = _protocolSession.getVirtualHost(); + _queue = BrokerTestHelper.createQueue(getTestName(), _virtualHost); + _messageStore = (TestableMemoryMessageStore)_virtualHost.getMessageStore(); + } + + @Override + protected void tearDown() throws Exception + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + + private void publishMessages(int count) throws AMQException + { + publishMessages(count, false); + } + + private void publishMessages(int count, boolean persistent) throws AMQException + { + _queue.registerSubscription(_subscription,false); + for (int i = 1; i <= count; i++) + { + // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) + // TODO: Establish some way to determine the version for the test. + MessagePublishInfo publishBody = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return new AMQShortString("someExchange"); + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return new AMQShortString("rk"); + } + }; + final IncomingMessage msg = new IncomingMessage(publishBody); + //IncomingMessage msg2 = null; + BasicContentHeaderProperties b = new BasicContentHeaderProperties(); + ContentHeaderBody cb = new ContentHeaderBody(); + cb.setProperties(b); + + if (persistent) + { + //This is DeliveryMode.PERSISTENT + b.setDeliveryMode((byte) 2); + } + + msg.setContentHeaderBody(cb); + + // we increment the reference here since we are not delivering the messaging to any queues, which is where + // the reference is normally incremented. The test is easier to construct if we have direct access to the + // subscription + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(_queue); + msg.enqueue(qs); + MessageMetaData mmd = msg.headersReceived(System.currentTimeMillis()); + final StoredMessage storedMessage = _messageStore.addMessage(mmd); + msg.setStoredMessage(storedMessage); + final AMQMessage message = new AMQMessage(storedMessage); + if(msg.allContentReceived()) + { + ServerTransaction txn = new AutoCommitTransaction(_messageStore); + txn.enqueue(_queue, message, new ServerTransaction.Action() { + public void postCommit() + { + try + { + + _queue.enqueue(message); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + + public void onRollback() + { + //To change body of implemented methods use File | Settings | File Templates. + } + }); + + } + // we manually send the message to the subscription + //_subscription.send(new QueueEntry(_queue,msg), _queue); + } + try + { + Thread.sleep(2000L); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + + } + + /** + * Tests that the acknowledgements are correctly associated with a channel and + * order is preserved when acks are enabled + */ + public void testAckChannelAssociationTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true, null, false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertEquals("Unextpected size for unacknowledge message map",msgCount,map.size()); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i); + i++; + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + } + + } + + /** + * Tests that in no-ack mode no messages are retained + */ + public void testNoAckMode() throws AMQException + { + // false arg means no acks expected + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, false, null, false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + assertTrue(_messageStore.getMessageCount() == 0); + + + } + + /** + * Tests that in no-ack mode no messages are retained + */ + public void testPersistentNoAckMode() throws AMQException + { + // false arg means no acks expected + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, false,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount, true); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + assertTrue(_messageStore.getMessageCount() == 0); + + + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + public void testSingleAckReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, false); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertEquals("Map not expected size",msgCount - 1,map.size()); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + // 5 is the delivery tag of the message that *should* be removed + if (++i == 5) + { + ++i; + } + } + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + public void testMultiAckReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + + + _channel.acknowledgeMessage(5, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i + 5); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + ++i; + } + } + + /** + * Tests that a multiple acknowledgement is handled correctly. When ack'ing all pending msgs. + */ + public void testMultiAckAllReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(0, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i + 5); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + ++i; + } + } + + /** + * A regression fixing QPID-1136 showed this up + * + * @throws Exception + */ + public void testMessageDequeueRestoresCreditTest() throws Exception + { + // Send 10 messages + Pre0_10CreditManager creditManager = new Pre0_10CreditManager(0l, 1); + + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, + DEFAULT_CONSUMER_TAG, true, null, false, creditManager); + final int msgCount = 1; + publishMessages(msgCount); + + _queue.deliverAsync(_subscription); + + _channel.acknowledgeMessage(1, false); + + // Check credit available + assertTrue("No credit available", creditManager.hasCredit()); + + } + + +/* + public void testPrefetchHighLow() throws AMQException + { + int lowMark = 5; + int highMark = 10; + + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + _channel.setPrefetchLowMarkCount(lowMark); + _channel.setPrefetchHighMarkCount(highMark); + + assertTrue(_channel.getPrefetchLowMarkCount() == lowMark); + assertTrue(_channel.getPrefetchHighMarkCount() == highMark); + + publishMessages(highMark); + + // at this point we should have sent out only highMark messages + // which have not bee received so will be queued up in the channel + // which should be suspended + assertTrue(_subscription.isSuspended()); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == highMark); + + //acknowledge messages so we are just above lowMark + _channel.acknowledgeMessage(lowMark - 1, true); + + //we should still be suspended + assertTrue(_subscription.isSuspended()); + assertTrue(map.size() == lowMark + 1); + + //acknowledge one more message + _channel.acknowledgeMessage(lowMark, true); + + //and suspension should be lifted + assertTrue(!_subscription.isSuspended()); + + //pubilsh more msgs so we are just below the limit + publishMessages(lowMark - 1); + + //we should not be suspended + assertTrue(!_subscription.isSuspended()); + + //acknowledge all messages + _channel.acknowledgeMessage(0, true); + try + { + Thread.sleep(3000); + } + catch (InterruptedException e) + { + _log.error("Error: " + e, e); + } + //map will be empty + assertTrue(map.size() == 0); + } + +*/ +/* + public void testPrefetch() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + _channel.setMessageCredit(5); + + assertTrue(_channel.getPrefetchCount() == 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 now 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 junit.framework.TestSuite(AckTest.class); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AcknowledgeTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AcknowledgeTest.java new file mode 100644 index 0000000000..43f88ca2a3 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/AcknowledgeTest.java @@ -0,0 +1,181 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.queue.SimpleAMQQueue; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.List; + +public class AcknowledgeTest extends QpidTestCase +{ + private AMQChannel _channel; + private SimpleAMQQueue _queue; + private MessageStore _messageStore; + private String _queueName; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _channel = BrokerTestHelper_0_8.createChannel(); + VirtualHost virtualHost = _channel.getVirtualHost(); + _queueName = getTestName(); + _queue = BrokerTestHelper.createQueue(_queueName, virtualHost); + _messageStore = virtualHost.getMessageStore(); + } + + @Override + public void tearDown() throws Exception + { + try + { + if (_channel != null) + { + _channel.getVirtualHost().close(); + } + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + private AMQChannel getChannel() + { + return _channel; + } + + private InternalTestProtocolSession getSession() + { + return (InternalTestProtocolSession)_channel.getProtocolSession(); + } + + private SimpleAMQQueue getQueue() + { + return _queue; + } + + public void testTransactionalSingleAck() throws AMQException + { + getChannel().setLocalTransactional(); + runMessageAck(1, 1, 1, false, 0); + } + + public void testTransactionalMultiAck() throws AMQException + { + getChannel().setLocalTransactional(); + runMessageAck(10, 1, 5, true, 5); + } + + public void testTransactionalAckAll() throws AMQException + { + getChannel().setLocalTransactional(); + runMessageAck(10, 1, 0, true, 0); + } + + public void testNonTransactionalSingleAck() throws AMQException + { + runMessageAck(1, 1, 1, false, 0); + } + + public void testNonTransactionalMultiAck() throws AMQException + { + runMessageAck(10, 1, 5, true, 5); + } + + public void testNonTransactionalAckAll() throws AMQException + { + runMessageAck(10, 1, 0, true, 0); + } + + protected void runMessageAck(int sendMessageCount, long firstDeliveryTag, long acknowledgeDeliveryTag, boolean acknowldegeMultiple, int remainingUnackedMessages) throws AMQException + { + //Check store is empty + checkStoreContents(0); + + //Send required messsages to the queue + BrokerTestHelper_0_8.publishMessages(getChannel(), + sendMessageCount, + _queueName, + ExchangeDefaults.DEFAULT_EXCHANGE_NAME); + + if (getChannel().isTransactional()) + { + getChannel().commit(); + } + + //Ensure they are stored + checkStoreContents(sendMessageCount); + + //Check that there are no unacked messages + assertEquals("Channel should have no unacked msgs ", 0, getChannel().getUnacknowledgedMessageMap().size()); + + //Subscribe to the queue + AMQShortString subscriber = _channel.subscribeToQueue(null, _queue, true, null, false, true); + + getQueue().deliverAsync(); + + //Wait for the messages to be delivered + getSession().awaitDelivery(sendMessageCount); + + //Check that they are all waiting to be acknoledged + assertEquals("Channel should have unacked msgs", sendMessageCount, getChannel().getUnacknowledgedMessageMap().size()); + + List<InternalTestProtocolSession.DeliveryPair> messages = getSession().getDelivers(getChannel().getChannelId(), subscriber, sendMessageCount); + + //Double check we received the right number of messages + assertEquals(sendMessageCount, messages.size()); + + //Check that the first message has the expected deliveryTag + assertEquals("First message does not have expected deliveryTag", firstDeliveryTag, messages.get(0).getDeliveryTag()); + + //Send required Acknowledgement + getChannel().acknowledgeMessage(acknowledgeDeliveryTag, acknowldegeMultiple); + + if (getChannel().isTransactional()) + { + getChannel().commit(); + } + + // Check Remaining Acknowledgements + assertEquals("Channel unacked msgs count incorrect", remainingUnackedMessages, getChannel().getUnacknowledgedMessageMap().size()); + + //Check store contents are also correct. + checkStoreContents(remainingUnackedMessages); + } + + private void checkStoreContents(int messageCount) + { + assertEquals("Message header count incorrect in the MetaDataMap", messageCount, ((TestableMemoryMessageStore) _messageStore).getMessageCount()); + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/BrokerTestHelper_0_8.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/BrokerTestHelper_0_8.java new file mode 100644 index 0000000000..0919607bd7 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/BrokerTestHelper_0_8.java @@ -0,0 +1,99 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BrokerTestHelper_0_8 extends BrokerTestHelper +{ + + public static AMQChannel createChannel(int channelId, AMQProtocolSession session) throws AMQException + { + AMQChannel channel = new AMQChannel(session, channelId, session.getVirtualHost().getMessageStore()); + session.addChannel(channel); + return channel; + } + + public static AMQChannel createChannel(int channelId) throws Exception + { + InternalTestProtocolSession session = createProtocolSession(); + return createChannel(channelId, session); + } + + public static AMQChannel createChannel() throws Exception + { + return createChannel(1); + } + + public static InternalTestProtocolSession createProtocolSession() throws Exception + { + return createProtocolSession("test"); + } + + public static InternalTestProtocolSession createProtocolSession(String hostName) throws Exception + { + VirtualHost virtualHost = createVirtualHost(hostName); + return new InternalTestProtocolSession(virtualHost, createBrokerMock()); + } + + public static void publishMessages(AMQChannel channel, int numberOfMessages, String queueName, String exchangeName) throws AMQException + { + AMQShortString rouningKey = new AMQShortString(queueName); + AMQShortString exchangeNameAsShortString = new AMQShortString(exchangeName); + MessagePublishInfo info = mock(MessagePublishInfo.class); + when(info.getExchange()).thenReturn(exchangeNameAsShortString); + when(info.getRoutingKey()).thenReturn(rouningKey); + + Exchange exchange = channel.getVirtualHost().getExchange(exchangeName); + for (int count = 0; count < numberOfMessages; count++) + { + channel.setPublishFrame(info, exchange); + + // Set the body size + ContentHeaderBody _headerBody = new ContentHeaderBody(); + _headerBody.setBodySize(0); + + // Set Minimum properties + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + + properties.setExpiration(0L); + properties.setTimestamp(System.currentTimeMillis()); + + // Make Message Persistent + properties.setDeliveryMode((byte) 2); + + _headerBody.setProperties(properties); + + channel.publishContentHeader(_headerBody); + } + channel.sync(); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/ExtractResendAndRequeueTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/ExtractResendAndRequeueTest.java new file mode 100644 index 0000000000..7f36b4a081 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/ExtractResendAndRequeueTest.java @@ -0,0 +1,253 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import junit.framework.TestCase; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.QueueEntryIterator; +import org.apache.qpid.server.queue.SimpleQueueEntryList; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestMemoryMessageStore; +import org.apache.qpid.server.subscription.MockSubscription; +import org.apache.qpid.server.subscription.Subscription; + +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; + +/** + * QPID-1385 : Race condition between added to unacked map and resending due to a rollback. + * + * In AMQChannel _unackedMap.clear() was done after the visit. This meant that the clear was not in the same + * synchronized block as as the preparation to resend. + * + * This clearing/prep for resend was done as a result of the rollback call. HOWEVER, the delivery thread was still + * in the process of sending messages to the client. It is therefore possible that a message could block on the + * _unackedMap lock waiting for the visit to compelete so that it can add the new message to the unackedMap.... + * which is then cleared by the resend/rollback thread. + * + * This problem was encountered by the testSend2ThenRollback test. + * + * To try and increase the chance of the race condition occuring this test will send multiple messages so that the + * delivery thread will be in progress while the rollback method is called. Hopefully this will cause the + * deliveryTag to be lost + */ +public class ExtractResendAndRequeueTest extends TestCase +{ + + private UnacknowledgedMessageMapImpl _unacknowledgedMessageMap; + private static final int INITIAL_MSG_COUNT = 10; + private AMQQueue _queue = new MockAMQQueue(getName()); + private MessageStore _messageStore = new TestMemoryMessageStore(); + private LinkedList<QueueEntry> _referenceList = new LinkedList<QueueEntry>(); + + @Override + public void setUp() throws AMQException + { + _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(100); + + long id = 0; + SimpleQueueEntryList list = new SimpleQueueEntryList(_queue); + + // Add initial messages to QueueEntryList + for (int count = 0; count < INITIAL_MSG_COUNT; count++) + { + AMQMessage msg = new MockAMQMessage(id); + + list.add(msg); + + //Increment ID; + id++; + } + + // Iterate through the QueueEntryList and add entries to unacknowledgeMessageMap and referecenList + QueueEntryIterator queueEntries = list.iterator(); + while(queueEntries.advance()) + { + QueueEntry entry = queueEntries.getNode(); + _unacknowledgedMessageMap.add(entry.getMessage().getMessageNumber(), entry); + + // Store the entry for future inspection + _referenceList.add(entry); + } + + assertEquals("Map does not contain correct setup data", INITIAL_MSG_COUNT, _unacknowledgedMessageMap.size()); + } + + /** + * Helper method to create a new subscription and aquire the given messages. + * + * @param messageList The messages to aquire + * + * @return Subscription that performed the aquire + */ + private Subscription createSubscriptionAndAquireMessages(LinkedList<QueueEntry> messageList) + { + Subscription subscription = new MockSubscription(); + + // Aquire messages in subscription + for (QueueEntry entry : messageList) + { + entry.acquire(subscription); + } + + return subscription; + } + + /** + * This is the normal consumer rollback method. + * + * An active consumer that has aquired messages expects those messasges to be reset when rollback is requested. + * + * This test validates that the msgToResend map includes all the messages and none are left behind. + * + * @throws AMQException the visit interface throws this + */ + public void testResend() throws AMQException + { + //We don't need the subscription object here. + createSubscriptionAndAquireMessages(_referenceList); + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend doesn't matter here. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, _messageStore)); + + assertEquals("Message count for resend not correct.", INITIAL_MSG_COUNT, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * This is the normal consumer close method. + * + * When a consumer that has aquired messages expects closes the messages that it has aquired should be removed from + * the unacknowledgeMap and placed in msgToRequeue + * + * This test validates that the msgToRequeue map includes all the messages and none are left behind. + * + * @throws AMQException the visit interface throws this + */ + public void testRequeueDueToSubscriptionClosure() throws AMQException + { + Subscription subscription = createSubscriptionAndAquireMessages(_referenceList); + + // Close subscription + subscription.close(); + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend doesn't matter here. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, _messageStore)); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", INITIAL_MSG_COUNT, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * If the subscription is null, due to message being retrieved via a GET, And we request that messages are requeued + * requeueIfUnabletoResend(set to true) then all messages should be sent to the msgToRequeue map. + * + * @throws AMQException the visit interface throws this + */ + + public void testRequeueDueToMessageHavingNoConsumerTag() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend = true so all messages should go to msgToRequeue + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, _messageStore)); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", INITIAL_MSG_COUNT, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * If the subscription is null, due to message being retrieved via a GET, And we request that we don't + * requeueIfUnabletoResend(set to false) then all messages should be dropped as we do not have a dead letter queue. + * + * @throws AMQException the visit interface throws this + */ + + public void testDrop() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend = false so all messages should be dropped all maps should be empty + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, false, _messageStore)); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + + + for (QueueEntry entry : _referenceList) + { + assertTrue("Message was not discarded", entry.isDeleted()); + } + + } + + /** + * If the subscription is null, due to message being retrieved via a GET, AND the queue upon which the message was + * delivered has been deleted then it is not possible to requeue. Currently we simply discar the message but in the + * future we may wish to dead letter the message. + * + * Validate that at the end of the visit all Maps are empty and all messages are marked as deleted + * + * @throws AMQException the visit interface throws this + */ + public void testDiscard() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + _queue.delete(); + + // requeueIfUnabletoResend : value doesn't matter here as queue has been deleted + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, false, _messageStore)); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + + for (QueueEntry entry : _referenceList) + { + assertTrue("Message was not discarded", entry.isDeleted()); + } + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/InternalTestProtocolSession.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/InternalTestProtocolSession.java new file mode 100644 index 0000000000..e8fda2bc65 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/InternalTestProtocolSession.java @@ -0,0 +1,269 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.protocol.v0_8.AMQChannel; +import org.apache.qpid.server.protocol.v0_8.AMQMessage; +import org.apache.qpid.server.message.MessageContentSource; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.protocol.v0_8.AMQProtocolEngine; +import org.apache.qpid.server.protocol.v0_8.output.ProtocolOutputConverter; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.protocol.v0_8.SubscriptionImpl; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.TestNetworkConnection; + +import javax.security.auth.Subject; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class InternalTestProtocolSession extends AMQProtocolEngine implements ProtocolOutputConverter +{ + // ChannelID(LIST) -> LinkedList<Pair> + private final Map<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>> _channelDelivers; + private AtomicInteger _deliveryCount = new AtomicInteger(0); + private static final AtomicLong ID_GENERATOR = new AtomicLong(0); + + public InternalTestProtocolSession(VirtualHost virtualHost, Broker broker) throws AMQException + { + super(broker, new TestNetworkConnection(), ID_GENERATOR.getAndIncrement(), null, null); + + _channelDelivers = new HashMap<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>>(); + + setTestAuthorizedSubject(); + setVirtualHost(virtualHost); + } + + private void setTestAuthorizedSubject() + { + Principal principal = new AuthenticatedPrincipal(new UsernamePrincipal("InternalTestProtocolSession")); + Subject authorizedSubject = new Subject( + true, + Collections.singleton(principal), + Collections.emptySet(), + Collections.emptySet()); + + setAuthorizedSubject(authorizedSubject); + } + + public ProtocolOutputConverter getProtocolOutputConverter() + { + return this; + } + + public byte getProtocolMajorVersion() + { + return (byte) 8; + } + + public void writeReturn(MessagePublishInfo messagePublishInfo, + ContentHeaderBody header, + MessageContentSource msgContent, + int channelId, + int replyCode, + AMQShortString replyText) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public byte getProtocolMinorVersion() + { + return (byte) 0; + } + + // *** + + public List<DeliveryPair> getDelivers(int channelId, AMQShortString consumerTag, int count) + { + synchronized (_channelDelivers) + { + List<DeliveryPair> all =_channelDelivers.get(channelId).get(consumerTag); + + if (all == null) + { + return new ArrayList<DeliveryPair>(0); + } + + List<DeliveryPair> msgs = all.subList(0, count); + + List<DeliveryPair> response = new ArrayList<DeliveryPair>(msgs); + + //Remove the msgs from the receivedList. + msgs.clear(); + + return response; + } + } + + // *** ProtocolOutputConverter Implementation + public void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText) throws AMQException + { + } + + public ClientDeliveryMethod createDeliveryMethod(int channelId) + { + return new InternalWriteDeliverMethod(channelId); + } + + public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag) + { + } + + public void writeDeliver(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag) throws AMQException + { + _deliveryCount.incrementAndGet(); + + synchronized (_channelDelivers) + { + Map<AMQShortString, LinkedList<DeliveryPair>> consumers = _channelDelivers.get(channelId); + + if (consumers == null) + { + consumers = new HashMap<AMQShortString, LinkedList<DeliveryPair>>(); + _channelDelivers.put(channelId, consumers); + } + + LinkedList<DeliveryPair> consumerDelivers = consumers.get(consumerTag); + + if (consumerDelivers == null) + { + consumerDelivers = new LinkedList<DeliveryPair>(); + consumers.put(consumerTag, consumerDelivers); + } + + consumerDelivers.add(new DeliveryPair(deliveryTag, (AMQMessage)entry.getMessage())); + } + } + + public void writeGetOk(QueueEntry message, int channelId, long deliveryTag, int queueSize) throws AMQException + { + } + + public void awaitDelivery(int msgs) + { + while (msgs > _deliveryCount.get()) + { + try + { + Thread.sleep(100); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + public class DeliveryPair + { + private long _deliveryTag; + private AMQMessage _message; + + public DeliveryPair(long deliveryTag, AMQMessage message) + { + _deliveryTag = deliveryTag; + _message = message; + } + + public AMQMessage getMessage() + { + return _message; + } + + public long getDeliveryTag() + { + return _deliveryTag; + } + } + + public void closeProtocolSession() + { + // Override as we don't have a real IOSession to close. + // The alternative is to fully implement the TestIOSession to return a CloseFuture from close(); + // Then the AMQMinaProtocolSession can join on the returning future without a NPE. + } + + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException + { + super.closeSession(session, cause, message); + + //Simulate the Client responding with a CloseOK + // should really update the StateManger but we don't have access here + // changeState(AMQState.CONNECTION_CLOSED); + ((AMQChannel)session).getProtocolSession().closeSession(); + + } + + private class InternalWriteDeliverMethod implements ClientDeliveryMethod + { + private int _channelId; + + public InternalWriteDeliverMethod(int channelId) + { + _channelId = channelId; + } + + + public void deliverToClient(Subscription sub, QueueEntry entry, long deliveryTag) throws AMQException + { + _deliveryCount.incrementAndGet(); + + synchronized (_channelDelivers) + { + Map<AMQShortString, LinkedList<DeliveryPair>> consumers = _channelDelivers.get(_channelId); + + if (consumers == null) + { + consumers = new HashMap<AMQShortString, LinkedList<DeliveryPair>>(); + _channelDelivers.put(_channelId, consumers); + } + + LinkedList<DeliveryPair> consumerDelivers = consumers.get(((SubscriptionImpl)sub).getConsumerTag()); + + if (consumerDelivers == null) + { + consumerDelivers = new LinkedList<DeliveryPair>(); + consumers.put(((SubscriptionImpl)sub).getConsumerTag(), consumerDelivers); + } + + consumerDelivers.add(new DeliveryPair(deliveryTag, (AMQMessage)entry.getMessage())); + } + } + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MaxChannelsTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MaxChannelsTest.java new file mode 100644 index 0000000000..a77475c05f --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MaxChannelsTest.java @@ -0,0 +1,83 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; + +/** Test class to test MBean operations for AMQMinaProtocolSession. */ +public class MaxChannelsTest extends QpidTestCase +{ + private AMQProtocolEngine _session; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _session = BrokerTestHelper_0_8.createProtocolSession(); + } + + public void testChannels() throws Exception + { + // check the channel count is correct + int channelCount = _session.getChannels().size(); + assertEquals("Initial channel count wrong", 0, channelCount); + + long maxChannels = 10L; + _session.setMaximumNumberOfChannels(maxChannels); + assertEquals("Number of channels not correctly set.", new Long(maxChannels), _session.getMaximumNumberOfChannels()); + + for (long currentChannel = 0L; currentChannel < maxChannels; currentChannel++) + { + _session.addChannel(new AMQChannel(_session, (int) currentChannel, null)); + } + + try + { + _session.addChannel(new AMQChannel(_session, (int) maxChannels, null)); + fail("Cannot create more channels then maximum"); + } + catch (AMQException e) + { + assertEquals("Wrong exception recevied.", e.getErrorCode(), AMQConstant.NOT_ALLOWED); + } + assertEquals("Maximum number of channels not set.", new Long(maxChannels), new Long(_session.getChannels().size())); + } + + @Override + public void tearDown() throws Exception + { + try + { + _session.getVirtualHost().close(); + _session.closeSession(); + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockAMQMessage.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockAMQMessage.java new file mode 100644 index 0000000000..1cc3607298 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockAMQMessage.java @@ -0,0 +1,40 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +public class MockAMQMessage extends AMQMessage +{ + public MockAMQMessage(long messageId) + { + super(new MockStoredMessage(messageId)); + } + + public MockAMQMessage(long messageId, String headerName, Object headerValue) + { + super(new MockStoredMessage(messageId, headerName, headerValue)); + } + + @Override + public long getSize() + { + return 0l; + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockMessagePublishInfo.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockMessagePublishInfo.java new file mode 100644 index 0000000000..ab29e58a6c --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockMessagePublishInfo.java @@ -0,0 +1,52 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; + +public class MockMessagePublishInfo implements MessagePublishInfo +{ + public AMQShortString getExchange() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isMandatory() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public AMQShortString getRoutingKey() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockStoredMessage.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockStoredMessage.java new file mode 100755 index 0000000000..15573a871f --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/MockStoredMessage.java @@ -0,0 +1,115 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.StoredMessage; + +import java.nio.ByteBuffer; + +public class MockStoredMessage implements StoredMessage<MessageMetaData> +{ + private long _messageId; + private MessageMetaData _metaData; + private final ByteBuffer _content; + + public MockStoredMessage(long messageId) + { + this(messageId, (String)null, null); + } + + public MockStoredMessage(long messageId, String headerName, Object headerValue) + { + this(messageId, new MockMessagePublishInfo(), new ContentHeaderBody(new BasicContentHeaderProperties(), 60), headerName, headerValue); + } + + public MockStoredMessage(long messageId, MessagePublishInfo info, ContentHeaderBody chb) + { + this(messageId, info, chb, null, null); + } + + public MockStoredMessage(long messageId, MessagePublishInfo info, ContentHeaderBody chb, String headerName, Object headerValue) + { + _messageId = messageId; + if (headerName != null) + { + FieldTable headers = new FieldTable(); + headers.setString(headerName, headerValue == null? null :String.valueOf(headerValue)); + ((BasicContentHeaderProperties)chb.getProperties()).setHeaders(headers); + } + _metaData = new MessageMetaData(info, chb, 0); + _content = ByteBuffer.allocate(_metaData.getContentSize()); + } + + public MessageMetaData getMetaData() + { + return _metaData; + } + + public long getMessageNumber() + { + return _messageId; + } + + public void addContent(int offsetInMessage, ByteBuffer src) + { + src = src.duplicate(); + ByteBuffer dst = _content.duplicate(); + dst.position(offsetInMessage); + dst.put(src); + } + + public int getContent(int offset, ByteBuffer dst) + { + ByteBuffer src = _content.duplicate(); + src.position(offset); + src = src.slice(); + if(dst.remaining() < src.limit()) + { + src.limit(dst.remaining()); + } + dst.put(src); + return src.limit(); + } + + + + public ByteBuffer getContent(int offsetInMessage, int size) + { + ByteBuffer buf = ByteBuffer.allocate(size); + getContent(offsetInMessage, buf); + buf.position(0); + return buf; + } + + public StoreFuture flushToStore() + { + return StoreFuture.IMMEDIATE_FUTURE; + } + + public void remove() + { + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/QueueBrowserUsesNoAckTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/QueueBrowserUsesNoAckTest.java new file mode 100644 index 0000000000..4f9df3b38d --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/QueueBrowserUsesNoAckTest.java @@ -0,0 +1,149 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.SimpleAMQQueue; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.List; + +public class QueueBrowserUsesNoAckTest extends QpidTestCase +{ + private AMQChannel _channel; + private SimpleAMQQueue _queue; + private MessageStore _messageStore; + private String _queueName; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _channel = BrokerTestHelper_0_8.createChannel(); + VirtualHost virtualHost = _channel.getVirtualHost(); + _queueName = getTestName(); + _queue = BrokerTestHelper.createQueue(_queueName, virtualHost); + _messageStore = virtualHost.getMessageStore(); + } + + @Override + public void tearDown() throws Exception + { + try + { + if (_channel != null) + { + _channel.getVirtualHost().close(); + } + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + private AMQChannel getChannel() + { + return _channel; + } + + private InternalTestProtocolSession getSession() + { + return (InternalTestProtocolSession)_channel.getProtocolSession(); + } + + private SimpleAMQQueue getQueue() + { + return _queue; + } + + public void testQueueBrowserUsesNoAck() throws AMQException + { + int sendMessageCount = 2; + int prefetch = 1; + + //Check store is empty + checkStoreContents(0); + + //Send required messsages to the queue + BrokerTestHelper_0_8.publishMessages(getChannel(), + sendMessageCount, + _queueName, + ExchangeDefaults.DEFAULT_EXCHANGE_NAME); + + //Ensure they are stored + checkStoreContents(sendMessageCount); + + //Check that there are no unacked messages + assertEquals("Channel should have no unacked msgs ", 0, + getChannel().getUnacknowledgedMessageMap().size()); + + //Set the prefetch on the session to be less than the sent messages + getChannel().setCredit(0, prefetch); + + //browse the queue + AMQShortString browser = browse(getChannel(), getQueue()); + + getQueue().deliverAsync(); + + //Wait for messages to fill the prefetch + getSession().awaitDelivery(prefetch); + + //Get those messages + List<InternalTestProtocolSession.DeliveryPair> messages = + getSession().getDelivers(getChannel().getChannelId(), browser, + prefetch); + + //Ensure we recevied the prefetched messages + assertEquals(prefetch, messages.size()); + + //Check the process didn't suspend the subscription as this would + // indicate we are using the prefetch credit. i.e. using acks not No-Ack + assertTrue("The subscription has been suspended", + !getChannel().getSubscription(browser).getState() + .equals(Subscription.State.SUSPENDED)); + } + + private void checkStoreContents(int messageCount) + { + assertEquals("Message header count incorrect in the MetaDataMap", messageCount, ((TestableMemoryMessageStore) _messageStore).getMessageCount()); + } + + private AMQShortString browse(AMQChannel channel, AMQQueue queue) throws AMQException + { + FieldTable filters = new FieldTable(); + filters.put(AMQPFilterTypes.NO_CONSUME.getValue(), true); + + return channel.subscribeToQueue(null, queue, true, filters, false, true); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/ReferenceCountingTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/ReferenceCountingTest.java new file mode 100644 index 0000000000..87fbcfa9b3 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/ReferenceCountingTest.java @@ -0,0 +1,162 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.test.utils.QpidTestCase; + +/** + * Tests that reference counting works correctly with AMQMessage and the message store + */ +public class ReferenceCountingTest extends QpidTestCase +{ + private TestableMemoryMessageStore _store; + + + protected void setUp() throws Exception + { + _store = new TestableMemoryMessageStore(); + } + + /** + * Check that when the reference count is decremented the message removes itself from the store + */ + public void testMessageGetsRemoved() throws AMQException + { + ContentHeaderBody chb = createPersistentContentHeader(); + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + + + MessageMetaData mmd = new MessageMetaData(info, chb, 0); + StoredMessage storedMessage = _store.addMessage(mmd); + + + AMQMessage message = new AMQMessage(storedMessage); + + MessageReference ref = message.newReference(); + + assertEquals(1, _store.getMessageCount()); + + ref.release(); + + assertEquals(0, _store.getMessageCount()); + } + + private ContentHeaderBody createPersistentContentHeader() + { + ContentHeaderBody chb = new ContentHeaderBody(); + BasicContentHeaderProperties bchp = new BasicContentHeaderProperties(); + bchp.setDeliveryMode((byte)2); + chb.setProperties(bchp); + return chb; + } + + public void testMessageRemains() throws AMQException + { + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + final ContentHeaderBody chb = createPersistentContentHeader(); + + MessageMetaData mmd = new MessageMetaData(info, chb, 0); + StoredMessage storedMessage = _store.addMessage(mmd); + + AMQMessage message = new AMQMessage(storedMessage); + + + MessageReference ref = message.newReference(); + // we call routing complete to set up the handle + // message.routingComplete(_store, _storeContext, new MessageHandleFactory()); + + assertEquals(1, _store.getMessageCount()); + MessageReference ref2 = message.newReference(); + ref.release(); + assertEquals(1, _store.getMessageCount()); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(ReferenceCountingTest.class); + } +} diff --git a/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactoryImplTest.java b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactoryImplTest.java new file mode 100644 index 0000000000..e98dd63450 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-0-8-protocol/src/test/java/org/apache/qpid/server/protocol/v0_8/SubscriptionFactoryImplTest.java @@ -0,0 +1,96 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v0_8; + +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.logging.UnitTestMessageLogger; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; + +public class SubscriptionFactoryImplTest extends QpidTestCase +{ + private AMQChannel _channel; + private AMQProtocolSession _session; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _channel = BrokerTestHelper_0_8.createChannel(); + _session = _channel.getProtocolSession(); + GenericActor.setDefaultMessageLogger(new UnitTestMessageLogger(false)); + } + + @Override + public void tearDown() throws Exception + { + try + { + if (_channel != null) + { + _channel.getVirtualHost().close(); + } + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + /** + * Tests that while creating Subscriptions of various types, the + * ID numbers assigned are allocated from a common sequence + * (in increasing order). + */ + public void testDifferingSubscriptionTypesShareCommonIdNumberingSequence() throws Exception + { + //create a No-Ack subscription, get the first Subscription ID + long previousId = 0; + Subscription noAckSub = SubscriptionFactoryImpl.INSTANCE.createSubscription(1, _session, new AMQShortString("1"), false, null, false, _channel.getCreditManager()); + previousId = noAckSub.getSubscriptionID(); + + //create an ack subscription, verify the next Subscription ID is used + Subscription ackSub = SubscriptionFactoryImpl.INSTANCE.createSubscription(1, _session, new AMQShortString("1"), true, null, false, _channel.getCreditManager()); + assertEquals("Unexpected Subscription ID allocated", previousId + 1, ackSub.getSubscriptionID()); + previousId = ackSub.getSubscriptionID(); + + //create a browser subscription + FieldTable filters = new FieldTable(); + filters.put(AMQPFilterTypes.NO_CONSUME.getValue(), true); + Subscription browerSub = SubscriptionFactoryImpl.INSTANCE.createSubscription(1, _session, new AMQShortString("1"), true, null, false, _channel.getCreditManager()); + assertEquals("Unexpected Subscription ID allocated", previousId + 1, browerSub.getSubscriptionID()); + previousId = browerSub.getSubscriptionID(); + + //create an BasicGet NoAck subscription + Subscription getNoAckSub = SubscriptionFactoryImpl.INSTANCE.createBasicGetNoAckSubscription(_channel, _session, new AMQShortString("1"), null, false, + _channel.getCreditManager(),_channel.getClientDeliveryMethod(), _channel.getRecordDeliveryMethod()); + assertEquals("Unexpected Subscription ID allocated", previousId + 1, getNoAckSub.getSubscriptionID()); + previousId = getNoAckSub.getSubscriptionID(); + + } + +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/build.xml b/qpid/java/broker-plugins/amqp-1-0-protocol/build.xml new file mode 100644 index 0000000000..b83e3eeae1 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/build.xml @@ -0,0 +1,40 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins AMQP 1.0 Protocol" default="build"> + <property name="module.depends" value="common broker amqp-1-0-common" /> + <property name="module.test.depends" value="common/tests broker/tests" /> + + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + <property name="broker-plugins-amqp-1-0-protocol.libs" value="" /> + + <property name="broker.plugin" value="true"/> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks"/> + + <!-- Overrides, but depends on, target in module.xml --> + <target name="copy-broker-plugin-jars-deps" depends="module.copy-broker-plugin-jars-deps" if="broker.plugin" description="copy broker plugins dependencies for use in release packaging"> + <copy todir="${build.scratch.broker.plugins.lib}" failonerror="true" flatten="true"> + <fileset file="${build.lib}/${project.name}-amqp-1-0-common-${project.version}.jar"/> + </copy> + </target> + +</project> diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Connection_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Connection_1_0.java new file mode 100644 index 0000000000..320875cc97 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Connection_1_0.java @@ -0,0 +1,343 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.text.MessageFormat; +import java.util.Collection; +import org.apache.qpid.AMQException; +import org.apache.qpid.amqp_1_0.transport.ConnectionEndpoint; +import org.apache.qpid.amqp_1_0.transport.ConnectionEventListener; +import org.apache.qpid.amqp_1_0.transport.SessionEndpoint; + +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.CONNECTION_FORMAT; + +public class Connection_1_0 implements ConnectionEventListener +{ + + private final Port _port; + private VirtualHost _vhost; + private final Transport _transport; + private final ConnectionEndpoint _conn; + private final long _connectionId; + private final Collection<Session_1_0> _sessions = Collections.synchronizedCollection(new ArrayList<Session_1_0>()); + + + public static interface Task + { + public void doTask(Connection_1_0 connection); + } + + + private List<Task> _closeTasks = + Collections.synchronizedList(new ArrayList<Task>()); + + + + public Connection_1_0(VirtualHost virtualHost, + ConnectionEndpoint conn, + long connectionId, + Port port, + Transport transport) + { + _vhost = virtualHost; + _port = port; + _transport = transport; + _conn = conn; + _connectionId = connectionId; + _vhost.getConnectionRegistry().registerConnection(_model); + + } + + public void remoteSessionCreation(SessionEndpoint endpoint) + { + Session_1_0 session = new Session_1_0(_vhost, this); + _sessions.add(session); + endpoint.setSessionEventListener(session); + } + + void sessionEnded(Session_1_0 session) + { + _sessions.remove(session); + } + + void removeConnectionCloseTask(final Task task) + { + _closeTasks.remove( task ); + } + + void addConnectionCloseTask(final Task task) + { + _closeTasks.add( task ); + } + + public void closeReceived() + { + List<Task> taskCopy; + synchronized (_closeTasks) + { + taskCopy = new ArrayList<Task>(_closeTasks); + } + for(Task task : taskCopy) + { + task.doTask(this); + } + synchronized (_closeTasks) + { + _closeTasks.clear(); + } + _vhost.getConnectionRegistry().deregisterConnection(_model); + + + } + + public void closed() + { + closeReceived(); + } + + private final AMQConnectionModel _model = new AMQConnectionModel() + { + private StatisticsCounter _messageDeliveryStatistics = new StatisticsCounter(); + private StatisticsCounter _messageReceiptStatistics = new StatisticsCounter(); + private StatisticsCounter _dataDeliveryStatistics = new StatisticsCounter(); + private StatisticsCounter _dataReceiptStatistics = new StatisticsCounter(); + + private final LogSubject _logSubject = new LogSubject() + { + @Override + public String toLogString() + { + return "[" + + MessageFormat.format(CONNECTION_FORMAT, + getConnectionId(), + getClientId(), + getRemoteAddressString(), + _vhost.getName()) + + "] "; + + } + }; + + private volatile boolean _stopped; + + @Override + public void close(AMQConstant cause, String message) throws AMQException + { + _conn.close(); + } + + @Override + public void block() + { + // TODO + } + + @Override + public void unblock() + { + // TODO + } + + @Override + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException + { + // TODO + } + + @Override + public long getConnectionId() + { + return _connectionId; + } + + @Override + public List<AMQSessionModel> getSessionModels() + { + return new ArrayList<AMQSessionModel>(_sessions); + } + + @Override + public LogSubject getLogSubject() + { + return _logSubject; + } + + @Override + public String getUserName() + { + return getPrincipalAsString(); + } + + @Override + public boolean isSessionNameUnique(byte[] name) + { + return true; // TODO + } + + @Override + public String getRemoteAddressString() + { + return String.valueOf(_conn.getRemoteAddress()); + } + + @Override + public String getClientId() + { + return _conn.getRemoteContainerId(); + } + + @Override + public String getClientVersion() + { + return ""; //TODO + } + + @Override + public String getPrincipalAsString() + { + return String.valueOf(_conn.getUser()); + } + + @Override + public long getSessionCountLimit() + { + return 0; // TODO + } + + @Override + public long getLastIoTime() + { + return 0; // TODO + } + + @Override + public String getVirtualHostName() + { + return _vhost == null ? null : _vhost.getName(); + } + + @Override + public Port getPort() + { + return _port; + } + + @Override + public Transport getTransport() + { + return _transport; + } + + @Override + public void stop() + { + _stopped = true; + } + + @Override + public boolean isStopped() + { + return _stopped; + } + + @Override + public void initialiseStatistics() + { + _messageDeliveryStatistics = new StatisticsCounter("messages-delivered-" + getConnectionId()); + _dataDeliveryStatistics = new StatisticsCounter("data-delivered-" + getConnectionId()); + _messageReceiptStatistics = new StatisticsCounter("messages-received-" + getConnectionId()); + _dataReceiptStatistics = new StatisticsCounter("data-received-" + getConnectionId()); + } + + @Override + public void registerMessageReceived(long messageSize, long timestamp) + { + _messageReceiptStatistics.registerEvent(1L, timestamp); + _dataReceiptStatistics.registerEvent(messageSize, timestamp); + _vhost.registerMessageReceived(messageSize,timestamp); + + } + + @Override + public void registerMessageDelivered(long messageSize) + { + + _messageDeliveryStatistics.registerEvent(1L); + _dataDeliveryStatistics.registerEvent(messageSize); + _vhost.registerMessageDelivered(messageSize); + } + + @Override + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messageDeliveryStatistics; + } + + @Override + public StatisticsCounter getMessageReceiptStatistics() + { + return _messageReceiptStatistics; + } + + @Override + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDeliveryStatistics; + } + + @Override + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceiptStatistics; + } + + @Override + public void resetStatistics() + { + _dataDeliveryStatistics.reset(); + _dataReceiptStatistics.reset(); + _messageDeliveryStatistics.reset(); + _messageReceiptStatistics.reset(); + } + + + }; + + AMQConnectionModel getModel() + { + return _model; + } + + +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Destination.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Destination.java new file mode 100644 index 0000000000..d45758391c --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Destination.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + + +public interface Destination +{ + + +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ExchangeDestination.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ExchangeDestination.java new file mode 100644 index 0000000000..2cef27267b --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ExchangeDestination.java @@ -0,0 +1,108 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.util.List; +import org.apache.qpid.AMQException; +import org.apache.qpid.amqp_1_0.type.Outcome; +import org.apache.qpid.amqp_1_0.type.messaging.Accepted; +import org.apache.qpid.amqp_1_0.type.messaging.TerminusDurability; +import org.apache.qpid.amqp_1_0.type.messaging.TerminusExpiryPolicy; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.txn.ServerTransaction; + +public class ExchangeDestination implements ReceivingDestination, SendingDestination +{ + private static final Accepted ACCEPTED = new Accepted(); + private static final Outcome[] OUTCOMES = { ACCEPTED }; + + private Exchange _exchange; + private TerminusDurability _durability; + private TerminusExpiryPolicy _expiryPolicy; + + public ExchangeDestination(Exchange exchange, TerminusDurability durable, TerminusExpiryPolicy expiryPolicy) + { + _exchange = exchange; + _durability = durable; + _expiryPolicy = expiryPolicy; + } + + public Outcome[] getOutcomes() + { + return OUTCOMES; + } + + public Outcome send(final Message_1_0 message, ServerTransaction txn) + { + final List<? extends BaseQueue> queues = _exchange.route(message); + + txn.enqueue(queues,message, new ServerTransaction.Action() + { + + BaseQueue[] _queues = queues.toArray(new BaseQueue[queues.size()]); + + public void postCommit() + { + for(int i = 0; i < _queues.length; i++) + { + try + { + _queues[i].enqueue(message); + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + } + } + + public void onRollback() + { + // NO-OP + } + }); + + return ACCEPTED; + } + + TerminusDurability getDurability() + { + return _durability; + } + + TerminusExpiryPolicy getExpiryPolicy() + { + return _expiryPolicy; + } + + public int getCredit() + { + // TODO - fix + return 20000; + } + + public Exchange getExchange() + { + return _exchange; + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Link_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Link_1_0.java new file mode 100644 index 0000000000..5ce24f406d --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Link_1_0.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.server.protocol.LinkModel; + +public interface Link_1_0 extends LinkModel +{ + void start(); +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageConverter_to_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageConverter_to_1_0.java new file mode 100644 index 0000000000..be9b0323a3 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageConverter_to_1_0.java @@ -0,0 +1,245 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.io.EOFException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import org.apache.qpid.amqp_1_0.messaging.SectionEncoder; +import org.apache.qpid.amqp_1_0.messaging.SectionEncoderImpl; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.Section; +import org.apache.qpid.amqp_1_0.type.codec.AMQPDescribedTypeRegistry; +import org.apache.qpid.amqp_1_0.type.messaging.AmqpValue; +import org.apache.qpid.amqp_1_0.type.messaging.Data; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.plugin.MessageConverter; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.codec.BBDecoder; +import org.apache.qpid.typedmessage.TypedBytesContentReader; +import org.apache.qpid.typedmessage.TypedBytesFormatException; + +public abstract class MessageConverter_to_1_0<M extends ServerMessage> implements MessageConverter<M, Message_1_0> +{ + private final AMQPDescribedTypeRegistry _typeRegistry = AMQPDescribedTypeRegistry.newInstance() + .registerTransportLayer() + .registerMessagingLayer() + .registerTransactionLayer() + .registerSecurityLayer(); + + @Override + public final Class<Message_1_0> getOutputClass() + { + return Message_1_0.class; + } + + @Override + public final Message_1_0 convert(M message, VirtualHost vhost) + { + + SectionEncoder sectionEncoder = new SectionEncoderImpl(_typeRegistry); + return new Message_1_0(convertToStoredMessage(message, sectionEncoder)); + } + + + private StoredMessage<MessageMetaData_1_0> convertToStoredMessage(final M serverMessage, SectionEncoder sectionEncoder) + { + final MessageMetaData_1_0 metaData = convertMetaData(serverMessage, sectionEncoder); + return convertServerMessage(metaData, serverMessage, sectionEncoder); + } + + abstract protected MessageMetaData_1_0 convertMetaData(final M serverMessage, SectionEncoder sectionEncoder); + + + private static Section convertMessageBody(String mimeType, byte[] data) + { + if("text/plain".equals(mimeType) || "text/xml".equals(mimeType)) + { + String text = new String(data); + return new AmqpValue(text); + } + else if("jms/map-message".equals(mimeType)) + { + TypedBytesContentReader reader = new TypedBytesContentReader(ByteBuffer.wrap(data)); + + LinkedHashMap map = new LinkedHashMap(); + final int entries = reader.readIntImpl(); + for (int i = 0; i < entries; i++) + { + try + { + String propName = reader.readStringImpl(); + Object value = reader.readObject(); + map.put(propName, value); + } + catch (EOFException e) + { + throw new IllegalArgumentException(e); + } + catch (TypedBytesFormatException e) + { + throw new IllegalArgumentException(e); + } + + } + + return new AmqpValue(map); + + } + else if("amqp/map".equals(mimeType)) + { + BBDecoder decoder = new BBDecoder(); + decoder.init(ByteBuffer.wrap(data)); + return new AmqpValue(decoder.readMap()); + + } + else if("amqp/list".equals(mimeType)) + { + BBDecoder decoder = new BBDecoder(); + decoder.init(ByteBuffer.wrap(data)); + return new AmqpValue(decoder.readList()); + } + else if("jms/stream-message".equals(mimeType)) + { + TypedBytesContentReader reader = new TypedBytesContentReader(ByteBuffer.wrap(data)); + + List list = new ArrayList(); + while (reader.remaining() != 0) + { + try + { + list.add(reader.readObject()); + } + catch (TypedBytesFormatException e) + { + throw new RuntimeException(e); // TODO - Implement + } + catch (EOFException e) + { + throw new RuntimeException(e); // TODO - Implement + } + } + return new AmqpValue(list); + } + else + { + return new Data(new Binary(data)); + + } + } + + private StoredMessage<MessageMetaData_1_0> convertServerMessage(final MessageMetaData_1_0 metaData, + final ServerMessage serverMessage, + SectionEncoder sectionEncoder) + { + final String mimeType = serverMessage.getMessageHeader().getMimeType(); + byte[] data = new byte[(int) serverMessage.getSize()]; + serverMessage.getContent(ByteBuffer.wrap(data), 0); + + Section bodySection = convertMessageBody(mimeType, data); + + final ByteBuffer allData = encodeConvertedMessage(metaData, bodySection, sectionEncoder); + + return new StoredMessage<MessageMetaData_1_0>() + { + @Override + public MessageMetaData_1_0 getMetaData() + { + return metaData; + } + + @Override + public long getMessageNumber() + { + return serverMessage.getMessageNumber(); + } + + @Override + public void addContent(int offsetInMessage, ByteBuffer src) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getContent(int offsetInMessage, ByteBuffer dst) + { + ByteBuffer buf = allData.duplicate(); + buf.position(offsetInMessage); + buf = buf.slice(); + int size; + if(dst.remaining()<buf.remaining()) + { + buf.limit(dst.remaining()); + size = dst.remaining(); + } + else + { + size = buf.remaining(); + } + dst.put(buf); + return size; + } + + @Override + public ByteBuffer getContent(int offsetInMessage, int size) + { + ByteBuffer buf = allData.duplicate(); + buf.position(offsetInMessage); + buf = buf.slice(); + if(size < buf.remaining()) + { + buf.limit(size); + } + return buf; + } + + @Override + public StoreFuture flushToStore() + { + throw new UnsupportedOperationException(); + } + + @Override + public void remove() + { + serverMessage.getStoredMessage().remove(); + } + }; + } + + private ByteBuffer encodeConvertedMessage(MessageMetaData_1_0 metaData, Section bodySection, SectionEncoder sectionEncoder) + { + int headerSize = (int) metaData.getStorableSize(); + + sectionEncoder.reset(); + sectionEncoder.encodeObject(bodySection); + Binary dataEncoding = sectionEncoder.getEncoding(); + + final ByteBuffer allData = ByteBuffer.allocate(headerSize + dataEncoding.getLength()); + metaData.writeToBuffer(0,allData); + allData.put(dataEncoding.getArray(),dataEncoding.getArrayOffset(),dataEncoding.getLength()); + return allData; + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageMetaDataType_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageMetaDataType_1_0.java new file mode 100644 index 0000000000..44b1de74e1 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageMetaDataType_1_0.java @@ -0,0 +1,67 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.nio.ByteBuffer; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.store.StoredMessage; + +public class MessageMetaDataType_1_0 implements MessageMetaDataType<MessageMetaData_1_0> +{ + + public static final int TYPE = 2; + + @Override + public int ordinal() + { + return TYPE; + } + + @Override + public MessageMetaData_1_0 createMetaData(ByteBuffer buf) + { + return MessageMetaData_1_0.FACTORY.createMetaData(buf); + } + + @Override + public ServerMessage<MessageMetaData_1_0> createMessage(StoredMessage<MessageMetaData_1_0> msg) + { + return new Message_1_0(msg); + } + + public int hashCode() + { + return ordinal(); + } + + public boolean equals(Object o) + { + return o != null && o.getClass() == getClass(); + } + + @Override + public String getType() + { + return AmqpProtocolVersion.v1_0_0.toString(); + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageMetaData_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageMetaData_1_0.java new file mode 100755 index 0000000000..8d48d70d9a --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/MessageMetaData_1_0.java @@ -0,0 +1,569 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.protocol.v1_0; + +import java.nio.ByteBuffer; +import java.util.*; +import org.apache.qpid.amqp_1_0.codec.ValueHandler; +import org.apache.qpid.amqp_1_0.messaging.SectionDecoder; +import org.apache.qpid.amqp_1_0.messaging.SectionEncoder; +import org.apache.qpid.amqp_1_0.type.AmqpErrorException; +import org.apache.qpid.amqp_1_0.type.Section; +import org.apache.qpid.amqp_1_0.type.Symbol; +import org.apache.qpid.amqp_1_0.type.codec.AMQPDescribedTypeRegistry; +import org.apache.qpid.amqp_1_0.type.messaging.AmqpSequence; +import org.apache.qpid.amqp_1_0.type.messaging.AmqpValue; +import org.apache.qpid.amqp_1_0.type.messaging.ApplicationProperties; +import org.apache.qpid.amqp_1_0.type.messaging.Data; +import org.apache.qpid.amqp_1_0.type.messaging.DeliveryAnnotations; +import org.apache.qpid.amqp_1_0.type.messaging.Footer; +import org.apache.qpid.amqp_1_0.type.messaging.Header; +import org.apache.qpid.amqp_1_0.type.messaging.MessageAnnotations; +import org.apache.qpid.amqp_1_0.type.messaging.Properties; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.store.StorableMessageMetaData; + +public class MessageMetaData_1_0 implements StorableMessageMetaData +{ + // TODO move to somewhere more useful + public static final Symbol JMS_TYPE = Symbol.valueOf("x-opt-jms-type"); + public static final MessageMetaDataType.Factory<MessageMetaData_1_0> FACTORY = new MetaDataFactory(); + private static final MessageMetaDataType_1_0 TYPE = new MessageMetaDataType_1_0(); + + + private Header _header; + private Properties _properties; + private Map _deliveryAnnotations; + private Map _messageAnnotations; + private Map _appProperties; + private Map _footer; + + private List<ByteBuffer> _encodedSections = new ArrayList<ByteBuffer>(3); + + private volatile ByteBuffer _encoded; + private MessageHeader_1_0 _messageHeader; + + + public MessageMetaData_1_0(List<Section> sections, SectionEncoder encoder) + { + this(sections, encodeSections(sections, encoder)); + } + + private static ArrayList<ByteBuffer> encodeSections(final List<Section> sections, final SectionEncoder encoder) + { + ArrayList<ByteBuffer> encodedSections = new ArrayList<ByteBuffer>(sections.size()); + for(Section section : sections) + { + encoder.encodeObject(section); + encodedSections.add(encoder.getEncoding().asByteBuffer()); + encoder.reset(); + } + return encodedSections; + } + + public MessageMetaData_1_0(ByteBuffer[] fragments, SectionDecoder decoder) + { + this(fragments, decoder, new ArrayList<ByteBuffer>(3)); + } + + public MessageMetaData_1_0(ByteBuffer[] fragments, SectionDecoder decoder, List<ByteBuffer> immuatableSections) + { + this(constructSections(fragments, decoder,immuatableSections), immuatableSections); + } + + private MessageMetaData_1_0(List<Section> sections, List<ByteBuffer> encodedSections) + { + _encodedSections = encodedSections; + + Iterator<Section> sectIter = sections.iterator(); + + Section section = sectIter.hasNext() ? sectIter.next() : null; + if(section instanceof Header) + { + _header = (Header) section; + section = sectIter.hasNext() ? sectIter.next() : null; + } + + if(section instanceof DeliveryAnnotations) + { + _deliveryAnnotations = ((DeliveryAnnotations) section).getValue(); + section = sectIter.hasNext() ? sectIter.next() : null; + } + + if(section instanceof MessageAnnotations) + { + _messageAnnotations = ((MessageAnnotations) section).getValue(); + section = sectIter.hasNext() ? sectIter.next() : null; + } + + if(section instanceof Properties) + { + _properties = (Properties) section; + section = sectIter.hasNext() ? sectIter.next() : null; + } + + if(section instanceof ApplicationProperties) + { + _appProperties = ((ApplicationProperties) section).getValue(); + section = sectIter.hasNext() ? sectIter.next() : null; + } + + if(section instanceof Footer) + { + _footer = ((Footer) section).getValue(); + section = sectIter.hasNext() ? sectIter.next() : null; + } + + _messageHeader = new MessageHeader_1_0(); + + } + + private static List<Section> constructSections(final ByteBuffer[] fragments, final SectionDecoder decoder, List<ByteBuffer> encodedSections) + { + List<Section> sections = new ArrayList<Section>(3); + + ByteBuffer src; + if(fragments.length == 1) + { + src = fragments[0].duplicate(); + } + else + { + int size = 0; + for(ByteBuffer buf : fragments) + { + size += buf.remaining(); + } + src = ByteBuffer.allocate(size); + for(ByteBuffer buf : fragments) + { + src.put(buf.duplicate()); + } + src.flip(); + + } + + try + { + int startBarePos = -1; + int lastPos = src.position(); + Section s = decoder.readSection(src); + + + + if(s instanceof Header) + { + sections.add(s); + lastPos = src.position(); + s = src.hasRemaining() ? decoder.readSection(src) : null; + } + + if(s instanceof DeliveryAnnotations) + { + sections.add(s); + lastPos = src.position(); + s = src.hasRemaining() ? decoder.readSection(src) : null; + } + + if(s instanceof MessageAnnotations) + { + sections.add(s); + lastPos = src.position(); + s = src.hasRemaining() ? decoder.readSection(src) : null; + } + + if(s instanceof Properties) + { + sections.add(s); + if(startBarePos == -1) + { + startBarePos = lastPos; + } + s = src.hasRemaining() ? decoder.readSection(src) : null; + } + + if(s instanceof ApplicationProperties) + { + sections.add(s); + if(startBarePos == -1) + { + startBarePos = lastPos; + } + s = src.hasRemaining() ? decoder.readSection(src) : null; + } + + if(s instanceof AmqpValue) + { + if(startBarePos == -1) + { + startBarePos = lastPos; + } + s = src.hasRemaining() ? decoder.readSection(src) : null; + } + else if(s instanceof Data) + { + if(startBarePos == -1) + { + startBarePos = lastPos; + } + do + { + s = src.hasRemaining() ? decoder.readSection(src) : null; + } while(s instanceof Data); + } + else if(s instanceof AmqpSequence) + { + if(startBarePos == -1) + { + startBarePos = lastPos; + } + do + { + s = src.hasRemaining() ? decoder.readSection(src) : null; + } + while(s instanceof AmqpSequence); + } + + if(s instanceof Footer) + { + sections.add(s); + } + + + int pos = 0; + for(ByteBuffer buf : fragments) + { +/* + if(pos < startBarePos) + { + if(pos + buf.remaining() > startBarePos) + { + ByteBuffer dup = buf.duplicate(); + dup.position(dup.position()+startBarePos-pos); + dup.slice(); + encodedSections.add(dup); + } + } + else +*/ + { + encodedSections.add(buf.duplicate()); + } + pos += buf.remaining(); + } + + return sections; + } + catch (AmqpErrorException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + throw new IllegalArgumentException(e); + } + } + + + public MessageMetaDataType getType() + { + return TYPE; + } + + + public int getStorableSize() + { + int size = 0; + + for(ByteBuffer bin : _encodedSections) + { + size += bin.limit(); + } + + return size; + } + + private ByteBuffer encodeAsBuffer() + { + ByteBuffer buf = ByteBuffer.allocate(getStorableSize()); + + for(ByteBuffer bin : _encodedSections) + { + buf.put(bin.duplicate()); + } + + return buf; + } + + public int writeToBuffer(int offsetInMetaData, ByteBuffer dest) + { + ByteBuffer buf = _encoded; + + if(buf == null) + { + buf = encodeAsBuffer(); + _encoded = buf; + } + + buf = buf.duplicate(); + + buf.position(offsetInMetaData); + + if(dest.remaining() < buf.limit()) + { + buf.limit(dest.remaining()); + } + dest.put(buf); + return buf.limit(); + } + + public int getContentSize() + { + ByteBuffer buf = _encoded; + + if(buf == null) + { + buf = encodeAsBuffer(); + _encoded = buf; + } + return buf.remaining(); + } + + public boolean isPersistent() + { + return _header != null && Boolean.TRUE.equals(_header.getDurable()); + } + + public MessageHeader_1_0 getMessageHeader() + { + return _messageHeader; + } + + + + + private static class MetaDataFactory implements MessageMetaDataType.Factory<MessageMetaData_1_0> + { + private final AMQPDescribedTypeRegistry _typeRegistry = AMQPDescribedTypeRegistry.newInstance(); + + private MetaDataFactory() + { + _typeRegistry.registerTransportLayer(); + _typeRegistry.registerMessagingLayer(); + _typeRegistry.registerTransactionLayer(); + _typeRegistry.registerSecurityLayer(); + } + + public MessageMetaData_1_0 createMetaData(ByteBuffer buf) + { + ValueHandler valueHandler = new ValueHandler(_typeRegistry); + + ArrayList<Section> sections = new ArrayList<Section>(3); + ArrayList<ByteBuffer> encodedSections = new ArrayList<ByteBuffer>(3); + + while(buf.hasRemaining()) + { + try + { + ByteBuffer encodedBuf = buf.duplicate(); + Object parse = valueHandler.parse(buf); + sections.add((Section) parse); + encodedBuf.limit(buf.position()); + encodedSections.add(encodedBuf); + + } + catch (AmqpErrorException e) + { + //TODO + throw new RuntimeException(e); + } + + } + + return new MessageMetaData_1_0(sections,encodedSections); + + } + } + + public class MessageHeader_1_0 implements AMQMessageHeader + { + + public String getCorrelationId() + { + if(_properties == null || _properties.getCorrelationId() == null) + { + return null; + } + else + { + return _properties.getMessageId().toString(); + } + } + + public long getExpiration() + { + return 0; //TODO + } + + public String getMessageId() + { + if(_properties == null || _properties.getCorrelationId() == null) + { + return null; + } + else + { + return _properties.getCorrelationId().toString(); + } + } + + public String getMimeType() + { + + if(_properties == null || _properties.getContentType() == null) + { + return null; + } + else + { + return _properties.getContentType().toString(); + } + } + + public String getEncoding() + { + return null; //TODO + } + + public byte getPriority() + { + if(_header == null || _header.getPriority() == null) + { + return 4; //javax.jms.Message.DEFAULT_PRIORITY; + } + else + { + return _header.getPriority().byteValue(); + } + } + + public long getTimestamp() + { + if(_properties == null || _properties.getCreationTime() == null) + { + return 0L; + } + else + { + return _properties.getCreationTime().getTime(); + } + + } + + public String getType() + { + + if(_messageAnnotations == null || _messageAnnotations.get(JMS_TYPE) == null) + { + return null; + } + else + { + return _messageAnnotations.get(JMS_TYPE).toString(); + } + } + + public String getReplyTo() + { + if(_properties == null || _properties.getReplyTo() == null) + { + return null; + } + else + { + return _properties.getReplyTo().toString(); + } + } + + public String getReplyToExchange() + { + return null; //TODO + } + + public String getReplyToRoutingKey() + { + return null; //TODO + } + + public String getAppId() + { + //TODO + return null; + } + + public String getUserId() + { + // TODO + return null; + } + + public Object getHeader(final String name) + { + return _appProperties == null ? null : _appProperties.get(name); + } + + public boolean containsHeaders(final Set<String> names) + { + if(_appProperties == null) + { + return false; + } + + for(String key : names) + { + if(!_appProperties.containsKey(key)) + { + return false; + } + } + return true; + } + + @Override + public Collection<String> getHeaderNames() + { + if(_appProperties == null) + { + return Collections.emptySet(); + } + return Collections.unmodifiableCollection(_appProperties.keySet()); + } + + public boolean containsHeader(final String name) + { + return _appProperties != null && _appProperties.containsKey(name); + } + + public String getSubject() + { + return _properties == null ? null : _properties.getSubject(); + } + } + +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Message_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Message_1_0.java new file mode 100644 index 0000000000..68e9a88b0b --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Message_1_0.java @@ -0,0 +1,257 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + + +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.store.StoredMessage; + +public class Message_1_0 implements ServerMessage<MessageMetaData_1_0>, InboundMessage +{ + + + private static final AtomicIntegerFieldUpdater<Message_1_0> _refCountUpdater = + AtomicIntegerFieldUpdater.newUpdater(Message_1_0.class, "_referenceCount"); + + private volatile int _referenceCount = 0; + + private final StoredMessage<MessageMetaData_1_0> _storedMessage; + private List<ByteBuffer> _fragments; + private WeakReference<Session_1_0> _session; + private long _arrivalTime; + + + public Message_1_0(final StoredMessage<MessageMetaData_1_0> storedMessage) + { + _storedMessage = storedMessage; + _session = null; + _fragments = restoreFragments(storedMessage); + } + + private static List<ByteBuffer> restoreFragments(StoredMessage<MessageMetaData_1_0> storedMessage) + { + ArrayList<ByteBuffer> fragments = new ArrayList<ByteBuffer>(); + final int FRAGMENT_SIZE = 2048; + int offset = 0; + ByteBuffer b; + do + { + + b = storedMessage.getContent(offset,FRAGMENT_SIZE); + if(b.hasRemaining()) + { + fragments.add(b); + offset+= b.remaining(); + } + } + while(b.hasRemaining()); + return fragments; + } + + public Message_1_0(final StoredMessage<MessageMetaData_1_0> storedMessage, + final List<ByteBuffer> fragments, + final Session_1_0 session) + { + _storedMessage = storedMessage; + _fragments = fragments; + _session = new WeakReference<Session_1_0>(session); + _arrivalTime = System.currentTimeMillis(); + } + + public String getRoutingKey() + { + Object routingKey = getMessageHeader().getHeader("routing-key"); + if(routingKey != null) + { + return routingKey.toString(); + } + else + { + return getMessageHeader().getSubject(); + } + } + + private MessageMetaData_1_0 getMessageMetaData() + { + return _storedMessage.getMetaData(); + } + + public MessageMetaData_1_0.MessageHeader_1_0 getMessageHeader() + { + return getMessageMetaData().getMessageHeader(); + } + + public StoredMessage getStoredMessage() + { + return _storedMessage; + } + + public boolean isPersistent() + { + return getMessageMetaData().isPersistent(); + } + + public boolean isRedelivered() + { + // TODO + return false; + } + + public long getSize() + { + long size = 0l; + if(_fragments != null) + { + for(ByteBuffer buf : _fragments) + { + size += buf.remaining(); + } + } + + return size; + } + + public boolean isImmediate() + { + return false; + } + + public long getExpiration() + { + return getMessageHeader().getExpiration(); + } + + public MessageReference<Message_1_0> newReference() + { + return new Reference(this); + } + + public long getMessageNumber() + { + return _storedMessage.getMessageNumber(); + } + + public long getArrivalTime() + { + return _arrivalTime; + } + + public int getContent(final ByteBuffer buf, final int offset) + { + return _storedMessage.getContent(offset, buf); + } + + public ByteBuffer getContent(int offset, int size) + { + ByteBuffer buf = ByteBuffer.allocate(size); + buf.limit(getContent(buf, offset)); + + return buf; + } + + public List<ByteBuffer> getFragments() + { + return _fragments; + } + + public Session_1_0 getSession() + { + return _session == null ? null : _session.get(); + } + + + public boolean incrementReference() + { + if(_refCountUpdater.incrementAndGet(this) <= 0) + { + _refCountUpdater.decrementAndGet(this); + return false; + } + else + { + return true; + } + } + + /** + * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the + * message store. + */ + + public void decrementReference() + { + int count = _refCountUpdater.decrementAndGet(this); + + // 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 (count == 0) + { + // set the reference count way below 0 so that we can detect that the message has been deleted + // this is to guard against the message being spontaneously recreated (from the mgmt console) + // by copying from other queues at the same time as it is being removed. + _refCountUpdater.set(this,Integer.MIN_VALUE/2); + + // must check if the handle is null since there may be cases where we decide to throw away a message + // and the handle has not yet been constructed + if (_storedMessage != null) + { + _storedMessage.remove(); + } + } + else + { + if (count < 0) + { + throw new RuntimeException("Reference count for message id " + getMessageNumber() + + " has gone below 0."); + } + } + } + + public static class Reference extends MessageReference<Message_1_0> + { + public Reference(Message_1_0 message) + { + super(message); + } + + protected void onReference(Message_1_0 message) + { + message.incrementReference(); + } + + protected void onRelease(Message_1_0 message) + { + message.decrementReference(); + } + + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngineCreator_1_0_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngineCreator_1_0_0.java new file mode 100644 index 0000000000..c06af603de --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngineCreator_1_0_0.java @@ -0,0 +1,80 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.plugin.ProtocolEngineCreator; +import org.apache.qpid.transport.network.NetworkConnection; + +public class ProtocolEngineCreator_1_0_0 implements ProtocolEngineCreator +{ + private static final byte[] AMQP_1_0_0_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 0, + (byte) 1, + (byte) 0, + (byte) 0 + }; + + public ProtocolEngineCreator_1_0_0() + { + } + + public AmqpProtocolVersion getVersion() + { + return AmqpProtocolVersion.v1_0_0; + } + + + public byte[] getHeaderIdentifier() + { + return AMQP_1_0_0_HEADER; + } + + public ServerProtocolEngine newProtocolEngine(Broker broker, + NetworkConnection network, + Port port, + Transport transport, + long id) + { + return new ProtocolEngine_1_0_0(network, broker, id, port, transport); + } + + private static ProtocolEngineCreator INSTANCE = new ProtocolEngineCreator_1_0_0(); + + public static ProtocolEngineCreator getInstance() + { + return INSTANCE; + } + + @Override + public String getType() + { + return getVersion().toString() + "_NO_SASL"; + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngineCreator_1_0_0_SASL.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngineCreator_1_0_0_SASL.java new file mode 100644 index 0000000000..d3936782da --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngineCreator_1_0_0_SASL.java @@ -0,0 +1,80 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.plugin.ProtocolEngineCreator; +import org.apache.qpid.transport.network.NetworkConnection; + +public class ProtocolEngineCreator_1_0_0_SASL implements ProtocolEngineCreator +{ + private static final byte[] AMQP_SASL_1_0_0_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 3, + (byte) 1, + (byte) 0, + (byte) 0 + }; + + public ProtocolEngineCreator_1_0_0_SASL() + { + } + + public AmqpProtocolVersion getVersion() + { + return AmqpProtocolVersion.v1_0_0; + } + + + public byte[] getHeaderIdentifier() + { + return AMQP_SASL_1_0_0_HEADER; + } + + public ServerProtocolEngine newProtocolEngine(Broker broker, + NetworkConnection network, + Port port, + Transport transport, + long id) + { + return new ProtocolEngine_1_0_0_SASL(network, broker, id, port, transport); + } + + private static ProtocolEngineCreator INSTANCE = new ProtocolEngineCreator_1_0_0_SASL(); + + public static ProtocolEngineCreator getInstance() + { + return INSTANCE; + } + + @Override + public String getType() + { + return getVersion().toString(); + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngine_1_0_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngine_1_0_0.java new file mode 100755 index 0000000000..1bddda2f38 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngine_1_0_0.java @@ -0,0 +1,427 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.security.Principal; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import org.apache.qpid.amqp_1_0.codec.FrameWriter; +import org.apache.qpid.amqp_1_0.framing.AMQFrame; +import org.apache.qpid.amqp_1_0.framing.FrameHandler; +import org.apache.qpid.amqp_1_0.framing.OversizeFrameException; +import org.apache.qpid.amqp_1_0.transport.SaslServerProvider; +import org.apache.qpid.amqp_1_0.transport.ConnectionEndpoint; +import org.apache.qpid.amqp_1_0.transport.Container; +import org.apache.qpid.amqp_1_0.transport.FrameOutputHandler; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.FrameBody; +import org.apache.qpid.amqp_1_0.type.Symbol; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.common.ServerPropertyNames; +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.v1_0.Connection_1_0; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.network.NetworkConnection; + +public class ProtocolEngine_1_0_0 implements ServerProtocolEngine, FrameOutputHandler +{ + static final AtomicLong _connectionIdSource = new AtomicLong(0L); + private final Port _port; + private final Transport _transport; + + //private NetworkConnection _networkDriver; + private long _readBytes; + private long _writtenBytes; + private long _lastReadTime; + private long _lastWriteTime; + private final Broker _broker; + private long _createTime = System.currentTimeMillis(); + private ConnectionEndpoint _conn; + private final long _connectionId; + + private static final ByteBuffer HEADER = + ByteBuffer.wrap(new byte[] + { + (byte)'A', + (byte)'M', + (byte)'Q', + (byte)'P', + (byte) 0, + (byte) 1, + (byte) 0, + (byte) 0 + }); + + private FrameWriter _frameWriter; + private FrameHandler _frameHandler; + private Object _sendLock = new Object(); + private byte _major; + private byte _minor; + private byte _revision; + private NetworkConnection _network; + private Sender<ByteBuffer> _sender; + + + static enum State { + A, + M, + Q, + P, + PROTOCOL, + MAJOR, + MINOR, + REVISION, + FRAME + } + + private State _state = State.A; + + + + public ProtocolEngine_1_0_0(final NetworkConnection networkDriver, + final Broker broker, + long id, + Port port, + Transport transport) + { + _broker = broker; + _port = port; + _transport = transport; + _connectionId = id; + if(networkDriver != null) + { + setNetworkConnection(networkDriver, networkDriver.getSender()); + } + } + + + public SocketAddress getRemoteAddress() + { + return _network.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _network.getLocalAddress(); + } + + public long getReadBytes() + { + return _readBytes; + } + + public long getWrittenBytes() + { + return _writtenBytes; + } + + public void writerIdle() + { + //Todo + } + + public void readerIdle() + { + //Todo + } + + public void setNetworkConnection(final NetworkConnection network, final Sender<ByteBuffer> sender) + { + _network = network; + _sender = sender; + + Container container = new Container(_broker.getId().toString()); + + VirtualHost virtualHost = _broker.getVirtualHostRegistry().getVirtualHost((String)_broker.getAttribute(Broker.DEFAULT_VIRTUAL_HOST)); + + _conn = new ConnectionEndpoint(container, asSaslServerProvider(_broker.getSubjectCreator( + getLocalAddress()))); + + Map<Symbol,Object> serverProperties = new LinkedHashMap<Symbol, Object>(); + serverProperties.put(Symbol.valueOf(ServerPropertyNames.PRODUCT), QpidProperties.getProductName()); + serverProperties.put(Symbol.valueOf(ServerPropertyNames.VERSION), QpidProperties.getReleaseVersion()); + serverProperties.put(Symbol.valueOf(ServerPropertyNames.QPID_BUILD), QpidProperties.getBuildVersion()); + serverProperties.put(Symbol.valueOf(ServerPropertyNames.QPID_INSTANCE_NAME), _broker.getName()); + + _conn.setProperties(serverProperties); + + _conn.setRemoteAddress(_network.getRemoteAddress()); + _conn.setConnectionEventListener(new Connection_1_0(virtualHost, _conn, _connectionId, _port, _transport)); + _conn.setFrameOutputHandler(this); + + _frameWriter = new FrameWriter(_conn.getDescribedTypeRegistry()); + _frameHandler = new FrameHandler(_conn); + + _sender.send(HEADER.duplicate()); + _sender.flush(); + } + + private SaslServerProvider asSaslServerProvider(final SubjectCreator subjectCreator) + { + return new SaslServerProvider() + { + @Override + public SaslServer getSaslServer(String mechanism, String fqdn) throws SaslException + { + return subjectCreator.createSaslServer(mechanism, fqdn, null); + } + + @Override + public Principal getAuthenticatedPrincipal(SaslServer server) + { + return new UsernamePrincipal(server.getAuthorizationID()); + } + }; + } + + public String getAddress() + { + return getRemoteAddress().toString(); + } + + public boolean isDurable() + { + return false; + } + + public synchronized void received(ByteBuffer msg) + { + _lastReadTime = System.currentTimeMillis(); + if(RAW_LOGGER.isLoggable(Level.FINE)) + { + ByteBuffer dup = msg.duplicate(); + byte[] data = new byte[dup.remaining()]; + dup.get(data); + Binary bin = new Binary(data); + RAW_LOGGER.fine("RECV[" + getRemoteAddress() + "] : " + bin.toString()); + } + _readBytes += msg.remaining(); + switch(_state) + { + case A: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + break; + } + case M: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + _state = State.M; + break; + } + + case Q: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + _state = State.Q; + break; + } + case P: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + _state = State.P; + break; + } + case PROTOCOL: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + _state = State.PROTOCOL; + break; + } + case MAJOR: + if(msg.hasRemaining()) + { + _major = msg.get(); + } + else + { + _state = State.MAJOR; + break; + } + case MINOR: + if(msg.hasRemaining()) + { + _minor = msg.get(); + } + else + { + _state = State.MINOR; + break; + } + case REVISION: + if(msg.hasRemaining()) + { + _revision = msg.get(); + + _state = State.FRAME; + } + else + { + _state = State.REVISION; + break; + } + case FRAME: + if(msg.hasRemaining()) + { + _frameHandler.parse(msg); + } + } + + } + + public void exception(Throwable t) + { + t.printStackTrace(); + } + + public void closed() + { + _conn.inputClosed(); + if(_conn != null && _conn.getConnectionEventListener() != null) + { + ((Connection_1_0)_conn.getConnectionEventListener()).closed(); + } + + } + + public long getCreateTime() + { + return _createTime; + } + + + public boolean canSend() + { + return true; + } + + public void send(final AMQFrame amqFrame) + { + send(amqFrame, null); + } + + private final Logger FRAME_LOGGER = Logger.getLogger("FRM"); + private final Logger RAW_LOGGER = Logger.getLogger("RAW"); + + + public void send(final AMQFrame amqFrame, ByteBuffer buf) + { + synchronized(_sendLock) + { + + _lastWriteTime = System.currentTimeMillis(); + if(FRAME_LOGGER.isLoggable(Level.FINE)) + { + FRAME_LOGGER.fine("SEND[" + getRemoteAddress() + "|" + amqFrame.getChannel() + "] : " + amqFrame.getFrameBody()); + } + + + _frameWriter.setValue(amqFrame); + + + + ByteBuffer dup = ByteBuffer.allocate(_conn.getMaxFrameSize()); + + int size = _frameWriter.writeToBuffer(dup); + if(size > _conn.getMaxFrameSize()) + { + throw new OversizeFrameException(amqFrame,size); + } + + dup.flip(); + _writtenBytes += dup.limit(); + + if(RAW_LOGGER.isLoggable(Level.FINE)) + { + ByteBuffer dup2 = dup.duplicate(); + byte[] data = new byte[dup2.remaining()]; + dup2.get(data); + Binary bin = new Binary(data); + RAW_LOGGER.fine("SEND[" + getRemoteAddress() + "] : " + bin.toString()); + } + + + _sender.send(dup); + _sender.flush(); + + } + } + + public void send(short channel, FrameBody body) + { + AMQFrame frame = AMQFrame.createAMQFrame(channel, body); + send(frame); + + } + + public void close() + { + //TODO + } + + public long getConnectionId() + { + return _connectionId; + } + + public long getLastReadTime() + { + return _lastReadTime; + } + + public long getLastWriteTime() + { + return _lastWriteTime; + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngine_1_0_0_SASL.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngine_1_0_0_SASL.java new file mode 100644 index 0000000000..d614f44981 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ProtocolEngine_1_0_0_SASL.java @@ -0,0 +1,462 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.io.PrintWriter; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.security.Principal; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import org.apache.qpid.amqp_1_0.codec.FrameWriter; +import org.apache.qpid.amqp_1_0.codec.ProtocolHandler; +import org.apache.qpid.amqp_1_0.framing.AMQFrame; +import org.apache.qpid.amqp_1_0.framing.OversizeFrameException; +import org.apache.qpid.amqp_1_0.framing.SASLFrameHandler; +import org.apache.qpid.amqp_1_0.transport.SaslServerProvider; +import org.apache.qpid.amqp_1_0.transport.ConnectionEndpoint; +import org.apache.qpid.amqp_1_0.transport.Container; +import org.apache.qpid.amqp_1_0.transport.FrameOutputHandler; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.FrameBody; +import org.apache.qpid.amqp_1_0.type.Symbol; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.common.ServerPropertyNames; +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.v1_0.Connection_1_0; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.network.NetworkConnection; + +public class ProtocolEngine_1_0_0_SASL implements ServerProtocolEngine, FrameOutputHandler +{ + private final Port _port; + private final Transport _transport; + private long _readBytes; + private long _writtenBytes; + + private long _lastReadTime; + private long _lastWriteTime; + private final Broker _broker; + private long _createTime = System.currentTimeMillis(); + private ConnectionEndpoint _conn; + private long _connectionId; + + private static final ByteBuffer HEADER = + ByteBuffer.wrap(new byte[] + { + (byte)'A', + (byte)'M', + (byte)'Q', + (byte)'P', + (byte) 3, + (byte) 1, + (byte) 0, + (byte) 0 + }); + + private static final ByteBuffer PROTOCOL_HEADER = + ByteBuffer.wrap(new byte[] + { + (byte)'A', + (byte)'M', + (byte)'Q', + (byte)'P', + (byte) 0, + (byte) 1, + (byte) 0, + (byte) 0 + }); + + + private FrameWriter _frameWriter; + private ProtocolHandler _frameHandler; + private ByteBuffer _buf = ByteBuffer.allocate(1024 * 1024); + private Object _sendLock = new Object(); + private byte _major; + private byte _minor; + private byte _revision; + private PrintWriter _out; + private NetworkConnection _network; + private Sender<ByteBuffer> _sender; + + + static enum State { + A, + M, + Q, + P, + PROTOCOL, + MAJOR, + MINOR, + REVISION, + FRAME + } + + private State _state = State.A; + + + public ProtocolEngine_1_0_0_SASL(final NetworkConnection networkDriver, final Broker broker, + long id, Port port, Transport transport) + { + _connectionId = id; + _broker = broker; + _port = port; + _transport = transport; + if(networkDriver != null) + { + setNetworkConnection(networkDriver, networkDriver.getSender()); + } + } + + + public SocketAddress getRemoteAddress() + { + return _network.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _network.getLocalAddress(); + } + + public long getReadBytes() + { + return _readBytes; + } + + public long getWrittenBytes() + { + return _writtenBytes; + } + + public void writerIdle() + { + //Todo + } + + public void readerIdle() + { + //Todo + } + + public void setNetworkConnection(final NetworkConnection network, final Sender<ByteBuffer> sender) + { + _network = network; + _sender = sender; + + Container container = new Container(_broker.getId().toString()); + + VirtualHost virtualHost = _broker.getVirtualHostRegistry().getVirtualHost((String)_broker.getAttribute(Broker.DEFAULT_VIRTUAL_HOST)); + SubjectCreator subjectCreator = _broker.getSubjectCreator(getLocalAddress()); + _conn = new ConnectionEndpoint(container, asSaslServerProvider(subjectCreator)); + + Map<Symbol,Object> serverProperties = new LinkedHashMap<Symbol, Object>(); + serverProperties.put(Symbol.valueOf(ServerPropertyNames.PRODUCT), QpidProperties.getProductName()); + serverProperties.put(Symbol.valueOf(ServerPropertyNames.VERSION), QpidProperties.getReleaseVersion()); + serverProperties.put(Symbol.valueOf(ServerPropertyNames.QPID_BUILD), QpidProperties.getBuildVersion()); + serverProperties.put(Symbol.valueOf(ServerPropertyNames.QPID_INSTANCE_NAME), _broker.getName()); + + _conn.setProperties(serverProperties); + + _conn.setRemoteAddress(getRemoteAddress()); + _conn.setConnectionEventListener(new Connection_1_0(virtualHost, _conn, _connectionId, _port, _transport)); + _conn.setFrameOutputHandler(this); + _conn.setSaslFrameOutput(this); + + _conn.setOnSaslComplete(new Runnable() + { + public void run() + { + if(_conn.isAuthenticated()) + { + _sender.send(PROTOCOL_HEADER.duplicate()); + _sender.flush(); + } + else + { + _network.close(); + } + } + }); + _frameWriter = new FrameWriter(_conn.getDescribedTypeRegistry()); + _frameHandler = new SASLFrameHandler(_conn); + + _sender.send(HEADER.duplicate()); + _sender.flush(); + + _conn.initiateSASL(subjectCreator.getMechanisms().split(" ")); + + + } + + private SaslServerProvider asSaslServerProvider(final SubjectCreator subjectCreator) + { + return new SaslServerProvider() + { + @Override + public SaslServer getSaslServer(String mechanism, String fqdn) throws SaslException + { + return subjectCreator.createSaslServer(mechanism, fqdn, _network.getPeerPrincipal()); + } + + @Override + public Principal getAuthenticatedPrincipal(SaslServer server) + { + return new UsernamePrincipal(server.getAuthorizationID()); + } + }; + } + + public String getAddress() + { + return getRemoteAddress().toString(); + } + + public boolean isDurable() + { + return false; + } + + private final Logger RAW_LOGGER = Logger.getLogger("RAW"); + + + public synchronized void received(ByteBuffer msg) + { + _lastReadTime = System.currentTimeMillis(); + if(RAW_LOGGER.isLoggable(Level.FINE)) + { + ByteBuffer dup = msg.duplicate(); + byte[] data = new byte[dup.remaining()]; + dup.get(data); + Binary bin = new Binary(data); + RAW_LOGGER.fine("RECV[" + getRemoteAddress() + "] : " + bin.toString()); + } + _readBytes += msg.remaining(); + switch(_state) + { + case A: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + break; + } + case M: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + _state = State.M; + break; + } + + case Q: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + _state = State.Q; + break; + } + case P: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + _state = State.P; + break; + } + case PROTOCOL: + if(msg.hasRemaining()) + { + msg.get(); + } + else + { + _state = State.PROTOCOL; + break; + } + case MAJOR: + if(msg.hasRemaining()) + { + _major = msg.get(); + } + else + { + _state = State.MAJOR; + break; + } + case MINOR: + if(msg.hasRemaining()) + { + _minor = msg.get(); + } + else + { + _state = State.MINOR; + break; + } + case REVISION: + if(msg.hasRemaining()) + { + _revision = msg.get(); + + _state = State.FRAME; + } + else + { + _state = State.REVISION; + break; + } + case FRAME: + if(msg.hasRemaining()) + { + _frameHandler = _frameHandler.parse(msg); + } + } + + } + + public void exception(Throwable t) + { + t.printStackTrace(); + } + + public void closed() + { + // todo + _conn.inputClosed(); + if (_conn != null && _conn.getConnectionEventListener() != null) + { + ((Connection_1_0) _conn.getConnectionEventListener()).closed(); + } + + } + + public long getCreateTime() + { + return _createTime; + } + + + public boolean canSend() + { + return true; + } + + public void send(final AMQFrame amqFrame) + { + send(amqFrame, null); + } + + private static final Logger FRAME_LOGGER = Logger.getLogger("FRM"); + + + public void send(final AMQFrame amqFrame, ByteBuffer buf) + { + + synchronized (_sendLock) + { + _lastWriteTime = System.currentTimeMillis(); + if (FRAME_LOGGER.isLoggable(Level.FINE)) + { + FRAME_LOGGER.fine("SEND[" + getRemoteAddress() + "|" + amqFrame.getChannel() + "] : " + amqFrame.getFrameBody()); + } + + _frameWriter.setValue(amqFrame); + + ByteBuffer dup = ByteBuffer.allocate(_conn.getMaxFrameSize()); + + int size = _frameWriter.writeToBuffer(dup); + if (size > _conn.getMaxFrameSize()) + { + throw new OversizeFrameException(amqFrame, size); + } + + dup.flip(); + _writtenBytes += dup.limit(); + + if (RAW_LOGGER.isLoggable(Level.FINE)) + { + ByteBuffer dup2 = dup.duplicate(); + byte[] data = new byte[dup2.remaining()]; + dup2.get(data); + Binary bin = new Binary(data); + RAW_LOGGER.fine("SEND[" + getRemoteAddress() + "] : " + bin.toString()); + } + + _sender.send(dup); + _sender.flush(); + + + } + } + + public void send(short channel, FrameBody body) + { + AMQFrame frame = AMQFrame.createAMQFrame(channel, body); + send(frame); + + } + + public void close() + { + _sender.close(); + } + + public void setLogOutput(final PrintWriter out) + { + _out = out; + } + + public long getConnectionId() + { + return _connectionId; + } + + public long getLastReadTime() + { + return _lastReadTime; + } + + public long getLastWriteTime() + { + return _lastWriteTime; + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/QueueDestination.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/QueueDestination.java new file mode 100644 index 0000000000..af3f0b7872 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/QueueDestination.java @@ -0,0 +1,100 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.amqp_1_0.type.Outcome; +import org.apache.qpid.amqp_1_0.type.messaging.Accepted; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQQueue; + +import org.apache.qpid.server.txn.ServerTransaction; + +import java.util.Arrays; + +public class QueueDestination implements SendingDestination, ReceivingDestination +{ + private static final Accepted ACCEPTED = new Accepted(); + private static final Outcome[] OUTCOMES = new Outcome[] { ACCEPTED }; + + + private AMQQueue _queue; + + public QueueDestination(AMQQueue queue) + { + _queue = queue; + } + + public Outcome[] getOutcomes() + { + return OUTCOMES; + } + + public Outcome send(final Message_1_0 message, ServerTransaction txn) + { + + try + { + txn.enqueue(_queue,message, new ServerTransaction.Action() + { + + + public void postCommit() + { + try + { + + _queue.enqueue(message); + } + catch (Exception e) + { + // TODO + throw new RuntimeException(e); + } + + } + + public void onRollback() + { + // NO-OP + } + }); + } + catch(Exception e) + { + e.printStackTrace(); + throw new RuntimeException(e); + } + return ACCEPTED; + } + + public int getCredit() + { + // TODO - fix + return 100; + } + + public AMQQueue getQueue() + { + return _queue; + } + +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingDestination.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingDestination.java new file mode 100644 index 0000000000..4ae0596e25 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingDestination.java @@ -0,0 +1,35 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.amqp_1_0.type.Outcome; + +import org.apache.qpid.server.txn.ServerTransaction; + +public interface ReceivingDestination extends Destination +{ + + Outcome[] getOutcomes(); + + Outcome send(Message_1_0 message, ServerTransaction txn); + + int getCredit(); +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLinkAttachment.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLinkAttachment.java new file mode 100644 index 0000000000..46b9682c74 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLinkAttachment.java @@ -0,0 +1,71 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.amqp_1_0.transport.DeliveryStateHandler; +import org.apache.qpid.amqp_1_0.transport.ReceivingLinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.SendingLinkEndpoint; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.DeliveryState; +import org.apache.qpid.amqp_1_0.type.Source; +import org.apache.qpid.amqp_1_0.type.Target; + +public class ReceivingLinkAttachment +{ + private final Session_1_0 _session; + private final ReceivingLinkEndpoint _endpoint; + + public ReceivingLinkAttachment(final Session_1_0 session, final ReceivingLinkEndpoint endpoint) + { + _session = session; + _endpoint = endpoint; + } + + public Session_1_0 getSession() + { + return _session; + } + + public ReceivingLinkEndpoint getEndpoint() + { + return _endpoint; + } + + public Source getSource() + { + return getEndpoint().getSource(); + } + + public void setDeliveryStateHandler(final DeliveryStateHandler handler) + { + getEndpoint().setDeliveryStateHandler(handler); + } + + public void updateDisposition(final Binary deliveryTag, final DeliveryState state, final boolean settled) + { + getEndpoint().updateDisposition(deliveryTag, state, settled); + } + + public Target getTarget() + { + return getEndpoint().getTarget(); + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLink_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLink_1_0.java new file mode 100644 index 0000000000..e971672767 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLink_1_0.java @@ -0,0 +1,306 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.qpid.amqp_1_0.messaging.SectionDecoderImpl; +import org.apache.qpid.amqp_1_0.transport.DeliveryStateHandler; +import org.apache.qpid.amqp_1_0.transport.LinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.ReceivingLinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.ReceivingLinkListener; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.DeliveryState; +import org.apache.qpid.amqp_1_0.type.Outcome; +import org.apache.qpid.amqp_1_0.type.UnsignedInteger; +import org.apache.qpid.amqp_1_0.type.messaging.Target; +import org.apache.qpid.amqp_1_0.type.messaging.TerminusDurability; +import org.apache.qpid.amqp_1_0.type.transaction.TransactionalState; +import org.apache.qpid.amqp_1_0.type.transport.Detach; +import org.apache.qpid.amqp_1_0.type.transport.ReceiverSettleMode; +import org.apache.qpid.amqp_1_0.type.transport.Transfer; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ReceivingLink_1_0 implements ReceivingLinkListener, Link_1_0, DeliveryStateHandler +{ + private VirtualHost _vhost; + + private ReceivingDestination _destination; + private SectionDecoderImpl _sectionDecoder; + private volatile ReceivingLinkAttachment _attachment; + + + private ArrayList<Transfer> _incompleteMessage; + private TerminusDurability _durability; + + private Map<Binary, Outcome> _unsettledMap = Collections.synchronizedMap(new HashMap<Binary, Outcome>()); + private boolean _resumedMessage; + private Binary _messageDeliveryTag; + private ReceiverSettleMode _receivingSettlementMode; + + + public ReceivingLink_1_0(ReceivingLinkAttachment receivingLinkAttachment, VirtualHost vhost, + ReceivingDestination destination) + { + _vhost = vhost; + _destination = destination; + _attachment = receivingLinkAttachment; + receivingLinkAttachment.setDeliveryStateHandler(this); + + _durability = ((Target)receivingLinkAttachment.getTarget()).getDurable(); + + _sectionDecoder = new SectionDecoderImpl(receivingLinkAttachment.getEndpoint().getSession().getConnection().getDescribedTypeRegistry()); + + + } + + public void messageTransfer(Transfer xfr) + { + // TODO - cope with fragmented messages + + List<ByteBuffer> fragments = null; + + + + if(Boolean.TRUE.equals(xfr.getMore()) && _incompleteMessage == null) + { + _incompleteMessage = new ArrayList<Transfer>(); + _incompleteMessage.add(xfr); + _resumedMessage = Boolean.TRUE.equals(xfr.getResume()); + _messageDeliveryTag = xfr.getDeliveryTag(); + return; + } + else if(_incompleteMessage != null) + { + _incompleteMessage.add(xfr); + + if(Boolean.TRUE.equals(xfr.getMore())) + { + return; + } + + fragments = new ArrayList<ByteBuffer>(_incompleteMessage.size()); + for(Transfer t : _incompleteMessage) + { + fragments.add(t.getPayload()); + } + _incompleteMessage=null; + + } + else + { + _resumedMessage = Boolean.TRUE.equals(xfr.getResume()); + _messageDeliveryTag = xfr.getDeliveryTag(); + fragments = Collections.singletonList(xfr.getPayload()); + } + + if(_resumedMessage) + { + if(_unsettledMap.containsKey(_messageDeliveryTag)) + { + Outcome outcome = _unsettledMap.get(_messageDeliveryTag); + boolean settled = ReceiverSettleMode.FIRST.equals(getReceivingSettlementMode()); + getEndpoint().updateDisposition(_messageDeliveryTag, (DeliveryState) outcome, settled); + if(settled) + { + _unsettledMap.remove(_messageDeliveryTag); + } + } + else + { + System.err.println("UNEXPECTED!!"); + System.err.println("Delivery Tag: " + _messageDeliveryTag); + System.err.println("_unsettledMap: " + _unsettledMap); + + } + } + else + { + MessageMetaData_1_0 mmd = null; + List<ByteBuffer> immutableSections = new ArrayList<ByteBuffer>(3); + mmd = new MessageMetaData_1_0(fragments.toArray(new ByteBuffer[fragments.size()]), + _sectionDecoder, + immutableSections); + + StoredMessage<MessageMetaData_1_0> storedMessage = _vhost.getMessageStore().addMessage(mmd); + + boolean skipping = true; + int offset = 0; + + for(ByteBuffer bareMessageBuf : immutableSections) + { + storedMessage.addContent(offset, bareMessageBuf.duplicate()); + offset += bareMessageBuf.remaining(); + } + + storedMessage.flushToStore(); + + Message_1_0 message = new Message_1_0(storedMessage, fragments, getSession()); + + + Binary transactionId = null; + org.apache.qpid.amqp_1_0.type.DeliveryState xfrState = xfr.getState(); + if(xfrState != null) + { + if(xfrState instanceof TransactionalState) + { + transactionId = ((TransactionalState)xfrState).getTxnId(); + } + } + + ServerTransaction transaction = null; + if(transactionId != null) + { + transaction = getSession().getTransaction(transactionId); + } + else + { + Session_1_0 session = getSession(); + transaction = session != null ? session.getTransaction(null) : new AutoCommitTransaction(_vhost.getMessageStore()); + } + + Outcome outcome = _destination.send(message, transaction); + + DeliveryState resultantState; + + if(transactionId == null) + { + resultantState = (DeliveryState) outcome; + } + else + { + TransactionalState transactionalState = new TransactionalState(); + transactionalState.setOutcome(outcome); + transactionalState.setTxnId(transactionId); + resultantState = transactionalState; + + } + + + boolean settled = transaction instanceof AutoCommitTransaction && ReceiverSettleMode.FIRST.equals(getReceivingSettlementMode()); + + final Binary deliveryTag = xfr.getDeliveryTag(); + + if(!settled) + { + _unsettledMap.put(deliveryTag, outcome); + } + + getEndpoint().updateDisposition(deliveryTag, resultantState, settled); + + getSession().getConnectionModel().registerMessageReceived(message.getSize(), message.getArrivalTime()); + + if(!(transaction instanceof AutoCommitTransaction)) + { + ServerTransaction.Action a; + transaction.addPostTransactionAction(new ServerTransaction.Action() + { + public void postCommit() + { + getEndpoint().updateDisposition(deliveryTag, null, true); + } + + public void onRollback() + { + getEndpoint().updateDisposition(deliveryTag, null, true); + } + }); + } + } + } + + private ReceiverSettleMode getReceivingSettlementMode() + { + return _receivingSettlementMode; + } + + public void remoteDetached(LinkEndpoint endpoint, Detach detach) + { + //TODO + // if not durable or close + if(!TerminusDurability.UNSETTLED_STATE.equals(_durability) || + (detach != null && Boolean.TRUE.equals(detach.getClosed()))) + { + endpoint.close(); + } + else if(detach == null || detach.getError() != null) + { + _attachment = null; + } + } + + public void start() + { + getEndpoint().setLinkCredit(UnsignedInteger.valueOf(_destination.getCredit())); + getEndpoint().setCreditWindow(); + } + + public ReceivingLinkEndpoint getEndpoint() + { + return _attachment.getEndpoint(); + } + + + public Session_1_0 getSession() + { + ReceivingLinkAttachment attachment = _attachment; + return attachment == null ? null : attachment.getSession(); + } + + public void handle(Binary deliveryTag, DeliveryState state, Boolean settled) + { + if(Boolean.TRUE.equals(settled)) + { + _unsettledMap.remove(deliveryTag); + } + } + + public void setLinkAttachment(ReceivingLinkAttachment linkAttachment) + { + _attachment = linkAttachment; + _receivingSettlementMode = linkAttachment.getEndpoint().getReceivingSettlementMode(); + ReceivingLinkEndpoint endpoint = linkAttachment.getEndpoint(); + Map initialUnsettledMap = endpoint.getInitialUnsettledMap(); + + Map<Binary, Outcome> unsettledCopy = new HashMap<Binary, Outcome>(_unsettledMap); + for(Map.Entry<Binary, Outcome> entry : unsettledCopy.entrySet()) + { + Binary deliveryTag = entry.getKey(); + if(!initialUnsettledMap.containsKey(deliveryTag)) + { + _unsettledMap.remove(deliveryTag); + } + } + + } + + public Map getUnsettledOutcomeMap() + { + return _unsettledMap; + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingDestination.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingDestination.java new file mode 100644 index 0000000000..6d601c9dda --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingDestination.java @@ -0,0 +1,27 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + + +public interface SendingDestination extends Destination +{ + +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingLinkAttachment.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingLinkAttachment.java new file mode 100644 index 0000000000..09a2ddea3a --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingLinkAttachment.java @@ -0,0 +1,64 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.amqp_1_0.transport.DeliveryStateHandler; +import org.apache.qpid.amqp_1_0.transport.SendingLinkEndpoint; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.DeliveryState; +import org.apache.qpid.amqp_1_0.type.Source; + +public class SendingLinkAttachment +{ + private final Session_1_0 _session; + private final SendingLinkEndpoint _endpoint; + + public SendingLinkAttachment(final Session_1_0 session, final SendingLinkEndpoint endpoint) + { + _session = session; + _endpoint = endpoint; + } + + public Session_1_0 getSession() + { + return _session; + } + + public SendingLinkEndpoint getEndpoint() + { + return _endpoint; + } + + public Source getSource() + { + return getEndpoint().getSource(); + } + + public void setDeliveryStateHandler(final DeliveryStateHandler handler) + { + getEndpoint().setDeliveryStateHandler(handler); + } + + public void updateDisposition(final Binary deliveryTag, final DeliveryState state, final boolean settled) + { + getEndpoint().updateDisposition(deliveryTag, state, settled); + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingLink_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingLink_1_0.java new file mode 100644 index 0000000000..35f24afbce --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/SendingLink_1_0.java @@ -0,0 +1,687 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.amqp_1_0.transport.DeliveryStateHandler; +import org.apache.qpid.amqp_1_0.transport.LinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.SendingLinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.SendingLinkListener; +import org.apache.qpid.amqp_1_0.type.AmqpErrorException; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.DeliveryState; +import org.apache.qpid.amqp_1_0.type.Outcome; +import org.apache.qpid.amqp_1_0.type.Symbol; +import org.apache.qpid.amqp_1_0.type.UnsignedInteger; +import org.apache.qpid.amqp_1_0.type.messaging.Accepted; +import org.apache.qpid.amqp_1_0.type.messaging.ExactSubjectFilter; +import org.apache.qpid.amqp_1_0.type.messaging.Filter; +import org.apache.qpid.amqp_1_0.type.messaging.MatchingSubjectFilter; +import org.apache.qpid.amqp_1_0.type.messaging.Modified; +import org.apache.qpid.amqp_1_0.type.messaging.NoLocalFilter; +import org.apache.qpid.amqp_1_0.type.messaging.Released; +import org.apache.qpid.amqp_1_0.type.messaging.Source; +import org.apache.qpid.amqp_1_0.type.messaging.StdDistMode; +import org.apache.qpid.amqp_1_0.type.messaging.TerminusDurability; +import org.apache.qpid.amqp_1_0.type.transport.AmqpError; +import org.apache.qpid.amqp_1_0.type.transport.Detach; +import org.apache.qpid.amqp_1_0.type.transport.Error; +import org.apache.qpid.amqp_1_0.type.transport.Transfer; +import org.apache.qpid.filter.SelectorParsingException; +import org.apache.qpid.filter.selector.ParseException; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.DirectExchange; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.filter.JMSSelectorFilter; +import org.apache.qpid.server.filter.SimpleFilterManager; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class SendingLink_1_0 implements SendingLinkListener, Link_1_0, DeliveryStateHandler +{ + private VirtualHost _vhost; + private SendingDestination _destination; + + private Subscription_1_0 _subscription; + private boolean _draining; + private final Map<Binary, QueueEntry> _unsettledMap = + new HashMap<Binary, QueueEntry>(); + + private final ConcurrentHashMap<Binary, UnsettledAction> _unsettledActionMap = + new ConcurrentHashMap<Binary, UnsettledAction>(); + private volatile SendingLinkAttachment _linkAttachment; + private TerminusDurability _durability; + private List<QueueEntry> _resumeFullTransfers = new ArrayList<QueueEntry>(); + private List<Binary> _resumeAcceptedTransfers = new ArrayList<Binary>(); + private Runnable _closeAction; + + public SendingLink_1_0(final SendingLinkAttachment linkAttachment, + final VirtualHost vhost, + final SendingDestination destination) + throws AmqpErrorException + { + _vhost = vhost; + _destination = destination; + _linkAttachment = linkAttachment; + final Source source = (Source) linkAttachment.getSource(); + _durability = source.getDurable(); + linkAttachment.setDeliveryStateHandler(this); + QueueDestination qd = null; + AMQQueue queue = null; + + + + boolean noLocal = false; + JMSSelectorFilter messageFilter = null; + + if(destination instanceof QueueDestination) + { + queue = ((QueueDestination) _destination).getQueue(); + + if(queue.getAvailableAttributes().contains("topic")) + { + source.setDistributionMode(StdDistMode.COPY); + } + + qd = (QueueDestination) destination; + + Map<Symbol,Filter> filters = source.getFilter(); + + Map<Symbol,Filter> actualFilters = new HashMap<Symbol,Filter>(); + + if(filters != null) + { + for(Map.Entry<Symbol,Filter> entry : filters.entrySet()) + { + if(entry.getValue() instanceof NoLocalFilter) + { + actualFilters.put(entry.getKey(), entry.getValue()); + noLocal = true; + } + else if(messageFilter == null && entry.getValue() instanceof org.apache.qpid.amqp_1_0.type.messaging.JMSSelectorFilter) + { + + org.apache.qpid.amqp_1_0.type.messaging.JMSSelectorFilter selectorFilter = (org.apache.qpid.amqp_1_0.type.messaging.JMSSelectorFilter) entry.getValue(); + try + { + messageFilter = new JMSSelectorFilter(selectorFilter.getValue()); + + actualFilters.put(entry.getKey(), entry.getValue()); + } + catch (ParseException e) + { + Error error = new Error(); + error.setCondition(AmqpError.INVALID_FIELD); + error.setDescription("Invalid JMS Selector: " + selectorFilter.getValue()); + error.setInfo(Collections.singletonMap(Symbol.valueOf("field"), Symbol.valueOf("filter"))); + throw new AmqpErrorException(error); + } + catch (SelectorParsingException e) + { + Error error = new Error(); + error.setCondition(AmqpError.INVALID_FIELD); + error.setDescription("Invalid JMS Selector: " + selectorFilter.getValue()); + error.setInfo(Collections.singletonMap(Symbol.valueOf("field"), Symbol.valueOf("filter"))); + throw new AmqpErrorException(error); + } + + + } + } + } + source.setFilter(actualFilters.isEmpty() ? null : actualFilters); + + _subscription = new Subscription_1_0(this, qd); + } + else if(destination instanceof ExchangeDestination) + { + try + { + + ExchangeDestination exchangeDestination = (ExchangeDestination) destination; + + boolean isDurable = exchangeDestination.getDurability() == TerminusDurability.CONFIGURATION + || exchangeDestination.getDurability() == TerminusDurability.UNSETTLED_STATE; + String name; + if(isDurable) + { + String remoteContainerId = getEndpoint().getSession().getConnection().getRemoteContainerId(); + remoteContainerId = remoteContainerId.replace("_","__").replace(".", "_:"); + + String endpointName = linkAttachment.getEndpoint().getName(); + endpointName = endpointName + .replace("_", "__") + .replace(".", "_:") + .replace("(", "_O") + .replace(")", "_C") + .replace("<", "_L") + .replace(">", "_R"); + name = "qpid_/" + remoteContainerId + "_/" + endpointName; + } + else + { + name = UUID.randomUUID().toString(); + } + + queue = _vhost.getQueue(name); + Exchange exchange = exchangeDestination.getExchange(); + + if(queue == null) + { + queue = _vhost.createQueue( + UUIDGenerator.generateQueueUUID(name, _vhost.getName()), + name, + isDurable, + null, + true, + true, + true, + Collections.EMPTY_MAP); + } + else + { + List<Binding> bindings = queue.getBindings(); + List<Binding> bindingsToRemove = new ArrayList<Binding>(); + for(Binding existingBinding : bindings) + { + if(existingBinding.getExchange() != _vhost.getDefaultExchange() + && existingBinding.getExchange() != exchange) + { + bindingsToRemove.add(existingBinding); + } + } + for(Binding existingBinding : bindingsToRemove) + { + existingBinding.getExchange().removeBinding(existingBinding); + } + } + + + String binding = ""; + + Map<Symbol,Filter> filters = source.getFilter(); + Map<Symbol,Filter> actualFilters = new HashMap<Symbol,Filter>(); + boolean hasBindingFilter = false; + if(filters != null && !filters.isEmpty()) + { + + for(Map.Entry<Symbol,Filter> entry : filters.entrySet()) + { + if(!hasBindingFilter + && entry.getValue() instanceof ExactSubjectFilter + && exchange.getType() == DirectExchange.TYPE) + { + ExactSubjectFilter filter = (ExactSubjectFilter) filters.values().iterator().next(); + source.setFilter(filters); + binding = filter.getValue(); + actualFilters.put(entry.getKey(), entry.getValue()); + hasBindingFilter = true; + } + else if(!hasBindingFilter + && entry.getValue() instanceof MatchingSubjectFilter + && exchange.getType() == TopicExchange.TYPE) + { + MatchingSubjectFilter filter = (MatchingSubjectFilter) filters.values().iterator().next(); + source.setFilter(filters); + binding = filter.getValue(); + actualFilters.put(entry.getKey(), entry.getValue()); + hasBindingFilter = true; + } + else if(entry.getValue() instanceof NoLocalFilter) + { + actualFilters.put(entry.getKey(), entry.getValue()); + noLocal = true; + } + else if(messageFilter == null && entry.getValue() instanceof org.apache.qpid.amqp_1_0.type.messaging.JMSSelectorFilter) + { + + org.apache.qpid.amqp_1_0.type.messaging.JMSSelectorFilter selectorFilter = (org.apache.qpid.amqp_1_0.type.messaging.JMSSelectorFilter) entry.getValue(); + try + { + messageFilter = new JMSSelectorFilter(selectorFilter.getValue()); + + actualFilters.put(entry.getKey(), entry.getValue()); + } + catch (ParseException e) + { + Error error = new Error(); + error.setCondition(AmqpError.INVALID_FIELD); + error.setDescription("Invalid JMS Selector: " + selectorFilter.getValue()); + error.setInfo(Collections.singletonMap(Symbol.valueOf("field"), Symbol.valueOf("filter"))); + throw new AmqpErrorException(error); + } + catch (SelectorParsingException e) + { + Error error = new Error(); + error.setCondition(AmqpError.INVALID_FIELD); + error.setDescription("Invalid JMS Selector: " + selectorFilter.getValue()); + error.setInfo(Collections.singletonMap(Symbol.valueOf("field"), Symbol.valueOf("filter"))); + throw new AmqpErrorException(error); + } + + + } + } + } + source.setFilter(actualFilters.isEmpty() ? null : actualFilters); + + exchange.addBinding(binding,queue,null); + source.setDistributionMode(StdDistMode.COPY); + + if(!isDurable) + { + final String queueName = name; + final AMQQueue tempQueue = queue; + + final Connection_1_0.Task deleteQueueTask = + new Connection_1_0.Task() + { + public void doTask(Connection_1_0 session) + { + if (_vhost.getQueue(queueName) == tempQueue) + { + try + { + _vhost.removeQueue(tempQueue); + } + catch (AMQException e) + { + e.printStackTrace(); //TODO. + } + } + } + }; + + getSession().getConnection().addConnectionCloseTask(deleteQueueTask); + + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) + { + getSession().getConnection().removeConnectionCloseTask(deleteQueueTask); + } + + + }); + } + + qd = new QueueDestination(queue); + } + catch (AMQSecurityException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + catch (AMQInternalException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + _subscription = new Subscription_1_0(this, qd, true); + + } + + if(_subscription != null) + { + _subscription.setNoLocal(noLocal); + if(messageFilter!=null) + { + _subscription.setFilters(new SimpleFilterManager(messageFilter)); + } + + try + { + + queue.registerSubscription(_subscription, false); + } + catch (AMQException e) + { + e.printStackTrace(); //TODO + } + } + + } + + public void resume(SendingLinkAttachment linkAttachment) + { + _linkAttachment = linkAttachment; + + } + + public void remoteDetached(final LinkEndpoint endpoint, final Detach detach) + { + //TODO + // if not durable or close + if(!TerminusDurability.UNSETTLED_STATE.equals(_durability)) + { + AMQQueue queue = _subscription.getQueue(); + + try + { + + queue.unregisterSubscription(_subscription); + + } + catch (AMQException e) + { + e.printStackTrace(); //TODO + } + + Modified state = new Modified(); + state.setDeliveryFailed(true); + + for(UnsettledAction action : _unsettledActionMap.values()) + { + + action.process(state,Boolean.TRUE); + } + _unsettledActionMap.clear(); + + endpoint.close(); + + if(_destination instanceof ExchangeDestination + && (_durability == TerminusDurability.CONFIGURATION + || _durability == TerminusDurability.UNSETTLED_STATE)) + { + try + { + queue.getVirtualHost().removeQueue(queue); + } + catch(AMQException e) + { + e.printStackTrace(); // TODO - Implement + } + } + + if(_closeAction != null) + { + _closeAction.run(); + } + } + else if(detach == null || detach.getError() != null) + { + _linkAttachment = null; + _subscription.flowStateChanged(); + } + else + { + endpoint.detach(); + } + } + + public void start() + { + //TODO + } + + public SendingLinkEndpoint getEndpoint() + { + return _linkAttachment == null ? null : _linkAttachment.getEndpoint() ; + } + + public Session_1_0 getSession() + { + return _linkAttachment == null ? null : _linkAttachment.getSession(); + } + + public void flowStateChanged() + { + if(Boolean.TRUE.equals(getEndpoint().getDrain()) + && hasCredit()) + { + _draining = true; + } + + while(!_resumeAcceptedTransfers.isEmpty() && getEndpoint().hasCreditToSend()) + { + Accepted accepted = new Accepted(); + synchronized(getLock()) + { + + Transfer xfr = new Transfer(); + Binary dt = _resumeAcceptedTransfers.remove(0); + xfr.setDeliveryTag(dt); + xfr.setState(accepted); + xfr.setResume(Boolean.TRUE); + getEndpoint().transfer(xfr); + } + + } + if(_resumeAcceptedTransfers.isEmpty()) + { + _subscription.flowStateChanged(); + } + + } + + boolean hasCredit() + { + return getEndpoint().getLinkCredit().compareTo(UnsignedInteger.ZERO) > 0; + } + + public boolean isDraining() + { + return false; //TODO + } + + public boolean drained() + { + if(getEndpoint() != null) + { + synchronized(getEndpoint().getLock()) + { + if(_draining) + { + //TODO + getEndpoint().drained(); + _draining = false; + return true; + } + else + { + return false; + } + } + } + else + { + return false; + } + } + + public void addUnsettled(Binary tag, UnsettledAction unsettledAction, QueueEntry queueEntry) + { + _unsettledActionMap.put(tag,unsettledAction); + if(getTransactionId() == null) + { + _unsettledMap.put(tag, queueEntry); + } + } + + public void removeUnsettled(Binary tag) + { + _unsettledActionMap.remove(tag); + } + + public void handle(Binary deliveryTag, DeliveryState state, Boolean settled) + { + UnsettledAction action = _unsettledActionMap.get(deliveryTag); + boolean localSettle = false; + if(action != null) + { + localSettle = action.process(state, settled); + if(localSettle && !Boolean.TRUE.equals(settled)) + { + _linkAttachment.updateDisposition(deliveryTag, state, true); + } + } + if(Boolean.TRUE.equals(settled) || localSettle) + { + _unsettledActionMap.remove(deliveryTag); + _unsettledMap.remove(deliveryTag); + } + } + + ServerTransaction getTransaction(Binary transactionId) + { + return _linkAttachment.getSession().getTransaction(transactionId); + } + + public Binary getTransactionId() + { + SendingLinkEndpoint endpoint = getEndpoint(); + return endpoint == null ? null : endpoint.getTransactionId(); + } + + public synchronized Object getLock() + { + return _linkAttachment == null ? this : getEndpoint().getLock(); + } + + public boolean isDetached() + { + return _linkAttachment == null || getEndpoint().isDetached(); + } + + public boolean isAttached() + { + return _linkAttachment != null && getEndpoint().isAttached(); + } + + public synchronized void setLinkAttachment(SendingLinkAttachment linkAttachment) + { + + if(_subscription.isActive()) + { + _subscription.suspend(); + } + + _linkAttachment = linkAttachment; + + SendingLinkEndpoint endpoint = linkAttachment.getEndpoint(); + endpoint.setDeliveryStateHandler(this); + Map initialUnsettledMap = endpoint.getInitialUnsettledMap(); + Map<Binary, QueueEntry> unsettledCopy = new HashMap<Binary, QueueEntry>(_unsettledMap); + _resumeAcceptedTransfers.clear(); + _resumeFullTransfers.clear(); + + for(Map.Entry<Binary, QueueEntry> entry : unsettledCopy.entrySet()) + { + Binary deliveryTag = entry.getKey(); + final QueueEntry queueEntry = entry.getValue(); + if(initialUnsettledMap == null || !initialUnsettledMap.containsKey(deliveryTag)) + { + queueEntry.setRedelivered(); + queueEntry.release(); + _unsettledMap.remove(deliveryTag); + } + else if(initialUnsettledMap != null && (initialUnsettledMap.get(deliveryTag) instanceof Outcome)) + { + Outcome outcome = (Outcome) initialUnsettledMap.get(deliveryTag); + + if(outcome instanceof Accepted) + { + AutoCommitTransaction txn = new AutoCommitTransaction(_vhost.getMessageStore()); + if(_subscription.acquires()) + { + txn.dequeue(Collections.singleton(queueEntry), + new ServerTransaction.Action() + { + public void postCommit() + { + queueEntry.discard(); + } + + public void onRollback() + { + //To change body of implemented methods use File | Settings | File Templates. + } + }); + } + } + else if(outcome instanceof Released) + { + AutoCommitTransaction txn = new AutoCommitTransaction(_vhost.getMessageStore()); + if(_subscription.acquires()) + { + txn.dequeue(Collections.singleton(queueEntry), + new ServerTransaction.Action() + { + public void postCommit() + { + queueEntry.release(); + } + + public void onRollback() + { + //To change body of implemented methods use File | Settings | File Templates. + } + }); + } + } + //_unsettledMap.remove(deliveryTag); + initialUnsettledMap.remove(deliveryTag); + _resumeAcceptedTransfers.add(deliveryTag); + } + else + { + _resumeFullTransfers.add(queueEntry); + // exists in receivers map, but not yet got an outcome ... should resend with resume = true + } + // TODO - else + } + + + } + + public Map getUnsettledOutcomeMap() + { + Map<Binary, QueueEntry> unsettled = new HashMap<Binary, QueueEntry>(_unsettledMap); + + for(Map.Entry<Binary, QueueEntry> entry : unsettled.entrySet()) + { + entry.setValue(null); + } + + return unsettled; + } + + public void setCloseAction(Runnable action) + { + _closeAction = action; + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Session_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Session_1_0.java new file mode 100644 index 0000000000..d3962c779c --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Session_1_0.java @@ -0,0 +1,619 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.text.MessageFormat; +import org.apache.qpid.amqp_1_0.transport.LinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.ReceivingLinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.SendingLinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.SessionEventListener; +import org.apache.qpid.amqp_1_0.type.*; +import org.apache.qpid.amqp_1_0.type.messaging.*; +import org.apache.qpid.amqp_1_0.type.messaging.Source; +import org.apache.qpid.amqp_1_0.type.messaging.Target; +import org.apache.qpid.amqp_1_0.type.transaction.Coordinator; +import org.apache.qpid.amqp_1_0.type.transaction.TxnCapability; +import org.apache.qpid.amqp_1_0.type.transport.*; + +import org.apache.qpid.amqp_1_0.type.transport.Error; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.protocol.LinkRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.*; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.CHANNEL_FORMAT; + +public class Session_1_0 implements SessionEventListener, AMQSessionModel, LogSubject +{ + private static final Symbol LIFETIME_POLICY = Symbol.valueOf("lifetime-policy"); + private VirtualHost _vhost; + private AutoCommitTransaction _transaction; + + private final LinkedHashMap<Integer, ServerTransaction> _openTransactions = + new LinkedHashMap<Integer, ServerTransaction>(); + private final Connection_1_0 _connection; + private UUID _id = UUID.randomUUID(); + + + public Session_1_0(VirtualHost vhost, final Connection_1_0 connection) + { + _vhost = vhost; + _transaction = new AutoCommitTransaction(vhost.getMessageStore()); + _connection = connection; + + } + + public void remoteLinkCreation(final LinkEndpoint endpoint) + { + + + Destination destination; + Link_1_0 link = null; + Error error = null; + + final + LinkRegistry + linkRegistry = _vhost.getLinkRegistry(endpoint.getSession().getConnection().getRemoteContainerId()); + + + if(endpoint.getRole() == Role.SENDER) + { + + SendingLink_1_0 previousLink = (SendingLink_1_0) linkRegistry.getDurableSendingLink(endpoint.getName()); + + if(previousLink == null) + { + + Target target = (Target) endpoint.getTarget(); + Source source = (Source) endpoint.getSource(); + + + if(source != null) + { + if(Boolean.TRUE.equals(source.getDynamic())) + { + AMQQueue tempQueue = createTemporaryQueue(source.getDynamicNodeProperties()); + source.setAddress(tempQueue.getName()); + } + String addr = source.getAddress(); + AMQQueue queue = _vhost.getQueue(addr); + if(queue != null) + { + + destination = new QueueDestination(queue); + + + + } + else + { + Exchange exchg = _vhost.getExchange(addr); + if(exchg != null) + { + destination = new ExchangeDestination(exchg, source.getDurable(), source.getExpiryPolicy()); + } + else + { + + endpoint.setSource(null); + destination = null; + } + } + + } + else + { + destination = null; + } + + if(destination != null) + { + final SendingLinkEndpoint sendingLinkEndpoint = (SendingLinkEndpoint) endpoint; + try + { + final SendingLink_1_0 sendingLink = new SendingLink_1_0(new SendingLinkAttachment(this, sendingLinkEndpoint), + _vhost, + (SendingDestination) destination + ); + sendingLinkEndpoint.setLinkEventListener(sendingLink); + link = sendingLink; + if(TerminusDurability.UNSETTLED_STATE.equals(source.getDurable())) + { + linkRegistry.registerSendingLink(endpoint.getName(), sendingLink); + } + } + catch(AmqpErrorException e) + { + e.printStackTrace(); + destination = null; + sendingLinkEndpoint.setSource(null); + error = e.getError(); + } + } + } + else + { + Source newSource = (Source) endpoint.getSource(); + + Source oldSource = (Source) previousLink.getEndpoint().getSource(); + final TerminusDurability newSourceDurable = newSource == null ? null : newSource.getDurable(); + if(newSourceDurable != null) + { + oldSource.setDurable(newSourceDurable); + if(newSourceDurable.equals(TerminusDurability.NONE)) + { + linkRegistry.unregisterSendingLink(endpoint.getName()); + } + } + endpoint.setSource(oldSource); + SendingLinkEndpoint sendingLinkEndpoint = (SendingLinkEndpoint) endpoint; + previousLink.setLinkAttachment(new SendingLinkAttachment(this, sendingLinkEndpoint)); + sendingLinkEndpoint.setLinkEventListener(previousLink); + link = previousLink; + endpoint.setLocalUnsettled(previousLink.getUnsettledOutcomeMap()); + } + } + else + { + if(endpoint.getTarget() instanceof Coordinator) + { + Coordinator coordinator = (Coordinator) endpoint.getTarget(); + TxnCapability[] capabilities = coordinator.getCapabilities(); + boolean localTxn = false; + boolean multiplePerSession = false; + if(capabilities != null) + { + for(TxnCapability capability : capabilities) + { + if(capability.equals(TxnCapability.LOCAL_TXN)) + { + localTxn = true; + } + else if(capability.equals(TxnCapability.MULTI_TXNS_PER_SSN)) + { + multiplePerSession = true; + } + else + { + error = new Error(); + error.setCondition(AmqpError.NOT_IMPLEMENTED); + error.setDescription("Unsupported capability: " + capability); + break; + } + } + } + + /* if(!localTxn) + { + capabilities.add(TxnCapabilities.LOCAL_TXN); + }*/ + + final ReceivingLinkEndpoint receivingLinkEndpoint = (ReceivingLinkEndpoint) endpoint; + final TxnCoordinatorLink_1_0 coordinatorLink = + new TxnCoordinatorLink_1_0(_vhost, this, receivingLinkEndpoint, _openTransactions); + receivingLinkEndpoint.setLinkEventListener(coordinatorLink); + link = coordinatorLink; + + + } + else + { + + ReceivingLink_1_0 previousLink = + (ReceivingLink_1_0) linkRegistry.getDurableReceivingLink(endpoint.getName()); + + if(previousLink == null) + { + + Target target = (Target) endpoint.getTarget(); + + if(target != null) + { + if(Boolean.TRUE.equals(target.getDynamic())) + { + + AMQQueue tempQueue = createTemporaryQueue(target.getDynamicNodeProperties()); + target.setAddress(tempQueue.getName()); + } + + String addr = target.getAddress(); + Exchange exchg = _vhost.getExchange(addr); + if(exchg != null) + { + destination = new ExchangeDestination(exchg, target.getDurable(), + target.getExpiryPolicy()); + } + else + { + AMQQueue queue = _vhost.getQueue(addr); + if(queue != null) + { + + destination = new QueueDestination(queue); + } + else + { + endpoint.setTarget(null); + destination = null; + } + + } + + + } + else + { + destination = null; + } + if(destination != null) + { + final ReceivingLinkEndpoint receivingLinkEndpoint = (ReceivingLinkEndpoint) endpoint; + final ReceivingLink_1_0 receivingLink = new ReceivingLink_1_0(new ReceivingLinkAttachment(this, receivingLinkEndpoint), _vhost, + (ReceivingDestination) destination); + receivingLinkEndpoint.setLinkEventListener(receivingLink); + link = receivingLink; + if(TerminusDurability.UNSETTLED_STATE.equals(target.getDurable())) + { + linkRegistry.registerReceivingLink(endpoint.getName(), receivingLink); + } + } + } + else + { + ReceivingLinkEndpoint receivingLinkEndpoint = (ReceivingLinkEndpoint) endpoint; + previousLink.setLinkAttachment(new ReceivingLinkAttachment(this, receivingLinkEndpoint)); + receivingLinkEndpoint.setLinkEventListener(previousLink); + link = previousLink; + endpoint.setLocalUnsettled(previousLink.getUnsettledOutcomeMap()); + + } + } + } + + endpoint.attach(); + + if(link == null) + { + if(error == null) + { + error = new Error(); + error.setCondition(AmqpError.NOT_FOUND); + } + endpoint.detach(error); + } + else + { + link.start(); + } + } + + + private AMQQueue createTemporaryQueue(Map properties) + { + final String queueName = UUID.randomUUID().toString(); + AMQQueue queue = null; + try + { + LifetimePolicy lifetimePolicy = properties == null + ? null + : (LifetimePolicy) properties.get(LIFETIME_POLICY); + + final AMQQueue tempQueue = queue = _vhost.createQueue( UUIDGenerator.generateQueueUUID(queueName, _vhost.getName()), + queueName, + false, // durable + null, // owner + false, // autodelete + false, // exclusive + false, + properties); + + + + if (lifetimePolicy == null || lifetimePolicy instanceof DeleteOnClose) + { + final Connection_1_0.Task deleteQueueTask = + new Connection_1_0.Task() + { + public void doTask(Connection_1_0 session) + { + if (_vhost.getQueue(queueName) == tempQueue) + { + try + { + _vhost.removeQueue(tempQueue); + } + catch (AMQException e) + { + e.printStackTrace(); //TODO. + } + } + } + }; + + _connection.addConnectionCloseTask(deleteQueueTask); + + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) + { + _connection.removeConnectionCloseTask(deleteQueueTask); + } + + + }); + } + else if(lifetimePolicy instanceof DeleteOnNoLinks) + { + + } + else if(lifetimePolicy instanceof DeleteOnNoMessages) + { + + } + else if(lifetimePolicy instanceof DeleteOnNoLinksOrMessages) + { + + } + } + catch (AMQSecurityException e) + { + e.printStackTrace(); //TODO. + } catch (AMQException e) + { + e.printStackTrace(); //TODO + } + + return queue; + } + + public ServerTransaction getTransaction(Binary transactionId) + { + // TODO should treat invalid id differently to null + ServerTransaction transaction = _openTransactions.get(binaryToInteger(transactionId)); + return transaction == null ? _transaction : transaction; + } + + public void remoteEnd(End end) + { + Iterator<Map.Entry<Integer, ServerTransaction>> iter = _openTransactions.entrySet().iterator(); + + while(iter.hasNext()) + { + Map.Entry<Integer, ServerTransaction> entry = iter.next(); + entry.getValue().rollback(); + iter.remove(); + } + + _connection.sessionEnded(this); + + } + + Integer binaryToInteger(final Binary txnId) + { + if(txnId == null) + { + return null; + } + + if(txnId.getLength() > 4) + throw new IllegalArgumentException(); + + int id = 0; + byte[] data = txnId.getArray(); + for(int i = 0; i < txnId.getLength(); i++) + { + id <<= 8; + id += data[i+txnId.getArrayOffset()]; + } + + return id; + + } + + Binary integerToBinary(final int txnId) + { + byte[] data = new byte[4]; + data[3] = (byte) (txnId & 0xff); + data[2] = (byte) ((txnId & 0xff00) >> 8); + data[1] = (byte) ((txnId & 0xff0000) >> 16); + data[0] = (byte) ((txnId & 0xff000000) >> 24); + return new Binary(data); + + } + + public void forceEnd() + { + } + + + @Override + public UUID getId() + { + return _id; + } + + @Override + public AMQConnectionModel getConnectionModel() + { + return _connection.getModel(); + } + + @Override + public String getClientID() + { + // TODO + return ""; + } + + @Override + public void close() throws AMQException + { + // TODO - required for AMQSessionModel / management initiated closing + } + + + @Override + public void close(AMQConstant cause, String message) throws AMQException + { + // TODO - required for AMQSessionModel + } + + @Override + public LogSubject getLogSubject() + { + return this; + } + + @Override + public void checkTransactionStatus(long openWarn, long openClose, long idleWarn, long idleClose) throws AMQException + { + // TODO - required for AMQSessionModel / long running transaction detection + } + + @Override + public void block(AMQQueue queue) + { + // TODO - required for AMQSessionModel / producer side flow control + } + + @Override + public void unblock(AMQQueue queue) + { + // TODO - required for AMQSessionModel / producer side flow control + } + + @Override + public void block() + { + // TODO - required for AMQSessionModel / producer side flow control + } + + @Override + public void unblock() + { + // TODO - required for AMQSessionModel / producer side flow control + } + + @Override + public boolean getBlocking() + { + // TODO + return false; + } + + @Override + public boolean onSameConnection(InboundMessage inbound) + { + // TODO + return false; + } + + @Override + public int getUnacknowledgedMessageCount() + { + // TODO + return 0; + } + + @Override + public Long getTxnCount() + { + // TODO + return 0l; + } + + @Override + public Long getTxnStart() + { + // TODO + return 0l; + } + + @Override + public Long getTxnCommits() + { + // TODO + return 0l; + } + + @Override + public Long getTxnRejects() + { + // TODO + return 0l; + } + + @Override + public int getChannelId() + { + // TODO + return 0; + } + + @Override + public int getConsumerCount() + { + // TODO + return 0; + } + + + public String toLogString() + { + long connectionId = getConnectionModel().getConnectionId(); + + String remoteAddress = getConnectionModel().getRemoteAddressString(); + + return "[" + + MessageFormat.format(CHANNEL_FORMAT, + connectionId, + getClientID(), + remoteAddress, + _vhost.getName(), // TODO - virtual host + 0) // TODO - channel) + + "] "; + } + + @Override + public int compareTo(AMQSessionModel o) + { + return getId().compareTo(o.getId()); + } + + public Connection_1_0 getConnection() + { + return _connection; + } + +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Subscription_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Subscription_1_0.java new file mode 100644 index 0000000000..b9695ba87a --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/Subscription_1_0.java @@ -0,0 +1,695 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import org.apache.qpid.AMQException; +import org.apache.qpid.amqp_1_0.codec.ValueHandler; +import org.apache.qpid.amqp_1_0.messaging.SectionEncoder; +import org.apache.qpid.amqp_1_0.messaging.SectionEncoderImpl; +import org.apache.qpid.amqp_1_0.transport.SendingLinkEndpoint; +import org.apache.qpid.amqp_1_0.type.AmqpErrorException; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.DeliveryState; +import org.apache.qpid.amqp_1_0.type.Outcome; +import org.apache.qpid.amqp_1_0.type.UnsignedInteger; +import org.apache.qpid.amqp_1_0.type.codec.AMQPDescribedTypeRegistry; +import org.apache.qpid.amqp_1_0.type.messaging.Accepted; +import org.apache.qpid.amqp_1_0.type.messaging.Header; +import org.apache.qpid.amqp_1_0.type.messaging.Modified; +import org.apache.qpid.amqp_1_0.type.messaging.Released; +import org.apache.qpid.amqp_1_0.type.messaging.Source; +import org.apache.qpid.amqp_1_0.type.messaging.StdDistMode; +import org.apache.qpid.amqp_1_0.type.transaction.TransactionalState; +import org.apache.qpid.amqp_1_0.type.transport.SenderSettleMode; +import org.apache.qpid.amqp_1_0.type.transport.Transfer; +import org.apache.qpid.server.plugin.MessageConverter; +import org.apache.qpid.server.protocol.MessageConverterRegistry; +import org.apache.qpid.server.filter.FilterManager; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.ServerTransaction; + +class Subscription_1_0 implements Subscription +{ + private SendingLink_1_0 _link; + + private AMQQueue _queue; + + private final AtomicReference<State> _state = new AtomicReference<State>(State.SUSPENDED); + + private final QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this); + private final long _id; + private final boolean _acquires; + private volatile AMQQueue.Context _queueContext; + private Map<String, Object> _properties = new ConcurrentHashMap<String, Object>(); + private ReentrantLock _stateChangeLock = new ReentrantLock(); + + private boolean _noLocal; + private FilterManager _filters; + + private long _deliveryTag = 0L; + private StateListener _stateListener; + + private Binary _transactionId; + private final AMQPDescribedTypeRegistry _typeRegistry = AMQPDescribedTypeRegistry.newInstance() + .registerTransportLayer() + .registerMessagingLayer() + .registerTransactionLayer() + .registerSecurityLayer(); + private SectionEncoder _sectionEncoder = new SectionEncoderImpl(_typeRegistry); + + public Subscription_1_0(final SendingLink_1_0 link, final QueueDestination destination) + { + this(link, destination, ((Source)link.getEndpoint().getSource()).getDistributionMode() != StdDistMode.COPY); + } + + public Subscription_1_0(final SendingLink_1_0 link, final QueueDestination destination, boolean acquires) + { + _link = link; + _queue = destination.getQueue(); + _id = getEndpoint().getLocalHandle().longValue(); + _acquires = acquires; + } + + private SendingLinkEndpoint getEndpoint() + { + return _link.getEndpoint(); + } + + public LogActor getLogActor() + { + return null; //TODO + } + + public boolean isTransient() + { + return true; //TODO + } + + public AMQQueue getQueue() + { + return _queue; + } + + public QueueEntry.SubscriptionAcquiredState getOwningState() + { + return _owningState; + } + + public void setQueue(final AMQQueue queue, final boolean exclusive) + { + //TODO + } + + public void setNoLocal(final boolean noLocal) + { + _noLocal = noLocal; + } + + public long getSubscriptionID() + { + return _id; + } + + public boolean isSuspended() + { + return _link.getSession().getConnectionModel().isStopped() || !isActive();// || !getEndpoint().hasCreditToSend(); + + } + + public boolean hasInterest(final QueueEntry entry) + { + if(entry.getMessage() instanceof Message_1_0) + { + if(_noLocal && ((Message_1_0)entry.getMessage()).getSession() == getSession()) + { + return false; + } + } + else if(MessageConverterRegistry.getConverter(entry.getMessage().getClass(), Message_1_0.class)==null) + { + return false; + } + return checkFilters(entry); + + } + + private boolean checkFilters(final QueueEntry entry) + { + return (_filters == null) || _filters.allAllow(entry); + } + + public boolean isClosed() + { + return !getEndpoint().isAttached(); + } + + public boolean acquires() + { + return _acquires; + } + + public boolean seesRequeues() + { + // TODO + return acquires(); + } + + public void close() + { + getEndpoint().detach(); + } + + public void send(QueueEntry entry, boolean batch) throws AMQException + { + // TODO + send(entry); + } + + public void flushBatched() + { + // TODO + } + + public void send(final QueueEntry queueEntry) throws AMQException + { + ServerMessage serverMessage = queueEntry.getMessage(); + Message_1_0 message; + if(serverMessage instanceof Message_1_0) + { + message = (Message_1_0) serverMessage; + } + else + { + final MessageConverter converter = MessageConverterRegistry.getConverter(serverMessage.getClass(), Message_1_0.class); + message = (Message_1_0) converter.convert(serverMessage, queueEntry.getQueue().getVirtualHost()); + } + + Transfer transfer = new Transfer(); + //TODO + + + List<ByteBuffer> fragments = message.getFragments(); + ByteBuffer payload; + if(fragments.size() == 1) + { + payload = fragments.get(0); + } + else + { + int size = 0; + for(ByteBuffer fragment : fragments) + { + size += fragment.remaining(); + } + + payload = ByteBuffer.allocate(size); + + for(ByteBuffer fragment : fragments) + { + payload.put(fragment.duplicate()); + } + + payload.flip(); + } + + if(queueEntry.getDeliveryCount() != 0) + { + payload = payload.duplicate(); + ValueHandler valueHandler = new ValueHandler(_typeRegistry); + + Header oldHeader = null; + try + { + ByteBuffer encodedBuf = payload.duplicate(); + Object value = valueHandler.parse(payload); + if(value instanceof Header) + { + oldHeader = (Header) value; + } + else + { + payload.position(0); + } + } + catch (AmqpErrorException e) + { + //TODO + throw new RuntimeException(e); + } + + Header header = new Header(); + if(oldHeader != null) + { + header.setDurable(oldHeader.getDurable()); + header.setPriority(oldHeader.getPriority()); + header.setTtl(oldHeader.getTtl()); + } + header.setDeliveryCount(UnsignedInteger.valueOf(queueEntry.getDeliveryCount())); + _sectionEncoder.reset(); + _sectionEncoder.encodeObject(header); + Binary encodedHeader = _sectionEncoder.getEncoding(); + + ByteBuffer oldPayload = payload; + payload = ByteBuffer.allocate(oldPayload.remaining() + encodedHeader.getLength()); + payload.put(encodedHeader.getArray(),encodedHeader.getArrayOffset(),encodedHeader.getLength()); + payload.put(oldPayload); + payload.flip(); + } + + transfer.setPayload(payload); + byte[] data = new byte[8]; + ByteBuffer.wrap(data).putLong(_deliveryTag++); + final Binary tag = new Binary(data); + + transfer.setDeliveryTag(tag); + + synchronized(_link.getLock()) + { + if(_link.isAttached()) + { + if(SenderSettleMode.SETTLED.equals(getEndpoint().getSendingSettlementMode())) + { + transfer.setSettled(true); + } + else + { + UnsettledAction action = _acquires + ? new DispositionAction(tag, queueEntry) + : new DoNothingAction(tag, queueEntry); + + _link.addUnsettled(tag, action, queueEntry); + } + + if(_transactionId != null) + { + TransactionalState state = new TransactionalState(); + state.setTxnId(_transactionId); + transfer.setState(state); + } + // TODO - need to deal with failure here + if(_acquires && _transactionId != null) + { + ServerTransaction txn = _link.getTransaction(_transactionId); + if(txn != null) + { + txn.addPostTransactionAction(new ServerTransaction.Action(){ + + public void postCommit() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void onRollback() + { + if(queueEntry.isAcquiredBy(Subscription_1_0.this)) + { + queueEntry.release(); + _link.getEndpoint().updateDisposition(tag, (DeliveryState)null, true); + + + } + } + }); + } + + } + getSession().getConnectionModel().registerMessageDelivered(message.getSize()); + getEndpoint().transfer(transfer); + } + else + { + queueEntry.release(); + } + } + + } + + public void queueDeleted(final AMQQueue queue) + { + //TODO + getEndpoint().setSource(null); + getEndpoint().detach(); + } + + public boolean wouldSuspend(final QueueEntry msg) + { + synchronized (_link.getLock()) + { + final boolean hasCredit = _link.isAttached() && getEndpoint().hasCreditToSend(); + if(!hasCredit && getState() == State.ACTIVE) + { + suspend(); + } + + return !hasCredit; + } + } + + public boolean trySendLock() + { + return _stateChangeLock.tryLock(); + } + + public void suspend() + { + synchronized(_link.getLock()) + { + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + } + } + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public void releaseQueueEntry(QueueEntry queueEntryImpl) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + + public void onDequeue(final QueueEntry queueEntry) + { + //TODO + } + + public void restoreCredit(final QueueEntry queueEntry) + { + //TODO + } + + public void setStateListener(final StateListener listener) + { + _stateListener = listener; + } + + public State getState() + { + return _state.get(); + } + + public AMQQueue.Context getQueueContext() + { + return _queueContext; + } + + public void setQueueContext(AMQQueue.Context queueContext) + { + _queueContext = queueContext; + } + + + public boolean isActive() + { + return getState() == State.ACTIVE; + } + + public void set(String key, Object value) + { + _properties.put(key, value); + } + + public Object get(String key) + { + return _properties.get(key); + } + + public boolean isSessionTransactional() + { + return false; //TODO + } + + public void queueEmpty() + { + synchronized(_link.getLock()) + { + if(_link.drained()) + { + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + } + } + } + + public void flowStateChanged() + { + synchronized(_link.getLock()) + { + if(isSuspended() && getEndpoint() != null) + { + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + _transactionId = _link.getTransactionId(); + } + } + } + + public Session_1_0 getSession() + { + return _link.getSession(); + } + + private class DispositionAction implements UnsettledAction + { + + private final QueueEntry _queueEntry; + private final Binary _deliveryTag; + + public DispositionAction(Binary tag, QueueEntry queueEntry) + { + _deliveryTag = tag; + _queueEntry = queueEntry; + } + + public boolean process(DeliveryState state, final Boolean settled) + { + + Binary transactionId = null; + final Outcome outcome; + // If disposition is settled this overrides the txn? + if(state instanceof TransactionalState) + { + transactionId = ((TransactionalState)state).getTxnId(); + outcome = ((TransactionalState)state).getOutcome(); + } + else if (state instanceof Outcome) + { + outcome = (Outcome) state; + } + else + { + outcome = null; + } + + + ServerTransaction txn = _link.getTransaction(transactionId); + + if(outcome instanceof Accepted) + { + txn.dequeue(_queueEntry.getQueue(), _queueEntry.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + if(_queueEntry.isAcquiredBy(Subscription_1_0.this)) + { + _queueEntry.discard(); + } + } + + public void onRollback() + { + + } + }); + txn.addPostTransactionAction(new ServerTransaction.Action() + { + public void postCommit() + { + //_link.getEndpoint().settle(_deliveryTag); + _link.getEndpoint().updateDisposition(_deliveryTag, (DeliveryState)outcome, true); + _link.getEndpoint().sendFlowConditional(); + } + + public void onRollback() + { + if(Boolean.TRUE.equals(settled)) + { + final Modified modified = new Modified(); + modified.setDeliveryFailed(true); + _link.getEndpoint().updateDisposition(_deliveryTag, modified, true); + _link.getEndpoint().sendFlowConditional(); + } + } + }); + } + else if(outcome instanceof Released) + { + txn.addPostTransactionAction(new ServerTransaction.Action() + { + public void postCommit() + { + + _queueEntry.release(); + _link.getEndpoint().settle(_deliveryTag); + } + + public void onRollback() + { + _link.getEndpoint().settle(_deliveryTag); + } + }); + } + + else if(outcome instanceof Modified) + { + txn.addPostTransactionAction(new ServerTransaction.Action() + { + public void postCommit() + { + + _queueEntry.release(); + if(Boolean.TRUE.equals(((Modified)outcome).getDeliveryFailed())) + { + _queueEntry.incrementDeliveryCount(); + } + _link.getEndpoint().settle(_deliveryTag); + } + + public void onRollback() + { + if(Boolean.TRUE.equals(settled)) + { + final Modified modified = new Modified(); + modified.setDeliveryFailed(true); + _link.getEndpoint().updateDisposition(_deliveryTag, modified, true); + _link.getEndpoint().sendFlowConditional(); + } + } + }); + } + + return (transactionId == null && outcome != null); + } + } + + private class DoNothingAction implements UnsettledAction + { + public DoNothingAction(final Binary tag, + final QueueEntry queueEntry) + { + } + + public boolean process(final DeliveryState state, final Boolean settled) + { + Binary transactionId = null; + Outcome outcome = null; + // If disposition is settled this overrides the txn? + if(state instanceof TransactionalState) + { + transactionId = ((TransactionalState)state).getTxnId(); + outcome = ((TransactionalState)state).getOutcome(); + } + else if (state instanceof Outcome) + { + outcome = (Outcome) state; + } + return true; + } + } + + public FilterManager getFilters() + { + return _filters; + } + + public void setFilters(final FilterManager filters) + { + _filters = filters; + } + + @Override + public AMQSessionModel getSessionModel() + { + // TODO + return getSession(); + } + + @Override + public long getBytesOut() + { + // TODO + return 0; + } + + @Override + public long getMessagesOut() + { + // TODO + return 0; + } + + @Override + public long getUnacknowledgedBytes() + { + // TODO + return 0; + } + + @Override + public long getUnacknowledgedMessages() + { + // TODO + return 0; + } + + @Override + public String getConsumerName() + { + //TODO + return "TODO"; + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/TxnCoordinatorLink_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/TxnCoordinatorLink_1_0.java new file mode 100644 index 0000000000..a05d14816a --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/TxnCoordinatorLink_1_0.java @@ -0,0 +1,195 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.amqp_1_0.messaging.SectionDecoder; +import org.apache.qpid.amqp_1_0.messaging.SectionDecoderImpl; +import org.apache.qpid.amqp_1_0.transport.LinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.ReceivingLinkEndpoint; +import org.apache.qpid.amqp_1_0.transport.ReceivingLinkListener; +import org.apache.qpid.amqp_1_0.type.*; +import org.apache.qpid.amqp_1_0.type.DeliveryState; +import org.apache.qpid.amqp_1_0.type.messaging.*; +import org.apache.qpid.amqp_1_0.type.transaction.Declare; +import org.apache.qpid.amqp_1_0.type.transaction.Declared; +import org.apache.qpid.amqp_1_0.type.transaction.Discharge; +import org.apache.qpid.amqp_1_0.type.transport.*; +import org.apache.qpid.amqp_1_0.type.transport.Error; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.nio.ByteBuffer; +import java.util.*; + +public class TxnCoordinatorLink_1_0 implements ReceivingLinkListener, Link_1_0 +{ + private VirtualHost _vhost; + private ReceivingLinkEndpoint _endpoint; + + private ArrayList<Transfer> _incompleteMessage; + private SectionDecoder _sectionDecoder; + private LinkedHashMap<Integer, ServerTransaction> _openTransactions; + private Session_1_0 _session; + + + public TxnCoordinatorLink_1_0(VirtualHost vhost, + Session_1_0 session_1_0, ReceivingLinkEndpoint endpoint, + LinkedHashMap<Integer, ServerTransaction> openTransactions) + { + _vhost = vhost; + _session = session_1_0; + _endpoint = endpoint; + _sectionDecoder = new SectionDecoderImpl(endpoint.getSession().getConnection().getDescribedTypeRegistry()); + _openTransactions = openTransactions; + } + + public void messageTransfer(Transfer xfr) + { + // TODO - cope with fragmented messages + + ByteBuffer payload = null; + + + if(Boolean.TRUE.equals(xfr.getMore()) && _incompleteMessage == null) + { + _incompleteMessage = new ArrayList<Transfer>(); + _incompleteMessage.add(xfr); + return; + } + else if(_incompleteMessage != null) + { + _incompleteMessage.add(xfr); + if(Boolean.TRUE.equals(xfr.getMore())) + { + return; + } + + int size = 0; + for(Transfer t : _incompleteMessage) + { + size += t.getPayload().limit(); + } + payload = ByteBuffer.allocate(size); + for(Transfer t : _incompleteMessage) + { + payload.put(t.getPayload().duplicate()); + } + payload.flip(); + _incompleteMessage=null; + + } + else + { + payload = xfr.getPayload(); + } + + + // Only interested int he amqp-value section that holds the message to the co-ordinator + try + { + List<Section> sections = _sectionDecoder.parseAll(payload); + + for(Section section : sections) + { + if(section instanceof AmqpValue) + { + Object command = ((AmqpValue) section).getValue(); + + if(command instanceof Declare) + { + Integer txnId = Integer.valueOf(0); + Iterator<Integer> existingTxn = _openTransactions.keySet().iterator(); + while(existingTxn.hasNext()) + { + txnId = existingTxn.next(); + } + txnId = Integer.valueOf(txnId.intValue() + 1); + + _openTransactions.put(txnId, new LocalTransaction(_vhost.getMessageStore())); + + Declared state = new Declared(); + + + + state.setTxnId(_session.integerToBinary(txnId)); + _endpoint.updateDisposition(xfr.getDeliveryTag(), state, true); + + } + else if(command instanceof Discharge) + { + Discharge discharge = (Discharge) command; + + DeliveryState state = xfr.getState(); + discharge(_session.binaryToInteger(discharge.getTxnId()), discharge.getFail()); + _endpoint.updateDisposition(xfr.getDeliveryTag(), new Accepted(), true); + + } + } + } + + } + catch (AmqpErrorException e) + { + e.printStackTrace(); //TODO. + } + + } + + public void remoteDetached(LinkEndpoint endpoint, Detach detach) + { + //TODO + endpoint.detach(); + } + + private Error discharge(Integer transactionId, boolean fail) + { + Error error = null; + ServerTransaction txn = _openTransactions.get(transactionId); + if(txn != null) + { + if(fail) + { + txn.rollback(); + } + else + { + txn.commit(); + } + _openTransactions.remove(transactionId); + } + else + { + error = new Error(); + error.setCondition(AmqpError.NOT_FOUND); + error.setDescription("Unkown transactionId" + transactionId); + } + return error; + } + + + + public void start() + { + _endpoint.setLinkCredit(UnsignedInteger.ONE); + _endpoint.setCreditWindow(); + } +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/UnsettledAction.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/UnsettledAction.java new file mode 100644 index 0000000000..0fee4086b4 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/UnsettledAction.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.v1_0; + +import org.apache.qpid.amqp_1_0.type.DeliveryState; + +public interface UnsettledAction +{ + boolean process(DeliveryState state, Boolean settled); +} diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType new file mode 100644 index 0000000000..91c673215c --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageMetaDataType @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.v1_0.MessageMetaDataType_1_0 diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator new file mode 100644 index 0000000000..fafbacd0a1 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ProtocolEngineCreator @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.v1_0.ProtocolEngineCreator_1_0_0 +org.apache.qpid.server.protocol.v1_0.ProtocolEngineCreator_1_0_0_SASL diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/build.xml b/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/build.xml new file mode 100644 index 0000000000..cddbcf5bae --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/build.xml @@ -0,0 +1,33 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins AMQP 0.8 to 1.0 MessageConversion" default="build"> + <property name="module.depends" value="common broker amqp-1-0-common broker-plugins/amqp-1-0-protocol broker-plugins/amqp-0-10-protocol" /> + <property name="module.test.depends" value="common/tests broker/tests" /> + + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided -Sqpid-amqp-1-0-common=provided -Sqpid-broker-plugins-amqp-0-10-protocol=provided -Sqpid-broker-plugins-amqp-1-0-protocol=provided"/> + <property name="broker-plugins-amqp-msg-conv-0-10-to-1-0.libs" value="" /> + + <property name="broker.plugin" value="true"/> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks"/> + +</project> diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/src/main/java/org/apache/qpid/server/protocol/converter/v0_10_v1_0/MessageConverter_0_10_to_1_0.java b/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/src/main/java/org/apache/qpid/server/protocol/converter/v0_10_v1_0/MessageConverter_0_10_to_1_0.java new file mode 100644 index 0000000000..a70bd4b243 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/src/main/java/org/apache/qpid/server/protocol/converter/v0_10_v1_0/MessageConverter_0_10_to_1_0.java @@ -0,0 +1,140 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.protocol.converter.v0_10_v1_0; + +import java.util.ArrayList; +import java.util.List; +import org.apache.qpid.amqp_1_0.messaging.SectionEncoder; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.Section; +import org.apache.qpid.amqp_1_0.type.Symbol; +import org.apache.qpid.amqp_1_0.type.UnsignedByte; +import org.apache.qpid.amqp_1_0.type.UnsignedInteger; +import org.apache.qpid.amqp_1_0.type.messaging.ApplicationProperties; +import org.apache.qpid.amqp_1_0.type.messaging.Header; +import org.apache.qpid.amqp_1_0.type.messaging.Properties; +import org.apache.qpid.server.protocol.v0_10.MessageTransferMessage; +import org.apache.qpid.server.protocol.v1_0.MessageConverter_to_1_0; +import org.apache.qpid.server.protocol.v1_0.MessageMetaData_1_0; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.MessageDeliveryMode; +import org.apache.qpid.transport.MessageProperties; + +public class MessageConverter_0_10_to_1_0 extends MessageConverter_to_1_0<MessageTransferMessage> +{ + @Override + public Class<MessageTransferMessage> getInputClass() + { + return MessageTransferMessage.class; + } + + + @Override + protected MessageMetaData_1_0 convertMetaData(MessageTransferMessage serverMessage, + SectionEncoder sectionEncoder) + { + List<Section> sections = new ArrayList<Section>(3); + final MessageProperties msgProps = serverMessage.getHeader().getMessageProperties(); + final DeliveryProperties deliveryProps = serverMessage.getHeader().getDeliveryProperties(); + + Header header = new Header(); + if(deliveryProps != null) + { + header.setDurable(deliveryProps.hasDeliveryMode() && deliveryProps.getDeliveryMode() == MessageDeliveryMode.PERSISTENT); + if(deliveryProps.hasPriority()) + { + header.setPriority(UnsignedByte.valueOf((byte) deliveryProps.getPriority().getValue())); + } + if(deliveryProps.hasTtl()) + { + header.setTtl(UnsignedInteger.valueOf(deliveryProps.getTtl())); + } + sections.add(header); + } + + Properties props = new Properties(); + + /* + TODO: the current properties are not currently set: + + absoluteExpiryTime + creationTime + groupId + groupSequence + replyToGroupId + to + */ + + if(msgProps != null) + { + if(msgProps.hasContentEncoding()) + { + props.setContentEncoding(Symbol.valueOf(msgProps.getContentEncoding())); + } + + if(msgProps.hasCorrelationId()) + { + props.setCorrelationId(msgProps.getCorrelationId()); + } + + if(msgProps.hasMessageId()) + { + props.setMessageId(msgProps.getMessageId()); + } + if(msgProps.hasReplyTo()) + { + props.setReplyTo(msgProps.getReplyTo().getExchange()+"/"+msgProps.getReplyTo().getRoutingKey()); + } + if(msgProps.hasContentType()) + { + props.setContentType(Symbol.valueOf(msgProps.getContentType())); + + // Modify the content type when we are dealing with java object messages produced by the Qpid 0.x client + if(props.getContentType() == Symbol.valueOf("application/java-object-stream")) + { + props.setContentType(Symbol.valueOf("application/x-java-serialized-object")); + } + } + + props.setSubject(serverMessage.getRoutingKey()); + + if(msgProps.hasUserId()) + { + props.setUserId(new Binary(msgProps.getUserId())); + } + + sections.add(props); + + if(msgProps.getApplicationHeaders() != null) + { + sections.add(new ApplicationProperties(msgProps.getApplicationHeaders())); + } + } + return new MessageMetaData_1_0(sections, sectionEncoder); + } + + @Override + public String getType() + { + return "v0-10 to v1-0"; + } +} diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter b/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter new file mode 100644 index 0000000000..045eb72cb1 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-10-to-1-0/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.converter.v0_10_v1_0.MessageConverter_0_10_to_1_0 diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/build.xml b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/build.xml new file mode 100644 index 0000000000..f405eb8868 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/build.xml @@ -0,0 +1,33 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins AMQP 0.8 to 0.10 MessageConversion" default="build"> + <property name="module.depends" value="common broker broker-plugins/amqp-0-8-protocol broker-plugins/amqp-0-10-protocol" /> + <property name="module.test.depends" value="common/tests broker/tests" /> + + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided -Sqpid-broker-plugins-amqp-0-10-protocol=provided -Sqpid-broker-plugins-amqp-0-8-protocol=provided"/> + <property name="broker-plugins-amqp-msg-conv-0-8-to-0-10.libs" value="" /> + + <property name="broker.plugin" value="true"/> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks"/> + +</project> diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v0_10/MessageConverter_0_10_to_0_8.java b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v0_10/MessageConverter_0_10_to_0_8.java new file mode 100644 index 0000000000..e832ef7569 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v0_10/MessageConverter_0_10_to_0_8.java @@ -0,0 +1,271 @@ +package org.apache.qpid.server.protocol.converter.v0_8_v0_10; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import org.apache.qpid.AMQPInvalidClassException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.plugin.MessageConverter; +import org.apache.qpid.server.protocol.v0_10.MessageTransferMessage; +import org.apache.qpid.server.protocol.v0_8.AMQMessage; +import org.apache.qpid.server.protocol.v0_8.MessageMetaData; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.MessageDeliveryMode; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.ReplyTo; + +public class MessageConverter_0_10_to_0_8 implements MessageConverter<MessageTransferMessage, AMQMessage> +{ + private static final int BASIC_CLASS_ID = 60; + + public static BasicContentHeaderProperties convertContentHeaderProperties(MessageTransferMessage messageTransferMessage, + VirtualHost vhost) + { + BasicContentHeaderProperties props = new BasicContentHeaderProperties(); + + Header header = messageTransferMessage.getHeader(); + DeliveryProperties deliveryProps = header.getDeliveryProperties(); + MessageProperties messageProps = header.getMessageProperties(); + + if(deliveryProps != null) + { + if(deliveryProps.hasDeliveryMode()) + { + props.setDeliveryMode((byte) (deliveryProps.getDeliveryMode() == MessageDeliveryMode.PERSISTENT + ? BasicContentHeaderProperties.PERSISTENT + : BasicContentHeaderProperties.NON_PERSISTENT)); + } + if(deliveryProps.hasExpiration()) + { + props.setExpiration(deliveryProps.getExpiration()); + } + if(deliveryProps.hasPriority()) + { + props.setPriority((byte) deliveryProps.getPriority().getValue()); + } + if(deliveryProps.hasTimestamp()) + { + props.setTimestamp(deliveryProps.getTimestamp()); + } + } + if(messageProps != null) + { + if(messageProps.hasAppId()) + { + props.setAppId(new AMQShortString(messageProps.getAppId())); + } + if(messageProps.hasContentType()) + { + props.setContentType(messageProps.getContentType()); + } + if(messageProps.hasCorrelationId()) + { + props.setCorrelationId(new AMQShortString(messageProps.getCorrelationId())); + } + if(messageProps.hasContentEncoding()) + { + props.setEncoding(messageProps.getContentEncoding()); + } + if(messageProps.hasMessageId()) + { + props.setMessageId("ID:" + messageProps.getMessageId().toString()); + } + if(messageProps.hasReplyTo()) + { + ReplyTo replyTo = messageProps.getReplyTo(); + String exchangeName = replyTo.getExchange(); + String routingKey = replyTo.getRoutingKey(); + if(exchangeName == null) + { + exchangeName = ""; + } + + Exchange exchange = vhost.getExchange(exchangeName); + String exchangeClass = exchange == null + ? ExchangeDefaults.DIRECT_EXCHANGE_CLASS + : exchange.getTypeName(); + props.setReplyTo(exchangeClass + "://" + exchangeName + "//?routingkey='" + (routingKey == null + ? "" + : routingKey + "'")); + + } + if(messageProps.hasUserId()) + { + props.setUserId(new AMQShortString(messageProps.getUserId())); + } + + if(messageProps.hasApplicationHeaders()) + { + Map<String, Object> appHeaders = new HashMap<String, Object>(messageProps.getApplicationHeaders()); + if(messageProps.getApplicationHeaders().containsKey("x-jms-type")) + { + props.setType(String.valueOf(appHeaders.remove("x-jms-type"))); + } + + FieldTable ft = new FieldTable(); + for(Map.Entry<String, Object> entry : appHeaders.entrySet()) + { + try + { + ft.put(new AMQShortString(entry.getKey()), entry.getValue()); + } + catch (AMQPInvalidClassException e) + { + // TODO + // log here, but ignore - just can;t convert + } + } + props.setHeaders(ft); + + } + } + + return props; + } + + @Override + public Class<MessageTransferMessage> getInputClass() + { + return MessageTransferMessage.class; + } + + @Override + public Class<AMQMessage> getOutputClass() + { + return AMQMessage.class; + } + + @Override + public AMQMessage convert(MessageTransferMessage message, VirtualHost vhost) + { + return new AMQMessage(convertToStoredMessage(message, vhost)); + } + + private StoredMessage<MessageMetaData> convertToStoredMessage(final MessageTransferMessage message, + VirtualHost vhost) + { + final MessageMetaData metaData = convertMetaData(message, vhost); + return new StoredMessage<org.apache.qpid.server.protocol.v0_8.MessageMetaData>() + { + @Override + public MessageMetaData getMetaData() + { + return metaData; + } + + @Override + public long getMessageNumber() + { + return message.getMessageNumber(); + } + + @Override + public void addContent(int offsetInMessage, ByteBuffer src) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getContent(int offsetInMessage, ByteBuffer dst) + { + return message.getContent(dst, offsetInMessage); + } + + @Override + public ByteBuffer getContent(int offsetInMessage, int size) + { + return message.getContent(offsetInMessage, size); + } + + @Override + public StoreFuture flushToStore() + { + return StoreFuture.IMMEDIATE_FUTURE; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } + + private MessageMetaData convertMetaData(MessageTransferMessage message, VirtualHost vhost) + { + return new MessageMetaData(convertPublishBody(message), + convertContentHeaderBody(message, vhost), + 1, + message.getArrivalTime()); + } + + private ContentHeaderBody convertContentHeaderBody(MessageTransferMessage message, VirtualHost vhost) + { + BasicContentHeaderProperties props = convertContentHeaderProperties(message, vhost); + ContentHeaderBody chb = new ContentHeaderBody(props, BASIC_CLASS_ID); + chb.setBodySize(message.getSize()); + return chb; + } + + private MessagePublishInfo convertPublishBody(MessageTransferMessage message) + { + DeliveryProperties delvProps = message.getHeader().getDeliveryProperties(); + final AMQShortString exchangeName = (delvProps == null || delvProps.getExchange() == null) + ? null + : new AMQShortString(delvProps.getExchange()); + final AMQShortString routingKey = (delvProps == null || delvProps.getRoutingKey() == null) + ? null + : new AMQShortString(delvProps.getRoutingKey()); + final boolean immediate = delvProps != null && delvProps.getImmediate(); + final boolean mandatory = delvProps != null && !delvProps.getDiscardUnroutable(); + + return new MessagePublishInfo() + { + @Override + public AMQShortString getExchange() + { + return exchangeName; + } + + @Override + public void setExchange(AMQShortString exchange) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isImmediate() + { + return immediate; + } + + @Override + public boolean isMandatory() + { + return mandatory; + } + + @Override + public AMQShortString getRoutingKey() + { + return routingKey; + } + }; + } + + @Override + public String getType() + { + return "v0-10 to v0-8"; + } +} diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v0_10/MessageConverter_0_8_to_0_10.java b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v0_10/MessageConverter_0_8_to_0_10.java new file mode 100644 index 0000000000..e1e8fbd9d3 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v0_10/MessageConverter_0_8_to_0_10.java @@ -0,0 +1,226 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.converter.v0_8_v0_10; + +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.UUID; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.plugin.MessageConverter; +import org.apache.qpid.server.protocol.v0_10.MessageMetaData_0_10; +import org.apache.qpid.server.protocol.v0_10.MessageTransferMessage; +import org.apache.qpid.server.protocol.v0_8.AMQMessage; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.MessageDeliveryPriority; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.ReplyTo; +import org.apache.qpid.url.AMQBindingURL; + +public class MessageConverter_0_8_to_0_10 implements MessageConverter<AMQMessage, MessageTransferMessage> +{ + @Override + public Class<AMQMessage> getInputClass() + { + return AMQMessage.class; + } + + @Override + public Class<MessageTransferMessage> getOutputClass() + { + return MessageTransferMessage.class; + } + + @Override + public MessageTransferMessage convert(AMQMessage message_0_8, VirtualHost vhost) + { + return new MessageTransferMessage(convertToStoredMessage(message_0_8), null); + } + + private StoredMessage<MessageMetaData_0_10> convertToStoredMessage(final AMQMessage message_0_8) + { + final MessageMetaData_0_10 messageMetaData_0_10 = convertMetaData(message_0_8); + return new StoredMessage<MessageMetaData_0_10>() + { + @Override + public MessageMetaData_0_10 getMetaData() + { + return messageMetaData_0_10; + } + + @Override + public long getMessageNumber() + { + return message_0_8.getMessageNumber(); + } + + @Override + public void addContent(int offsetInMessage, ByteBuffer src) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getContent(int offsetInMessage, ByteBuffer dst) + { + return message_0_8.getContent(dst, offsetInMessage); + } + + @Override + public ByteBuffer getContent(int offsetInMessage, int size) + { + return message_0_8.getContent(offsetInMessage, size); + } + + @Override + public StoreFuture flushToStore() + { + return StoreFuture.IMMEDIATE_FUTURE; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } + + private MessageMetaData_0_10 convertMetaData(AMQMessage message_0_8) + { + DeliveryProperties deliveryProps = new DeliveryProperties(); + MessageProperties messageProps = new MessageProperties(); + + int size = (int) message_0_8.getSize(); + ByteBuffer body = ByteBuffer.allocate(size); + message_0_8.getContent(body, 0); + body.flip(); + + BasicContentHeaderProperties properties = + (BasicContentHeaderProperties) message_0_8.getContentHeaderBody().getProperties(); + + final AMQShortString exchange = message_0_8.getMessagePublishInfo().getExchange(); + if(exchange != null) + { + deliveryProps.setExchange(exchange.toString()); + } + + deliveryProps.setExpiration(message_0_8.getExpiration()); + deliveryProps.setImmediate(message_0_8.isImmediate()); + deliveryProps.setPriority(MessageDeliveryPriority.get(properties.getPriority())); + deliveryProps.setRoutingKey(message_0_8.getRoutingKey()); + deliveryProps.setTimestamp(properties.getTimestamp()); + + messageProps.setContentEncoding(properties.getEncodingAsString()); + messageProps.setContentLength(size); + if(properties.getAppId() != null) + { + messageProps.setAppId(properties.getAppId().getBytes()); + } + messageProps.setContentType(properties.getContentTypeAsString()); + if(properties.getCorrelationId() != null) + { + messageProps.setCorrelationId(properties.getCorrelationId().getBytes()); + } + + if(properties.getReplyTo() != null && properties.getReplyTo().length() != 0) + { + String origReplyToString = properties.getReplyTo().asString(); + ReplyTo replyTo = new ReplyTo(); + // if the string looks like a binding URL, then attempt to parse it... + try + { + AMQBindingURL burl = new AMQBindingURL(origReplyToString); + AMQShortString routingKey = burl.getRoutingKey(); + if(routingKey != null) + { + replyTo.setRoutingKey(routingKey.asString()); + } + + AMQShortString exchangeName = burl.getExchangeName(); + if(exchangeName != null) + { + replyTo.setExchange(exchangeName.asString()); + } + } + catch (URISyntaxException e) + { + replyTo.setRoutingKey(origReplyToString); + } + messageProps.setReplyTo(replyTo); + + } + + if(properties.getMessageId() != null) + { + try + { + String messageIdAsString = properties.getMessageIdAsString(); + if(messageIdAsString.startsWith("ID:")) + { + messageIdAsString = messageIdAsString.substring(3); + } + UUID uuid = UUID.fromString(messageIdAsString); + messageProps.setMessageId(uuid); + } + catch(IllegalArgumentException e) + { + // ignore - can't parse + } + } + + + + if(properties.getUserId() != null) + { + messageProps.setUserId(properties.getUserId().getBytes()); + } + + FieldTable fieldTable = properties.getHeaders(); + + Map<String, Object> appHeaders = FieldTable.convertToMap(fieldTable); + + if(properties.getType() != null) + { + appHeaders.put("x-jms-type", properties.getTypeAsString()); + } + + + messageProps.setApplicationHeaders(appHeaders); + + Header header = new Header(deliveryProps, messageProps, null); + + + return new MessageMetaData_0_10(header, size, message_0_8.getArrivalTime()); + } + + @Override + public String getType() + { + return "v0-8 to v0-10"; + } +} diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter new file mode 100644 index 0000000000..5aeef786ae --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-0-10/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.converter.v0_8_v0_10.MessageConverter_0_8_to_0_10 +org.apache.qpid.server.protocol.converter.v0_8_v0_10.MessageConverter_0_10_to_0_8 diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/build.xml b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/build.xml new file mode 100644 index 0000000000..897da14d1f --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/build.xml @@ -0,0 +1,33 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins AMQP 0.8 to 1.0 MessageConversion" default="build"> + <property name="module.depends" value="common broker amqp-1-0-common broker-plugins/amqp-0-8-protocol broker-plugins/amqp-1-0-protocol" /> + <property name="module.test.depends" value="common/tests broker/tests" /> + + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided -Sqpid-amqp-1-0-common=provided -Sqpid-broker-plugins-amqp-1-0-protocol=provided -Sqpid-broker-plugins-amqp-0-8-protocol=provided"/> + <property name="broker-plugins-amqp-msg-conv-0-8-to-1-0.libs" value="" /> + + <property name="broker.plugin" value="true"/> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks"/> + +</project> diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v1_0/MessageConverter_0_8_to_1_0.java b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v1_0/MessageConverter_0_8_to_1_0.java new file mode 100644 index 0000000000..0d9d59ff56 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/src/main/java/org/apache/qpid/server/protocol/converter/v0_8_v1_0/MessageConverter_0_8_to_1_0.java @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.protocol.converter.v0_8_v1_0; + +import java.util.ArrayList; +import java.util.List; +import org.apache.qpid.amqp_1_0.messaging.SectionEncoder; +import org.apache.qpid.amqp_1_0.type.Binary; +import org.apache.qpid.amqp_1_0.type.Section; +import org.apache.qpid.amqp_1_0.type.Symbol; +import org.apache.qpid.amqp_1_0.type.UnsignedByte; +import org.apache.qpid.amqp_1_0.type.UnsignedInteger; +import org.apache.qpid.amqp_1_0.type.messaging.ApplicationProperties; +import org.apache.qpid.amqp_1_0.type.messaging.Header; +import org.apache.qpid.amqp_1_0.type.messaging.Properties; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.protocol.v0_8.AMQMessage; +import org.apache.qpid.server.protocol.v1_0.MessageConverter_to_1_0; +import org.apache.qpid.server.protocol.v1_0.MessageMetaData_1_0; + +public class MessageConverter_0_8_to_1_0 extends MessageConverter_to_1_0<AMQMessage> +{ + @Override + public Class<AMQMessage> getInputClass() + { + return AMQMessage.class; + } + + protected MessageMetaData_1_0 convertMetaData(final AMQMessage serverMessage, SectionEncoder sectionEncoder) + { + + List<Section> sections = new ArrayList<Section>(3); + + Header header = new Header(); + + header.setDurable(serverMessage.isPersistent()); + + BasicContentHeaderProperties contentHeader = + (BasicContentHeaderProperties) serverMessage.getContentHeaderBody().getProperties(); + + header.setPriority(UnsignedByte.valueOf(contentHeader.getPriority())); + final long expiration = serverMessage.getExpiration(); + final long arrivalTime = serverMessage.getArrivalTime(); + + if(expiration > arrivalTime) + { + header.setTtl(UnsignedInteger.valueOf(expiration - arrivalTime)); + } + sections.add(header); + + + Properties props = new Properties(); + + /* + TODO: The following properties are not currently set: + + creationTime + groupId + groupSequence + replyToGroupId + to + */ + + props.setContentEncoding(Symbol.valueOf(contentHeader.getEncodingAsString())); + + props.setContentType(Symbol.valueOf(contentHeader.getContentTypeAsString())); + + // Modify the content type when we are dealing with java object messages produced by the Qpid 0.x client + if(props.getContentType() == Symbol.valueOf("application/java-object-stream")) + { + props.setContentType(Symbol.valueOf("application/x-java-serialized-object")); + } + + final AMQShortString correlationId = contentHeader.getCorrelationId(); + if(correlationId != null) + { + props.setCorrelationId(new Binary(correlationId.getBytes())); + } + + final AMQShortString messageId = contentHeader.getMessageId(); + if(messageId != null) + { + props.setMessageId(new Binary(messageId.getBytes())); + } + props.setReplyTo(String.valueOf(contentHeader.getReplyTo())); + + props.setSubject(serverMessage.getRoutingKey()); + if(contentHeader.getUserId() != null) + { + props.setUserId(new Binary(contentHeader.getUserId().getBytes())); + } + + sections.add(props); + + sections.add(new ApplicationProperties(FieldTable.convertToMap(contentHeader.getHeaders()))); + + return new MessageMetaData_1_0(sections, sectionEncoder); + } + + @Override + public String getType() + { + return "v0-8 to v1-0"; + } +} diff --git a/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter new file mode 100644 index 0000000000..cf4643f2b8 --- /dev/null +++ b/qpid/java/broker-plugins/amqp-msg-conv-0-8-to-1-0/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageConverter @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.protocol.converter.v0_8_v1_0.MessageConverter_0_8_to_1_0 diff --git a/qpid/java/broker-plugins/derby-store/build.xml b/qpid/java/broker-plugins/derby-store/build.xml index e93b81aad7..be3d72f059 100644 --- a/qpid/java/broker-plugins/derby-store/build.xml +++ b/qpid/java/broker-plugins/derby-store/build.xml @@ -22,6 +22,7 @@ <property name="module.genpom" value="true"/> <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + <property name="broker-plugins-derby-store.libs" value="" /> <property name="broker.plugin" value="true"/> diff --git a/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStore.java b/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStore.java index ac310d02c9..bc8d157346 100644 --- a/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStore.java +++ b/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStore.java @@ -132,7 +132,7 @@ public class DerbyMessageStore extends AbstractJDBCMessageStore implements Messa _driverClass = (Class<Driver>) Class.forName(SQL_DRIVER_NAME); String defaultPath = System.getProperty("QPID_WORK") + File.separator + "derbyDB"; - String databasePath = (String) virtualHost.getAttribute(VirtualHost.STORE_PATH); + String databasePath = isConfigStoreOnly() ? (String) virtualHost.getAttribute(VirtualHost.CONFIG_STORE_PATH) : (String) virtualHost.getAttribute(VirtualHost.STORE_PATH); if(databasePath == null) { databasePath = defaultPath; diff --git a/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStoreFactory.java b/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStoreFactory.java index 1b111ad65e..47a451ccf6 100644 --- a/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStoreFactory.java +++ b/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStoreFactory.java @@ -24,10 +24,12 @@ import java.util.Collections; import java.util.Map; import org.apache.commons.configuration.Configuration; import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.plugin.DurableConfigurationStoreFactory; import org.apache.qpid.server.plugin.MessageStoreFactory; +import org.apache.qpid.server.store.DurableConfigurationStore; import org.apache.qpid.server.store.MessageStore; -public class DerbyMessageStoreFactory implements MessageStoreFactory +public class DerbyMessageStoreFactory implements MessageStoreFactory, DurableConfigurationStoreFactory { @Override @@ -37,6 +39,12 @@ public class DerbyMessageStoreFactory implements MessageStoreFactory } @Override + public DurableConfigurationStore createDurableConfigurationStore() + { + return new DerbyMessageStore(); + } + + @Override public MessageStore createMessageStore() { return new DerbyMessageStore(); @@ -52,12 +60,25 @@ public class DerbyMessageStoreFactory implements MessageStoreFactory @Override public void validateAttributes(Map<String, Object> attributes) { - Object storePath = attributes.get(VirtualHost.STORE_PATH); - if(!(storePath instanceof String)) + if(getType().equals(attributes.get(VirtualHost.STORE_TYPE))) + { + Object storePath = attributes.get(VirtualHost.STORE_PATH); + if(!(storePath instanceof String)) + { + throw new IllegalArgumentException("Attribute '"+ VirtualHost.STORE_PATH + +"' is required and must be of type String."); + + } + } + if(getType().equals(attributes.get(VirtualHost.CONFIG_STORE_TYPE))) { - throw new IllegalArgumentException("Attribute '"+ VirtualHost.STORE_PATH - +"' is required and must be of type String."); + Object storePath = attributes.get(VirtualHost.CONFIG_STORE_PATH); + if(!(storePath instanceof String)) + { + throw new IllegalArgumentException("Attribute '"+ VirtualHost.CONFIG_STORE_PATH + +"' is required and must be of type String."); + } } } diff --git a/qpid/java/broker-plugins/derby-store/src/main/resources/services/org.apache.qpid.server.plugin.MessageStoreFactory b/qpid/java/broker-plugins/derby-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory index 88ca1fed5e..88ca1fed5e 100644 --- a/qpid/java/broker-plugins/derby-store/src/main/resources/services/org.apache.qpid.server.plugin.MessageStoreFactory +++ b/qpid/java/broker-plugins/derby-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory diff --git a/qpid/java/broker-plugins/derby-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory b/qpid/java/broker-plugins/derby-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory new file mode 100644 index 0000000000..88ca1fed5e --- /dev/null +++ b/qpid/java/broker-plugins/derby-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.store.derby.DerbyMessageStoreFactory diff --git a/qpid/java/broker-plugins/derby-store/src/test/java/org/apache/qpid/server/store/derby/DerbyMessageStoreConfigurationTest.java b/qpid/java/broker-plugins/derby-store/src/test/java/org/apache/qpid/server/store/derby/DerbyMessageStoreConfigurationTest.java index ffb6ac479a..4a1a3251e3 100644 --- a/qpid/java/broker-plugins/derby-store/src/test/java/org/apache/qpid/server/store/derby/DerbyMessageStoreConfigurationTest.java +++ b/qpid/java/broker-plugins/derby-store/src/test/java/org/apache/qpid/server/store/derby/DerbyMessageStoreConfigurationTest.java @@ -28,18 +28,17 @@ public class DerbyMessageStoreConfigurationTest extends AbstractDurableConfigura private DerbyMessageStore _derbyMessageStore; @Override - protected void onReopenStore() - { - _derbyMessageStore = null; - } - - @Override protected DerbyMessageStore createMessageStore() throws Exception { createStoreIfNecessary(); return _derbyMessageStore; } + @Override + protected void closeMessageStore() throws Exception + { + closeStoreIfNecessary(); + } private void createStoreIfNecessary() { @@ -55,4 +54,19 @@ public class DerbyMessageStoreConfigurationTest extends AbstractDurableConfigura createStoreIfNecessary(); return _derbyMessageStore; } + + @Override + protected void closeConfigStore() throws Exception + { + closeStoreIfNecessary(); + } + + private void closeStoreIfNecessary() throws Exception + { + if (_derbyMessageStore != null) + { + _derbyMessageStore.close(); + _derbyMessageStore = null; + } + } } diff --git a/qpid/java/broker-plugins/jdbc-provider-bone/build.xml b/qpid/java/broker-plugins/jdbc-provider-bone/build.xml index df21d3ed38..eb3f40734f 100644 --- a/qpid/java/broker-plugins/jdbc-provider-bone/build.xml +++ b/qpid/java/broker-plugins/jdbc-provider-bone/build.xml @@ -21,6 +21,8 @@ <property name="module.genpom" value="true"/> <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + <property name="broker.plugin" value="true"/> + <import file="../../module.xml" /> <condition property="download.bonecp.jar"> diff --git a/qpid/java/broker-plugins/jdbc-store/build.xml b/qpid/java/broker-plugins/jdbc-store/build.xml index de6ec59845..9b6aeb32d6 100644 --- a/qpid/java/broker-plugins/jdbc-store/build.xml +++ b/qpid/java/broker-plugins/jdbc-store/build.xml @@ -22,6 +22,7 @@ <property name="module.genpom" value="true"/> <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + <property name="broker-plugins-jdbc-store.libs" value="" /> <property name="broker.plugin" value="true"/> diff --git a/qpid/java/broker-plugins/jdbc-store/src/main/java/org/apache/qpid/server/store/jdbc/JDBCMessageStore.java b/qpid/java/broker-plugins/jdbc-store/src/main/java/org/apache/qpid/server/store/jdbc/JDBCMessageStore.java index f8d93536bb..6fdfa40561 100644 --- a/qpid/java/broker-plugins/jdbc-store/src/main/java/org/apache/qpid/server/store/jdbc/JDBCMessageStore.java +++ b/qpid/java/broker-plugins/jdbc-store/src/main/java/org/apache/qpid/server/store/jdbc/JDBCMessageStore.java @@ -51,6 +51,7 @@ public class JDBCMessageStore extends AbstractJDBCMessageStore implements Messag public static final String TYPE = "JDBC"; public static final String CONNECTION_URL = "connectionURL"; + public static final String CONFIG_CONNECTION_URL = "configConnectionURL"; protected String _connectionURL; private ConnectionProvider _connectionProvider; @@ -280,11 +281,20 @@ public class JDBCMessageStore extends AbstractJDBCMessageStore implements Messag throws ClassNotFoundException, SQLException { + String connectionURL; + if(!isConfigStoreOnly()) + { + connectionURL = virtualHost.getAttribute(CONNECTION_URL) == null + ? String.valueOf(virtualHost.getAttribute(VirtualHost.STORE_PATH)) + : String.valueOf(virtualHost.getAttribute(CONNECTION_URL)); + } + else + { + connectionURL = virtualHost.getAttribute(CONFIG_CONNECTION_URL) == null + ? String.valueOf(virtualHost.getAttribute(VirtualHost.CONFIG_STORE_PATH)) + : String.valueOf(virtualHost.getAttribute(CONFIG_CONNECTION_URL)); - String connectionURL = virtualHost.getAttribute(CONNECTION_URL) == null - ? String.valueOf(virtualHost.getAttribute(VirtualHost.STORE_PATH)) - : String.valueOf(virtualHost.getAttribute(CONNECTION_URL)); - + } JDBCDetails details = null; String[] components = connectionURL.split(":",3); diff --git a/qpid/java/broker-plugins/jdbc-store/src/main/java/org/apache/qpid/server/store/jdbc/JDBCMessageStoreFactory.java b/qpid/java/broker-plugins/jdbc-store/src/main/java/org/apache/qpid/server/store/jdbc/JDBCMessageStoreFactory.java index 82d2275156..1144eaaf18 100644 --- a/qpid/java/broker-plugins/jdbc-store/src/main/java/org/apache/qpid/server/store/jdbc/JDBCMessageStoreFactory.java +++ b/qpid/java/broker-plugins/jdbc-store/src/main/java/org/apache/qpid/server/store/jdbc/JDBCMessageStoreFactory.java @@ -24,10 +24,12 @@ import java.util.HashMap; import java.util.Map; import org.apache.commons.configuration.Configuration; import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.plugin.DurableConfigurationStoreFactory; import org.apache.qpid.server.plugin.MessageStoreFactory; +import org.apache.qpid.server.store.DurableConfigurationStore; import org.apache.qpid.server.store.MessageStore; -public class JDBCMessageStoreFactory implements MessageStoreFactory +public class JDBCMessageStoreFactory implements MessageStoreFactory, DurableConfigurationStoreFactory { @Override @@ -43,6 +45,12 @@ public class JDBCMessageStoreFactory implements MessageStoreFactory } @Override + public DurableConfigurationStore createDurableConfigurationStore() + { + return new JDBCMessageStore(); + } + + @Override public Map<String, Object> convertStoreConfiguration(Configuration storeConfiguration) { Map<String,Object> convertedMap = new HashMap<String,Object>(); @@ -67,15 +75,32 @@ public class JDBCMessageStoreFactory implements MessageStoreFactory @Override public void validateAttributes(Map<String, Object> attributes) { - Object connectionURL = attributes.get(JDBCMessageStore.CONNECTION_URL); - if(!(connectionURL instanceof String)) + if(getType().equals(attributes.get(VirtualHost.STORE_TYPE))) + { + Object connectionURL = attributes.get(JDBCMessageStore.CONNECTION_URL); + if(!(connectionURL instanceof String)) + { + Object storePath = attributes.get(VirtualHost.STORE_PATH); + if(!(storePath instanceof String)) + { + throw new IllegalArgumentException("Attribute '"+ JDBCMessageStore.CONNECTION_URL + +"' is required and must be of type String."); + + } + } + } + if(getType().equals(attributes.get(VirtualHost.CONFIG_STORE_TYPE))) { - Object storePath = attributes.get(VirtualHost.STORE_PATH); - if(!(storePath instanceof String)) + Object connectionURL = attributes.get(JDBCMessageStore.CONFIG_CONNECTION_URL); + if(!(connectionURL instanceof String)) { - throw new IllegalArgumentException("Attribute '"+ JDBCMessageStore.CONNECTION_URL - +"' is required and must be of type String."); + Object storePath = attributes.get(VirtualHost.CONFIG_STORE_PATH); + if(!(storePath instanceof String)) + { + throw new IllegalArgumentException("Attribute '"+ JDBCMessageStore.CONFIG_CONNECTION_URL + +"' is required and must be of type String."); + } } } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/virtualhost/store/memory/add.html b/qpid/java/broker-plugins/jdbc-store/src/main/java/resources/virtualhost/store/pool/none/add.html index e69de29bb2..e69de29bb2 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/virtualhost/store/memory/add.html +++ b/qpid/java/broker-plugins/jdbc-store/src/main/java/resources/virtualhost/store/pool/none/add.html diff --git a/qpid/java/broker-plugins/jdbc-store/src/main/resources/services/org.apache.qpid.server.plugin.MessageStoreFactory b/qpid/java/broker-plugins/jdbc-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory index a77458f27d..a77458f27d 100644 --- a/qpid/java/broker-plugins/jdbc-store/src/main/resources/services/org.apache.qpid.server.plugin.MessageStoreFactory +++ b/qpid/java/broker-plugins/jdbc-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory diff --git a/qpid/java/broker-plugins/jdbc-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory b/qpid/java/broker-plugins/jdbc-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory new file mode 100644 index 0000000000..a77458f27d --- /dev/null +++ b/qpid/java/broker-plugins/jdbc-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.store.jdbc.JDBCMessageStoreFactory diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java index d87a1755da..c6623aefcf 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java @@ -20,9 +20,9 @@ */ package org.apache.qpid.server.management.plugin; -import java.io.File; import java.lang.reflect.Type; import java.net.SocketAddress; +import java.security.GeneralSecurityException; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; @@ -31,6 +31,7 @@ import java.util.HashSet; import java.util.Map; import java.util.UUID; +import javax.net.ssl.SSLContext; import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.logging.actors.CurrentActor; @@ -39,11 +40,15 @@ import org.apache.qpid.server.management.plugin.filter.ForbiddingAuthorisationFi import org.apache.qpid.server.management.plugin.filter.RedirectingAuthorisationFilter; import org.apache.qpid.server.management.plugin.servlet.DefinedFileServlet; import org.apache.qpid.server.management.plugin.servlet.FileServlet; +import org.apache.qpid.server.management.plugin.servlet.LogFileServlet; import org.apache.qpid.server.management.plugin.servlet.rest.HelperServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.LogFileListingServlet; import org.apache.qpid.server.management.plugin.servlet.rest.LogRecordsServlet; import org.apache.qpid.server.management.plugin.servlet.rest.LogoutServlet; import org.apache.qpid.server.management.plugin.servlet.rest.MessageContentServlet; import org.apache.qpid.server.management.plugin.servlet.rest.MessageServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.PreferencesServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.UserPreferencesServlet; import org.apache.qpid.server.management.plugin.servlet.rest.RestServlet; import org.apache.qpid.server.management.plugin.servlet.rest.SaslServlet; import org.apache.qpid.server.management.plugin.servlet.rest.StructureServlet; @@ -60,6 +65,7 @@ import org.apache.qpid.server.model.GroupProvider; import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.Plugin; import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.PreferencesProvider; import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.Session; @@ -70,7 +76,6 @@ import org.apache.qpid.server.model.User; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.adapter.AbstractPluginAdapter; import org.apache.qpid.server.plugin.PluginFactory; -import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.util.MapValueConverter; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.DispatcherType; @@ -238,13 +243,17 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem { throw new IllegalConfigurationException("Key store is not configured. Cannot start management on HTTPS port without keystore"); } - String keyStorePath = (String)keyStore.getAttribute(KeyStore.PATH); - String keyStorePassword = keyStore.getPassword(); - SslContextFactory factory = new SslContextFactory(); - factory.setKeyStorePath(keyStorePath); - factory.setKeyStorePassword(keyStorePassword); - + try + { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyStore.getKeyManagers(), null, null); + factory.setSslContext(sslContext); + } + catch (GeneralSecurityException e) + { + throw new RuntimeException("Cannot configure port " + port.getName() + " for transport " + Transport.SSL, e); + } connector = new SslSocketConnector(factory); } else @@ -288,7 +297,10 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem addRestServlet(root, "keystore", KeyStore.class); addRestServlet(root, "truststore", TrustStore.class); addRestServlet(root, "plugin", Plugin.class); + addRestServlet(root, "preferencesprovider", AuthenticationProvider.class, PreferencesProvider.class); + root.addServlet(new ServletHolder(new UserPreferencesServlet()), "/rest/userpreferences/*"); + root.addServlet(new ServletHolder(new PreferencesServlet()), "/rest/preferences"); root.addServlet(new ServletHolder(new StructureServlet()), "/rest/structure"); root.addServlet(new ServletHolder(new MessageServlet()), "/rest/message/*"); root.addServlet(new ServletHolder(new MessageContentServlet()), "/rest/message-content/*"); @@ -312,6 +324,15 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.txt"); root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.xsl"); root.addServlet(new ServletHolder(new HelperServlet()), "/rest/helper"); + root.addServlet(new ServletHolder(new LogFileListingServlet()), "/rest/logfiles"); + root.addServlet(new ServletHolder(new LogFileServlet()), "/rest/logfile"); + + String[] timeZoneFiles = {"africa", "antarctica", "asia", "australasia", "backward", + "etcetera", "europe", "northamerica", "pacificnew", "southamerica"}; + for (String timeZoneFile : timeZoneFiles) + { + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "/dojo/dojox/date/zoneinfo/" + timeZoneFile); + } final SessionManager sessionManager = root.getSessionHandler().getSessionManager(); sessionManager.setSessionCookie(JSESSIONID_COOKIE_PREFIX + lastPort); @@ -407,9 +428,9 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem } @Override - public SubjectCreator getSubjectCreator(SocketAddress localAddress) + public AuthenticationProvider getAuthenticationProvider(SocketAddress localAddress) { - return getBroker().getSubjectCreator(localAddress); + return getBroker().getAuthenticationProvider(localAddress); } @Override diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java index 56919e2e6b..7d89daa427 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java @@ -22,7 +22,7 @@ package org.apache.qpid.server.management.plugin; import java.net.SocketAddress; -import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.model.AuthenticationProvider; public interface HttpManagementConfiguration { @@ -34,5 +34,5 @@ public interface HttpManagementConfiguration boolean isHttpBasicAuthenticationEnabled(); - SubjectCreator getSubjectCreator(SocketAddress localAddress); + AuthenticationProvider getAuthenticationProvider(SocketAddress localAddress); } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java index 4c6e5bf63e..990ff1c53b 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java @@ -168,7 +168,7 @@ public class HttpManagementUtil { Subject subject = null; SocketAddress localAddress = getSocketAddress(request); - SubjectCreator subjectCreator = managementConfig.getSubjectCreator(localAddress); + SubjectCreator subjectCreator = managementConfig.getAuthenticationProvider(localAddress).getSubjectCreator(); String remoteUser = request.getRemoteUser(); if (remoteUser != null || subjectCreator.isAnonymousAuthenticationAllowed()) diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileDetails.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileDetails.java new file mode 100644 index 0000000000..09dabd0e73 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileDetails.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.qpid.server.management.plugin.log; + +import java.io.File; + +public class LogFileDetails +{ + private String _name; + private File _location; + private String _mimeType; + private long _size; + private long _lastModified; + private String _appenderName; + + public LogFileDetails(String name, String appenderName, File location, String mimeType, long fileSize, long lastUpdateTime) + { + super(); + _name = name; + _location = location; + _mimeType = mimeType; + _size = fileSize; + _lastModified = lastUpdateTime; + _appenderName = appenderName; + } + + public String getName() + { + return _name; + } + + public File getLocation() + { + return _location; + } + + public String getMimeType() + { + return _mimeType; + } + + public long getSize() + { + return _size; + } + + public long getLastModified() + { + return _lastModified; + } + + public String getAppenderName() + { + return _appenderName; + } + + @Override + public String toString() + { + return "LogFileDetails [name=" + _name + "]"; + } + +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileHelper.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileHelper.java new file mode 100644 index 0000000000..03d98d020b --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileHelper.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.qpid.server.management.plugin.log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.log4j.Appender; +import org.apache.log4j.FileAppender; +import org.apache.log4j.QpidCompositeRollingAppender; + +public class LogFileHelper +{ + public static final String GZIP_MIME_TYPE = "application/x-gzip"; + public static final String TEXT_MIME_TYPE = "text/plain"; + public static final String ZIP_MIME_TYPE = "application/zip"; + public static final String GZIP_EXTENSION = ".gz"; + private static final int BUFFER_LENGTH = 1024 * 4; + private Collection<Appender> _appenders; + + public LogFileHelper(Collection<Appender> appenders) + { + super(); + _appenders = appenders; + } + + public List<LogFileDetails> findLogFileDetails(String[] requestedFiles) + { + List<LogFileDetails> logFiles = new ArrayList<LogFileDetails>(); + Map<String, List<LogFileDetails>> cache = new HashMap<String, List<LogFileDetails>>(); + for (int i = 0; i < requestedFiles.length; i++) + { + String[] paths = requestedFiles[i].split("/"); + if (paths.length != 2) + { + throw new IllegalArgumentException("Log file name '" + requestedFiles[i] + "' does not include an appender name"); + } + + String appenderName = paths[0]; + String fileName = paths[1]; + + List<LogFileDetails> appenderFiles = cache.get(appenderName); + if (appenderFiles == null) + { + Appender fileAppender = null; + for (Appender appender : _appenders) + { + if (appenderName.equals(appender.getName())) + { + fileAppender = appender; + break; + } + } + if (fileAppender == null) + { + continue; + } + appenderFiles = getAppenderFiles(fileAppender, true); + if (appenderFiles == null) + { + continue; + } + cache.put(appenderName, appenderFiles); + } + for (LogFileDetails logFileDetails : appenderFiles) + { + if (logFileDetails.getName().equals(fileName)) + { + logFiles.add(logFileDetails); + } + } + } + return logFiles; + } + + public List<LogFileDetails> getLogFileDetails(boolean includeLogFileLocation) + { + List<LogFileDetails> results = new ArrayList<LogFileDetails>(); + for (Appender appender : _appenders) + { + List<LogFileDetails> appenderFiles = getAppenderFiles(appender, includeLogFileLocation); + if (appenderFiles != null) + { + results.addAll(appenderFiles); + } + } + return results; + } + + public void writeLogFiles(List<LogFileDetails> logFiles, OutputStream os) throws IOException + { + ZipOutputStream out = new ZipOutputStream(os); + try + { + addLogFileEntries(logFiles, out); + } + finally + { + out.close(); + } + } + + public void writeLogFile(File file, OutputStream os) throws IOException + { + FileInputStream fis = new FileInputStream(file); + try + { + byte[] bytes = new byte[BUFFER_LENGTH]; + int length = 1; + while ((length = fis.read(bytes)) != -1) + { + os.write(bytes, 0, length); + } + } + finally + { + fis.close(); + } + } + + private List<LogFileDetails> getAppenderFiles(Appender appender, boolean includeLogFileLocation) + { + if (appender instanceof QpidCompositeRollingAppender) + { + return listAppenderFiles((QpidCompositeRollingAppender) appender, includeLogFileLocation); + } + else if (appender instanceof FileAppender) + { + return listAppenderFiles((FileAppender) appender, includeLogFileLocation); + } + return null; + } + + private List<LogFileDetails> listAppenderFiles(FileAppender appender, boolean includeLogFileLocation) + { + String appenderFilePath = appender.getFile(); + File appenderFile = new File(appenderFilePath); + if (appenderFile.exists()) + { + return listLogFiles(appenderFile.getParentFile(), appenderFile.getName(), appender.getName(), "", includeLogFileLocation); + } + return Collections.emptyList(); + } + + private List<LogFileDetails> listAppenderFiles(QpidCompositeRollingAppender appender, boolean includeLogFileLocation) + { + List<LogFileDetails> files = listAppenderFiles((FileAppender) appender, includeLogFileLocation); + String appenderFilePath = appender.getFile(); + File appenderFile = new File(appenderFilePath); + File backupFolder = new File(appender.getBackupFilesToPath()); + if (backupFolder.exists()) + { + String backFolderName = backupFolder.getName() + "/"; + List<LogFileDetails> backedUpFiles = listLogFiles(backupFolder, appenderFile.getName(), appender.getName(), + backFolderName, includeLogFileLocation); + files.addAll(backedUpFiles); + } + return files; + } + + private List<LogFileDetails> listLogFiles(File parent, String baseFileName, String appenderName, String relativePath, + boolean includeLogFileLocation) + { + List<LogFileDetails> files = new ArrayList<LogFileDetails>(); + for (File file : parent.listFiles()) + { + String name = file.getName(); + if (name.startsWith(baseFileName)) + { + files.add(new LogFileDetails(name, appenderName, includeLogFileLocation ? file : null, getMimeType(name), file.length(), + file.lastModified())); + } + } + return files; + } + + private String getMimeType(String fileName) + { + if (fileName.endsWith(GZIP_EXTENSION)) + { + return GZIP_MIME_TYPE; + } + return TEXT_MIME_TYPE; + } + + private void addLogFileEntries(List<LogFileDetails> files, ZipOutputStream out) throws IOException + { + for (LogFileDetails logFileDetails : files) + { + File file = logFileDetails.getLocation(); + if (file.exists()) + { + ZipEntry entry = new ZipEntry(logFileDetails.getAppenderName() + "/" + logFileDetails.getName()); + entry.setSize(file.length()); + out.putNextEntry(entry); + writeLogFile(file, out); + out.closeEntry(); + } + out.flush(); + } + } + +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/LogFileServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/LogFileServlet.java new file mode 100644 index 0000000000..1fa03dc3dc --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/LogFileServlet.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.qpid.server.management.plugin.servlet; + +import java.io.IOException; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.LogManager; +import org.apache.qpid.server.management.plugin.log.LogFileDetails; +import org.apache.qpid.server.management.plugin.log.LogFileHelper; +import org.apache.qpid.server.management.plugin.servlet.rest.AbstractServlet; + +public class LogFileServlet extends AbstractServlet +{ + private static final long serialVersionUID = 1L; + + public static final String LOGS_FILE_NAME = "qpid-logs-%s.zip"; + public static final String DATE_FORMAT = "yyyy-MM-dd-mmHHss"; + + @SuppressWarnings("unchecked") + private LogFileHelper _helper = new LogFileHelper(Collections.list(LogManager.getRootLogger().getAllAppenders())); + + @Override + protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException, + ServletException + { + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + + if (!getBroker().getSecurityManager().authoriseLogsAccess()) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Log files download is denied"); + return; + } + + String[] requestedFiles = request.getParameterValues("l"); + + if (requestedFiles == null || requestedFiles.length == 0) + { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + List<LogFileDetails> logFiles = null; + + try + { + logFiles = _helper.findLogFileDetails(requestedFiles); + } + catch(IllegalArgumentException e) + { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + if (logFiles.size() == 0) + { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + + String fileName = String.format(LOGS_FILE_NAME, new SimpleDateFormat(DATE_FORMAT).format(new Date())); + response.setStatus(HttpServletResponse.SC_OK); + response.setHeader("Content-Disposition", "attachment;filename=" + fileName); + response.setContentType(LogFileHelper.ZIP_MIME_TYPE); + + OutputStream os = response.getOutputStream(); + try + { + _helper.writeLogFiles(logFiles, os); + } + finally + { + if (os != null) + { + os.close(); + } + } + } + +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java index 9614ded3d8..c0f4b55f64 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java @@ -21,6 +21,7 @@ package org.apache.qpid.server.management.plugin.servlet.rest; import java.io.IOException; +import java.io.PrintWriter; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; @@ -40,6 +41,10 @@ import org.apache.qpid.server.management.plugin.HttpManagementConfiguration; import org.apache.qpid.server.management.plugin.HttpManagementUtil; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.security.SecurityManager; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; public abstract class AbstractServlet extends HttpServlet { @@ -219,14 +224,7 @@ public abstract class AbstractServlet extends HttpServlet } finally { - try - { - SecurityManager.setThreadSubject(null); - } - finally - { - AMQShortString.clearLocalCache(); - } + SecurityManager.setThreadSubject(null); } } @@ -261,4 +259,20 @@ public abstract class AbstractServlet extends HttpServlet throw new RuntimeException("Failed to send error response code " + errorCode, e); } } + + protected void sendJsonResponse(Object object, HttpServletResponse response) throws IOException, + JsonGenerationException, JsonMappingException + { + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader ("Expires", 0); + response.setContentType("application/json"); + + final PrintWriter writer = response.getWriter(); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, object); + } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/HelperServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/HelperServlet.java index 75e5bd9842..9ba36bb5c2 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/HelperServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/HelperServlet.java @@ -41,6 +41,8 @@ import org.codehaus.jackson.map.SerializationConfig; public class HelperServlet extends AbstractServlet { + private static final long serialVersionUID = 1L; + private static final String PARAM_ACTION = "action"; private Map<String, Action> _actions; @@ -55,6 +57,8 @@ public class HelperServlet extends AbstractServlet new ListAuthenticationProviderAttributes(), new ListBrokerAttribute(Broker.SUPPORTED_VIRTUALHOST_STORE_TYPES, "ListMessageStoreTypes"), new ListBrokerAttribute(Broker.SUPPORTED_VIRTUALHOST_TYPES, "ListVirtualHostTypes"), + new ListBrokerAttribute(Broker.SUPPORTED_PREFERENCES_PROVIDERS_TYPES, "ListPreferencesProvidersTypes"), + new ListBrokerAttribute(Broker.PRODUCT_VERSION, "version"), new ListGroupProviderAttributes(), new ListAccessControlProviderAttributes(), new PluginClassProviderAction() diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogFileListingServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogFileListingServlet.java new file mode 100644 index 0000000000..b6face18e3 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogFileListingServlet.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.LogManager; +import org.apache.qpid.server.management.plugin.log.LogFileDetails; +import org.apache.qpid.server.management.plugin.log.LogFileHelper; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +public class LogFileListingServlet extends AbstractServlet +{ + private static final long serialVersionUID = 1L; + + @SuppressWarnings("unchecked") + private LogFileHelper _helper = new LogFileHelper(Collections.list(LogManager.getRootLogger().getAllAppenders())); + + @Override + protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException, + ServletException + { + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + + if (!getBroker().getSecurityManager().authoriseLogsAccess()) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Log files download is denied"); + return; + } + + List<LogFileDetails> logFiles = _helper.getLogFileDetails(false); + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + final PrintWriter writer = response.getWriter(); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, logFiles); + + response.setStatus(HttpServletResponse.SC_OK); + } + +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java index f2cf5d7734..35523ddf0f 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java @@ -31,6 +31,10 @@ import org.codehaus.jackson.map.SerializationConfig; public class LogRecordsServlet extends AbstractServlet { + private static final long serialVersionUID = 2L; + + public static final String PARAM_LAST_LOG_ID = "lastLogId"; + public LogRecordsServlet() { super(); @@ -46,12 +50,31 @@ public class LogRecordsServlet extends AbstractServlet response.setHeader("Pragma","no-cache"); response.setDateHeader ("Expires", 0); + if (!getBroker().getSecurityManager().authoriseLogsAccess()) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Broker logs access is denied"); + return; + } + + long lastLogId = 0; + try + { + lastLogId = Long.parseLong(request.getParameter(PARAM_LAST_LOG_ID)); + } + catch(Exception e) + { + // ignore null and incorrect parameter values + } + List<Map<String,Object>> logRecords = new ArrayList<Map<String, Object>>(); LogRecorder logRecorder = getBroker().getLogRecorder(); for(LogRecorder.Record record : logRecorder) { - logRecords.add(logRecordToObject(record)); + if (record.getId() > lastLogId) + { + logRecords.add(logRecordToObject(record)); + } } final PrintWriter writer = response.getWriter(); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java index 49e0c2b1bf..83208516c7 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java @@ -80,6 +80,7 @@ public class MessageServlet extends AbstractServlet response.setHeader("Cache-Control","no-cache"); response.setHeader("Pragma","no-cache"); response.setDateHeader ("Expires", 0); + response.setContentType("application/json"); final PrintWriter writer = response.getWriter(); ObjectMapper mapper = new ObjectMapper(); @@ -352,20 +353,32 @@ public class MessageServlet extends AbstractServlet if(messageHeader != null) { addIfPresent(object, "messageId", messageHeader.getMessageId()); - addIfPresent(object, "expirationTime", messageHeader.getExpiration()); + addIfPresentAndNotZero(object, "expirationTime", messageHeader.getExpiration()); addIfPresent(object, "applicationId", messageHeader.getAppId()); addIfPresent(object, "correlationId", messageHeader.getCorrelationId()); addIfPresent(object, "encoding", messageHeader.getEncoding()); addIfPresent(object, "mimeType", messageHeader.getMimeType()); addIfPresent(object, "priority", messageHeader.getPriority()); addIfPresent(object, "replyTo", messageHeader.getReplyTo()); - addIfPresent(object, "timestamp", messageHeader.getTimestamp()); + addIfPresentAndNotZero(object, "timestamp", messageHeader.getTimestamp()); addIfPresent(object, "type", messageHeader.getType()); addIfPresent(object, "userId", messageHeader.getUserId()); } } + private void addIfPresentAndNotZero(Map<String, Object> object, String name, Object property) + { + if(property instanceof Number) + { + Number value = (Number)property; + if (value.longValue() != 0) + { + object.put(name, property); + } + } + } + private void addIfPresent(Map<String, Object> object, String name, Object property) { if(property != null) diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/PreferencesServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/PreferencesServlet.java new file mode 100644 index 0000000000..bf2a88a2c1 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/PreferencesServlet.java @@ -0,0 +1,137 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.io.IOException; +import java.net.SocketAddress; +import java.security.Principal; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.qpid.server.management.plugin.HttpManagementUtil; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.codehaus.jackson.map.ObjectMapper; + +public class PreferencesServlet extends AbstractServlet +{ + private static final long serialVersionUID = 1L; + + @Override + protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException, + ServletException + { + PreferencesProvider preferencesProvider = getPreferencesProvider(request); + if (preferencesProvider == null) + { + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Preferences provider is not configured"); + return; + } + String userName = getAuthenticatedUserName(request); + Map<String, Object> preferences = preferencesProvider.getPreferences(userName); + if (preferences == null) + { + preferences = Collections.<String, Object>emptyMap(); + } + sendJsonResponse(preferences, response); + } + + /* + * replace preferences + */ + @Override + protected void doPutWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + PreferencesProvider preferencesProvider = getPreferencesProvider(request); + if (preferencesProvider == null) + { + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Preferences provider is not configured"); + return; + } + String userName = getAuthenticatedUserName(request); + + ObjectMapper mapper = new ObjectMapper(); + + @SuppressWarnings("unchecked") + Map<String, Object> newPreferences = mapper.readValue(request.getInputStream(), LinkedHashMap.class); + + preferencesProvider.deletePreferences(userName); + Map<String, Object> preferences = preferencesProvider.setPreferences(userName, newPreferences); + if (preferences == null) + { + preferences = Collections.<String, Object>emptyMap(); + } + sendJsonResponse(preferences, response); + } + + /* + * update preferences + */ + @Override + protected void doPostWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + PreferencesProvider preferencesProvider = getPreferencesProvider(request); + if (preferencesProvider == null) + { + throw new IllegalStateException("Preferences provider is not configured"); + } + String userName = getAuthenticatedUserName(request); + + ObjectMapper mapper = new ObjectMapper(); + + @SuppressWarnings("unchecked") + Map<String, Object> newPreferences = mapper.readValue(request.getInputStream(), LinkedHashMap.class); + Map<String, Object> preferences = preferencesProvider.setPreferences(userName, newPreferences); + if (preferences == null) + { + preferences = Collections.<String, Object>emptyMap(); + } + sendJsonResponse(preferences, response); + } + + private String getAuthenticatedUserName(HttpServletRequest request) + { + Subject subject = getAuthorisedSubject(request); + Principal principal = AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(subject); + return principal.getName(); + } + + private PreferencesProvider getPreferencesProvider(HttpServletRequest request) + { + SocketAddress localAddress = HttpManagementUtil.getSocketAddress(request); + AuthenticationProvider authenticationProvider = getManagementConfiguration().getAuthenticationProvider(localAddress); + if (authenticationProvider == null) + { + throw new IllegalStateException("Authentication provider is not found"); + } + return authenticationProvider.getPreferencesProvider(); + } +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java index 1cebe3ec19..8ea0aa538a 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java @@ -123,7 +123,7 @@ public class RestServlet extends AbstractServlet if(names.size() > _hierarchy.length) { - throw new IllegalArgumentException("Too many entries in path"); + throw new IllegalArgumentException("Too many entries in path. Expected " + _hierarchy.length + "; path: " + names); } } @@ -337,7 +337,7 @@ public class RestServlet extends AbstractServlet if(names.size() != _hierarchy.length) { throw new IllegalArgumentException("Path to object to create must be fully specified. " - + "Found " + names.size() + " expecting " + _hierarchy.length); + + "Found " + names + " of size " + names.size() + " expecting " + _hierarchy.length); } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java index b67c83dc7a..2b035fed8f 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java @@ -278,7 +278,7 @@ public class SaslServlet extends AbstractServlet private SubjectCreator getSubjectCreator(HttpServletRequest request) { SocketAddress localAddress = HttpManagementUtil.getSocketAddress(request); - return HttpManagementUtil.getManagementConfiguration(getServletContext()).getSubjectCreator(localAddress); + return HttpManagementUtil.getManagementConfiguration(getServletContext()).getAuthenticationProvider(localAddress).getSubjectCreator(); } @Override diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/UserPreferencesServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/UserPreferencesServlet.java new file mode 100644 index 0000000000..808e3210dd --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/UserPreferencesServlet.java @@ -0,0 +1,215 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.User; +import org.apache.qpid.server.security.access.Operation; + +public class UserPreferencesServlet extends AbstractServlet +{ + private static final Logger LOGGER = Logger.getLogger(UserPreferencesServlet.class); + private static final long serialVersionUID = 1L; + + @Override + protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException, + ServletException + { + String[] pathElements = null; + if (request.getPathInfo() != null && request.getPathInfo().length() > 0) + { + pathElements = request.getPathInfo().substring(1).split("/"); + } + if (pathElements != null && pathElements.length > 1) + { + getUserPreferences(pathElements[0], pathElements[1], response); + } + else + { + getUserList(pathElements, response); + } + } + + private void getUserPreferences(String authenticationProviderName, String userId, HttpServletResponse response) + throws IOException + { + if (!userPreferencesOperationAuthorized(userId)) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Vieweing of preferences is not allowed"); + return; + } + Map<String, Object> preferences = null; + PreferencesProvider preferencesProvider = getPreferencesProvider(authenticationProviderName); + if (preferencesProvider == null) + { + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Preferences provider is not configured"); + return; + } + preferences = preferencesProvider.getPreferences(userId); + + sendJsonResponse(preferences, response); + } + + private void getUserList(String[] pathElements, HttpServletResponse response) throws IOException + { + List<Map<String, Object>> users = null; + try + { + users = getUsers(pathElements); + } + catch (Exception e) + { + LOGGER.debug("Bad preferences request", e); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); + } + sendJsonResponse(users, response); + } + + private PreferencesProvider getPreferencesProvider(String authenticationProviderName) + { + AuthenticationProvider authenticationProvider = getAuthenticationProvider(authenticationProviderName); + if (authenticationProvider == null) + { + throw new IllegalArgumentException(String.format("Authentication provider '%s' is not found", + authenticationProviderName)); + } + PreferencesProvider preferencesProvider = authenticationProvider.getPreferencesProvider(); + return preferencesProvider; + } + + private AuthenticationProvider getAuthenticationProvider(String authenticationProviderName) + { + Broker broker = getBroker(); + Collection<AuthenticationProvider> authenticationProviders = broker.getAuthenticationProviders(); + for (AuthenticationProvider authenticationProvider : authenticationProviders) + { + if (authenticationProviderName.equals(authenticationProvider.getName())) + { + return authenticationProvider; + } + } + return null; + } + + private List<Map<String, Object>> getUsers(String[] pathElements) + { + List<Map<String, Object>> users = new ArrayList<Map<String, Object>>(); + String authenticationProviderName = pathElements != null && pathElements.length > 0 ? pathElements[0] : null; + + Broker broker = getBroker(); + Collection<AuthenticationProvider> authenticationProviders = broker.getAuthenticationProviders(); + for (AuthenticationProvider authenticationProvider : authenticationProviders) + { + if (authenticationProviderName != null && !authenticationProvider.getName().equals(authenticationProviderName)) + { + continue; + } + PreferencesProvider preferencesProvider = authenticationProvider.getPreferencesProvider(); + if (preferencesProvider != null) + { + Set<String> usernames = preferencesProvider.listUserIDs(); + for (String name : usernames) + { + Map<String, Object> userMap = new HashMap<String, Object>(); + userMap.put(User.NAME, name); + userMap.put("authenticationProvider", authenticationProvider.getName()); + users.add(userMap); + } + } + } + return users; + } + + /* + * removes preferences + */ + @Override + protected void doDeleteWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException + { + final List<String[]> userData = new ArrayList<String[]>(); + for (String name : request.getParameterValues("user")) + { + String[] elements = name.split("/"); + if (elements.length != 2) + { + throw new IllegalArgumentException("Illegal parameter"); + } + userData.add(elements); + } + + if (!userData.isEmpty()) + { + Broker broker = getBroker(); + Collection<AuthenticationProvider> authenticationProviders = broker.getAuthenticationProviders(); + for (Iterator<String[]> it = userData.iterator(); it.hasNext();) + { + String[] data = (String[]) it.next(); + String authenticationProviderName = data[0]; + String userId = data[1]; + + for (AuthenticationProvider authenticationProvider : authenticationProviders) + { + if (authenticationProviderName.equals(authenticationProvider.getName())) + { + PreferencesProvider preferencesProvider = authenticationProvider.getPreferencesProvider(); + if (preferencesProvider != null) + { + Set<String> usernames = preferencesProvider.listUserIDs(); + if (usernames.contains(userId)) + { + if (!userPreferencesOperationAuthorized(userId)) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Deletion of preferences is not allowed"); + return; + } + preferencesProvider.deletePreferences(userId); + } + } + break; + } + } + } + } + + } + + private boolean userPreferencesOperationAuthorized(String userId) + { + return getBroker().getSecurityManager().authoriseUserOperation(Operation.UPDATE, userId); + } +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/addPreferencesProvider.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/addPreferencesProvider.html new file mode 100644 index 0000000000..ac5dd32119 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/addPreferencesProvider.html @@ -0,0 +1,47 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<div class="dijitHidden"> + <div data-dojo-type="dijit.Dialog" data-dojo-props="title:'Preferences Provider'" id="addPreferencesProvider"> + <form id="formAddPreferencesProvider" method="post" data-dojo-type="dijit.form.Form"> + <input type="hidden" id="preferencesProvider.id" name="id"/> + <div style="height:100px; width:420px; overflow: auto"> + <table class="tableContainer-table tableContainer-table-horiz" width="100%" cellspacing="1"> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Type*:</strong></td> + <td class="tableContainer-valueCell"><div id="addPreferencesProvider.selectPreferencesProviderDiv"></div></td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Name*:</strong></td> + <td class="tableContainer-valueCell"><input type="text" name="name" + id="preferencesProvider.name" data-dojo-type="dijit.form.ValidationTextBox" + data-dojo-props="placeHolder: 'Name', + required: true, + missingMessage: 'A name must be supplied', + title: 'Enter name', + pattern: '^[\x20-\x2e\x30-\x7F]{1,255}$'" /></td> + </tr> + </table> + <div id="preferencesProvider.fieldsContainer"></div> + </div> + <div class="dijitDialogPaneActionBar"> + <!-- submit buttons --> + <input type="submit" value="Save Preferences Provider" data-dojo-props="label: 'Save Preferences Provider'" data-dojo-type="dijit.form.Button" /> + <input type="button" value="Cancel" data-dojo-props="label: 'Cancel'" data-dojo-type="dijit.form.Button" id="addPreferencesProvider.cancelButton"/> + </div> + </form> + </div> +</div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/preferences/filesystempreferences/add.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/preferences/filesystempreferences/add.html new file mode 100644 index 0000000000..f46da4b017 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/preferences/filesystempreferences/add.html @@ -0,0 +1,29 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<table class="tableContainer-table tableContainer-table-horiz" style="margin:0; width:100%" cellspacing="1"> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Path*: </strong></td> + <td class="tableContainer-valueCell" > + <input type="text" name="path" + id="preferencesProvider.path" + data-dojo-type="dijit.form.ValidationTextBox" + data-dojo-props="placeHolder: 'Path/to/file', + required: true, + missingMessage: 'A path must be supplied', + title: 'Enter path'"/></td> + </tr> +</table> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/preferences/filesystempreferences/show.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/preferences/filesystempreferences/show.html new file mode 100644 index 0000000000..bc302d1e65 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/preferences/filesystempreferences/show.html @@ -0,0 +1,21 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 100px;">Path:</div> + <div class="fileSystemPreferencesProviderPath" style="float:left;"></div> +</div>
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/common/TimeZoneSelector.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/common/TimeZoneSelector.html new file mode 100644 index 0000000000..7027a4555c --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/common/TimeZoneSelector.html @@ -0,0 +1,55 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<table cellpadding="0" cellspacing="2"> + <tr> + <td>Region</td> + <td> + <select class='timezoneRegion' name="region" data-dojo-type="dijit/form/FilteringSelect" data-dojo-props=" + placeholder: 'Select region', + required: true, + missingMessage: 'A region must be supplied', + title: 'Select region', + autoComplete: true, + value:'undefined'"> + <option value="undefined">Undefined</option> + <option value="Africa">Africa</option> + <option value="America">America</option> + <option value="Antarctica">Antarctica</option> + <option value="Arctic">Arctic</option> + <option value="Asia">Asia</option> + <option value="Atlantic">Atlantic</option> + <option value="Australia">Australia</option> + <option value="Europe">Europe</option> + <option value="Indian">Indian</option> + <option value="Pacific">Pacific</option> + </select> + </td> + <td>City</td> + <td> + <select class='timezoneCity' name="city" data-dojo-type="dijit/form/FilteringSelect" data-dojo-props=" + placeholder: 'Select city', + required: true, + missingMessage: 'A city must be supplied', + title: 'Select city'"> + </select> + </td> + </tr> +</table>
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/css/common.css b/qpid/java/broker-plugins/management-http/src/main/java/resources/css/common.css index 4c8b79ab82..d9064f40c9 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/css/common.css +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/css/common.css @@ -92,4 +92,74 @@ div .messages { .formLabel-labelCell { font-weight: bold; +} + +.columnDefDialogButtonIcon { + background: url("../dojo/dojox/grid/enhanced/resources/images/sprite_icons.png") no-repeat; + background-position: -260px 2px; + width: 14px; + height: 14px; +} + +.logViewerIcon { + background: url("../images/log-viewer.png") no-repeat; + width: 14px; + height: 16px; +} + +.downloadLogsIcon { + background: url("../images/download.png") no-repeat; + width: 14px; + height: 14px; +} + +.dojoxGridFBarClearFilterButtontnIcon +{ + background: url("../dojo/dojox/grid/enhanced/resources/images/sprite_icons.png") no-repeat; + background-position: -120px -18px; + width: 14px; + height: 14px; +} + +.rowNumberLimitIcon +{ + background: url("../dojo/dojox/grid/enhanced/resources/images/sprite_icons.png") no-repeat; + background-position: -240px -18px; + width: 14px; + height: 14px; +} + +.gridRefreshIcon +{ + background: url("../images/refresh.png") no-repeat; + width: 16px; + height: 16px; +} + +.gridAutoRefreshIcon +{ + background: url("../images/auto-refresh.png") no-repeat; + width: 16px; + height: 16px; +} + +.redBackground tr{ background-color:#ffdcd7 !important; background-image: none !important;} +.yellowBackground tr{background-color:#fbfddf !important; background-image: none !important;} +.grayBackground tr{background-color:#eeeeee !important; background-image: none !important;} +.dojoxGridRowOdd.grayBackground tr{ background-color:#e9e9e9 !important; background-image: none !important;} +.dojoxGridRowOdd.yellowBackground tr{background-color:#fafdd5 !important; background-image: none !important;} +.dojoxGridRowOdd.redBackground tr{background-color:#f4c1c1 !important; background-image: none !important;} + +.preferencesIcon +{ + background: url("../images/gear.png") no-repeat; + width: 16px; + height: 16px; +} + +.helpIcon +{ + background: url("../images/help.png") no-repeat; + width: 16px; + height: 16px; }
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/grid/showColumnDefDialog.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/grid/showColumnDefDialog.html new file mode 100644 index 0000000000..5b6b8ad774 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/grid/showColumnDefDialog.html @@ -0,0 +1,32 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div> + <div> + <div>Select columns to display:</div> + <div class="columnList"></div> + </div> + <div class="dijitDialogPaneActionBar"> + <button value="Display" data-dojo-type="dijit.form.Button" + class="displayButton" data-dojo-props="label: 'Display' "></button> + <button value="Cancel" data-dojo-type="dijit.form.Button" data-dojo-props="label: 'Cancel'" + class="cancelButton"></button> + </div> +</div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/grid/showRowNumberLimitDialog.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/grid/showRowNumberLimitDialog.html new file mode 100644 index 0000000000..087d54c0f9 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/grid/showRowNumberLimitDialog.html @@ -0,0 +1,33 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div> + <div> + <div>Set the maximum number of rows to cache and display:</div> + <input class="rowNumberLimit" data-dojo-type="dijit.form.NumberSpinner" + data-dojo-props="invalidMessage: 'Invalid value', required: true, smallDelta: 1,mconstraints: {min:1,max:65535,places:0, pattern: '#####'}, label: 'Maximum number of rows:', name: 'rowNumberLimit'"></input> + </div> + <div class="dijitDialogPaneActionBar"> + <button value="Submit" data-dojo-type="dijit.form.Button" + class="submitButton" data-dojo-props="label: 'Submit' "></button> + <button value="Cancel" data-dojo-type="dijit.form.Button" data-dojo-props="label: 'Cancel'" + class="cancelButton"></button> + </div> +</div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/images/auto-refresh.png b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/auto-refresh.png Binary files differnew file mode 100644 index 0000000000..493636f467 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/auto-refresh.png diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/images/download.png b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/download.png Binary files differnew file mode 100644 index 0000000000..b64b41d476 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/download.png diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/images/gear.png b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/gear.png Binary files differnew file mode 100644 index 0000000000..0bb4394b46 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/gear.png diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/images/help.png b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/help.png Binary files differnew file mode 100644 index 0000000000..f7d3698d25 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/help.png diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/images/log-viewer.png b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/log-viewer.png Binary files differnew file mode 100644 index 0000000000..858fd48beb --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/log-viewer.png diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/images/qpid-logo.png b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/qpid-logo.png Binary files differindex 95d49ea469..ae0fbb462f 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/images/qpid-logo.png +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/qpid-logo.png diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/images/refresh.png b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/refresh.png Binary files differnew file mode 100644 index 0000000000..083044979b --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/images/refresh.png diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html index 4b97c464ec..4fc961ec12 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html @@ -42,7 +42,6 @@ var dojoConfig = { tlmSiblingOfDojo:false, - parseOnLoad:true, async:true, baseUrl: getContextPath(), packages:[ @@ -58,7 +57,14 @@ </script> <script> - require(["dijit/layout/BorderContainer", + var qpidHelpLocation = "http://qpid.apache.org/releases/qpid-"; + var qpidHelpURL = null; + var qpidPreferences = null; + require([ + "dojo/_base/xhr", + "dojo/parser", + "qpid/management/Preferences", + "dijit/layout/BorderContainer", "dijit/layout/TabContainer", "dijit/layout/ContentPane", "dijit/TitlePane", @@ -66,7 +72,18 @@ "qpid/management/treeView", "qpid/management/controller", "qpid/common/footer", - "qpid/authorization/checkUser"]); + "qpid/authorization/checkUser"], function(xhr, parser, Preferences){ + parser.parse(); + qpidPreferences = new Preferences(); + xhr.get({ + sync: true, + url: "rest/helper?action=version", + handleAs: "json" + }).then(function(qpidVersion) { + qpidHelpURL = qpidHelpLocation + qpidVersion + "/java-broker/book/index.html"; + }); + + }); </script> </head> @@ -75,7 +92,27 @@ <div id="pageLayout" data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="design: 'headline', gutters: false"> <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'top'"> <div id="header" class="header" style="float: left; width: 300px"></div> - <div id="login" style="float: right; display:none"><strong>User: </strong> <span id="authenticatedUser"></span><a href="logout">[logout]</a></div> + <div style="float: right;"> + <div id="login" style="display:none"><strong>User: </strong> <span id="authenticatedUser"></span><a href="logout">[logout]</a></div> + <div id="preferencesButton" style="float: right; margin-top: 0px;" data-dojo-type="dijit.form.DropDownButton" data-dojo-props="iconClass: 'preferencesIcon', title: 'Preferences', showLabel:false"> + <div data-dojo-type="dijit.Menu"> + <div data-dojo-type="dijit.MenuItem" data-dojo-props=" + iconClass: 'dijitIconFunction', + onClick: function(){ qpidPreferences.showDialog(); } "> + Preferences + </div> + <!-- + <div data-dojo-type="dijit.MenuItem" data-dojo-props="iconClass: 'dijitIconMail', onClick: function(){ console.log('TODO'); }"> + Contacts + </div> + --> + <div data-dojo-type="dijit.MenuItem" data-dojo-props="iconClass: 'helpIcon', onClick: function(){ + var newWindow = window.open(qpidHelpURL,'QpidHelp','height=600,width=600,scrollbars=1,location=1,resizable=1,status=0,toolbar=0,titlebar=1,menubar=0',true); newWindow.focus(); } "> + Help + </div> + </div> + </div> + </div> </div> <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'leading', splitter: true" style="width:20%"> <div qpid-type="treeView" qpid-props="query: 'rest/structure'" ></div> @@ -104,4 +141,4 @@ </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/checkUser.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/checkUser.js index 159c7458ed..d65e6c6e07 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/checkUser.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/checkUser.js @@ -29,7 +29,7 @@ var updateUI = function updateUI(data) if(data.user) { dom.byId("authenticatedUser").innerHTML = entities.encode(String(data.user)); - dom.byId("login").style.display = "block"; + dom.byId("login").style.display = "inline"; } }; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/TimeZoneSelector.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/TimeZoneSelector.js new file mode 100644 index 0000000000..287fbc9619 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/TimeZoneSelector.js @@ -0,0 +1,176 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define([ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/dom-construct", + "dojo/parser", + "dojo/query", + "dojo/store/Memory", + "dijit/_WidgetBase", + "dijit/registry", + "dojo/text!common/TimeZoneSelector.html", + "dijit/form/ComboBox", + "dijit/form/FilteringSelect", + "dojox/date/timezone", + "dojox/validate/us", + "dojox/validate/web", + "dojo/domReady!"], +function (declare, array, domConstruct, parser, query, Memory, _WidgetBase, registry, template) { + + var preferencesRegions = ["Africa","America","Antarctica","Arctic","Asia","Atlantic","Australia","Europe","Indian","Pacific"]; + + function initSupportedTimeZones() + { + var supportedTimeZones = []; + var allTimeZones = dojox.date.timezone.getAllZones(); + for(var i = 0; i < allTimeZones.length; i++) + { + var timeZone = allTimeZones[i]; + var elements = timeZone.split("/"); + if (elements.length > 1) + { + for(var j = 0; j<preferencesRegions.length; j++) + { + if (elements[0] == preferencesRegions[j]) + { + supportedTimeZones.push({id: timeZone, region: elements[0], city: elements.slice(1).join("/").replace("_", " ") }) + break; + } + } + } + } + return supportedTimeZones; + } + + function initSupportedRegions() + { + var supportedRegions = [{"id": "undefined", "name": "Undefined"}]; + for(var j = 0; j<preferencesRegions.length; j++) + { + supportedRegions.push({id: preferencesRegions[j], name: preferencesRegions[j] }); + } + return supportedRegions; + } + + return declare("qpid.common.TimeZoneSelector", [_WidgetBase], { + + value: null, + domNode: null, + _regionSelector: null, + _citySelector: null, + + constructor: function(args) + { + this._args = args; + }, + + buildRendering: function(){ + this.domNode = domConstruct.create("div", {innerHTML: template}); + parser.parse(this.domNode); + }, + + postCreate: function(){ + this.inherited(arguments); + + var supportedTimeZones = initSupportedTimeZones(); + + this._citySelector = registry.byNode(query(".timezoneCity", this.domNode)[0]); + this._citySelector.set("searchAttr", "city"); + this._citySelector.set("query", {region: /.*/}); + this._citySelector.set("labelAttr", "city"); + this._citySelector.set("store", new Memory({ data: supportedTimeZones })); + if (this._args.name) + { + this._citySelector.set("name", this._args.name); + } + this._regionSelector = registry.byNode(query(".timezoneRegion", this.domNode)[0]); + var supportedRegions = initSupportedRegions(); + this._regionSelector.set("store", new Memory({ data: supportedRegions })); + var self = this; + + this._regionSelector.on("change", function(value){ + if (value=="undefined") + { + self._citySelector.set("disabled", true); + self._citySelector.query.region = /.*/; + self.value = null; + self._citySelector.set("value", null); + } + else + { + self._citySelector.set("disabled", false); + self._citySelector.query.region = value || /.*/; + if (this.timeZone) + { + self._citySelector.set("value", this.timeZone); + this.timeZone = null; + } + else + { + self._citySelector.set("value", null); + } + } + }); + + this._citySelector.on("change", function(value){ + self.value = value; + }); + + this._setValueAttr(this._args.value); + }, + + _setValueAttr: function(value) + { + if (value) + { + var elements = value.split("/"); + if (elements.length > 1) + { + this._regionSelector.timeZone = value; + this._regionSelector.set("value", elements[0]); + this._citySelector.set("value", value); + } + else + { + this._regionSelector.set("value", "undefined"); + } + } + else + { + this._regionSelector.set("value", "undefined"); + } + this.value = value; + }, + + destroy: function() + { + if (this.domNode) + { + this.domNode.destroy(); + this.domNode = null; + } + _regionSelector: null; + _citySelector: null; + } + + }); +});
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js index f7ede1a7f7..ea3ba78372 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js @@ -23,12 +23,13 @@ define(["dojo/store/Memory", "dojo/data/ObjectStore", "dojo/store/Observable"], function (Memory, DataGrid, ObjectStore, Observable) { - function UpdatableStore( data, divName, structure, func, props, Grid ) { + function UpdatableStore( data, divName, structure, func, props, Grid, notObservable ) { var that = this; var GridType = DataGrid; - that.store = Observable(Memory({data: data, idProperty: "id"})); + that.memoryStore = new Memory({data: data, idProperty: "id"}); + that.store = notObservable? that.memoryStore : new Observable(that.memoryStore); that.dataStore = ObjectStore({objectStore: that.store}); var gridProperties = { store: that.dataStore, @@ -63,7 +64,7 @@ define(["dojo/store/Memory", UpdatableStore.prototype.update = function(data) { - + var changed = false; var store = this.store; var theItem; @@ -78,7 +79,7 @@ define(["dojo/store/Memory", } } store.remove(object.id); - + changed = true; }); // iterate over data... @@ -91,20 +92,84 @@ define(["dojo/store/Memory", if(theItem[ propName ] != data[i][ propName ]) { theItem[ propName ] = data[i][ propName ]; modified = true; + changed = true; } } } if(modified) { // ... check attributes for updates store.notify(theItem, data[i].id); + changed = true; } } else { // ,,, if not in the store then add store.put(data[i]); + changed = true; } } } + return changed; + }; + + function removeItemsFromArray(items, numberToRemove) + { + if (items) + { + if (numberToRemove > 0 && items.length > 0) + { + if (numberToRemove >= items.length) + { + numberToRemove = numberToRemove - items.length; + items.length = 0 + } + else + { + items.splice(0, numberToRemove); + numberToRemove = 0; + } + } + } + return numberToRemove; + }; + + UpdatableStore.prototype.append = function(data, limit) + { + var changed = false; + var items = this.memoryStore.data; + + if (limit) + { + var totalSize = items.length + (data ? data.length : 0); + var numberToRemove = totalSize - limit; + + if (numberToRemove > 0) + { + changed = true; + numberToRemove = removeItemsFromArray(items, numberToRemove); + if (numberToRemove > 0) + { + removeItemsFromArray(data, numberToRemove); + } + } + } + + if (data && data.length > 0) + { + changed = true; + items.push.apply(items, data); + } + + this.memoryStore.setData(items); + return changed; + }; + + UpdatableStore.prototype.close = function() + { + this.dataStore.close(); + this.dataStore = null; + this.store = null; + this.memoryStore = null; }; return UpdatableStore; }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/ColumnDefDialog.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/ColumnDefDialog.js new file mode 100644 index 0000000000..d285dfaad6 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/ColumnDefDialog.js @@ -0,0 +1,140 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +define([ + "dojo/_base/declare", + "dojo/_base/event", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/parser", + "dojo/dom-construct", + "dojo/query", + "dijit/registry", + "dijit/form/Button", + "dijit/form/CheckBox", + "dojox/grid/enhanced/plugins/Dialog", + "dojo/text!../../../grid/showColumnDefDialog.html", + "dojo/domReady!" +], function(declare, event, array, lang, parser, dom, query, registry, Button, CheckBox, Dialog, template ){ + + +return declare("qpid.common.grid.ColumnDefDialog", null, { + + grid: null, + containerNode: null, + _columns: [], + _dialog: null, + + constructor: function(args){ + var grid = this.grid = args.grid; + + this.containerNode = dom.create("div", {innerHTML: template}); + parser.parse(this.containerNode); + + var submitButton = registry.byNode(query(".displayButton", this.containerNode)[0]); + this.closeButton = registry.byNode(query(".cancelButton", this.containerNode)[0]); + var columnsContainer = query(".columnList", this.containerNode)[0]; + + this._buildColumnWidgets(columnsContainer); + + this._dialog = new Dialog({ + "refNode": this.grid.domNode, + "title": "Grid Columns", + "content": this.containerNode + }); + + var self = this; + submitButton.on("click", function(e){self._onColumnsSelect(e); }); + this.closeButton.on("click", function(e){self._dialog.hide(); }); + + this._dialog.startup(); + }, + + destroy: function(){ + this._dialog.destroyRecursive(); + this._dialog = null; + this.grid = null; + this.containerNode = null; + this._columns = null; + }, + + showDialog: function(){ + this._initColumnWidgets(); + this._dialog.show(); + }, + + _initColumnWidgets: function() + { + var cells = this.grid.layout.cells; + for(var i in cells) + { + var cell = cells[i]; + this._columns[cell.name].checked = !cell.hidden; + } + }, + + _onColumnsSelect: function(evt){ + event.stop(evt); + var grid = this.grid; + grid.beginUpdate(); + var cells = grid.layout.cells; + try + { + for(var i in cells) + { + var cell = cells[i]; + var widget = this._columns[cell.name]; + grid.layout.setColumnVisibility(i, widget.checked); + } + } + finally + { + grid.endUpdate(); + this._dialog.hide(); + } + }, + + _buildColumnWidgets: function(columnsContainer) + { + var cells = this.grid.layout.cells; + for(var i in cells) + { + var cell = cells[i]; + var widget = new dijit.form.CheckBox({ + required: false, + checked: !cell.hidden, + label: cell.name, + name: this.grid.id + "_cchb_ " + i + }); + + this._columns[cell.name] = widget; + + var div = dom.create("div"); + div.appendChild(widget.domNode); + div.appendChild(dom.create("span", {innerHTML: cell.name})); + + columnsContainer.appendChild(div); + } + } + + }); + +}); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilter.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilter.js new file mode 100644 index 0000000000..9c0baf3111 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilter.js @@ -0,0 +1,229 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/_base/array", + "dijit/Toolbar", + "dojox/grid/enhanced/_Plugin", + "dojox/grid/enhanced/plugins/Dialog", + "dojox/grid/enhanced/plugins/filter/FilterLayer", + "dojox/grid/enhanced/plugins/filter/FilterDefDialog", + "dojox/grid/enhanced/plugins/filter/FilterStatusTip", + "dojox/grid/enhanced/plugins/filter/ClearFilterConfirm", + "dojox/grid/EnhancedGrid", + "dojo/i18n!dojox/grid/enhanced/nls/Filter", + "qpid/common/grid/EnhancedFilterTools" +], function(declare, lang, array, Toolbar, _Plugin, + Dialog, FilterLayer, FilterDefDialog, FilterStatusTip, ClearFilterConfirm, EnhancedGrid, nls, EnhancedFilterTools){ + + // override CriteriaBox#_getColumnOptions to show criteria for hidden columns with EnhancedFilter + dojo.extend(dojox.grid.enhanced.plugins.filter.CriteriaBox, { + _getColumnOptions: function(){ + var colIdx = this.dlg.curColIdx >= 0 ? String(this.dlg.curColIdx) : "anycolumn"; + var filterHidden = this.plugin.filterHidden; + return array.map(array.filter(this.plugin.grid.layout.cells, function(cell){ + return !(cell.filterable === false || (!filterHidden && cell.hidden)); + }), function(cell){ + return { + label: cell.name || cell.field, + value: String(cell.index), + selected: colIdx == String(cell.index) + }; + }); + } + }); + + // Enhanced filter has extra functionality for refreshing, limiting rows, displaying/hiding columns in the grid + var EnhancedFilter = declare("qpid.common.grid.EnhancedFilter", _Plugin, { + // summary: + // Accept the same plugin parameters as dojox.grid.enhanced.plugins.Filter and the following: + // + // filterHidden: boolean: + // Whether to display filtering criteria for hidden columns. Default to true. + // + // defaulGridRowLimit: int: + // Default limit for numbers of items to cache in the gris dtore + // + // disableFiltering: boolean: + // Whether to disable a filtering including filter button, clear filter button and filter summary. + // + // toolbar: dijit.Toolbar: + // An instance of toolbar to add the enhanced filter widgets. + + + // name: String + // plugin name + name: "enhancedFilter", + + // filterHidden: Boolean + // whether to filter hidden columns + filterHidden: true, + + constructor: function(grid, args){ + // summary: + // See constructor of dojox.grid.enhanced._Plugin. + this.grid = grid; + this.nls = nls; + + args = this.args = lang.isObject(args) ? args : {}; + if(typeof args.ruleCount != 'number' || args.ruleCount < 0){ + args.ruleCount = 0; + } + var rc = this.ruleCountToConfirmClearFilter = args.ruleCountToConfirmClearFilter; + if(rc === undefined){ + this.ruleCountToConfirmClearFilter = 5; + } + + if (args.filterHidden){ + this.filterHidden = args.filterHidden; + } + this.defaulGridRowLimit = args.defaulGridRowLimit; + this.disableFiltering = args.disableFiltering; + + //Install UI components + var obj = { "plugin": this }; + + this.filterBar = ( args.toolbar && args.toolbar instanceof dijit.Toolbar) ? args.toolbar: new Toolbar(); + + if (!this.disableFiltering) + { + //Install filter layer + this._wrapStore(); + + this.clearFilterDialog = new Dialog({ + refNode: this.grid.domNode, + title: this.nls["clearFilterDialogTitle"], + content: new ClearFilterConfirm(obj) + }); + + this.filterDefDialog = new FilterDefDialog(obj); + + nls["statusTipTitleNoFilter"] = "Filter is not set"; + nls["statusTipMsg"] = "Click on 'Set Filter' button to specify filtering conditions"; + this.filterStatusTip = new FilterStatusTip(obj); + + var self = this; + var toggleClearFilterBtn = function (arg){ self.enhancedFilterTools.toggleClearFilterBtn(arg); }; + + this.filterBar.toggleClearFilterBtn = toggleClearFilterBtn; + + this.grid.isFilterBarShown = function (){return true}; + + this.connect(this.grid.layer("filter"), "onFilterDefined", function(filter){ + toggleClearFilterBtn(true); + }); + + //Expose the layer event to grid. + grid.onFilterDefined = function(){}; + this.connect(grid.layer("filter"), "onFilterDefined", function(filter){ + grid.onFilterDefined(grid.getFilter(), grid.getFilterRelation()); + }); + } + + // add extra buttons into toolbar + this.enhancedFilterTools = new EnhancedFilterTools({ + grid: grid, + toolbar: this.filterBar, + filterStatusTip: this.filterStatusTip, + clearFilterDialog: this.clearFilterDialog, + filterDefDialog: this.filterDefDialog, + defaulGridRowLimit: this.defaulGridRowLimit, + disableFiltering: this.disableFiltering, + nls: nls + }); + + this.filterBar.placeAt(this.grid.viewsHeaderNode, "before"); + this.filterBar.startup(); + + }, + + destroy: function(){ + this.inherited(arguments); + try + { + if (this.filterDefDialog) + { + this.filterDefDialog.destroy(); + this.filterDefDialog = null; + } + if (this.grid) + { + this.grid.unwrap("filter"); + this.grid = null; + } + if (this.filterBar) + { + this.filterBar.destroyRecursive(); + this.filterBar = null; + } + if (this.enhancedFilterTools) + { + this.enhancedFilterTools.destroy(); + this.enhancedFilterTools = null; + } + if (this.clearFilterDialog) + { + this.clearFilterDialog.destroyRecursive(); + this.clearFilterDialog = null; + } + if (this.filterStatusTip) + { + this.filterStatusTip.destroy(); + this.filterStatusTip = null; + } + this.args = null; + + }catch(e){ + console.warn("Filter.destroy() error:",e); + } + }, + + _wrapStore: function(){ + var g = this.grid; + var args = this.args; + var filterLayer = args.isServerSide ? new FilterLayer.ServerSideFilterLayer(args) : + new FilterLayer.ClientSideFilterLayer({ + cacheSize: args.filterCacheSize, + fetchAll: args.fetchAllOnFirstFilter, + getter: this._clientFilterGetter + }); + FilterLayer.wrap(g, "_storeLayerFetch", filterLayer); + + this.connect(g, "_onDelete", lang.hitch(filterLayer, "invalidate")); + }, + + onSetStore: function(store){ + this.filterDefDialog.clearFilter(true); + }, + + _clientFilterGetter: function(/* data item */ datarow,/* cell */cell, /* int */rowIndex){ + return cell.get(rowIndex, datarow); + } + + }); + + EnhancedGrid.registerPlugin(EnhancedFilter); + + return EnhancedFilter; + +}); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilterTools.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilterTools.js new file mode 100644 index 0000000000..b1645b4905 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilterTools.js @@ -0,0 +1,270 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +define([ + "dojo/_base/declare", + "dojo/_base/event", + "dijit/form/Button", + "dijit/form/ToggleButton", + "qpid/common/grid/RowNumberLimitDialog", + "qpid/common/grid/ColumnDefDialog", + "qpid/common/grid/FilterSummary" +], function(declare, event, Button, ToggleButton, RowNumberLimitDialog, ColumnDefDialog, FilterSummary){ + + var _stopEvent = function (evt){ + try{ + if(evt && evt.preventDefault){ + event.stop(evt); + } + }catch(e){} + }; + + return declare("qpid.common.grid.EnhancedFilterTools", null, { + + grid: null, + filterBar: null, + filterStatusTip: null, + clearFilterDialog: null, + filterDefDialog: null, + + columnDefDialog: null, + columnDefButton: null, + filterDefButton: null, + clearFilterButton: null, + filterSummary: null, + setRowNumberLimitButton: null, + setRowNumberLimitDialog: null, + refreshButton: null, + autoRefreshButton: null, + + constructor: function(params) + { + this.inherited(arguments); + + this.filterBar = params.toolbar; + this.grid = params.grid; + this.filterStatusTip= params.filterStatusTip; + this.clearFilterDialog = params.clearFilterDialog; + this.filterDefDialog = params.filterDefDialog; + + this._addRefreshButtons(); + this._addRowLimitButton(params.defaulGridRowLimit); + this._addColumnsButton(); + + if (!params.disableFiltering) + { + this._addFilteringTools(params.nls); + } + }, + + toggleClearFilterBtn: function(clearFlag) + { + var filterLayer = this.grid.layer("filter"); + var filterSet = filterLayer && filterLayer.filterDef && filterLayer.filterDef(); + this.clearFilterButton.set("disabled", !filterSet); + }, + + destroy: function() + { + this.inherited(arguments); + + if (this.columnDefDialog) + { + this.columnDefDialog.destroy(); + this.columnDefDialog = null; + } + if (this.columnDefButton) + { + this.columnDefButton.destroy(); + this.columnDefButton = null; + } + if (this.filterDefButton) + { + this.filterDefButton.destroy(); + this.filterDefButton = null; + } + if (this.clearFilterButton) + { + this.clearFilterButton.destroy(); + this.clearFilterButton = null; + } + if (this.filterSummary) + { + this.filterSummary.destroy(); + this.filterSummary = null; + } + if (this.setRowNumberLimitButton) + { + this.setRowNumberLimitButton.destroy(); + this.setRowNumberLimitButton = null; + } + if (this.setRowNumberLimitDialog) + { + this.setRowNumberLimitDialog.destroy(); + this.setRowNumberLimitDialog = null; + } + if (this.refreshButton) + { + this.refreshButton.destroy(); + this.refreshButton = null; + } + if (this.autoRefreshButton) + { + this.autoRefreshButton.destroy(); + this.autoRefreshButton = null; + } + + this.grid = null; + this.filterBar = null; + this.filterStatusTip = null; + this.clearFilterDialog = null; + this.filterDefDialog = null; + }, + + _addRefreshButtons: function() + { + var self = this; + this.refreshButton = new dijit.form.Button({ + label: "Refresh", + type: "button", + iconClass: "gridRefreshIcon", + title: "Manual Refresh" + }); + + this.autoRefreshButton = new dijit.form.ToggleButton({ + label: "Auto Refresh", + type: "button", + iconClass: "gridAutoRefreshIcon", + title: "Auto Refresh" + }); + + this.autoRefreshButton.on("change", function(value){ + self.grid.updater.updatable=value; + self.refreshButton.set("disabled", value); + }); + + this.refreshButton.on("click", function(value){ + self.grid.updater.performUpdate(); + }); + + this.filterBar.addChild(this.autoRefreshButton); + this.filterBar.addChild(this.refreshButton); + }, + + _addRowLimitButton: function(defaulGridRowLimit) + { + var self = this; + this.setRowNumberLimitButton = new dijit.form.Button({ + label: "Set Row Limit", + type: "button", + iconClass: "rowNumberLimitIcon", + title: "Set Row Number Limit" + }); + this.setRowNumberLimitButton.set("title", "Set Row Number Limit (Current: " + defaulGridRowLimit +")"); + + this.setRowNumberLimitDialog = new RowNumberLimitDialog(this.grid.domNode, function(newLimit){ + if (newLimit > 0 && self.grid.updater.appendLimit != newLimit ) + { + self.grid.updater.appendLimit = newLimit; + self.grid.updater.performRefresh([]); + self.setRowNumberLimitButton.set("title", "Set Row Number Limit (Current: " + newLimit +")"); + } + }); + + this.setRowNumberLimitButton.on("click", function(evt){ + self.setRowNumberLimitDialog.showDialog(self.grid.updater.appendLimit); + }); + + this.filterBar.addChild(this.setRowNumberLimitButton); + }, + + _addColumnsButton: function() + { + var self = this; + this.columnDefDialog = new ColumnDefDialog({grid: this.grid}); + + this.columnDefButton = new dijit.form.Button({ + label: "Display Columns", + type: "button", + iconClass: "columnDefDialogButtonIcon", + title: "Show/Hide Columns" + }); + + this.columnDefButton.on("click", function(e){ + _stopEvent(e); + self.columnDefDialog.showDialog(); + }); + + this.filterBar.addChild(this.columnDefButton); + }, + + _addFilteringTools: function(nls) + { + var self = this; + + this.filterDefButton = new dijit.form.Button({ + "class": "dojoxGridFBarBtn", + label: "Set Filter", + iconClass: "dojoxGridFBarDefFilterBtnIcon", + showLabel: "true", + title: "Define filter" + }); + + this.clearFilterButton = new dijit.form.Button({ + "class": "dojoxGridFBarBtn", + label: "Clear filter", + iconClass: "dojoxGridFBarClearFilterButtontnIcon", + showLabel: "true", + title: "Clear filter", + disabled: true + }); + + + this.filterDefButton.on("click", function(e){ + _stopEvent(e); + self.filterDefDialog.showDialog(); + }); + + this.clearFilterButton.on("click", function(e){ + _stopEvent(e); + if (self.ruleCountToConfirmClearFilter && self.filterDefDialog.getCriteria() >= self.ruleCountToConfirmClearFilter) + { + self.clearFilterDialog.show(); + } + else + { + self.grid.layer("filter").filterDef(null); + self.toggleClearFilterBtn(true) + } + }); + + this.filterSummary = new FilterSummary({grid: this.grid, filterStatusTip: this.filterStatusTip, nls: nls}); + + this.filterBar.addChild(this.filterDefButton); + this.filterBar.addChild(this.clearFilterButton); + + this.filterBar.addChild(new dijit.ToolbarSeparator()); + this.filterBar.addChild(this.filterSummary, "last"); + this.filterBar.getColumnIdx = function(coordX){return self.filterSummary._getColumnIdx(coordX);}; + + } + }); +});
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/FilterSummary.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/FilterSummary.js new file mode 100644 index 0000000000..2b1d960fa5 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/FilterSummary.js @@ -0,0 +1,173 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/_base/html", + "dojo/query", + "dojo/dom-construct", + "dojo/string", + "dojo/on", + "dijit/_WidgetBase" +], function(declare, lang, html, query, domConstruct, string, on, _WidgetBase){ + +return declare("qpid.common.grid.FilterSummary", [_WidgetBase], { + + domNode: null, + itemName: null, + filterStatusTip: null, + grid: null, + _handle_statusTooltip: null, + _timeout_statusTooltip: 300, + _nls: null, + + constructor: function(params) + { + this.inherited(arguments); + this.itemName = params.itemsName; + this.initialize(params.filterStatusTip, params.grid); + this._nls = params.nls; + }, + + buildRendering: function(){ + this.inherited(arguments); + var itemsName = this.itemName || this._nls["defaultItemsName"]; + var message = string.substitute(this._nls["filterBarMsgNoFilterTemplate"], [0, itemsName ]); + this.domNode = domConstruct.create("span", {innerHTML: message, "class": "dijit dijitReset dijitInline dijitButtonInline", role: "presentation" }); + }, + + postCreate: function(){ + this.inherited(arguments); + on(this.domNode, "mouseenter", lang.hitch(this, this._onMouseEnter)); + on(this.domNode, "mouseleave", lang.hitch(this, this._onMouseLeave)); + on(this.domNode, "mousemove", lang.hitch(this, this._onMouseMove)); + }, + + destroy: function() + { + this.inherited(arguments); + this.itemName = null; + this.filterStatusTip = null; + this.grid = null; + this._handle_statusTooltip = null; + this._filteredClass = null; + this._nls = null; + }, + + initialize: function(filterStatusTip, grid) + { + this.filterStatusTip = filterStatusTip; + this.grid = grid; + if (this.grid) + { + var filterLayer = grid.layer("filter"); + this.connect(filterLayer, "onFiltered", this.onFiltered); + } + }, + + onFiltered: function(filteredSize, originSize) + { + try + { + var itemsName = this.itemName || this._nls["defaultItemsName"], + msg = "", g = this.grid, + filterLayer = g.layer("filter"); + if(filterLayer.filterDef()){ + msg = string.substitute(this._nls["filterBarMsgHasFilterTemplate"], [filteredSize, originSize, itemsName]); + }else{ + msg = string.substitute(this._nls["filterBarMsgNoFilterTemplate"], [originSize, itemsName]); + } + this.domNode.innerHTML = msg; + } + catch(e) + { + // swallow and log exception + // otherwise grid rendering is screwed + console.error(e); + } + }, + + _getColumnIdx: function(coordX){ + var headers = query("[role='columnheader']", this.grid.viewsHeaderNode); + var idx = -1; + for(var i = headers.length - 1; i >= 0; --i){ + var coord = html.position(headers[i]); + if(coordX >= coord.x && coordX < coord.x + coord.w){ + idx = i; + break; + } + } + if(idx >= 0 && this.grid.layout.cells[idx].filterable !== false){ + return idx; + }else{ + return -1; + } + }, + + _setStatusTipTimeout: function(){ + this._clearStatusTipTimeout(); + this._handle_statusTooltip = setTimeout(lang.hitch(this,this._showStatusTooltip),this._timeout_statusTooltip); + }, + + _clearStatusTipTimeout: function(){ + if (this._handle_statusTooltip){ + clearTimeout(this._handle_statusTooltip); + } + this._handle_statusTooltip = null; + }, + + _showStatusTooltip: function(){ + this._handle_statusTooltip = null; + if(this.filterStatusTip){ + this.filterStatusTip.showDialog(this._tippos.x, this._tippos.y, this._getColumnIdx(this._tippos.x)); + } + }, + + _updateTipPosition: function(evt){ + this._tippos = { + x: evt.pageX, + y: evt.pageY + }; + }, + + _onMouseEnter: function(e){ + this._updateTipPosition(e); + if(this.filterStatusTip){ + this._setStatusTipTimeout(); + } + }, + + _onMouseMove: function(e){ + if(this.filterStatusTip){ + this._setStatusTipTimeout(); + if(this._handle_statusTooltip){ + this._updateTipPosition(e); + } + } + }, + + _onMouseLeave: function(e){ + this._clearStatusTipTimeout(); + }, + }); + +});
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/GridUpdater.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/GridUpdater.js new file mode 100644 index 0000000000..7016e4eb5b --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/GridUpdater.js @@ -0,0 +1,258 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/lang", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/UpdatableStore", + "qpid/common/util", + "dojo/store/Memory", + "dojo/data/ObjectStore", + "qpid/common/grid/EnhancedFilter", + "dojox/grid/enhanced/plugins/NestedSorting", + "dojo/domReady!"], + function (xhr, parser, array, lang, properties, updater, UpdatableStore, util, Memory, ObjectStore) { + + function GridUpdater(args, store) { + this.updatable = args.hasOwnProperty("updatable") ? args.updatable : true ; + this.serviceUrl = args.serviceUrl; + + this.onUpdate = args.onUpdate; + + this.appendData = args.append; + this.appendLimit = args.appendLimit; + this.initialData = args.data; + this.initializeStore(store); + }; + + GridUpdater.prototype.buildUpdatableGridArguments = function(args) + { + var filterPluginFound = args && args.hasOwnProperty("plugins") && args.plugins.filter ? true: false; + + var gridProperties = { + autoHeight: true, + plugins: { + pagination: { + defaultPageSize: 25, + pageSizes: [10, 25, 50, 100], + description: true, + sizeSwitch: true, + pageStepper: true, + gotoButton: true, + maxPageStep: 4, + position: "bottom" + }, + enhancedFilter: { + disableFiltering: filterPluginFound + } + } + }; + + if(args) + { + for(var argProperty in args) + { + if(args.hasOwnProperty(argProperty)) + { + if (argProperty == "plugins") + { + var argPlugins = args[ argProperty ]; + for(var argPlugin in argPlugins) + { + if(argPlugins.hasOwnProperty(argPlugin)) + { + var argPluginProperties = argPlugins[ argPlugin ]; + if (argPluginProperties && gridProperties.plugins.hasOwnProperty(argPlugin)) + { + var gridPlugin = gridProperties.plugins[ argPlugin ]; + for(var pluginProperty in argPluginProperties) + { + if(argPluginProperties.hasOwnProperty(pluginProperty)) + { + gridPlugin[pluginProperty] = argPluginProperties[pluginProperty]; + } + } + } + else + { + gridProperties.plugins[ argPlugin ] = argPlugins[ argPlugin ]; + } + } + } + } + else + { + gridProperties[ argProperty ] = args[ argProperty ]; + } + } + } + } + + gridProperties.updater = this; + gridProperties.store = this.dataStore; + + return gridProperties; + }; + + GridUpdater.prototype.initializeStore = function(store) + { + var self = this; + + function processData(data) + { + var dataSet = false; + if (!store) + { + store = new ObjectStore({objectStore: new Memory({data: data, idProperty: "id"})}); + dataSet = true; + } + self.dataStore = store + self.store = store; + if (store instanceof ObjectStore) + { + if( store.objectStore instanceof Memory) + { + self.memoryStore = store.objectStore; + } + self.store = store.objectStore + } + + if (data) + { + try + { + if ((dataSet || self.updateOrAppend(data)) && self.onUpdate) + { + self.onUpdate(data); + } + } + catch(e) + { + console.error(e); + } + } + }; + + if (this.serviceUrl) + { + var requestUrl = lang.isFunction(this.serviceUrl) ? this.serviceUrl() : this.serviceUrl; + xhr.get({url: requestUrl, sync: true, handleAs: "json"}).then(processData, util.errorHandler); + } + else + { + processData(this.initialData); + } + }; + + GridUpdater.prototype.start = function(grid) + { + this.grid = grid; + if (this.serviceUrl) + { + updater.add(this); + } + }; + + GridUpdater.prototype.destroy = function() + { + updater.remove(this); + if (this.dataStore) + { + this.dataStore.close(); + this.dataStore = null; + } + this.store = null; + this.memoryStore = null; + this.grid = null; + }; + + GridUpdater.prototype.updateOrAppend = function(data) + { + return this.appendData ? + UpdatableStore.prototype.append.call(this, data, this.appendLimit): + UpdatableStore.prototype.update.call(this, data); + }; + + GridUpdater.prototype.refresh = function(data) + { + this.updating = true; + try + { + if (this.updateOrAppend(data)) + { + // EnhancedGrid with Filter plugin has "filter" layer. + // The filter expression needs to be re-applied after the data update + var filterLayer = this.grid.layer("filter"); + if ( filterLayer && filterLayer.filterDef) + { + var currentFilter = filterLayer.filterDef(); + + if (currentFilter) + { + // re-apply filter in the filter layer + filterLayer.filterDef(currentFilter); + } + } + + // refresh grid to render updates + this.grid._refresh(); + } + } + finally + { + this.updating = false; + if (this.onUpdate) + { + this.onUpdate(data); + } + } + } + + GridUpdater.prototype.update = function() + { + if (this.updatable) + { + this.performUpdate(); + } + }; + + GridUpdater.prototype.performUpdate = function() + { + var self = this; + var requestUrl = lang.isFunction(this.serviceUrl) ? this.serviceUrl() : this.serviceUrl; + var requestArguments = {url: requestUrl, sync: properties.useSyncGet, handleAs: "json"}; + xhr.get(requestArguments).then(function(data){self.refresh(data);}); + }; + + GridUpdater.prototype.performRefresh = function(data) + { + if (!this.updating) + { + this.refresh(data); + } + }; + + return GridUpdater; + }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/RowNumberLimitDialog.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/RowNumberLimitDialog.js new file mode 100644 index 0000000000..db3ae5a2ea --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/RowNumberLimitDialog.js @@ -0,0 +1,96 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +define([ + "dojo/_base/declare", + "dojo/_base/event", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/parser", + "dojo/dom-construct", + "dojo/query", + "dijit/registry", + "dijit/form/Button", + "dijit/form/CheckBox", + "dojox/grid/enhanced/plugins/Dialog", + "dojo/text!../../../grid/showRowNumberLimitDialog.html", + "dojo/domReady!" +], function(declare, event, array, lang, parser, dom, query, registry, Button, CheckBox, Dialog, template ){ + + +return declare("qpid.management.logs.RowNumberLimitDialog", null, { + + grid: null, + dialog: null, + + constructor: function(domNode, limitChangedCallback){ + + this.containerNode = dom.create("div", {innerHTML: template}); + parser.parse(this.containerNode); + + this.rowNumberLimit = registry.byNode(query(".rowNumberLimit", this.containerNode)[0]) + this.submitButton = registry.byNode(query(".submitButton", this.containerNode)[0]); + this.closeButton = registry.byNode(query(".cancelButton", this.containerNode)[0]); + + this.dialog = new Dialog({ + "refNode": domNode, + "title": "Grid Rows Number", + "content": this.containerNode + }); + + var self = this; + this.submitButton.on("click", function(e){ + if (self.rowNumberLimit.value > 0) + { + try + { + limitChangedCallback(self.rowNumberLimit.value); + } + catch(e) + { + console.error(e); + } + finally + { + self.dialog.hide(); + } + } + }); + + this.closeButton.on("click", function(e){self.dialog.hide(); }); + this.dialog.startup(); + }, + + destroy: function(){ + this.submitButton.destroy(); + this.closeButton.destroy(); + this.dialog.destroy(); + this.dialog = null; + }, + + showDialog: function(currentLimit){ + this.rowNumberLimit.set("value", currentLimit); + this.dialog.show(); + } + + }); + +}); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/UpdatableGrid.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/UpdatableGrid.js new file mode 100644 index 0000000000..04041388bd --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/UpdatableGrid.js @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +define([ + "dojo/_base/declare", + "dojox/grid/EnhancedGrid", + "dojo/domReady!"], function(declare, EnhancedGrid){ + + return declare("qpid.common.grid.UpdatableGrid", [EnhancedGrid], { + + updater: null, + + postCreate: function(){ + this.inherited(arguments); + if (this.updater) + { + this.updater.start(this); + } + }, + + destroy: function(){ + if (this.updater) + { + try + { + this.updater.destroy(); + } + catch(e) + { + console.error(e) + } + this.updater = null; + } + this.inherited(arguments); + } + }); + +}); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js index 2c2096d390..3d349830ac 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js @@ -77,10 +77,10 @@ define(["dojo/_base/xhr", return exchangeName == null || exchangeName == "" || "<<default>>" == exchangeName || exchangeName.indexOf("amq.") == 0 || exchangeName.indexOf("qpid.") == 0; }; - util.deleteGridSelections = function(updater, grid, url, confirmationMessageStart) + util.deleteGridSelections = function(updater, grid, url, confirmationMessageStart, idParam) { var data = grid.selection.getSelected(); - + var success = false; if(data.length) { var confirmationMessage = null; @@ -114,18 +114,19 @@ define(["dojo/_base/xhr", { queryParam = "?"; } - queryParam += "id=" + data[i].id; + queryParam += ( idParam || "id" ) + "=" + encodeURIComponent(data[i].id); } var query = url + queryParam; - var success = true var failureReason = ""; xhr.del({url: query, sync: true, handleAs: "json"}).then( function(data) { - // TODO why query *?? - //grid.setQuery({id: "*"}); + success = true; grid.selection.deselectAll(); - updater.update(); + if (updater) + { + updater.update(); + } }, function(error) {success = false; failureReason = error;}); if(!success ) @@ -134,6 +135,7 @@ define(["dojo/_base/xhr", } } } + return success; } util.isProviderManagingUsers = function(type) @@ -353,5 +355,21 @@ define(["dojo/_base/xhr", } }; + util.errorHandler = function errorHandler(error) + { + if(error.status == 401) + { + alert("Authentication Failed"); + } + else if(error.status == 403) + { + alert("Access Denied"); + } + else + { + alert(error); + } + } + return util; });
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/AuthenticationProvider.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/AuthenticationProvider.js index 978ac4b45f..b5b4380a0d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/AuthenticationProvider.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/AuthenticationProvider.js @@ -32,10 +32,13 @@ define(["dojo/_base/xhr", "dijit/registry", "dojo/dom-style", "dojox/html/entities", - "dojox/grid/enhanced/plugins/Pagination", - "dojox/grid/enhanced/plugins/IndirectSelection", + "dojo/dom", + "qpid/management/addPreferencesProvider", + "qpid/management/PreferencesProvider", + "qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager", "dojo/domReady!"], - function (xhr, parser, query, connect, properties, updater, util, UpdatableStore, EnhancedGrid, addAuthenticationProvider, event, registry, domStyle, entities) { + function (xhr, parser, query, connect, properties, updater, util, UpdatableStore, EnhancedGrid, + addAuthenticationProvider, event, registry, domStyle, entities, dom, addPreferencesProvider, PreferencesProvider, PrincipalDatabaseAuthenticationManager) { function AuthenticationProvider(name, parent, controller) { this.name = name; @@ -48,7 +51,7 @@ define(["dojo/_base/xhr", } AuthenticationProvider.prototype.getTitle = function() { - return "AuthenticationProvider"; + return "AuthenticationProvider:" + this.name; }; AuthenticationProvider.prototype.open = function(contentPane) { @@ -62,8 +65,6 @@ define(["dojo/_base/xhr", that.authProviderUpdater = new AuthProviderUpdater(contentPane.containerNode, that.modelObj, that.controller, that); - updater.add( that.authProviderUpdater ); - that.authProviderUpdater.update(); var editButton = query(".editAuthenticationProviderButton", contentPane.containerNode)[0]; @@ -81,6 +82,15 @@ define(["dojo/_base/xhr", event.stop(evt); that.deleteAuthenticationProvider(); }); + + var addPreferencesProviderButton = query(".addPreferencesProviderButton", contentPane.containerNode)[0]; + var addPreferencesProviderWidget = registry.byNode(addPreferencesProviderButton); + connect.connect(addPreferencesProviderWidget, "onClick", + function(evt){ + event.stop(evt); + that.addPreferencesProvider(); + }); + updater.add( that.authProviderUpdater ); }}); }; @@ -111,6 +121,14 @@ define(["dojo/_base/xhr", } }; + AuthenticationProvider.prototype.addPreferencesProvider = function() { + if (this.authProviderUpdater && this.authProviderUpdater.authProviderData + && (!this.authProviderUpdater.authProviderData.preferencesproviders + || !this.authProviderUpdater.authProviderData.preferencesproviders[0])){ + addPreferencesProvider.show(this.name); + } + }; + function AuthProviderUpdater(node, authProviderObj, controller, authenticationProvider) { this.controller = controller; @@ -118,6 +136,13 @@ define(["dojo/_base/xhr", this.type = query(".type", node)[0]; this.state = query(".state", node)[0]; this.authenticationProvider = authenticationProvider; + this.preferencesProviderType=dom.byId("preferencesProviderType"); + this.preferencesProviderName=dom.byId("preferencesProviderName"); + this.preferencesProviderState=dom.byId("preferencesProviderState"); + this.addPreferencesProviderButton = query(".addPreferencesProviderButton", node)[0]; + this.editPreferencesProviderButton = query(".editPreferencesProviderButton", node)[0]; + this.deletePreferencesProviderButton = query(".deletePreferencesProviderButton", node)[0]; + this.preferencesProviderAttributes = dom.byId("preferencesProviderAttributes") this.query = "rest/authenticationprovider/" + encodeURIComponent(authProviderObj.name); @@ -139,16 +164,46 @@ define(["dojo/_base/xhr", if (util.isProviderManagingUsers(that.authProviderData.type)) { - require(["qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager"], - function(PrincipalDatabaseAuthenticationManager) { - that.details = new PrincipalDatabaseAuthenticationManager(node, data[0], controller, that); - that.details.update(); - }); + that.details = new PrincipalDatabaseAuthenticationManager(node, that.authProviderData, controller); + that.details.update(that.authProviderData); + } + if (that.authProviderData.type == "Anonymous") + { + var authenticationProviderPanel = registry.byNode( query(".preferencesPanel", node)[0]); + domStyle.set(authenticationProviderPanel.domNode, "display","none"); + } + else + { + var preferencesProviderData = that.authProviderData.preferencesproviders? that.authProviderData.preferencesproviders[0]: null; + that.preferencesNode = query(".preferencesProviderDetails", node)[0]; + that.updatePreferencesProvider(preferencesProviderData); } }); } + AuthProviderUpdater.prototype.updatePreferencesProvider = function(preferencesProviderData) + { + if (preferencesProviderData) + { + this.addPreferencesProviderButton.style.display = 'none'; + if (!this.preferencesProvider) + { + this.preferencesProvider=new PreferencesProvider(preferencesProviderData.name, this.authProviderData); + this.preferencesProvider.init(this.preferencesNode); + } + this.preferencesProvider.update(preferencesProviderData); + } + else + { + if (this.preferencesProvider) + { + this.preferencesProvider.update(null); + } + this.addPreferencesProviderButton.style.display = 'inline'; + } + }; + AuthProviderUpdater.prototype.updateHeader = function() { this.authenticationProvider.name = this.authProviderData[ "name" ] @@ -159,8 +214,44 @@ define(["dojo/_base/xhr", AuthProviderUpdater.prototype.update = function() { + var that = this; - var that = this; + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) { + that.authProviderData = data[0]; + that.name = data[0].name + util.flattenStatistics( that.authProviderData ); + that.updateHeader(); + if (that.details) + { + try + { + that.details.update(that.authProviderData); + } + catch(e) + { + if (console) + { + console.error(e); + } + } + } + var preferencesProviderData = that.authProviderData.preferencesproviders? that.authProviderData.preferencesproviders[0]: null; + if (preferencesProviderData) + { + try + { + that.updatePreferencesProvider(preferencesProviderData); + } + catch(e) + { + if (console) + { + console.error(e); + } + } + } + }); }; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js index f721ad6fa5..1bb0ca0afa 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js @@ -352,6 +352,12 @@ define(["dojo/_base/xhr", that.brokerUpdater.update(); + var logViewerButton = query(".logViewer", contentPane.containerNode)[0]; + registry.byNode(logViewerButton).on("click", function(evt){ + that.controller.show("logViewer", null, null); + }); + + var addProviderButton = query(".addAuthenticationProvider", contentPane.containerNode)[0]; connect.connect(registry.byNode(addProviderButton), "onClick", function(evt){ addAuthenticationProvider.show(); }); @@ -664,43 +670,6 @@ define(["dojo/_base/xhr", }, gridProperties, EnhancedGrid); that.displayACLWarnMessage(aclData); }); - - xhr.get({url: "rest/logrecords", sync: properties.useSyncGet, handleAs: "json"}) - .then(function(data) - { - that.logData = data; - - var gridProperties = { - height: 400, - plugins: { - pagination: { - pageSizes: ["10", "25", "50", "100"], - description: true, - sizeSwitch: true, - pageStepper: true, - gotoButton: true, - maxPageStep: 4, - position: "bottom" - } - }}; - - - that.logfileGrid = - new UpdatableStore(that.logData, query(".broker-logfile")[0], - [ { name: "Timestamp", field: "timestamp", width: "200px", - formatter: function(val) { - var d = new Date(0); - d.setUTCSeconds(val/1000); - - return d.toLocaleString(); - }}, - { name: "Level", field: "level", width: "60px"}, - { name: "Logger", field: "logger", width: "280px"}, - { name: "Thread", field: "thread", width: "120px"}, - { name: "Log Message", field: "message", width: "100%"} - - ], null, gridProperties, EnhancedGrid); - }); } BrokerUpdater.prototype.updateHeader = function() @@ -805,15 +774,6 @@ define(["dojo/_base/xhr", that.displayACLWarnMessage(data); } }); - - - xhr.get({url: "rest/logrecords", sync: properties.useSyncGet, handleAs: "json"}) - .then(function(data) - { - that.logData = data; - that.logfileGrid.update(that.logData); - }); - }; BrokerUpdater.prototype.showReadOnlyAttributes = function() diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Preferences.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Preferences.js new file mode 100644 index 0000000000..735a657c61 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Preferences.js @@ -0,0 +1,204 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define([ + "dojo/_base/declare", + "dojo/_base/xhr", + "dojo/_base/event", + "dojo/_base/connect", + "dojo/dom", + "dojo/dom-construct", + "dojo/parser", + "dojo/json", + "dojo/store/Memory", + "dojo/data/ObjectStore", + "dojox/html/entities", + "dijit/registry", + "qpid/common/TimeZoneSelector", + "dojo/text!../../showPreferences.html", + "qpid/common/util", + "dijit/Dialog", + "dijit/form/NumberSpinner", + "dijit/form/CheckBox", + "dijit/form/Textarea", + "dijit/form/FilteringSelect", + "dijit/form/TextBox", + "dijit/form/DropDownButton", + "dijit/form/Button", + "dijit/form/Form", + "dijit/layout/TabContainer", + "dijit/layout/ContentPane", + "dojox/grid/EnhancedGrid", + "dojox/validate/us", + "dojox/validate/web", + "dojo/domReady!"], +function (declare, xhr, event, connect, dom, domConstruct, parser, json, Memory, ObjectStore, entities, registry, TimeZoneSelector, markup, util) { + + var preferenceNames = ["timeZone", "updatePeriod", "saveTabs"]; + + return declare("qpid.management.Preferences", null, { + + preferencesDialog: null, + saveButton: null, + cancelButton: null, + + constructor: function() + { + var that = this; + + this.domNode = domConstruct.create("div", {innerHTML: markup}); + this.preferencesDialog = parser.parse(this.domNode)[0]; + + for(var i=0; i<preferenceNames.length; i++) + { + var name = preferenceNames[i]; + this[name] = registry.byId("preferences." + name); + } + + this.saveButton = registry.byId("preferences.saveButton"); + this.cancelButton = registry.byId("preferences.cancelButton"); + this.theForm = registry.byId("preferences.preferencesForm"); + this.users = registry.byId("preferences.users"); + this.users.set("structure", [ { name: "User", field: "name", width: "50%"}, + { name: "Authentication Provider", field: "authenticationProvider", width: "50%"}]); + this.cancelButton.on("click", function(){that.preferencesDialog.hide();}); + this.deletePreferencesButton = registry.byId("preferences.deletePreeferencesButton"); + this.deletePreferencesButton.on("click", function(){ + if (util.deleteGridSelections( + null, + that.users, + "rest/userpreferences", + "Are you sure you want to delete preferences for user", + "user")) + { + that._updateUsersWithPreferences(); + } + }); + var deletePreferencesButtonToggler = function(rowIndex){ + var data = that.users.selection.getSelected(); + that.deletePreferencesButton.set("disabled",!data.length ); + }; + connect.connect(this.users.selection, 'onSelected', deletePreferencesButtonToggler); + connect.connect(this.users.selection, 'onDeselected', deletePreferencesButtonToggler); + this.theForm.on("submit", function(e){ + event.stop(e); + if(that.theForm.validate()){ + var preferences = {}; + for(var i=0; i<preferenceNames.length; i++) + { + var name = preferenceNames[i]; + var preferenceWidget = that[name]; + if (preferenceWidget) + { + preferences[name] = preferenceWidget.get("value"); + } + } + xhr.post({ + url: "rest/preferences", + sync: true, + handleAs: "json", + headers: { "Content-Type": "application/json"}, + postData: json.stringify(preferences), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;} + }); + if(that.success === true) + { + that.preferencesDialog.hide(); + } + else + { + alert("Error:" + that.failureReason); + } + } + return false; + }); + this.preferencesDialog.startup(); + }, + + showDialog: function(){ + var that = this; + xhr.get({ + url: "rest/preferences", + sync: true, + handleAs: "json", + load: function(data) { + that._updatePreferencesWidgets(data); + that._updateUsersWithPreferences(); + that.preferencesDialog.show(); + }, + error: function(error){ + alert("Cannot load user preferences : " + error); + } + }); + }, + + destroy: function() + { + if (this.preferencesDialog) + { + this.preferencesDialog.destroyRecursevly(); + this.preferencesDialog = null; + } + }, + + _updatePreferencesWidgets: function(data) + { + for(var i=0; i<preferenceNames.length; i++) + { + var preference = preferenceNames[i]; + if (this.hasOwnProperty(preference)) + { + var value = data ? data[preference] : null; + if (typeof value == "string") + { + value = entities.encode(String(value)) + } + this[preference].set("value", value); + } + } + }, + + _updateUsersWithPreferences: function() + { + var that = this; + xhr.get({ + url: "rest/userpreferences", + sync: false, + handleAs: "json" + }).then( + function(users) { + for(var i=0; i<users.length; i++) + { + users[i].id = users[i].authenticationProvider + "/" + users[i].name; + } + var usersStore = new Memory({data: users, idProperty: "id"}); + var usersDataStore = new ObjectStore({objectStore: usersStore}); + if (that.users.store) + { + that.users.store.close(); + } + that.users.set("store", usersDataStore); + that.users._refresh(); + }); + } + + }); +});
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/PreferencesProvider.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/PreferencesProvider.js new file mode 100644 index 0000000000..c8e6f9845c --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/PreferencesProvider.js @@ -0,0 +1,179 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/_base/connect", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "dojo/_base/event", + "dijit/registry", + "dojo/dom-style", + "dojox/html/entities", + "qpid/management/addPreferencesProvider", + "dojo/domReady!"], + function (xhr, parser, query, connect, properties, updater, util, event, registry, domStyle, entities, addPreferencesProvider) { + + function PreferencesProvider(name, parent, controller) { + this.name = name; + this.controller = controller; + this.modelObj = { type: "preferencesprovider", name: name }; + this.authenticationProviderName = parent.name; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[parent.type] = parent; + } + } + + PreferencesProvider.prototype.getTitle = function() { + return "PreferencesProvider:" + this.authenticationProviderName + "/" + this.name ; + }; + + PreferencesProvider.prototype.init = function(node) { + var that = this; + xhr.get({url: "showPreferencesProvider.html", + sync: true, + load: function(data) { + node.innerHTML = data; + parser.parse(node); + + that.preferencesProviderType=query(".preferencesProviderType", node)[0]; + that.preferencesProviderName=query(".preferencesProviderName", node)[0]; + that.preferencesProviderState=query(".preferencesProviderState", node)[0]; + that.editPreferencesProviderButton = query(".editPreferencesProviderButton", node)[0]; + that.deletePreferencesProviderButton = query(".deletePreferencesProviderButton", node)[0]; + that.preferencesProviderAttributes = query(".preferencesProviderAttributes", node)[0]; + that.preferencesDetailsDiv = query(".preferencesDetails", node)[0]; + var editPreferencesProviderWidget = registry.byNode(that.editPreferencesProviderButton); + editPreferencesProviderWidget.on("click", function(evt){ event.stop(evt); that.editPreferencesProvider();}); + var deletePreferencesProviderWidget = registry.byNode(that.deletePreferencesProviderButton); + deletePreferencesProviderWidget.on("click", function(evt){ event.stop(evt); that.deletePreferencesProvider();}); + }}); + this.reload(); + }; + + PreferencesProvider.prototype.open = function(contentPane) { + this.contentPane = contentPane; + this.init(contentPane.containerNode); + this.updater = new PreferencesProviderUpdater(this); + updater.add(this.updater); + }; + + PreferencesProvider.prototype.close = function() { + if (this.updater) + { + updater.remove( this.updater); + } + }; + + PreferencesProvider.prototype.deletePreferencesProvider = function() { + if (this.preferencesProviderData){ + var preferencesProviderData = this.preferencesProviderData; + if(confirm("Are you sure you want to delete preferences provider '" + preferencesProviderData.name + "'?")) { + var query = "rest/preferencesprovider/" + encodeURIComponent(this.authenticationProviderName) + "/" + encodeURIComponent(preferencesProviderData.name); + this.success = true + var that = this; + xhr.del({url: query, sync: true, handleAs: "json"}).then( + function(data) { + that.update(null); + + // if opened in tab + if (that.contentPane) + { + that.close(); + that.contentPane.onClose() + that.controller.tabContainer.removeChild(that.contentPane); + that.contentPane.destroyRecursive(); + } + }, + function(error) {that.success = false; that.failureReason = error;}); + if(!this.success ) { + alert("Error:" + this.failureReason); + } + } + } + }; + + PreferencesProvider.prototype.editPreferencesProvider = function() { + if (this.preferencesProviderData){ + addPreferencesProvider.show(this.authenticationProviderName, this.name); + } + }; + + PreferencesProvider.prototype.update = function(data) { + this.preferencesProviderData = data; + if (data) + { + this.name = data.name; + this.preferencesProviderAttributes.style.display = 'block'; + this.editPreferencesProviderButton.style.display = 'inline'; + this.deletePreferencesProviderButton.style.display = 'inline'; + this.preferencesProviderType.innerHTML = entities.encode(String(data.type)); + this.preferencesProviderName.innerHTML = entities.encode(String(data.name)); + this.preferencesProviderState.innerHTML = entities.encode(String(data.state)); + if (!this.details) + { + var that = this; + require(["qpid/management/authenticationprovider/preferences/" + data.type.toLowerCase() + "/show"], + function(PreferencesProviderDetails) { + that.details = new PreferencesProviderDetails(that.preferencesDetailsDiv); + that.details.update(data); + }); + } + else + { + this.details.update(data); + } + } + else + { + this.editPreferencesProviderButton.style.display = 'none'; + this.deletePreferencesProviderButton.style.display = 'none'; + this.preferencesProviderAttributes.style.display = 'none'; + this.details = null; + } + }; + + PreferencesProvider.prototype.reload = function() + { + var query = "rest/preferencesprovider/" + encodeURIComponent(this.authenticationProviderName) + "/" + encodeURIComponent(this.name); + var that = this; + xhr.get({url: query, sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) { + var preferencesProviderData = data[0]; + util.flattenStatistics( preferencesProviderData ); + that.update(preferencesProviderData); + }); + }; + + function PreferencesProviderUpdater(preferencesProvider) + { + this.preferencesProvider = preferencesProvider; + }; + + PreferencesProviderUpdater.prototype.update = function() + { + this.preferencesProvider.reload(); + }; + + return PreferencesProvider; + }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addAuthenticationProvider.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addAuthenticationProvider.js index d2891c7d3b..3737e41da4 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addAuthenticationProvider.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addAuthenticationProvider.js @@ -31,6 +31,7 @@ define(["dojo/_base/xhr", "dijit/form/FilteringSelect", "dojo/_base/connect", "dojo/dom-style", + "qpid/management/addPreferencesProvider", /* dojox/ validate resources */ "dojox/validate/us", "dojox/validate/web", /* basic dijit classes */ @@ -44,7 +45,7 @@ define(["dojo/_base/xhr", "dojox/form/BusyButton", "dojox/form/CheckedMultiSelect", "dojox/layout/TableContainer", "dojo/domReady!"], - function (xhr, dom, construct, win, registry, parser, array, event, json, Memory, FilteringSelect, connect, domStyle) { + function (xhr, dom, construct, win, registry, parser, array, event, json, Memory, FilteringSelect, connect, domStyle, addPreferencesProvider) { var addAuthenticationProvider = {}; @@ -163,6 +164,10 @@ define(["dojo/_base/xhr", if(this.success === true) { registry.byId("addAuthenticationProvider").hide(); + if (newAuthenticationManager.type != "Anonymous" && dojo.byId("formAddAuthenticationProvider.id").value == "") + { + addPreferencesProvider.show(newAuthenticationManager.name); + } } else { diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPreferencesProvider.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPreferencesProvider.js new file mode 100644 index 0000000000..818dc32366 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPreferencesProvider.js @@ -0,0 +1,198 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define(["dojo/_base/xhr", + "dojo/dom", + "dojo/dom-construct", + "dojo/query", + "dojo/_base/window", + "dijit/registry", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/event", + 'dojo/_base/json', + "dojo/store/Memory", + "dijit/form/FilteringSelect", + "dojo/_base/connect", + "dojo/dom-style", + "dojo/string", + "dojox/html/entities", + "dojox/validate/us", + "dojox/validate/web", + "dijit/Dialog", + "dijit/form/CheckBox", + "dijit/form/Textarea", + "dijit/form/TextBox", + "dijit/form/ValidationTextBox", + "dijit/form/Button", + "dijit/form/Form", + "dojox/form/BusyButton", + "dojox/form/CheckedMultiSelect", + "dojox/layout/TableContainer", + "dojo/domReady!"], + function (xhr, dom, construct, query, win, registry, parser, array, event, json, Memory, FilteringSelect, connect, domStyle, string, entities) { + + var addPreferencesProvider = {}; + + var node = construct.create("div", null, win.body(), "last"); + + var convertToPreferencesProvider = function convertToPreferencesProvider(formValues) + { + var newProvider = {}; + + newProvider.name = dijit.byId("preferencesProvider.name").value; + newProvider.type = dijit.byId("preferencesProvider.type").value; + var id = dojo.byId("preferencesProvider.id").value; + if (id) + { + newProvider.id = id; + } + for(var propName in formValues) + { + if(formValues.hasOwnProperty(propName)) + { + if(formValues[ propName ] !== "") { + newProvider[ propName ] = formValues[propName]; + } + + } + } + return newProvider; + } + + var selectPreferencesProviderType = function(type) { + if(type && string.trim(type) != "") + { + require(["qpid/management/authenticationprovider/preferences/" + type.toLowerCase() + "/add"], + function(addType) + { + addType.show(dom.byId("preferencesProvider.fieldsContainer"), addPreferencesProvider.data) + }); + } + } + + xhr.get({url: "addPreferencesProvider.html", + sync: true, + load: function(data) { + node.innerHTML = data; + addPreferencesProvider.dialogNode = dom.byId("addPreferencesProvider"); + parser.instantiate([addPreferencesProvider.dialogNode]); + + var cancelButton = registry.byId("addPreferencesProvider.cancelButton"); + cancelButton.on("click", function(){ + registry.byId("addPreferencesProvider").hide(); + }); + var theForm = registry.byId("formAddPreferencesProvider"); + theForm.on("submit", function(e) { + + event.stop(e); + if(theForm.validate()){ + var newProvider = convertToPreferencesProvider(theForm.getValues()); + var that = this; + var nameWidget = registry.byId("preferencesProvider.name") + xhr.put({url: "rest/preferencesprovider/" +encodeURIComponent(addPreferencesProvider.authenticationProviderName) + "/" + encodeURIComponent(nameWidget.value), + sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(newProvider), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + if(this.success === true) + { + registry.byId("addPreferencesProvider").hide(); + } + else + { + alert("Error:" + this.failureReason); + } + return false; + }else{ + alert('Form contains invalid data. Please correct first'); + return false; + } + }); + xhr.get({ + sync: true, + url: "rest/helper?action=ListPreferencesProvidersTypes", + handleAs: "json" + }).then( + function(data) { + var preferencesProvidersTypes = data; + var storeData = []; + for (var i =0 ; i < preferencesProvidersTypes.length; i++) + { + storeData[i]= {id: preferencesProvidersTypes[i], name: preferencesProvidersTypes[i]}; + } + var store = new Memory({ data: storeData }); + var preferencesProviderTypesDiv = dom.byId("addPreferencesProvider.selectPreferencesProviderDiv"); + var input = construct.create("input", {id: "preferencesProviderType", required: true}, preferencesProviderTypesDiv); + addPreferencesProvider.preferencesProviderTypeChooser = new FilteringSelect({ id: "preferencesProvider.type", + name: "type", + store: store, + searchAttr: "name", + required: true, + onChange: selectPreferencesProviderType }, input); + addPreferencesProvider.preferencesProviderTypeChooser.startup(); + }); + }}); + + addPreferencesProvider.show = function(authenticationProviderName, providerName) { + this.authenticationProviderName = authenticationProviderName; + this.data = null; + var that = this; + var theForm = registry.byId("formAddPreferencesProvider"); + theForm.reset(); + dojo.byId("preferencesProvider.id").value=""; + var nameWidget = registry.byId("preferencesProvider.name"); + nameWidget.set("disabled", false); + registry.byId("preferencesProvider.type").set("disabled", false); + if (this.preferencesProviderTypeChooser) + { + this.preferencesProviderTypeChooser.set("disabled", false); + this.preferencesProviderTypeChooser.set("value", null); + } + var dialog = registry.byId("addPreferencesProvider"); + dialog.set("title", (providerName ? "Edit preference provider '" + entities.encode(String(providerName)) + "' " : "Add preferences provider ") + " for authentication provider '" + entities.encode(String(authenticationProviderName)) + "' ") + if (providerName) + { + xhr.get({ + url: "rest/preferencesprovider/" +encodeURIComponent(authenticationProviderName) + "/" + encodeURIComponent(providerName), + sync: false, + handleAs: "json" + }).then( + function(data) { + var provider = data[0]; + var providerType = provider.type; + that.data = provider; + nameWidget.set("value", entities.encode(String(provider.name))); + nameWidget.set("disabled", true); + that.preferencesProviderTypeChooser.set("value", providerType); + that.preferencesProviderTypeChooser.set("disabled", true); + dojo.byId("preferencesProvider.id").value=provider.id; + dialog.show(); + }); + } + else + { + dialog.show(); + } + } + + return addPreferencesProvider; + });
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js index 0a607c71d4..09433b196d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js @@ -30,7 +30,6 @@ define(["dojo/_base/xhr", "dijit/registry", "qpid/common/util", "qpid/common/properties", - "qpid/common/updater", "qpid/common/UpdatableStore", "dojox/grid/EnhancedGrid", "dojox/grid/enhanced/plugins/Pagination", @@ -43,8 +42,8 @@ define(["dojo/_base/xhr", "dijit/form/Form", "dijit/form/DateTextBox", "dojo/domReady!"], - function (xhr, dom, parser, query, construct, connect, win, event, json, registry, util, properties, updater, UpdatableStore, EnhancedGrid) { - function DatabaseAuthManager(containerNode, authProviderObj, controller, authenticationManagerUpdater) { + function (xhr, dom, parser, query, construct, connect, win, event, json, registry, util, properties, UpdatableStore, EnhancedGrid) { + function DatabaseAuthManager(containerNode, authProviderObj, controller) { var node = construct.create("div", null, containerNode, "last"); var that = this; this.name = authProviderObj.name; @@ -53,14 +52,7 @@ define(["dojo/_base/xhr", load: function(data) { node.innerHTML = data; parser.parse(node); - - - that.authDatabaseUpdater= new AuthProviderUpdater(node, authProviderObj, controller, authenticationManagerUpdater); - updater.add( that.authDatabaseUpdater); - - that.authDatabaseUpdater.update(); - - + that.init(node, authProviderObj, controller); }}); } @@ -72,19 +64,12 @@ define(["dojo/_base/xhr", updater.remove( this.authDatabaseUpdater ); }; - function AuthProviderUpdater(node, authProviderObj, controller, authenticationManagerUpdater) + DatabaseAuthManager.prototype.init = function(node, authProviderObj, controller) { this.controller = controller; - this.query = "rest/authenticationprovider?id="+encodeURIComponent(authProviderObj.id); - this.name = authProviderObj.name; - this.authenticationManagerUpdater = authenticationManagerUpdater; var that = this; - xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) - .then(function(data) { - that.authProviderData = data[0]; - that.name = data[0].name - util.flattenStatistics( that.authProviderData ); + that.authProviderData = authProviderObj; var userDiv = query(".users")[0]; @@ -131,10 +116,9 @@ define(["dojo/_base/xhr", event.stop(evt); that.deleteUsers(); }); - }); } - AuthProviderUpdater.prototype.deleteUsers = function() + DatabaseAuthManager.prototype.deleteUsers = function() { var grid = this.usersGrid.grid; var data = grid.selection.getSelected(); @@ -168,24 +152,11 @@ define(["dojo/_base/xhr", } }; - AuthProviderUpdater.prototype.update = function() + DatabaseAuthManager.prototype.update = function(data) { - - var that = this; - - xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) - .then(function(data) { - that.authProviderData = data[0]; - that.name = data[0].name - util.flattenStatistics( that.authProviderData ); - - that.usersGrid.update(that.authProviderData.users); - - that.authenticationManagerUpdater.authProviderData = data[0]; - that.authenticationManagerUpdater.updateHeader(); - }); - - + this.authProviderData = data; + this.name = data.name + this.usersGrid.update(this.authProviderData.users); }; var addUser = {}; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/preferences/filesystempreferences/add.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/preferences/filesystempreferences/add.js new file mode 100644 index 0000000000..80b50fbbb8 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/preferences/filesystempreferences/add.js @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define(["dojo/_base/xhr", + "dojo/dom", + "dojo/dom-construct", + "dojo/_base/window", + "dijit/registry", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/event", + "dojo/_base/json", + "dojo/string", + "dojox/html/entities", + "dojo/text!../../../../../authenticationprovider/preferences/filesystempreferences/add.html", + "dojo/domReady!"], + function (xhr, dom, domConstruct, win, registry, parser, array, event, json, string, entities, template) { + return { + show: function(node, data) { + dojo.forEach(dijit.findWidgets(node), function(w) { + w.destroyRecursive(); + }); + node.innerHTML = template; + parser.parse(node); + var pathWidget = registry.byId("preferencesProvider.path") + if (data) + { + pathWidget.set("value", entities.encode(String(data["path"]))); + } + } + }; + }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/preferences/filesystempreferences/show.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/preferences/filesystempreferences/show.js new file mode 100644 index 0000000000..7521f820b9 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/preferences/filesystempreferences/show.js @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/string", + "dojox/html/entities", + "dojo/query", + "dojo/domReady!"], + function (xhr, parser, json, entities, query) { + + function FileSystemPreferences(containerNode) { + var that = this; + xhr.get({url: "authenticationprovider/preferences/filesystempreferences/show.html", + sync: true, + load: function(template) { + containerNode.innerHTML = template; + parser.parse(containerNode); + that.preferencesProviderPath=query(".fileSystemPreferencesProviderPath", containerNode)[0]; + }}); + } + + FileSystemPreferences.prototype.update=function(data) + { + this.preferencesProviderPath.innerHTML = entities.encode(String(data["path"])); + }; + + return FileSystemPreferences; +}); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js index b7eddbbb77..e8d7bdd9cf 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js @@ -35,10 +35,12 @@ define(["dojo/dom", "qpid/management/AccessControlProvider", "qpid/management/Port", "qpid/management/Plugin", + "qpid/management/logs/LogViewer", + "qpid/management/PreferencesProvider", "dojo/ready", "dojo/domReady!"], function (dom, registry, ContentPane, entities, Broker, VirtualHost, Exchange, Queue, Connection, AuthProvider, - GroupProvider, Group, KeyStore, TrustStore, AccessControlProvider, Port, Plugin, ready) { + GroupProvider, Group, KeyStore, TrustStore, AccessControlProvider, Port, Plugin, LogViewer, PreferencesProvider, ready) { var controller = {}; var constructors = { broker: Broker, virtualhost: VirtualHost, exchange: Exchange, @@ -46,7 +48,7 @@ define(["dojo/dom", authenticationprovider: AuthProvider, groupprovider: GroupProvider, group: Group, keystore: KeyStore, truststore: TrustStore, accesscontrolprovider: AccessControlProvider, port: Port, - plugin: Plugin}; + plugin: Plugin, logViewer: LogViewer, preferencesprovider: PreferencesProvider}; var tabDiv = dom.byId("managedViews"); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogFileDownloadDialog.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogFileDownloadDialog.js new file mode 100644 index 0000000000..c25fd7c609 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogFileDownloadDialog.js @@ -0,0 +1,175 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define([ + "dojo/_base/declare", + "dojo/_base/event", + "dojo/_base/xhr", + "dojo/_base/connect", + "dojo/dom-construct", + "dojo/query", + "dojo/parser", + "dojo/store/Memory", + "dojo/data/ObjectStore", + "dojo/date/locale", + "dojo/number", + "dijit/registry", + "dijit/Dialog", + "dijit/form/Button", + "dojox/grid/EnhancedGrid", + "dojo/text!../../../logs/showLogFileDownloadDialog.html", + "dojo/domReady!" +], function(declare, event, xhr, connect, domConstruct, query, parser, Memory, ObjectStore, locale, number, + registry, Dialog, Button, EnhancedGrid, template){ + + +return declare("qpid.management.logs.LogFileDownloadDialog", null, { + + templateString: template, + containerNode: null, + widgetsInTemplate: true, + logFileDialog: null, + logFilesGrid: null, + downloadLogsButton: null, + closeButton: null, + + constructor: function(args){ + this.containerNode = domConstruct.create("div", {innerHTML: template}); + parser.parse(this.containerNode); + + this.logFileTreeDiv = query(".logFilesGrid", this.containerNode)[0]; + this.downloadLogsButton = registry.byNode(query(".downloadLogsButton", this.containerNode)[0]); + this.closeButton = registry.byNode(query(".downloadLogsDialogCloseButton", this.containerNode)[0]); + + var self = this; + this.closeButton.on("click", function(e){self._onCloseButtonClick(e);}); + this.downloadLogsButton.on("click", function(e){self._onDownloadButtonClick(e);}); + this.downloadLogsButton.set("disabled", true) + + this.logFileDialog = new Dialog({ + title:"Broker Log Files", + style: "width: 600px", + content: this.containerNode + }); + + var layout = [ + { name: "Appender", field: "appenderName", width: "auto"}, + { name: "Name", field: "name", width: "auto"}, + { name: "Size", field: "size", width: "60px", + formatter: function(val){ + return val > 1024 ? (val > 1048576? number.round(val/1048576) + "MB": number.round(val/1024) + "KB") : val + "bytes"; + } + }, + { name: "Last Modified", field: "lastModified", width: "250px", + formatter: function(val) { + var d = new Date(val); + return locale.format(d, {selector:"date", datePattern: "EEE, MMM d yy, HH:mm:ss z (ZZZZ)"}); + } + } + ]; + + var gridProperties = { + store: new ObjectStore({objectStore: new Memory({data: [], idProperty: "id"}) }), + structure: layout, + autoHeight: true, + plugins: { + pagination: { + pageSizes: [10, 25, 50, 100], + description: true, + sizeSwitch: true, + pageStepper: true, + gotoButton: true, + maxPageStep: 4, + position: "bottom" + }, + indirectSelection: { + headerSelector:true, + width:"20px", + styles:"text-align: center;" + } + } + }; + + this.logFilesGrid = new EnhancedGrid(gridProperties, this.logFileTreeDiv); + var self = this; + var downloadButtonToggler = function(rowIndex){ + var data = self.logFilesGrid.selection.getSelected(); + self.downloadLogsButton.set("disabled",!data.length ); + }; + connect.connect(this.logFilesGrid.selection, 'onSelected', downloadButtonToggler); + connect.connect(this.logFilesGrid.selection, 'onDeselected', downloadButtonToggler); + }, + + _onCloseButtonClick: function(evt){ + event.stop(evt); + this.logFileDialog.hide(); + }, + + _onDownloadButtonClick: function(evt){ + event.stop(evt); + var data = this.logFilesGrid.selection.getSelected(); + if (data.length) + { + var query = ""; + for(var i = 0 ; i< data.length; i++) + { + if (i>0) + { + query+="&"; + } + query+="l="+encodeURIComponent(data[i].appenderName +'/' + data[i].name); + } + window.location="rest/logfile?" + query; + this.logFileDialog.hide(); + } + }, + + destroy: function(){ + this.inherited(arguments); + if (this.logFileDialog) + { + this.logFileDialog.destroyRecursive(); + this.logFileDialog = null; + } + }, + + showDialog: function(){ + var self = this; + var requestArguments = {url: "rest/logfiles", sync: true, handleAs: "json"}; + xhr.get(requestArguments).then(function(data){ + try + { + self.logFilesGrid.store.objectStore.setData(data); + self.logFilesGrid.startup(); + self.logFileDialog.startup(); + self.logFileDialog.show(); + self.logFilesGrid._refresh(); + + } + catch(e) + { + console.error(e); + } + }); + } + + }); + +}); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogViewer.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogViewer.js new file mode 100644 index 0000000000..56b37d0167 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogViewer.js @@ -0,0 +1,199 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/date/locale", + "dijit/registry", + "qpid/common/grid/GridUpdater", + "qpid/common/grid/UpdatableGrid", + "qpid/management/logs/LogFileDownloadDialog", + "dojo/text!../../../logs/showLogViewer.html", + "dojo/domReady!"], + function (xhr, parser, query, locale, registry, GridUpdater, UpdatableGrid, LogFileDownloadDialog, markup) { + + var defaulGridRowLimit = 4096; + + function LogViewer(name, parent, controller) { + var self = this; + + this.name = name; + this.lastLogId = 0; + this.contentPane = null; + this.downloadLogsButton = null; + this.downloadLogDialog = null; + } + + LogViewer.prototype.getTitle = function() { + return "Log Viewer"; + }; + + LogViewer.prototype.open = function(contentPane) { + var self = this; + this.contentPane = contentPane; + this.contentPane.containerNode.innerHTML = markup; + + parser.parse(this.contentPane.containerNode); + + this.downloadLogsButton = registry.byNode(query(".downloadLogs", contentPane.containerNode)[0]); + this.downloadLogDialog = new LogFileDownloadDialog(); + + this.downloadLogsButton.on("click", function(evt){ + self.downloadLogDialog.showDialog(); + }); + this._buildGrid(); + }; + + LogViewer.prototype._buildGrid = function() { + var self = this; + + var gridStructure = [ + { + hidden: true, + name: "ID", + field: "id", + width: "50px", + datatype: "number", + filterable: true + }, + { + name: "Date", field: "timestamp", width: "100px", datatype: "date", + formatter: function(val) { + var d = new Date(0); + d.setUTCSeconds(val/1000); + return locale.format(d, {selector:"date", datePattern: "EEE, MMM d yy"}); + }, + dataTypeArgs: { + selector: "date", + datePattern: "EEE MMMM d yyy" + } + }, + { name: "Time", field: "timestamp", width: "150px", datatype: "time", + formatter: function(val) { + var d = new Date(0); + d.setUTCSeconds(val/1000); + return locale.format(d, {selector:"time", timePattern: "HH:mm:ss z (ZZZZ)"}); + }, + dataTypeArgs: { + selector: "time", + timePattern: "HH:mm:ss ZZZZ" + } + }, + { name: "Level", field: "level", width: "50px", datatype: "string", autoComplete: true, hidden: true}, + { name: "Logger", field: "logger", width: "150px", datatype: "string", autoComplete: false, hidden: true}, + { name: "Thread", field: "thread", width: "100px", datatype: "string", hidden: true}, + { name: "Log Message", field: "message", width: "auto", datatype: "string"} + ]; + + var gridNode = query("#broker-logfile", this.contentPane.containerNode)[0]; + try + { + var updater = new GridUpdater({ + updatable: false, + serviceUrl: function() + { + return "rest/logrecords?lastLogId=" + self.lastLogId; + }, + onUpdate: function(items) + { + if (items) + { + var maxId = -1; + for(var i in items) + { + var item = items[i]; + if (item.id > maxId) + { + maxId = item.id + } + } + if (maxId != -1) + { + self.lastLogId = maxId + } + } + }, + append: true, + appendLimit: defaulGridRowLimit + }); + this.grid = new UpdatableGrid(updater.buildUpdatableGridArguments({ + structure: gridStructure, + selectable: true, + selectionMode: "none", + sortInfo: -1, + sortFields: [{attribute: 'timestamp', descending: true}], + plugins:{ + nestedSorting:true, + enhancedFilter:{defaulGridRowLimit: defaulGridRowLimit}, + indirectSelection: false + } + }), gridNode); + var onStyleRow = function(row) + { + var item = self.grid.getItem(row.index); + if(item){ + var level = self.grid.store.getValue(item, "level", null); + var changed = false; + if(level == "ERROR"){ + row.customClasses += " redBackground"; + changed = true; + } else if(level == "WARN"){ + row.customClasses += " yellowBackground"; + changed = true; + } else if(level == "DEBUG"){ + row.customClasses += " grayBackground"; + changed = true; + } + if (changed) + { + self.grid.focus.styleRow(row); + } + } + }; + this.grid.on("styleRow", onStyleRow); + this.grid.startup(); + } + catch(err) + { + console.error(err); + } + }; + + LogViewer.prototype.close = function() { + if (this.grid) + { + this.grid.destroy(); + this.grid = null; + } + if (this.downloadLogDialog) + { + this.downloadLogDialog.destroy(); + this.downloadLogDialog = null; + } + if (this.downloadLogsButton) + { + this.downloadLogsButton.destroy(); + this.downloadLogsButton = null; + } + }; + + return LogViewer; + }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/showMessage.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/showMessage.js index b1ccc0ca07..59822ec535 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/showMessage.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/showMessage.js @@ -37,15 +37,10 @@ define(["dojo/_base/xhr", return typeof val === 'string' ? entities.encode(val) : val; } + var populatedFields = []; var showMessage = {}; showMessage.hide = function () { - if(this.populatedFields) { - for(var i = 0 ; i < this.populatedFields.length; i++) { - this.populatedFields[i].innerHTML = ""; - } - this.populatedFields = []; - } registry.byId("showMessage").hide(); }; @@ -65,16 +60,22 @@ define(["dojo/_base/xhr", showMessage.populateShowMessage = function(data) { - this.populatedFields = []; + // clear fields set by previous invocation. + if(populatedFields) { + for(var i = 0 ; i < populatedFields.length; i++) { + populatedFields[i].innerHTML = ""; + } + populatedFields = []; + } for(var attrName in data) { if(data.hasOwnProperty(attrName)) { var fields = query(".message-"+attrName, this.dialogNode); if(fields && fields.length != 0) { var field = fields[0]; - this.populatedFields.push(field); + populatedFields.push(field); var val = data[attrName]; - if(val) { + if(val != null) { if(domClass.contains(field,"map")) { var tableStr = "<table style='border: 1pt'><tr><th style='width: 6em; font-weight: bold'>Header</th><th style='font-weight: bold'>Value</th></tr>"; for(var name in val) { @@ -112,7 +113,7 @@ define(["dojo/_base/xhr", + "/" + encodeURIComponent(showMessage.messageNumber) + "\" target=\"_blank\">Download</a>"; } - this.populatedFields.push(contentField); + populatedFields.push(contentField); registry.byId("showMessage").show(); }; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js index 8dc336b347..8770509c27 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js @@ -284,6 +284,8 @@ define(["dojo/_base/xhr", controller.show("accesscontrolprovider", details.accesscontrolprovider, {broker: {type:"broker", name:""}}); } else if (details.type == 'plugin') { controller.show("plugin", details.plugin, {broker: {type:"broker", name:""}}); + } else if (details.type == "preferencesprovider") { + controller.show("preferencesprovider", details.preferencesprovider, { type: "authenticationprovider", name: details.authenticationprovider, parent: {broker: {type:"broker", name:""}}}); } }; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/logs/showLogFileDownloadDialog.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/logs/showLogFileDownloadDialog.html new file mode 100644 index 0000000000..bc633d059a --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/logs/showLogFileDownloadDialog.html @@ -0,0 +1,33 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div> + <div class="contentArea" style="height:320px;overflow:auto"> + <div><b>Select log files to download</b></div> + <div class="logFilesGrid" style='height:300px;width: 580px'></div> + </div> + <div class="dijitDialogPaneActionBar"> + <button value="Download" data-dojo-type="dijit.form.Button" + class="downloadLogsButton" + data-dojo-props="iconClass: 'downloadLogsIcon', label: 'Download' "></button> + <button value="Close" data-dojo-type="dijit.form.Button" data-dojo-props="label: 'Close'" + class="downloadLogsDialogCloseButton"></button> + </div> +</div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/logs/showLogViewer.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/logs/showLogViewer.html new file mode 100644 index 0000000000..10ac09a406 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/logs/showLogViewer.html @@ -0,0 +1,29 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div class="logViewer"> + + <div id="broker-logfile"></div> + <br/> + <button data-dojo-type="dijit.form.Button" class="downloadLogs" + data-dojo-props="iconClass: 'downloadLogsIcon', title:'Download Log Files', name: 'downloadLogs'">Download</button> + <br/> +</div> + diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showAuthProvider.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showAuthProvider.html index 5e876fdc1f..aabaee1e9d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/showAuthProvider.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showAuthProvider.html @@ -27,4 +27,11 @@ <br/> <button data-dojo-type="dijit.form.Button" class="editAuthenticationProviderButton">Edit</button> <button data-dojo-type="dijit.form.Button" class="deleteAuthenticationProviderButton">Delete</button> + <br/> + <br/> + <div class="preferencesPanel" data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Preferences Provider', open: true"> + <div class="preferencesProviderDetails"></div> + <button data-dojo-type="dijit.form.Button" class="addPreferencesProviderButton">Add</button> + </div> + <br/> </div>
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html index d9991452af..366ef27c8a 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html @@ -190,9 +190,7 @@ <button data-dojo-type="dijit.form.Button" class="deleteAccessControlProvider">Delete Access Control Provider</button> </div> <br/> - <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Log File', open: false"> - <div class="broker-logfile"></div> - </div> - <br/> + <button data-dojo-type="dijit.form.Button" class="logViewer" data-dojo-props="iconClass: 'logViewerIcon'">Log Viewer</button> + <br/><br/> </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showMessage.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showMessage.html index 0dea508c60..9a6ec55686 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/showMessage.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showMessage.html @@ -45,7 +45,7 @@ </tr> <tr style="margin-bottom: 4pt"> <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Expiration:</span></td> - <td><span class="message-expiration datetime"></span></td> + <td><span class="message-expirationTime datetime"></span></td> </tr> <tr style="margin-bottom: 4pt"> <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">MIME Type:</span></td> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showPreferences.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showPreferences.html new file mode 100644 index 0000000000..ede111272a --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showPreferences.html @@ -0,0 +1,83 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div data-dojo-type="dijit/Dialog" data-dojo-props="title:'Preferences'" id="preferences.preferencesDialog"> + <div data-dojo-type="dijit/layout/TabContainer" style="width: 600px; height: 400px"> + <div data-dojo-type="dijit/layout/ContentPane" title="Own Preferences" data-dojo-props="selected:true" id="preferences.preferencesTab"> + <form method="post" data-dojo-type="dijit/form/Form" id="preferences.preferencesForm"> + <table cellpadding="0" cellspacing="2" style="overflow: auto; height: 300px;"> + <tr> + <td><strong>Time zone: </strong></td> + <td> + <span id="preferences.timeZone" data-dojo-type="qpid/common/TimeZoneSelector" data-dojo-props="name: 'timeZone'"></span> + </td> + </tr> + <tr> + <td><strong>Update period:</strong></td> + <td><input id="preferences.updatePeriod" name="updatePeriod" data-dojo-type="dijit/form/NumberSpinner" data-dojo-props=" + invalidMessage: 'Invalid value', + required: false, + smallDelta: 1, + value: 5, + constraints: {min:1,max:65535,places:0, pattern: '#####'}, + "/> + </td> + </tr> + <tr> + <td><strong>Save tabs:</strong></td> + <td><input id="preferences.saveTabs" type="checkbox" data-dojo-type="dijit/form/CheckBox" name="saveTabs"/></td> + </tr> + </table> + <div class="dijitDialogPaneActionBar"> + <input type="submit" value="Save Preferences" data-dojo-type="dijit/form/Button" data-dojo-props="label: 'Save Preferences'" id="preferences.saveButton"/> + <button value="Cancel" data-dojo-type="dijit/form/Button" data-dojo-props="label: 'Cancel'" id="preferences.cancelButton"></button> + </div> + </form> + </div> + <div data-dojo-type="dijit/layout/ContentPane" title="Users with Preferences" id="preferences.usersTab"> + <table id="preferences.users" data-dojo-type="dojox/grid/EnhancedGrid" data-dojo-props=" + label:'Trust Stores:', + plugins:{ + indirectSelection: true, + pagination: { + pageSizes: [10, 25, 50, 100], + description: true, + sizeSwitch: true, + pageStepper: true, + gotoButton: true, + maxPageStep: 4, + position: 'bottom' + } + }, + rowSelector:'0px' + " style="height: 300px;"> + <thead> + <tr> + <th field="name" style="width:50%">User</th> + <th field="authenticationProvider" style="width:50%">Authentication Provider</th> + </tr> + </thead> + </table> + <div class="dijitDialogPaneActionBar"> + <button id="preferences.deletePreeferencesButton" data-dojo-type="dijit/form/Button" data-dojo-props="label:'Delete Preferences', title:'Delete preferences for selected users'">Delete Preferences</button> + </div> + </div> + </div> +</div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showPreferencesProvider.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showPreferencesProvider.html new file mode 100644 index 0000000000..a1885acddf --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showPreferencesProvider.html @@ -0,0 +1,40 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> + <div class="preferencesProvider"> + <div class="preferencesProviderAttributes"> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 100px;">Type:</div> + <div class="preferencesProviderType" style="float:left;"></div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 100px;">Name:</div> + <div class="preferencesProviderName" style="float:left;"></div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 100px;">State:</div> + <div class="preferencesProviderState" style="float:left;"></div> + </div> + <div class="preferencesDetails"></div> + </div> + <br/> + <button data-dojo-type="dijit.form.Button" class="deletePreferencesProviderButton">Delete</button> + <button data-dojo-type="dijit.form.Button" class="editPreferencesProviderButton">Edit</button> +</div>
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/HttpManagementTest.java b/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/HttpManagementTest.java index 18d774e341..d0a357fd28 100644 --- a/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/HttpManagementTest.java +++ b/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/HttpManagementTest.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.test.utils.QpidTestCase; @@ -88,13 +89,13 @@ public class HttpManagementTest extends QpidTestCase _management.isHttpBasicAuthenticationEnabled()); } - public void testGetSubjectCreator() + public void testGetAuthenticationProvider() { SocketAddress localAddress = InetSocketAddress.createUnresolved("localhost", 8080); - SubjectCreator subjectCreator = mock(SubjectCreator.class); - when(_broker.getSubjectCreator(localAddress)).thenReturn(subjectCreator); - SubjectCreator httpManagementSubjectCreator = _management.getSubjectCreator(localAddress); - assertEquals("Unexpected subject creator", subjectCreator, httpManagementSubjectCreator); + AuthenticationProvider brokerAuthenticationProvider = mock(AuthenticationProvider.class); + when(_broker.getAuthenticationProvider(localAddress)).thenReturn(brokerAuthenticationProvider); + AuthenticationProvider authenticationProvider = _management.getAuthenticationProvider(localAddress); + assertEquals("Unexpected subject creator", brokerAuthenticationProvider, authenticationProvider); } } diff --git a/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/log/LogFileHelperTest.java b/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/log/LogFileHelperTest.java new file mode 100644 index 0000000000..608ef28f02 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/log/LogFileHelperTest.java @@ -0,0 +1,339 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.management.plugin.log; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.apache.log4j.Appender; +import org.apache.log4j.DailyRollingFileAppender; +import org.apache.log4j.FileAppender; +import org.apache.log4j.QpidCompositeRollingAppender; +import org.apache.log4j.RollingFileAppender; +import org.apache.log4j.varia.ExternallyRolledFileAppender; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; +import org.apache.qpid.util.FileUtils; + +public class LogFileHelperTest extends QpidTestCase +{ + private Map<String, List<File>> _appendersFiles; + private File _compositeRollingAppenderBackupFolder; + private List<Appender> _appenders; + private LogFileHelper _helper; + + public void setUp() throws Exception + { + super.setUp(); + _appendersFiles = new HashMap<String, List<File>>(); + _compositeRollingAppenderBackupFolder = new File(TMP_FOLDER, "_compositeRollingAppenderBackupFolder"); + _compositeRollingAppenderBackupFolder.mkdirs(); + + _appendersFiles.put(FileAppender.class.getSimpleName(), + Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "FileAppender"))); + _appendersFiles.put(DailyRollingFileAppender.class.getSimpleName(), + Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "DailyRollingFileAppender"))); + _appendersFiles.put(RollingFileAppender.class.getSimpleName(), + Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "RollingFileAppender"))); + _appendersFiles.put(ExternallyRolledFileAppender.class.getSimpleName(), + Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "ExternallyRolledFileAppender"))); + + File file = TestFileUtils.createTempFile(this, ".log", "QpidCompositeRollingAppender"); + File backUpFile = File.createTempFile(file.getName() + ".", ".1." + LogFileHelper.GZIP_EXTENSION); + _appendersFiles.put(QpidCompositeRollingAppender.class.getSimpleName(), Arrays.asList(file, backUpFile)); + + FileAppender fileAppender = new FileAppender(); + DailyRollingFileAppender dailyRollingFileAppender = new DailyRollingFileAppender(); + RollingFileAppender rollingFileAppender = new RollingFileAppender(); + ExternallyRolledFileAppender externallyRolledFileAppender = new ExternallyRolledFileAppender(); + QpidCompositeRollingAppender qpidCompositeRollingAppender = new QpidCompositeRollingAppender(); + qpidCompositeRollingAppender.setbackupFilesToPath(_compositeRollingAppenderBackupFolder.getPath()); + + _appenders = new ArrayList<Appender>(); + _appenders.add(fileAppender); + _appenders.add(dailyRollingFileAppender); + _appenders.add(rollingFileAppender); + _appenders.add(externallyRolledFileAppender); + _appenders.add(qpidCompositeRollingAppender); + + for (Appender appender : _appenders) + { + FileAppender fa = (FileAppender) appender; + fa.setName(fa.getClass().getSimpleName()); + fa.setFile(_appendersFiles.get(fa.getClass().getSimpleName()).get(0).getPath()); + } + + _helper = new LogFileHelper(_appenders); + } + + public void tearDown() throws Exception + { + try + { + for (List<File> files : _appendersFiles.values()) + { + for (File file : files) + { + try + { + FileUtils.delete(file, false); + } + catch (Exception e) + { + // ignore + } + } + } + FileUtils.delete(_compositeRollingAppenderBackupFolder, true); + } + finally + { + super.tearDown(); + } + } + + public void testGetLogFileDetailsWithLocations() throws Exception + { + List<LogFileDetails> details = _helper.getLogFileDetails(true); + + assertLogFiles(details, true); + } + + public void testGetLogFileDetailsWithoutLocations() throws Exception + { + List<LogFileDetails> details = _helper.getLogFileDetails(false); + + assertLogFiles(details, false); + } + + public void testWriteLogFilesForAllLogs() throws Exception + { + List<LogFileDetails> details = _helper.getLogFileDetails(true); + File f = TestFileUtils.createTempFile(this, ".zip"); + + FileOutputStream os = new FileOutputStream(f); + try + { + _helper.writeLogFiles(details, os); + } + finally + { + if (os != null) + { + os.close(); + } + } + + assertWrittenFile(f, details); + } + + public void testWriteLogFile() throws Exception + { + File file = _appendersFiles.get(FileAppender.class.getSimpleName()).get(0); + + File f = TestFileUtils.createTempFile(this, ".log"); + FileOutputStream os = new FileOutputStream(f); + try + { + _helper.writeLogFile(file, os); + } + finally + { + if (os != null) + { + os.close(); + } + } + + assertEquals("Unexpected log content", FileAppender.class.getSimpleName(), FileUtils.readFileAsString(f)); + } + + public void testFindLogFileDetails() + { + String[] logFileDisplayedPaths = new String[6]; + File[] files = new File[logFileDisplayedPaths.length]; + int i = 0; + for (Map.Entry<String, List<File>> entry : _appendersFiles.entrySet()) + { + String appenderName = entry.getKey(); + List<File> appenderFiles = entry.getValue(); + for (File logFile : appenderFiles) + { + logFileDisplayedPaths[i] = appenderName + "/" + logFile.getName(); + files[i++] = logFile; + } + } + + List<LogFileDetails> logFileDetails = _helper.findLogFileDetails(logFileDisplayedPaths); + assertEquals("Unexpected details size", logFileDisplayedPaths.length, logFileDetails.size()); + + boolean gzipFileFound = false; + for (int j = 0; j < logFileDisplayedPaths.length; j++) + { + String displayedPath = logFileDisplayedPaths[j]; + String[] parts = displayedPath.split("/"); + LogFileDetails d = logFileDetails.get(j); + assertEquals("Unexpected name", parts[1], d.getName()); + assertEquals("Unexpected appender", parts[0], d.getAppenderName()); + if (files[j].getName().endsWith(LogFileHelper.GZIP_EXTENSION)) + { + assertEquals("Unexpected mime type for gz file", LogFileHelper.GZIP_MIME_TYPE, d.getMimeType()); + gzipFileFound = true; + } + else + { + assertEquals("Unexpected mime type", LogFileHelper.TEXT_MIME_TYPE, d.getMimeType()); + } + assertEquals("Unexpecte file location", files[j], d.getLocation()); + assertEquals("Unexpecte file size", files[j].length(), d.getSize()); + assertEquals("Unexpecte file last modified date", files[j].lastModified(), d.getLastModified()); + } + assertTrue("Gzip log file is not found", gzipFileFound); + } + + public void testFindLogFileDetailsForNotExistingAppender() + { + String[] logFileDisplayedPaths = { "NotExistingAppender/qpid.log" }; + List<LogFileDetails> details = _helper.findLogFileDetails(logFileDisplayedPaths); + assertTrue("No details should be created for non-existing appender", details.isEmpty()); + } + + public void testFindLogFileDetailsForNotExistingFile() + { + String[] logFileDisplayedPaths = { "FileAppender/qpid-non-existing.log" }; + List<LogFileDetails> details = _helper.findLogFileDetails(logFileDisplayedPaths); + assertTrue("No details should be created for non-existing file", details.isEmpty()); + } + + public void testFindLogFileDetailsForIncorectlySpecifiedLogFilePath() + { + String[] logFileDisplayedPaths = { "FileAppender\\" + _appendersFiles.get("FileAppender").get(0).getName() }; + try + { + _helper.findLogFileDetails(logFileDisplayedPaths); + fail("Exception is expected for incorectly set path to log file"); + } + catch (IllegalArgumentException e) + { + // pass + } + } + + private void assertLogFiles(List<LogFileDetails> details, boolean includeLocation) + { + for (Map.Entry<String, List<File>> appenderData : _appendersFiles.entrySet()) + { + String appenderName = (String) appenderData.getKey(); + List<File> files = appenderData.getValue(); + + for (File logFile : files) + { + String logFileName = logFile.getName(); + LogFileDetails d = findLogFileDetails(logFileName, appenderName, details); + assertNotNull("Log file " + logFileName + " is not found for appender " + appenderName, d); + if (includeLocation) + { + assertEquals("Log file " + logFileName + " is different in appender " + appenderName, d.getLocation(), + logFile); + } + } + } + } + + private LogFileDetails findLogFileDetails(String logFileName, String appenderName, List<LogFileDetails> logFileDetails) + { + LogFileDetails d = null; + for (LogFileDetails lfd : logFileDetails) + { + if (lfd.getName().equals(logFileName) && lfd.getAppenderName().equals(appenderName)) + { + d = lfd; + break; + } + } + return d; + } + + private void assertWrittenFile(File f, List<LogFileDetails> details) throws FileNotFoundException, IOException + { + FileInputStream fis = new FileInputStream(f); + try + { + ZipInputStream zis = new ZipInputStream(fis); + ZipEntry ze = zis.getNextEntry(); + + while (ze != null) + { + String entryName = ze.getName(); + String[] parts = entryName.split("/"); + + String appenderName = parts[0]; + String logFileName = parts[1]; + + LogFileDetails d = findLogFileDetails(logFileName, appenderName, details); + + assertNotNull("Unexpected entry " + entryName, d); + details.remove(d); + + File logFile = d.getLocation(); + String logContent = FileUtils.readFileAsString(logFile); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = zis.read(buffer)) > 0) + { + baos.write(buffer, 0, len); + } + baos.close(); + + assertEquals("Unexpected log file content", logContent, baos.toString()); + + ze = zis.getNextEntry(); + } + + zis.closeEntry(); + zis.close(); + + } + finally + { + if (fis != null) + { + fis.close(); + } + } + assertEquals("Not all log files have been output", 0, details.size()); + } + +} diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java index d094134e11..32aac51008 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java @@ -20,6 +20,7 @@ */ package org.apache.qpid.server.jmx; +import javax.net.ssl.KeyManager; import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.BrokerProperties; import org.apache.qpid.server.logging.actors.CurrentActor; @@ -124,26 +125,19 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry { KeyStore keyStore = _connectorPort.getKeyStore(); - String keyStorePath = (String) keyStore.getAttribute(KeyStore.PATH); - String keyStorePassword = keyStore.getPassword(); - String keyStoreType = (String) keyStore.getAttribute(KeyStore.TYPE); - String keyManagerFactoryAlgorithm = (String) keyStore.getAttribute(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM); - SSLContext sslContext; try { - sslContext = SSLContextFactory.buildServerContext(keyStorePath, keyStorePassword, keyStoreType, keyManagerFactoryAlgorithm); + + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyStore.getKeyManagers(), null, null); } catch (GeneralSecurityException e) { throw new RuntimeException("Unable to create SSLContext for key store", e); } - catch (IOException e) - { - throw new RuntimeException("Unable to create SSLContext for key store", e); - } - CurrentActor.get().message(ManagementConsoleMessages.SSL_KEYSTORE(keyStorePath)); + CurrentActor.get().message(ManagementConsoleMessages.SSL_KEYSTORE(keyStore.getName())); //create the SSL RMI socket factories csf = new SslRMIClientSocketFactory(); diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java index 67ac1bdc7c..2c88f83405 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java @@ -48,6 +48,7 @@ import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueArgumentsConverter; @MBeanDescription("This MBean exposes the broker level management features") public class VirtualHostManagerMBean extends AbstractStatisticsGatheringMBean<VirtualHost> implements ManagedBroker @@ -180,7 +181,8 @@ public class VirtualHostManagerMBean extends AbstractStatisticsGatheringMBean<Vi throws IOException, JMException { final Map<String, Object> createArgs = processNewQueueArguments(queueName, owner, originalArguments); - getConfiguredObject().createQueue(queueName, State.ACTIVE, durable, false, LifetimePolicy.PERMANENT, 0l, createArgs); + getConfiguredObject().createQueue(queueName, State.ACTIVE, durable, false, LifetimePolicy.PERMANENT, 0l, + QueueArgumentsConverter.convertWireArgsToModel(createArgs)); } @@ -196,11 +198,11 @@ public class VirtualHostManagerMBean extends AbstractStatisticsGatheringMBean<Vi if (_moveNonExclusiveQueueOwnerToDescription && owner != null) { argumentsCopy = new HashMap<String, Object>(arguments == null ? new HashMap<String, Object>() : arguments); - if (!argumentsCopy.containsKey(AMQQueueFactory.X_QPID_DESCRIPTION)) + if (!argumentsCopy.containsKey(QueueArgumentsConverter.X_QPID_DESCRIPTION)) { - LOGGER.warn("Non-exclusive owner " + owner + " for new queue " + queueName + " moved to " + AMQQueueFactory.X_QPID_DESCRIPTION); + LOGGER.warn("Non-exclusive owner " + owner + " for new queue " + queueName + " moved to " + QueueArgumentsConverter.X_QPID_DESCRIPTION); - argumentsCopy.put(AMQQueueFactory.X_QPID_DESCRIPTION, owner); + argumentsCopy.put(QueueArgumentsConverter.X_QPID_DESCRIPTION, owner); } else { diff --git a/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java b/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java index e3fac9f711..4240dd5280 100644 --- a/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java +++ b/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java @@ -38,6 +38,7 @@ import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueArgumentsConverter; public class VirtualHostManagerMBeanTest extends TestCase { @@ -79,16 +80,16 @@ public class VirtualHostManagerMBeanTest extends TestCase { _virtualHostManagerMBean.createNewQueue(TEST_QUEUE_NAME, TEST_OWNER, true); - Map<String, Object> expectedArguments = Collections.singletonMap(AMQQueueFactory.X_QPID_DESCRIPTION, (Object)TEST_OWNER); + Map<String, Object> expectedArguments = Collections.singletonMap(Queue.DESCRIPTION, (Object)TEST_OWNER); verify(_mockVirtualHost).createQueue(TEST_QUEUE_NAME, State.ACTIVE, true, false, LifetimePolicy.PERMANENT, 0, expectedArguments); } public void testCreateQueueWithOwnerAndDescriptionDiscardsOwner() throws Exception { - Map<String, Object> arguments = Collections.singletonMap(AMQQueueFactory.X_QPID_DESCRIPTION, (Object)TEST_DESCRIPTION); + Map<String, Object> arguments = Collections.singletonMap(QueueArgumentsConverter.X_QPID_DESCRIPTION, (Object)TEST_DESCRIPTION); _virtualHostManagerMBean.createNewQueue(TEST_QUEUE_NAME, TEST_OWNER, true, arguments); - Map<String, Object> expectedArguments = Collections.singletonMap(AMQQueueFactory.X_QPID_DESCRIPTION, (Object)TEST_DESCRIPTION); + Map<String, Object> expectedArguments = Collections.singletonMap(Queue.DESCRIPTION, (Object)TEST_DESCRIPTION); verify(_mockVirtualHost).createQueue(TEST_QUEUE_NAME, State.ACTIVE, true, false, LifetimePolicy.PERMANENT, 0, expectedArguments); } diff --git a/qpid/java/broker-plugins/memory-store/build.xml b/qpid/java/broker-plugins/memory-store/build.xml new file mode 100644 index 0000000000..f265e68e94 --- /dev/null +++ b/qpid/java/broker-plugins/memory-store/build.xml @@ -0,0 +1,32 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins Memory Store" default="build"> + <property name="module.depends" value="common broker" /> + <property name="module.test.depends" value="common/tests broker/tests" /> + + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + + <property name="broker.plugin" value="true"/> + <property name="broker-plugins-memory-store.libs" value="" /> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks"/> +</project> diff --git a/qpid/java/broker-plugins/memory-store/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java b/qpid/java/broker-plugins/memory-store/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java new file mode 100644 index 0000000000..61fef91e83 --- /dev/null +++ b/qpid/java/broker-plugins/memory-store/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + + +/** A simple message store that stores the messages in a thread-safe structure in memory. */ +public class MemoryMessageStore extends AbstractMemoryMessageStore +{ + public static final String TYPE = "Memory"; + + @Override + public String getStoreType() + { + return TYPE; + } +} diff --git a/qpid/java/broker-plugins/memory-store/src/main/java/org/apache/qpid/server/store/MemoryMessageStoreFactory.java b/qpid/java/broker-plugins/memory-store/src/main/java/org/apache/qpid/server/store/MemoryMessageStoreFactory.java new file mode 100644 index 0000000000..49f823e7ee --- /dev/null +++ b/qpid/java/broker-plugins/memory-store/src/main/java/org/apache/qpid/server/store/MemoryMessageStoreFactory.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import java.util.Collections; +import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.plugin.MessageStoreFactory; + +public class MemoryMessageStoreFactory implements MessageStoreFactory +{ + + @Override + public String getType() + { + return MemoryMessageStore.TYPE; + } + + @Override + public MessageStore createMessageStore() + { + return new MemoryMessageStore(); + } + + @Override + public Map<String, Object> convertStoreConfiguration(Configuration configuration) + { + return Collections.emptyMap(); + } + + @Override + public void validateAttributes(Map<String, Object> attributes) + { + } +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/virtualhost/store/memory/add.js b/qpid/java/broker-plugins/memory-store/src/main/java/resources/js/qpid/management/virtualhost/store/memory/add.js index 3a9b23274d..3a9b23274d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/virtualhost/store/memory/add.js +++ b/qpid/java/broker-plugins/memory-store/src/main/java/resources/js/qpid/management/virtualhost/store/memory/add.js diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/virtualhost/store/pool/none/add.html b/qpid/java/broker-plugins/memory-store/src/main/java/resources/virtualhost/store/memory/add.html index e69de29bb2..e69de29bb2 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/virtualhost/store/pool/none/add.html +++ b/qpid/java/broker-plugins/memory-store/src/main/java/resources/virtualhost/store/memory/add.html diff --git a/qpid/java/broker-plugins/memory-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory b/qpid/java/broker-plugins/memory-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory new file mode 100644 index 0000000000..02f22eb21a --- /dev/null +++ b/qpid/java/broker-plugins/memory-store/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.store.MemoryMessageStoreFactory |
