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 > 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 }