View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.core.filterchain;
21  
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.Iterator;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.ListIterator;
28  import java.util.Map;
29  import java.util.Random;
30  import java.util.concurrent.CopyOnWriteArrayList;
31  
32  import org.apache.mina.core.filterchain.IoFilter.NextFilter;
33  import org.apache.mina.core.filterchain.IoFilterChain.Entry;
34  import org.apache.mina.core.session.IoSession;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * The default implementation of {@link IoFilterChainBuilder} which is useful
40   * in most cases.  {@link DefaultIoFilterChainBuilder} has an identical interface
41   * with {@link IoFilter}; it contains a list of {@link IoFilter}s that you can
42   * modify. The {@link IoFilter}s which are added to this builder will be appended
43   * to the {@link IoFilterChain} when {@link #buildFilterChain(IoFilterChain)} is
44   * invoked.
45   * <p>
46   * However, the identical interface doesn't mean that it behaves in an exactly
47   * same way with {@link IoFilterChain}.  {@link DefaultIoFilterChainBuilder}
48   * doesn't manage the life cycle of the {@link IoFilter}s at all, and the
49   * existing {@link IoSession}s won't get affected by the changes in this builder.
50   * {@link IoFilterChainBuilder}s affect only newly created {@link IoSession}s.
51   *
52   * <pre>
53   * IoAcceptor acceptor = ...;
54   * DefaultIoFilterChainBuilder builder = acceptor.getFilterChain();
55   * builder.addLast( "myFilter", new MyFilter() );
56   * ...
57   * </pre>
58   *
59   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
60   * @org.apache.xbean.XBean
61   */
62  public class DefaultIoFilterChainBuilder implements IoFilterChainBuilder {
63  
64      private final static Logger LOGGER = LoggerFactory.getLogger(DefaultIoFilterChainBuilder.class);
65  
66      private final List<Entry> entries;
67  
68      /**
69       * Creates a new instance with an empty filter list.
70       */
71      public DefaultIoFilterChainBuilder() {
72          entries = new CopyOnWriteArrayList<Entry>();
73      }
74  
75      /**
76       * Creates a new copy of the specified {@link DefaultIoFilterChainBuilder}.
77       */
78      public DefaultIoFilterChainBuilder(DefaultIoFilterChainBuilder filterChain) {
79          if (filterChain == null) {
80              throw new IllegalArgumentException("filterChain");
81          }
82          entries = new CopyOnWriteArrayList<Entry>(filterChain.entries);
83      }
84  
85      /**
86       * @see IoFilterChain#getEntry(String)
87       */
88      public Entry getEntry(String name) {
89          for (Entry e : entries) {
90              if (e.getName().equals(name)) {
91                  return e;
92              }
93          }
94  
95          return null;
96      }
97  
98      /**
99       * @see IoFilterChain#getEntry(IoFilter)
100      */
101     public Entry getEntry(IoFilter filter) {
102         for (Entry e : entries) {
103             if (e.getFilter() == filter) {
104                 return e;
105             }
106         }
107 
108         return null;
109     }
110 
111     /**
112      * @see IoFilterChain#getEntry(Class)
113      */
114     public Entry getEntry(Class<? extends IoFilter> filterType) {
115         for (Entry e : entries) {
116             if (filterType.isAssignableFrom(e.getFilter().getClass())) {
117                 return e;
118             }
119         }
120 
121         return null;
122     }
123 
124     /**
125      * @see IoFilterChain#get(String)
126      */
127     public IoFilter get(String name) {
128         Entry e = getEntry(name);
129         if (e == null) {
130             return null;
131         }
132 
133         return e.getFilter();
134     }
135 
136     /**
137      * @see IoFilterChain#get(Class)
138      */
139     public IoFilter get(Class<? extends IoFilter> filterType) {
140         Entry e = getEntry(filterType);
141         if (e == null) {
142             return null;
143         }
144 
145         return e.getFilter();
146     }
147 
148     /**
149      * @see IoFilterChain#getAll()
150      */
151     public List<Entry> getAll() {
152         return new ArrayList<Entry>(entries);
153     }
154 
155     /**
156      * @see IoFilterChain#getAllReversed()
157      */
158     public List<Entry> getAllReversed() {
159         List<Entry> result = getAll();
160         Collections.reverse(result);
161         return result;
162     }
163 
164     /**
165      * @see IoFilterChain#contains(String)
166      */
167     public boolean contains(String name) {
168         return getEntry(name) != null;
169     }
170 
171     /**
172      * @see IoFilterChain#contains(IoFilter)
173      */
174     public boolean contains(IoFilter filter) {
175         return getEntry(filter) != null;
176     }
177 
178     /**
179      * @see IoFilterChain#contains(Class)
180      */
181     public boolean contains(Class<? extends IoFilter> filterType) {
182         return getEntry(filterType) != null;
183     }
184 
185     /**
186      * @see IoFilterChain#addFirst(String, IoFilter)
187      */
188     public synchronized void addFirst(String name, IoFilter filter) {
189         register(0, new EntryImpl(name, filter));
190     }
191 
192     /**
193      * @see IoFilterChain#addLast(String, IoFilter)
194      */
195     public synchronized void addLast(String name, IoFilter filter) {
196         register(entries.size(), new EntryImpl(name, filter));
197     }
198 
199     /**
200      * @see IoFilterChain#addBefore(String, String, IoFilter)
201      */
202     public synchronized void addBefore(String baseName, String name, IoFilter filter) {
203         checkBaseName(baseName);
204 
205         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
206             Entry base = i.next();
207             if (base.getName().equals(baseName)) {
208                 register(i.previousIndex(), new EntryImpl(name, filter));
209                 break;
210             }
211         }
212     }
213 
214     /**
215      * @see IoFilterChain#addAfter(String, String, IoFilter)
216      */
217     public synchronized void addAfter(String baseName, String name, IoFilter filter) {
218         checkBaseName(baseName);
219 
220         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
221             Entry base = i.next();
222             if (base.getName().equals(baseName)) {
223                 register(i.nextIndex(), new EntryImpl(name, filter));
224                 break;
225             }
226         }
227     }
228 
229     /**
230      * @see IoFilterChain#remove(String)
231      */
232     public synchronized IoFilter remove(String name) {
233         if (name == null) {
234             throw new IllegalArgumentException("name");
235         }
236 
237         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
238             Entry e = i.next();
239             if (e.getName().equals(name)) {
240                 entries.remove(i.previousIndex());
241                 return e.getFilter();
242             }
243         }
244 
245         throw new IllegalArgumentException("Unknown filter name: " + name);
246     }
247 
248     /**
249      * @see IoFilterChain#remove(IoFilter)
250      */
251     public synchronized IoFilter remove(IoFilter filter) {
252         if (filter == null) {
253             throw new IllegalArgumentException("filter");
254         }
255 
256         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
257             Entry e = i.next();
258             if (e.getFilter() == filter) {
259                 entries.remove(i.previousIndex());
260                 return e.getFilter();
261             }
262         }
263 
264         throw new IllegalArgumentException("Filter not found: " + filter.getClass().getName());
265     }
266 
267     /**
268      * @see IoFilterChain#remove(Class)
269      */
270     public synchronized IoFilter remove(Class<? extends IoFilter> filterType) {
271         if (filterType == null) {
272             throw new IllegalArgumentException("filterType");
273         }
274 
275         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
276             Entry e = i.next();
277             if (filterType.isAssignableFrom(e.getFilter().getClass())) {
278                 entries.remove(i.previousIndex());
279                 return e.getFilter();
280             }
281         }
282 
283         throw new IllegalArgumentException("Filter not found: " + filterType.getName());
284     }
285 
286     public synchronized IoFilter replace(String name, IoFilter newFilter) {
287         checkBaseName(name);
288         EntryImpl e = (EntryImpl) getEntry(name);
289         IoFilter oldFilter = e.getFilter();
290         e.setFilter(newFilter);
291         return oldFilter;
292     }
293 
294     public synchronized void replace(IoFilter oldFilter, IoFilter newFilter) {
295         for (Entry e : entries) {
296             if (e.getFilter() == oldFilter) {
297                 ((EntryImpl) e).setFilter(newFilter);
298                 return;
299             }
300         }
301         throw new IllegalArgumentException("Filter not found: " + oldFilter.getClass().getName());
302     }
303 
304     public synchronized void replace(Class<? extends IoFilter> oldFilterType, IoFilter newFilter) {
305         for (Entry e : entries) {
306             if (oldFilterType.isAssignableFrom(e.getFilter().getClass())) {
307                 ((EntryImpl) e).setFilter(newFilter);
308                 return;
309             }
310         }
311         throw new IllegalArgumentException("Filter not found: " + oldFilterType.getName());
312     }
313 
314     /**
315      * @see IoFilterChain#clear()
316      */
317     public synchronized void clear() {
318         entries.clear();
319     }
320 
321     /**
322      * Clears the current list of filters and adds the specified
323      * filter mapping to this builder.  Please note that you must specify
324      * a {@link Map} implementation that iterates the filter mapping in the
325      * order of insertion such as {@link LinkedHashMap}.  Otherwise, it will
326      * throw an {@link IllegalArgumentException}.
327      */
328     public void setFilters(Map<String, ? extends IoFilter> filters) {
329         if (filters == null) {
330             throw new IllegalArgumentException("filters");
331         }
332 
333         if (!isOrderedMap(filters)) {
334             throw new IllegalArgumentException("filters is not an ordered map. Please try "
335                     + LinkedHashMap.class.getName() + ".");
336         }
337 
338         filters = new LinkedHashMap<String, IoFilter>(filters);
339         for (Map.Entry<String, ? extends IoFilter> e : filters.entrySet()) {
340             if (e.getKey() == null) {
341                 throw new IllegalArgumentException("filters contains a null key.");
342             }
343             if (e.getValue() == null) {
344                 throw new IllegalArgumentException("filters contains a null value.");
345             }
346         }
347 
348         synchronized (this) {
349             clear();
350             for (Map.Entry<String, ? extends IoFilter> e : filters.entrySet()) {
351                 addLast(e.getKey(), e.getValue());
352             }
353         }
354     }
355 
356     @SuppressWarnings("unchecked")
357     private boolean isOrderedMap(Map map) {
358         Class<?> mapType = map.getClass();
359         if (LinkedHashMap.class.isAssignableFrom(mapType)) {
360             if (LOGGER.isDebugEnabled()) {
361                 LOGGER.debug(mapType.getSimpleName() + " is an ordered map.");
362             }
363             return true;
364         }
365 
366         if (LOGGER.isDebugEnabled()) {
367             LOGGER.debug(mapType.getName() + " is not a " + LinkedHashMap.class.getSimpleName());
368         }
369 
370         // Detect Jakarta Commons Collections OrderedMap implementations.
371         Class<?> type = mapType;
372         while (type != null) {
373             for (Class<?> i : type.getInterfaces()) {
374                 if (i.getName().endsWith("OrderedMap")) {
375                     if (LOGGER.isDebugEnabled()) {
376                         LOGGER.debug(mapType.getSimpleName() + " is an ordered map (guessed from that it "
377                                 + " implements OrderedMap interface.)");
378                     }
379                     return true;
380                 }
381             }
382             type = type.getSuperclass();
383         }
384 
385         if (LOGGER.isDebugEnabled()) {
386             LOGGER.debug(mapType.getName() + " doesn't implement OrderedMap interface.");
387         }
388 
389         // Last resort: try to create a new instance and test if it maintains
390         // the insertion order.
391         LOGGER.debug("Last resort; trying to create a new map instance with a "
392                 + "default constructor and test if insertion order is " + "maintained.");
393 
394         Map newMap;
395         try {
396             newMap = (Map) mapType.newInstance();
397         } catch (Exception e) {
398             if (LOGGER.isDebugEnabled()) {
399                 LOGGER.debug("Failed to create a new map instance of '" + mapType.getName() + "'.", e);
400             }
401             return false;
402         }
403 
404         Random rand = new Random();
405         List<String> expectedNames = new ArrayList<String>();
406         IoFilter dummyFilter = new IoFilterAdapter();
407         for (int i = 0; i < 65536; i++) {
408             String filterName;
409             do {
410                 filterName = String.valueOf(rand.nextInt());
411             } while (newMap.containsKey(filterName));
412 
413             newMap.put(filterName, dummyFilter);
414             expectedNames.add(filterName);
415 
416             Iterator<String> it = expectedNames.iterator();
417             for (Object key : newMap.keySet()) {
418                 if (!it.next().equals(key)) {
419                     if (LOGGER.isDebugEnabled()) {
420                         LOGGER.debug("The specified map didn't pass the insertion " + "order test after " + (i + 1)
421                                 + " tries.");
422                     }
423                     return false;
424                 }
425             }
426         }
427 
428         if (LOGGER.isDebugEnabled()) {
429             LOGGER.debug("The specified map passed the insertion order test.");
430         }
431         return true;
432     }
433 
434     public void buildFilterChain(IoFilterChain chain) throws Exception {
435         for (Entry e : entries) {
436             chain.addLast(e.getName(), e.getFilter());
437         }
438     }
439 
440     @Override
441     public String toString() {
442         StringBuilder buf = new StringBuilder();
443         buf.append("{ ");
444 
445         boolean empty = true;
446 
447         for (Entry e : entries) {
448             if (!empty) {
449                 buf.append(", ");
450             } else {
451                 empty = false;
452             }
453 
454             buf.append('(');
455             buf.append(e.getName());
456             buf.append(':');
457             buf.append(e.getFilter());
458             buf.append(')');
459         }
460 
461         if (empty) {
462             buf.append("empty");
463         }
464 
465         buf.append(" }");
466 
467         return buf.toString();
468     }
469 
470     private void checkBaseName(String baseName) {
471         if (baseName == null) {
472             throw new IllegalArgumentException("baseName");
473         }
474 
475         if (!contains(baseName)) {
476             throw new IllegalArgumentException("Unknown filter name: " + baseName);
477         }
478     }
479 
480     private void register(int index, Entry e) {
481         if (contains(e.getName())) {
482             throw new IllegalArgumentException("Other filter is using the same name: " + e.getName());
483         }
484 
485         entries.add(index, e);
486     }
487 
488     private class EntryImpl implements Entry {
489         private final String name;
490 
491         private volatile IoFilter filter;
492 
493         private EntryImpl(String name, IoFilter filter) {
494             if (name == null) {
495                 throw new IllegalArgumentException("name");
496             }
497             if (filter == null) {
498                 throw new IllegalArgumentException("filter");
499             }
500 
501             this.name = name;
502             this.filter = filter;
503         }
504 
505         public String getName() {
506             return name;
507         }
508 
509         public IoFilter getFilter() {
510             return filter;
511         }
512 
513         private void setFilter(IoFilter filter) {
514             this.filter = filter;
515         }
516 
517         public NextFilter getNextFilter() {
518             throw new IllegalStateException();
519         }
520 
521         @Override
522         public String toString() {
523             return "(" + getName() + ':' + filter + ')';
524         }
525 
526         public void addAfter(String name, IoFilter filter) {
527             DefaultIoFilterChainBuilder.this.addAfter(getName(), name, filter);
528         }
529 
530         public void addBefore(String name, IoFilter filter) {
531             DefaultIoFilterChainBuilder.this.addBefore(getName(), name, filter);
532         }
533 
534         public void remove() {
535             DefaultIoFilterChainBuilder.this.remove(getName());
536         }
537 
538         public void replace(IoFilter newFilter) {
539             DefaultIoFilterChainBuilder.this.replace(getName(), newFilter);
540         }
541     }
542 }