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.Comparator; 020import java.util.HashSet; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Set; 024import java.util.SortedSet; 025import java.util.TreeSet; 026import java.util.function.Supplier; 027import java.util.stream.Collectors; 028 029import org.apache.activemq.command.ActiveMQDestination; 030 031/** 032 * A Map-like data structure allowing values to be indexed by 033 * {@link ActiveMQDestination} and retrieved by destination - supporting both * 034 * and > style of wildcard as well as composite destinations. <br> 035 * This class assumes that the index changes rarely but that fast lookup into 036 * the index is required. So this class maintains a pre-calculated index for 037 * destination steps. So looking up the values for "TEST.*" or "*.TEST" will be 038 * pretty fast. <br> 039 * Looking up of a value could return a single value or a List of matching 040 * values if a wildcard or composite destination is used. 041 */ 042public class DestinationMap { 043 protected static final String ANY_DESCENDENT = DestinationFilter.ANY_DESCENDENT; 044 protected static final String ANY_CHILD = DestinationFilter.ANY_CHILD; 045 046 private DestinationMapNode queueRootNode = new DestinationMapNode(null); 047 private DestinationMapNode tempQueueRootNode = new DestinationMapNode(null); 048 private DestinationMapNode topicRootNode = new DestinationMapNode(null); 049 private DestinationMapNode tempTopicRootNode = new DestinationMapNode(null); 050 051 052 /** 053 * Looks up the value(s) matching the given Destination key. For simple 054 * destinations this is typically a List of one single value, for wildcards 055 * or composite destinations this will typically be a List of matching 056 * values. 057 * 058 * @param key the destination to lookup 059 * @return a List of matching values or an empty list if there are no 060 * matching values. 061 */ 062 @SuppressWarnings({"rawtypes", "unchecked"}) 063 public synchronized Set get(ActiveMQDestination key) { 064 if (key.isComposite()) { 065 ActiveMQDestination[] destinations = key.getCompositeDestinations(); 066 Set answer = new HashSet(destinations.length); 067 for (int i = 0; i < destinations.length; i++) { 068 ActiveMQDestination childDestination = destinations[i]; 069 Object value = get(childDestination); 070 if (value instanceof Set) { 071 answer.addAll((Set) value); 072 } else if (value != null) { 073 answer.add(value); 074 } 075 } 076 return answer; 077 } 078 return findWildcardMatches(key); 079 } 080 081 public synchronized void put(ActiveMQDestination key, Object value) { 082 if (key.isComposite()) { 083 ActiveMQDestination[] destinations = key.getCompositeDestinations(); 084 for (int i = 0; i < destinations.length; i++) { 085 ActiveMQDestination childDestination = destinations[i]; 086 put(childDestination, value); 087 } 088 return; 089 } 090 String[] paths = key.getDestinationPaths(); 091 getRootNode(key).add(paths, 0, value); 092 } 093 094 095 /** 096 * Removes the value from the associated destination 097 */ 098 public synchronized void remove(ActiveMQDestination key, Object value) { 099 if (key.isComposite()) { 100 ActiveMQDestination[] destinations = key.getCompositeDestinations(); 101 for (int i = 0; i < destinations.length; i++) { 102 ActiveMQDestination childDestination = destinations[i]; 103 remove(childDestination, value); 104 } 105 return; 106 } 107 String[] paths = key.getDestinationPaths(); 108 getRootNode(key).remove(paths, 0, value); 109 110 } 111 112 public int getTopicRootChildCount() { 113 return topicRootNode.getChildCount(); 114 } 115 116 public int getQueueRootChildCount() { 117 return queueRootNode.getChildCount(); 118 } 119 120 public DestinationMapNode getQueueRootNode() { 121 return queueRootNode; 122 } 123 124 public DestinationMapNode getTopicRootNode() { 125 return topicRootNode; 126 } 127 128 public DestinationMapNode getTempQueueRootNode() { 129 return tempQueueRootNode; 130 } 131 132 public DestinationMapNode getTempTopicRootNode() { 133 return tempTopicRootNode; 134 } 135 136 // Implementation methods 137 // ------------------------------------------------------------------------- 138 139 /** 140 * A helper method to allow the destination map to be populated from a 141 * dependency injection framework such as Spring 142 */ 143 @SuppressWarnings({"rawtypes"}) 144 protected void setEntries(List<DestinationMapEntry> entries) { 145 for (Object element : entries) { 146 Class<? extends DestinationMapEntry> type = getEntryClass(); 147 if (type.isInstance(element)) { 148 DestinationMapEntry entry = (DestinationMapEntry) element; 149 put(entry.getDestination(), entry.getValue()); 150 } else { 151 throw new IllegalArgumentException("Each entry must be an instance of type: " + type.getName() + " but was: " + element); 152 } 153 } 154 } 155 156 /** 157 * Returns the type of the allowed entries which can be set via the 158 * {@link #setEntries(List)} method. This allows derived classes to further 159 * restrict the type of allowed entries to make a type safe destination map 160 * for custom policies. 161 */ 162 @SuppressWarnings({"rawtypes"}) 163 protected Class<? extends DestinationMapEntry> getEntryClass() { 164 return DestinationMapEntry.class; 165 } 166 167 @SuppressWarnings({"rawtypes", "unchecked"}) 168 protected Set findWildcardMatches(ActiveMQDestination key) { 169 return findWildcardMatches(key, true); 170 } 171 172 @SuppressWarnings({"rawtypes", "unchecked"}) 173 protected Set findWildcardMatches(ActiveMQDestination key, boolean deep) { 174 String[] paths = key.getDestinationPaths(); 175 Set answer = new HashSet(); 176 getRootNode(key).appendMatchingValues(answer, paths, 0, deep); 177 return answer; 178 } 179 180 /** 181 * @param key 182 * @return 183 */ 184 @SuppressWarnings({"rawtypes", "unchecked"}) 185 public Set removeAll(ActiveMQDestination key) { 186 Set rc = new HashSet(); 187 if (key.isComposite()) { 188 ActiveMQDestination[] destinations = key.getCompositeDestinations(); 189 for (int i = 0; i < destinations.length; i++) { 190 rc.add(removeAll(destinations[i])); 191 } 192 return rc; 193 } 194 String[] paths = key.getDestinationPaths(); 195 getRootNode(key).removeAll(rc, paths, 0); 196 return rc; 197 } 198 199 /** 200 * Returns the value which matches the given destination or null if there is 201 * no matching value. If there are multiple values, the results are sorted 202 * and the last item (the biggest) is returned. 203 * 204 * @param destination the destination to find the value for 205 * @return the largest matching value or null if no value matches 206 */ 207 @SuppressWarnings({"rawtypes", "unchecked"}) 208 public DestinationMapEntry chooseValue(final ActiveMQDestination destination) { 209 Set<DestinationMapEntry> set = get(destination); 210 if (set == null || set.isEmpty()) { 211 return null; 212 } 213 214 //Comparator to sort in order - we want to pick the exact match by destination or the 215 //closest parent that applies 216 final Comparator<DestinationMapEntry> comparator = new Comparator<DestinationMapEntry>() { 217 @Override 218 public int compare(DestinationMapEntry entry1, DestinationMapEntry entry2) { 219 return destination.equals(entry1.destination) ? -1 : (destination.equals(entry2.destination) ? 1 : entry1.compareTo(entry2)); 220 } 221 }; 222 223 //Sort and filter out any children and non matching entries 224 final SortedSet<DestinationMapEntry> sortedSet = set.stream() 225 .filter(entry -> isMatchOrParent(destination, (DestinationMapEntry)entry)) 226 .collect(Collectors.toCollection(() -> new TreeSet<DestinationMapEntry>(comparator))); 227 228 return sortedSet.size() > 0 ? sortedSet.first() : null; 229 } 230 231 @SuppressWarnings("rawtypes") 232 //Used to filter out any child/unmatching entries 233 private boolean isMatchOrParent(final ActiveMQDestination destination, final DestinationMapEntry entry) { 234 //If destination not set then do not filter out 235 if (entry.getDestination() == null) { 236 return true; 237 } 238 final DestinationFilter filter = DestinationFilter.parseFilter(entry.getDestination()); 239 return destination.equals(entry.getDestination()) || filter.matches(destination); 240 } 241 242 /** 243 * Returns the root node for the given destination type 244 */ 245 protected DestinationMapNode getRootNode(ActiveMQDestination key) { 246 if (key.isTemporary()) { 247 if (key.isQueue()) { 248 return tempQueueRootNode; 249 } else { 250 return tempTopicRootNode; 251 } 252 } else { 253 if (key.isQueue()) { 254 return queueRootNode; 255 } else { 256 return topicRootNode; 257 } 258 } 259 } 260 261 public void reset() { 262 queueRootNode = new DestinationMapNode(null); 263 tempQueueRootNode = new DestinationMapNode(null); 264 topicRootNode = new DestinationMapNode(null); 265 tempTopicRootNode = new DestinationMapNode(null); 266 } 267 268 public boolean isEmpty() { 269 return queueRootNode.isEmpty() && topicRootNode.isEmpty() && tempQueueRootNode.isEmpty() && tempTopicRootNode.isEmpty(); 270 } 271 272 public static Set union(Set existing, Set candidates) { 273 if (candidates != null) { 274 if (existing != null) { 275 for (Iterator<Object> iterator = existing.iterator(); iterator.hasNext(); ) { 276 Object toMatch = iterator.next(); 277 if (!candidates.contains(toMatch)) { 278 iterator.remove(); 279 } 280 } 281 } else { 282 existing = candidates; 283 } 284 } else if (existing != null) { 285 existing.clear(); 286 } 287 return existing; 288 } 289 290}