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.filter;
018
019import java.util.HashSet;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Set;
023import java.util.SortedSet;
024import java.util.TreeSet;
025
026import 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 */
041public 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}