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