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     * @version $Revision: 1.3 $
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 topicRootNode = new DestinationMapNode(null);
047    
048        /**
049         * Looks up the value(s) matching the given Destination key. For simple
050         * destinations this is typically a List of one single value, for wildcards
051         * or composite destinations this will typically be a List of matching
052         * values.
053         * 
054         * @param key the destination to lookup
055         * @return a List of matching values or an empty list if there are no
056         *         matching values.
057         */
058        public synchronized Set get(ActiveMQDestination key) {
059            if (key.isComposite()) {
060                ActiveMQDestination[] destinations = key.getCompositeDestinations();
061                Set answer = new HashSet(destinations.length);
062                for (int i = 0; i < destinations.length; i++) {
063                    ActiveMQDestination childDestination = destinations[i];
064                    Object value = get(childDestination);
065                    if (value instanceof Set) {
066                        answer.addAll((Set)value);
067                    } else if (value != null) {
068                        answer.add(value);
069                    }
070                }
071                return answer;
072            }
073            return findWildcardMatches(key);
074        }
075    
076        public synchronized void put(ActiveMQDestination key, Object value) {
077            if (key.isComposite()) {
078                ActiveMQDestination[] destinations = key.getCompositeDestinations();
079                for (int i = 0; i < destinations.length; i++) {
080                    ActiveMQDestination childDestination = destinations[i];
081                    put(childDestination, value);
082                }
083                return;
084            }
085            String[] paths = key.getDestinationPaths();
086            getRootNode(key).add(paths, 0, value);
087        }
088    
089        /**
090         * Removes the value from the associated destination
091         */
092        public synchronized void remove(ActiveMQDestination key, Object value) {
093            if (key.isComposite()) {
094                ActiveMQDestination[] destinations = key.getCompositeDestinations();
095                for (int i = 0; i < destinations.length; i++) {
096                    ActiveMQDestination childDestination = destinations[i];
097                    remove(childDestination, value);
098                }
099                return;
100            }
101            String[] paths = key.getDestinationPaths();
102            getRootNode(key).remove(paths, 0, value);
103    
104        }
105    
106        public int getTopicRootChildCount() {
107            return topicRootNode.getChildCount();
108        }
109    
110        public int getQueueRootChildCount() {
111            return queueRootNode.getChildCount();
112        }
113    
114        public DestinationMapNode getQueueRootNode() {
115            return queueRootNode;
116        }
117    
118        public DestinationMapNode getTopicRootNode() {
119            return topicRootNode;
120        }
121    
122        // Implementation methods
123        // -------------------------------------------------------------------------
124    
125        /**
126         * A helper method to allow the destination map to be populated from a
127         * dependency injection framework such as Spring
128         */
129        protected void setEntries(List entries) {
130            for (Iterator iter = entries.iterator(); iter.hasNext();) {
131                Object element = (Object)iter.next();
132                Class type = getEntryClass();
133                if (type.isInstance(element)) {
134                    DestinationMapEntry entry = (DestinationMapEntry)element;
135                    put(entry.getDestination(), entry.getValue());
136                } else {
137                    throw new IllegalArgumentException("Each entry must be an instance of type: " + type.getName() + " but was: " + element);
138                }
139            }
140        }
141    
142        /**
143         * Returns the type of the allowed entries which can be set via the
144         * {@link #setEntries(List)} method. This allows derived classes to further
145         * restrict the type of allowed entries to make a type safe destination map
146         * for custom policies.
147         */
148        protected Class getEntryClass() {
149            return DestinationMapEntry.class;
150        }
151    
152        protected Set findWildcardMatches(ActiveMQDestination key) {
153            String[] paths = key.getDestinationPaths();
154            Set answer = new HashSet();
155            getRootNode(key).appendMatchingValues(answer, paths, 0);
156            return answer;
157        }
158    
159        /**
160         * @param key
161         * @return
162         */
163        public Set removeAll(ActiveMQDestination key) {
164            Set rc = new HashSet();
165            if (key.isComposite()) {
166                ActiveMQDestination[] destinations = key.getCompositeDestinations();
167                for (int i = 0; i < destinations.length; i++) {
168                    rc.add(removeAll(destinations[i]));
169                }
170                return rc;
171            }
172            String[] paths = key.getDestinationPaths();
173            getRootNode(key).removeAll(rc, paths, 0);
174            return rc;
175        }
176    
177        /**
178         * Returns the value which matches the given destination or null if there is
179         * no matching value. If there are multiple values, the results are sorted
180         * and the last item (the biggest) is returned.
181         * 
182         * @param destination the destination to find the value for
183         * @return the largest matching value or null if no value matches
184         */
185        public Object chooseValue(ActiveMQDestination destination) {
186            Set set = get(destination);
187            if (set == null || set.isEmpty()) {
188                return null;
189            }
190            SortedSet sortedSet = new TreeSet(set);
191            return sortedSet.last();
192        }
193    
194        /**
195         * Returns the root node for the given destination type
196         */
197        protected DestinationMapNode getRootNode(ActiveMQDestination key) {
198            if (key.isQueue()) {
199                return queueRootNode;
200            } else {
201                return topicRootNode;
202            }
203        }
204    }