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