HeadersBinding.java
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() || !(Booleanheaders.processOverElements(new RequiredOrProcessor()))
177         {
178             return ((!matches.isEmpty()) && (Booleanheaders.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((Stringvalue)) return true;
204             if("all".equalsIgnoreCase((Stringvalue)) 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((Stringkey);
213     }
214 
215     static boolean isSpecial(String key)
216     {
217         return key.startsWith("X-"|| key.startsWith("x-");
218     }
219 }