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 synchronized Set get(ActiveMQDestination key) { 060 if (key.isComposite()) { 061 ActiveMQDestination[] destinations = key.getCompositeDestinations(); 062 Set answer = new HashSet(destinations.length); 063 for (int i = 0; i < destinations.length; i++) { 064 ActiveMQDestination childDestination = destinations[i]; 065 Object value = get(childDestination); 066 if (value instanceof Set) { 067 answer.addAll((Set) value); 068 } else if (value != null) { 069 answer.add(value); 070 } 071 } 072 return answer; 073 } 074 return findWildcardMatches(key); 075 } 076 077 public synchronized void put(ActiveMQDestination key, Object value) { 078 if (key.isComposite()) { 079 ActiveMQDestination[] destinations = key.getCompositeDestinations(); 080 for (int i = 0; i < destinations.length; i++) { 081 ActiveMQDestination childDestination = destinations[i]; 082 put(childDestination, value); 083 } 084 return; 085 } 086 String[] paths = key.getDestinationPaths(); 087 getRootNode(key).add(paths, 0, value); 088 } 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 @SuppressWarnings({"rawtypes"}) 140 protected void setEntries(List<DestinationMapEntry> entries) { 141 for (Object element : entries) { 142 Class<? extends DestinationMapEntry> 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 @SuppressWarnings({"rawtypes"}) 159 protected Class<? extends DestinationMapEntry> getEntryClass() { 160 return DestinationMapEntry.class; 161 } 162 163 @SuppressWarnings({"rawtypes", "unchecked"}) 164 protected Set findWildcardMatches(ActiveMQDestination key) { 165 return findWildcardMatches(key, true); 166 } 167 168 @SuppressWarnings({"rawtypes", "unchecked"}) 169 protected Set findWildcardMatches(ActiveMQDestination key, boolean deep) { 170 String[] paths = key.getDestinationPaths(); 171 Set answer = new HashSet(); 172 getRootNode(key).appendMatchingValues(answer, paths, 0, deep); 173 return answer; 174 } 175 176 /** 177 * @param key 178 * @return 179 */ 180 @SuppressWarnings({"rawtypes", "unchecked"}) 181 public Set removeAll(ActiveMQDestination key) { 182 Set rc = new HashSet(); 183 if (key.isComposite()) { 184 ActiveMQDestination[] destinations = key.getCompositeDestinations(); 185 for (int i = 0; i < destinations.length; i++) { 186 rc.add(removeAll(destinations[i])); 187 } 188 return rc; 189 } 190 String[] paths = key.getDestinationPaths(); 191 getRootNode(key).removeAll(rc, paths, 0); 192 return rc; 193 } 194 195 /** 196 * Returns the value which matches the given destination or null if there is 197 * no matching value. If there are multiple values, the results are sorted 198 * and the last item (the biggest) is returned. 199 * 200 * @param destination the destination to find the value for 201 * @return the largest matching value or null if no value matches 202 */ 203 @SuppressWarnings({"rawtypes", "unchecked"}) 204 public Object chooseValue(ActiveMQDestination destination) { 205 Set set = get(destination); 206 if (set == null || set.isEmpty()) { 207 return null; 208 } 209 SortedSet sortedSet = new TreeSet(set); 210 return sortedSet.first(); 211 } 212 213 /** 214 * Returns the root node for the given destination type 215 */ 216 protected DestinationMapNode getRootNode(ActiveMQDestination key) { 217 if (key.isTemporary()) { 218 if (key.isQueue()) { 219 return tempQueueRootNode; 220 } else { 221 return tempTopicRootNode; 222 } 223 } else { 224 if (key.isQueue()) { 225 return queueRootNode; 226 } else { 227 return topicRootNode; 228 } 229 } 230 } 231 232 public void reset() { 233 queueRootNode = new DestinationMapNode(null); 234 tempQueueRootNode = new DestinationMapNode(null); 235 topicRootNode = new DestinationMapNode(null); 236 tempTopicRootNode = new DestinationMapNode(null); 237 } 238 239 public boolean isEmpty() { 240 return queueRootNode.isEmpty() && topicRootNode.isEmpty() && tempQueueRootNode.isEmpty() && tempTopicRootNode.isEmpty(); 241 } 242 243 public static Set union(Set existing, Set candidates) { 244 if (candidates != null) { 245 if (existing != null) { 246 for (Iterator<Object> iterator = existing.iterator(); iterator.hasNext(); ) { 247 Object toMatch = iterator.next(); 248 if (!candidates.contains(toMatch)) { 249 iterator.remove(); 250 } 251 } 252 } else { 253 existing = candidates; 254 } 255 } else if (existing != null) { 256 existing.clear(); 257 } 258 return existing; 259 } 260 261}