001 /*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements. See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership. The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied. See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 *
020 */
021 package org.apache.qpid.server.exchange;
022
023 import java.util.HashMap;
024 import java.util.HashSet;
025 import java.util.Map;
026 import java.util.Set;
027
028 import org.apache.log4j.Logger;
029 import org.apache.qpid.framing.AMQTypedValue;
030 import org.apache.qpid.framing.FieldTable;
031
032 /**
033 * Defines binding and matching based on a set of headers.
034 */
035 class HeadersBinding
036 {
037 private static final Logger _logger = Logger.getLogger(HeadersBinding.class);
038
039 private final FieldTable _mappings;
040 private final Set<String> required = new HashSet<String>();
041 private final Map<String,Object> matches = new HashMap<String,Object>();
042 private boolean matchAny;
043
044 private final class MatchesOrProcessor implements FieldTable.FieldTableElementProcessor
045 {
046 private Boolean _result = Boolean.FALSE;
047
048 public boolean processElement(String propertyName, AMQTypedValue value)
049 {
050 if((value != null) && (value.getValue() != null) && value.getValue().equals(matches.get(propertyName)))
051 {
052 _result = Boolean.TRUE;
053 return false;
054 }
055 return true;
056 }
057
058 public Object getResult()
059 {
060 return _result;
061 }
062 }
063
064 private final class RequiredOrProcessor implements FieldTable.FieldTableElementProcessor
065 {
066 Boolean _result = Boolean.FALSE;
067
068 public boolean processElement(String propertyName, AMQTypedValue value)
069 {
070 if(required.contains(propertyName))
071 {
072 _result = Boolean.TRUE;
073 return false;
074 }
075 return true;
076 }
077
078 public Object getResult()
079 {
080 return _result;
081 }
082 }
083
084
085
086 /**
087 * Creates a binding for a set of mappings. Those mappings whose value is
088 * null or the empty string are assumed only to be required headers, with
089 * no constraint on the value. Those with a non-null value are assumed to
090 * define a required match of value.
091 * @param mappings the defined mappings this binding should use
092 */
093
094 HeadersBinding(FieldTable mappings)
095 {
096 _mappings = mappings;
097 initMappings();
098 }
099
100 private void initMappings()
101 {
102
103 _mappings.processOverElements(new FieldTable.FieldTableElementProcessor()
104 {
105
106 public boolean processElement(String propertyName, AMQTypedValue value)
107 {
108 if (isSpecial(propertyName))
109 {
110 processSpecial(propertyName, value.getValue());
111 }
112 else if (value.getValue() == null || value.getValue().equals(""))
113 {
114 required.add(propertyName);
115 }
116 else
117 {
118 matches.put(propertyName,value.getValue());
119 }
120
121 return true;
122 }
123
124 public Object getResult()
125 {
126 return null;
127 }
128 });
129 }
130
131 protected FieldTable getMappings()
132 {
133 return _mappings;
134 }
135
136 /**
137 * Checks whether the supplied headers match the requirements of this binding
138 * @param headers the headers to check
139 * @return true if the headers define any required keys and match any required
140 * values
141 */
142 public boolean matches(FieldTable headers)
143 {
144 if(headers == null)
145 {
146 return required.isEmpty() && matches.isEmpty();
147 }
148 else
149 {
150 return matchAny ? or(headers) : and(headers);
151 }
152 }
153
154 private boolean and(FieldTable headers)
155 {
156 if(headers.keys().containsAll(required))
157 {
158 for(Map.Entry<String, Object> e : matches.entrySet())
159 {
160 if(!e.getValue().equals(headers.getObject(e.getKey())))
161 {
162 return false;
163 }
164 }
165 return true;
166 }
167 else
168 {
169 return false;
170 }
171 }
172
173
174 private boolean or(final FieldTable headers)
175 {
176 if(required.isEmpty() || !(Boolean) headers.processOverElements(new RequiredOrProcessor()))
177 {
178 return ((!matches.isEmpty()) && (Boolean) headers.processOverElements(new MatchesOrProcessor()))
179 || (required.isEmpty() && matches.isEmpty());
180 }
181 else
182 {
183 return true;
184 }
185 }
186
187 private void processSpecial(String key, Object value)
188 {
189 if("X-match".equalsIgnoreCase(key))
190 {
191 matchAny = isAny(value);
192 }
193 else
194 {
195 _logger.warn("Ignoring special header: " + key);
196 }
197 }
198
199 private boolean isAny(Object value)
200 {
201 if(value instanceof String)
202 {
203 if("any".equalsIgnoreCase((String) value)) return true;
204 if("all".equalsIgnoreCase((String) value)) return false;
205 }
206 _logger.warn("Ignoring unrecognised match type: " + value);
207 return false;//default to all
208 }
209
210 static boolean isSpecial(Object key)
211 {
212 return key instanceof String && isSpecial((String) key);
213 }
214
215 static boolean isSpecial(String key)
216 {
217 return key.startsWith("X-") || key.startsWith("x-");
218 }
219 }
|