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     */
017    package org.apache.activemq.filter;
018    
019    import java.util.HashSet;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Set;
023    import java.util.SortedSet;
024    import java.util.TreeSet;
025    
026    import org.apache.activemq.command.ActiveMQDestination;
027    
028    /**
029     * A Map-like data structure allowing values to be indexed by
030     * {@link ActiveMQDestination} and retrieved by destination - supporting both *
031     * and &gt; style of wildcard as well as composite destinations. <br>
032     * This class assumes that the index changes rarely but that fast lookup into
033     * the index is required. So this class maintains a pre-calculated index for
034     * destination steps. So looking up the values for "TEST.*" or "*.TEST" will be
035     * pretty fast. <br>
036     * Looking up of a value could return a single value or a List of matching
037     * values if a wildcard or composite destination is used.
038     * 
039     * 
040     */
041    public class DestinationMap {
042        protected static final String ANY_DESCENDENT = DestinationFilter.ANY_DESCENDENT;
043        protected static final String ANY_CHILD = DestinationFilter.ANY_CHILD;
044    
045        private DestinationMapNode queueRootNode = new DestinationMapNode(null);
046        private DestinationMapNode tempQueueRootNode = new DestinationMapNode(null);
047        private DestinationMapNode topicRootNode = new DestinationMapNode(null);
048        private DestinationMapNode tempTopicRootNode = new DestinationMapNode(null);
049    
050        /**
051         * Looks up the value(s) matching the given Destination key. For simple
052         * destinations this is typically a List of one single value, for wildcards
053         * or composite destinations this will typically be a List of matching
054         * values.
055         * 
056         * @param key the destination to lookup
057         * @return a List of matching values or an empty list if there are no
058         *         matching values.
059         */
060        public synchronized Set get(ActiveMQDestination key) {
061            if (key.isComposite()) {
062                ActiveMQDestination[] destinations = key.getCompositeDestinations();
063                Set answer = new HashSet(destinations.length);
064                for (int i = 0; i < destinations.length; i++) {
065                    ActiveMQDestination childDestination = destinations[i];
066                    Object value = get(childDestination);
067                    if (value instanceof Set) {
068                        answer.addAll((Set)value);
069                    } else if (value != null) {
070                        answer.add(value);
071                    }
072                }
073                return answer;
074            }
075            return findWildcardMatches(key);
076        }
077    
078        public synchronized void put(ActiveMQDestination key, Object value) {
079            if (key.isComposite()) {
080                ActiveMQDestination[] destinations = key.getCompositeDestinations();
081                for (int i = 0; i < destinations.length; i++) {
082                    ActiveMQDestination childDestination = destinations[i];
083                    put(childDestination, value);
084                }
085                return;
086            }
087            String[] paths = key.getDestinationPaths();
088            getRootNode(key).add(paths, 0, value);
089        }
090    
091        /**
092         * Removes the value from the associated destination
093         */
094        public synchronized void remove(ActiveMQDestination key, Object value) {
095            if (key.isComposite()) {
096                ActiveMQDestination[] destinations = key.getCompositeDestinations();
097                for (int i = 0; i < destinations.length; i++) {
098                    ActiveMQDestination childDestination = destinations[i];
099                    remove(childDestination, value);
100                }
101                return;
102            }
103            String[] paths = key.getDestinationPaths();
104            getRootNode(key).remove(paths, 0, value);
105    
106        }
107    
108        public int getTopicRootChildCount() {
109            return topicRootNode.getChildCount();
110        }
111    
112        public int getQueueRootChildCount() {
113            return queueRootNode.getChildCount();
114        }
115    
116        public DestinationMapNode getQueueRootNode() {
117            return queueRootNode;
118        }
119    
120        public DestinationMapNode getTopicRootNode() {
121            return topicRootNode;
122        }
123    
124        public DestinationMapNode getTempQueueRootNode() {
125            return tempQueueRootNode;
126        }
127    
128        public DestinationMapNode getTempTopicRootNode() {
129            return tempTopicRootNode;
130        }
131    
132        // Implementation methods
133        // -------------------------------------------------------------------------
134    
135        /**
136         * A helper method to allow the destination map to be populated from a
137         * dependency injection framework such as Spring
138         */
139        protected void setEntries(List entries) {
140            for (Iterator iter = entries.iterator(); iter.hasNext();) {
141                Object element = (Object)iter.next();
142                Class type = getEntryClass();
143                if (type.isInstance(element)) {
144                    DestinationMapEntry entry = (DestinationMapEntry)element;
145                    put(entry.getDestination(), entry.getValue());
146                } else {
147                    throw new IllegalArgumentException("Each entry must be an instance of type: " + type.getName() + " but was: " + element);
148                }
149            }
150        }
151    
152        /**
153         * Returns the type of the allowed entries which can be set via the
154         * {@link #setEntries(List)} method. This allows derived classes to further
155         * restrict the type of allowed entries to make a type safe destination map
156         * for custom policies.
157         */
158        protected Class getEntryClass() {
159            return DestinationMapEntry.class;
160        }
161    
162        protected Set findWildcardMatches(ActiveMQDestination key) {
163            String[] paths = key.getDestinationPaths();
164            Set answer = new HashSet();
165            getRootNode(key).appendMatchingValues(answer, paths, 0);
166            return answer;
167        }
168    
169        /**
170         * @param key
171         * @return
172         */
173        public Set removeAll(ActiveMQDestination key) {
174            Set rc = new HashSet();
175            if (key.isComposite()) {
176                ActiveMQDestination[] destinations = key.getCompositeDestinations();
177                for (int i = 0; i < destinations.length; i++) {
178                    rc.add(removeAll(destinations[i]));
179                }
180                return rc;
181            }
182            String[] paths = key.getDestinationPaths();
183            getRootNode(key).removeAll(rc, paths, 0);
184            return rc;
185        }
186    
187        /**
188         * Returns the value which matches the given destination or null if there is
189         * no matching value. If there are multiple values, the results are sorted
190         * and the last item (the biggest) is returned.
191         * 
192         * @param destination the destination to find the value for
193         * @return the largest matching value or null if no value matches
194         */
195        public Object chooseValue(ActiveMQDestination destination) {
196            Set set = get(destination);
197            if (set == null || set.isEmpty()) {
198                return null;
199            }
200            SortedSet sortedSet = new TreeSet(set);
201            return sortedSet.last();
202        }
203    
204        /**
205         * Returns the root node for the given destination type
206         */
207        protected DestinationMapNode getRootNode(ActiveMQDestination key) {
208            if (key.isTemporary()){
209                if (key.isQueue()) {
210                    return tempQueueRootNode;
211                } else {
212                    return tempTopicRootNode;
213                }
214            } else {
215                if (key.isQueue()) {
216                    return queueRootNode;
217                } else {
218                    return topicRootNode;
219                }
220            }
221        }
222    }