001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.transport.amqp;
018
019import java.nio.ByteBuffer;
020import java.util.AbstractMap;
021import java.util.Map;
022
023import org.apache.activemq.command.ActiveMQDestination;
024import org.apache.qpid.proton.amqp.Binary;
025import org.apache.qpid.proton.amqp.DescribedType;
026import org.apache.qpid.proton.amqp.Symbol;
027import org.apache.qpid.proton.amqp.UnsignedLong;
028import org.apache.qpid.proton.amqp.transaction.Coordinator;
029import org.fusesource.hawtbuf.Buffer;
030
031/**
032 * Set of useful methods and definitions used in the AMQP protocol handling
033 */
034public class AmqpSupport {
035
036    // Identification values used to locating JMS selector types.
037    public static final UnsignedLong JMS_SELECTOR_CODE = UnsignedLong.valueOf(0x0000468C00000004L);
038    public static final Symbol JMS_SELECTOR_NAME = Symbol.valueOf("apache.org:selector-filter:string");
039    public static final Object[] JMS_SELECTOR_FILTER_IDS = new Object[] { JMS_SELECTOR_CODE, JMS_SELECTOR_NAME };
040    public static final UnsignedLong NO_LOCAL_CODE = UnsignedLong.valueOf(0x0000468C00000003L);
041    public static final Symbol NO_LOCAL_NAME = Symbol.valueOf("apache.org:no-local-filter:list");
042    public static final Object[] NO_LOCAL_FILTER_IDS = new Object[] { NO_LOCAL_CODE, NO_LOCAL_NAME };
043
044    // Capabilities used to identify destination type in some requests.
045    public static final Symbol TEMP_QUEUE_CAPABILITY = Symbol.valueOf("temporary-queue");
046    public static final Symbol TEMP_TOPIC_CAPABILITY = Symbol.valueOf("temporary-topic");
047    public static final Symbol QUEUE_CAPABILITY = Symbol.valueOf("queue");
048    public static final Symbol TOPIC_CAPABILITY = Symbol.valueOf("topic");
049
050    // Symbols used to announce connection information to remote peer.
051    public static final Symbol INVALID_FIELD = Symbol.valueOf("invalid-field");
052    public static final Symbol CONTAINER_ID = Symbol.valueOf("container-id");
053
054    // Symbols used to announce connection information to remote peer.
055    public static final Symbol ANONYMOUS_RELAY = Symbol.valueOf("ANONYMOUS-RELAY");
056    public static final Symbol DELAYED_DELIVERY = Symbol.valueOf("DELAYED_DELIVERY");
057    public static final Symbol QUEUE_PREFIX = Symbol.valueOf("queue-prefix");
058    public static final Symbol TOPIC_PREFIX = Symbol.valueOf("topic-prefix");
059    public static final Symbol CONNECTION_OPEN_FAILED = Symbol.valueOf("amqp:connection-establishment-failed");
060    public static final Symbol PRODUCT = Symbol.valueOf("product");
061    public static final Symbol VERSION = Symbol.valueOf("version");
062    public static final Symbol PLATFORM = Symbol.valueOf("platform");
063
064    // Symbols used in configuration of newly opened links.
065    public static final Symbol COPY = Symbol.getSymbol("copy");
066
067    // Lifetime policy symbols
068    public static final Symbol LIFETIME_POLICY = Symbol.valueOf("lifetime-policy");
069
070    /**
071     * Search for a given Symbol in a given array of Symbol object.
072     *
073     * @param symbols
074     *        the set of Symbols to search.
075     * @param key
076     *        the value to try and find in the Symbol array.
077     *
078     * @return true if the key is found in the given Symbol array.
079     */
080    public static boolean contains(Symbol[] symbols, Symbol key) {
081        if (symbols == null || symbols.length == 0) {
082            return false;
083        }
084
085        for (Symbol symbol : symbols) {
086            if (symbol.equals(key)) {
087                return true;
088            }
089        }
090
091        return false;
092    }
093
094    /**
095     * Search for a particular filter using a set of known indentification values
096     * in the Map of filters.
097     *
098     * @param filters
099     *        The filters map that should be searched.
100     * @param filterIds
101     *        The aliases for the target filter to be located.
102     *
103     * @return the filter if found in the mapping or null if not found.
104     */
105    public static Map.Entry<Symbol, DescribedType> findFilter(Map<Symbol, Object> filters, Object[] filterIds) {
106
107        if (filterIds == null || filterIds.length == 0) {
108            throw new IllegalArgumentException("Invalid empty Filter Ids array passed: ");
109        }
110
111        if (filters == null || filters.isEmpty()) {
112            return null;
113        }
114
115        for (Map.Entry<Symbol, Object> filter : filters.entrySet()) {
116            if (filter.getValue() instanceof DescribedType) {
117                DescribedType describedType = ((DescribedType) filter.getValue());
118                Object descriptor = describedType.getDescriptor();
119
120                for (Object filterId : filterIds) {
121                    if (descriptor.equals(filterId)) {
122                        return new AbstractMap.SimpleImmutableEntry<Symbol, DescribedType>(filter.getKey(), describedType);
123                    }
124                }
125            }
126        }
127
128        return null;
129    }
130
131    /**
132     * Conversion from Java ByteBuffer to a HawtBuf buffer.
133     *
134     * @param data
135     *        the ByteBuffer instance to convert.
136     *
137     * @return a new HawtBuf buffer converted from the given ByteBuffer.
138     */
139    public static Buffer toBuffer(ByteBuffer data) {
140        if (data == null) {
141            return null;
142        }
143
144        Buffer rc;
145
146        if (data.isDirect()) {
147            rc = new Buffer(data.remaining());
148            data.get(rc.data);
149        } else {
150            rc = new Buffer(data);
151            data.position(data.position() + data.remaining());
152        }
153
154        return rc;
155    }
156
157    /**
158     * Given a long value, convert it to a byte array for marshalling.
159     *
160     * @param value
161     *        the value to convert.
162     *
163     * @return a new byte array that holds the big endian value of the long.
164     */
165    public static byte[] toBytes(long value) {
166        Buffer buffer = new Buffer(8);
167        buffer.bigEndianEditor().writeLong(value);
168        return buffer.data;
169    }
170
171    /**
172     * Converts a Binary value to a long assuming that the contained value is
173     * stored in Big Endian encoding.
174     *
175     * @param value
176     *        the Binary object whose payload is converted to a long.
177     *
178     * @return a long value constructed from the bytes of the Binary instance.
179     */
180    public static long toLong(Binary value) {
181        Buffer buffer = new Buffer(value.getArray(), value.getArrayOffset(), value.getLength());
182        return buffer.bigEndianEditor().readLong();
183    }
184
185    /**
186     * Given an AMQP endpoint, deduce the appropriate ActiveMQDestination type and create
187     * a new instance.  By default if the endpoint address does not carry the standard prefix
188     * value then we default to a Queue type destination.  If the endpoint is null or is an
189     * AMQP Coordinator type endpoint this method returns null to indicate no destination
190     * can be mapped.
191     *
192     * @param endpoint
193     *        the AMQP endpoint to construct an ActiveMQDestination from.
194     *
195     * @return a new ActiveMQDestination that best matches the address of the given endpoint
196     *
197     * @throws AmqpProtocolException if an error occurs while deducing the destination type.
198     */
199    public static ActiveMQDestination createDestination(Object endpoint) throws AmqpProtocolException {
200        if (endpoint == null) {
201            return null;
202        } else if (endpoint instanceof Coordinator) {
203            return null;
204        } else if (endpoint instanceof org.apache.qpid.proton.amqp.messaging.Terminus) {
205            org.apache.qpid.proton.amqp.messaging.Terminus terminus = (org.apache.qpid.proton.amqp.messaging.Terminus) endpoint;
206            if (terminus.getAddress() == null || terminus.getAddress().length() == 0) {
207                if (terminus instanceof org.apache.qpid.proton.amqp.messaging.Source) {
208                    throw new AmqpProtocolException("amqp:invalid-field", "source address not set");
209                } else {
210                    throw new AmqpProtocolException("amqp:invalid-field", "target address not set");
211                }
212            }
213
214            return ActiveMQDestination.createDestination(terminus.getAddress(), ActiveMQDestination.QUEUE_TYPE);
215        } else {
216            throw new RuntimeException("Unexpected terminus type: " + endpoint);
217        }
218    }
219
220    /**
221     * Given an ActiveMQDestination return the proper Capability value for the concrete destination type.
222     *
223     * @param destination
224     *      The ActiveMQDestination whose capability is being requested.
225     *
226     * @return a Symbol that matches the defined Capability value for the ActiveMQDestiantion.
227     */
228    public static Symbol getDestinationTypeSymbol(ActiveMQDestination destination) {
229        if (destination.isQueue()) {
230            if (destination.isTemporary()) {
231                return TEMP_QUEUE_CAPABILITY;
232            } else {
233                return QUEUE_CAPABILITY;
234            }
235        } else {
236            if (destination.isTemporary()) {
237                return TEMP_TOPIC_CAPABILITY;
238            } else {
239                return TOPIC_CAPABILITY;
240            }
241        }
242    }
243}