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