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