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        @SuppressWarnings({ "rawtypes", "unchecked" })
061        public synchronized Set get(ActiveMQDestination key) {
062            if (key.isComposite()) {
063                ActiveMQDestination[] destinations = key.getCompositeDestinations();
064                Set answer = new HashSet(destinations.length);
065                for (int i = 0; i < destinations.length; i++) {
066                    ActiveMQDestination childDestination = destinations[i];
067                    Object value = get(childDestination);
068                    if (value instanceof Set) {
069                        answer.addAll((Set)value);
070                    } else if (value != null) {
071                        answer.add(value);
072                    }
073                }
074                return answer;
075            }
076            return findWildcardMatches(key);
077        }
078    
079        public synchronized void put(ActiveMQDestination key, Object value) {
080            if (key.isComposite()) {
081                ActiveMQDestination[] destinations = key.getCompositeDestinations();
082                for (int i = 0; i < destinations.length; i++) {
083                    ActiveMQDestination childDestination = destinations[i];
084                    put(childDestination, value);
085                }
086                return;
087            }
088            String[] paths = key.getDestinationPaths();
089            getRootNode(key).add(paths, 0, value);
090        }
091    
092        /**
093         * Removes the value from the associated destination
094         */
095        public synchronized void remove(ActiveMQDestination key, Object value) {
096            if (key.isComposite()) {
097                ActiveMQDestination[] destinations = key.getCompositeDestinations();
098                for (int i = 0; i < destinations.length; i++) {
099                    ActiveMQDestination childDestination = destinations[i];
100                    remove(childDestination, value);
101                }
102                return;
103            }
104            String[] paths = key.getDestinationPaths();
105            getRootNode(key).remove(paths, 0, value);
106    
107        }
108    
109        public int getTopicRootChildCount() {
110            return topicRootNode.getChildCount();
111        }
112    
113        public int getQueueRootChildCount() {
114            return queueRootNode.getChildCount();
115        }
116    
117        public DestinationMapNode getQueueRootNode() {
118            return queueRootNode;
119        }
120    
121        public DestinationMapNode getTopicRootNode() {
122            return topicRootNode;
123        }
124    
125        public DestinationMapNode getTempQueueRootNode() {
126            return tempQueueRootNode;
127        }
128    
129        public DestinationMapNode getTempTopicRootNode() {
130            return tempTopicRootNode;
131        }
132    
133        // Implementation methods
134        // -------------------------------------------------------------------------
135    
136        /**
137         * A helper method to allow the destination map to be populated from a
138         * dependency injection framework such as Spring
139         */
140        @SuppressWarnings({ "rawtypes" })
141        protected void setEntries(List<DestinationMapEntry>  entries) {
142            for (Object element : entries) {
143                Class<? extends DestinationMapEntry> type = getEntryClass();
144                if (type.isInstance(element)) {
145                    DestinationMapEntry entry = (DestinationMapEntry)element;
146                    put(entry.getDestination(), entry.getValue());
147                } else {
148                    throw new IllegalArgumentException("Each entry must be an instance of type: " + type.getName() + " but was: " + element);
149                }
150            }
151        }
152    
153        /**
154         * Returns the type of the allowed entries which can be set via the
155         * {@link #setEntries(List)} method. This allows derived classes to further
156         * restrict the type of allowed entries to make a type safe destination map
157         * for custom policies.
158         */
159        @SuppressWarnings({ "rawtypes" })
160        protected Class<? extends DestinationMapEntry> getEntryClass() {
161            return DestinationMapEntry.class;
162        }
163    
164        @SuppressWarnings({ "rawtypes", "unchecked" })
165        protected Set findWildcardMatches(ActiveMQDestination key) {
166            String[] paths = key.getDestinationPaths();
167            Set answer = new HashSet();
168            getRootNode(key).appendMatchingValues(answer, paths, 0);
169            return answer;
170        }
171    
172        /**
173         * @param key
174         * @return
175         */
176        @SuppressWarnings({ "rawtypes", "unchecked" })
177        public Set removeAll(ActiveMQDestination key) {
178            Set rc = new HashSet();
179            if (key.isComposite()) {
180                ActiveMQDestination[] destinations = key.getCompositeDestinations();
181                for (int i = 0; i < destinations.length; i++) {
182                    rc.add(removeAll(destinations[i]));
183                }
184                return rc;
185            }
186            String[] paths = key.getDestinationPaths();
187            getRootNode(key).removeAll(rc, paths, 0);
188            return rc;
189        }
190    
191        /**
192         * Returns the value which matches the given destination or null if there is
193         * no matching value. If there are multiple values, the results are sorted
194         * and the last item (the biggest) is returned.
195         *
196         * @param destination the destination to find the value for
197         * @return the largest matching value or null if no value matches
198         */
199        @SuppressWarnings({ "rawtypes", "unchecked" })
200        public Object chooseValue(ActiveMQDestination destination) {
201            Set set = get(destination);
202            if (set == null || set.isEmpty()) {
203                return null;
204            }
205            SortedSet sortedSet = new TreeSet(set);
206            return sortedSet.last();
207        }
208    
209        /**
210         * Returns the root node for the given destination type
211         */
212        protected DestinationMapNode getRootNode(ActiveMQDestination key) {
213            if (key.isTemporary()){
214                if (key.isQueue()) {
215                    return tempQueueRootNode;
216                } else {
217                    return tempTopicRootNode;
218                }
219            } else {
220                if (key.isQueue()) {
221                    return queueRootNode;
222                } else {
223                    return topicRootNode;
224                }
225            }
226        }
227    
228        public void reset() {
229            queueRootNode = new DestinationMapNode(null);
230            tempQueueRootNode = new DestinationMapNode(null);
231            topicRootNode = new DestinationMapNode(null);
232            tempTopicRootNode = new DestinationMapNode(null);
233        }
234    
235        public static Set union(Set existing, Set candidates) {
236            if ( candidates != null ) {
237                if (existing != null) {
238                    for (Iterator<Object> iterator = existing.iterator(); iterator.hasNext();) {
239                        Object toMatch = iterator.next();
240                        if (!candidates.contains(toMatch)) {
241                            iterator.remove();
242                        }
243                    }
244                } else {
245                    existing = candidates;
246                }
247            } else if ( existing != null ) {
248                existing.clear();
249            }
250            return existing;
251        }
252    
253    }