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 The Apache MINA Project (dev@mina.apache.org)
60   * @version $Rev: 594846 $, $Date: 2007-11-14 13:03:07 +0100 (Wed, 14 Nov 2007) $
61   */
62  public class DefaultIoFilterChainBuilder implements IoFilterChainBuilder {
63      
64      private final Logger logger = LoggerFactory.getLogger(getClass());
65      private final List<Entry> entries;
66  
67      /**
68       * Creates a new instance with an empty filter list.
69       */
70      public DefaultIoFilterChainBuilder() {
71          entries = new CopyOnWriteArrayList<Entry>();
72      }
73  
74      /**
75       * Creates a new copy of the specified {@link DefaultIoFilterChainBuilder}.
76       */
77      public DefaultIoFilterChainBuilder(DefaultIoFilterChainBuilder filterChain) {
78          if (filterChain == null) {
79              throw new NullPointerException("filterChain");
80          }
81          entries = new CopyOnWriteArrayList<Entry>(filterChain.entries);
82      }
83  
84      /**
85       * @see IoFilterChain#getEntry(String)
86       */
87      public Entry getEntry(String name) {
88          for (Entry e: entries) {
89              if (e.getName().equals(name)) {
90                  return e;
91              }
92          }
93  
94          return null;
95      }
96  
97      /**
98       * @see IoFilterChain#getEntry(IoFilter)
99       */
100     public Entry getEntry(IoFilter filter) {
101         for (Entry e: entries) {
102             if (e.getFilter() == filter) {
103                 return e;
104             }
105         }
106 
107         return null;
108     }
109 
110     /**
111      * @see IoFilterChain#getEntry(Class)
112      */
113     public Entry getEntry(Class<? extends IoFilter> filterType) {
114         for (Entry e: entries) {
115             if (filterType.isAssignableFrom(e.getFilter().getClass())) {
116                 return e;
117             }
118         }
119 
120         return null;
121     }
122 
123     /**
124      * @see IoFilterChain#get(String)
125      */
126     public IoFilter get(String name) {
127         Entry e = getEntry(name);
128         if (e == null) {
129             return null;
130         }
131 
132         return e.getFilter();
133     }
134 
135     /**
136      * @see IoFilterChain#get(Class)
137      */
138     public IoFilter get(Class<? extends IoFilter> filterType) {
139         Entry e = getEntry(filterType);
140         if (e == null) {
141             return null;
142         }
143 
144         return e.getFilter();
145     }
146 
147     /**
148      * @see IoFilterChain#getAll()
149      */
150     public List<Entry> getAll() {
151         return new ArrayList<Entry>(entries);
152     }
153 
154     /**
155      * @see IoFilterChain#getAllReversed()
156      */
157     public List<Entry> getAllReversed() {
158         List<Entry> result = getAll();
159         Collections.reverse(result);
160         return result;
161     }
162 
163     /**
164      * @see IoFilterChain#contains(String)
165      */
166     public boolean contains(String name) {
167         return getEntry(name) != null;
168     }
169 
170     /**
171      * @see IoFilterChain#contains(IoFilter)
172      */
173     public boolean contains(IoFilter filter) {
174         return getEntry(filter) != null;
175     }
176 
177     /**
178      * @see IoFilterChain#contains(Class)
179      */
180     public boolean contains(Class<? extends IoFilter> filterType) {
181         return getEntry(filterType) != null;
182     }
183 
184     /**
185      * @see IoFilterChain#addFirst(String, IoFilter)
186      */
187     public synchronized void addFirst(String name, IoFilter filter) {
188         register(0, new EntryImpl(name, filter));
189     }
190 
191     /**
192      * @see IoFilterChain#addLast(String, IoFilter)
193      */
194     public synchronized void addLast(String name, IoFilter filter) {
195         register(entries.size(), new EntryImpl(name, filter));
196     }
197 
198     /**
199      * @see IoFilterChain#addBefore(String, String, IoFilter)
200      */
201     public synchronized void addBefore(String baseName, String name,
202             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,
218             IoFilter filter) {
219         checkBaseName(baseName);
220 
221         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
222             Entry base = i.next();
223             if (base.getName().equals(baseName)) {
224                 register(i.nextIndex(), new EntryImpl(name, filter));
225                 break;
226             }
227         }
228     }
229 
230     /**
231      * @see IoFilterChain#remove(String)
232      */
233     public synchronized IoFilter remove(String name) {
234         if (name == null) {
235             throw new NullPointerException("name");
236         }
237 
238         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
239             Entry e = i.next();
240             if (e.getName().equals(name)) {
241                 entries.remove(i.previousIndex());
242                 return e.getFilter();
243             }
244         }
245 
246         throw new IllegalArgumentException("Unknown filter name: " + name);
247     }
248 
249     /**
250      * @see IoFilterChain#remove(IoFilter)
251      */
252     public synchronized IoFilter remove(IoFilter filter) {
253         if (filter == null) {
254             throw new NullPointerException("filter");
255         }
256 
257         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
258             Entry e = i.next();
259             if (e.getFilter() == filter) {
260                 entries.remove(i.previousIndex());
261                 return e.getFilter();
262             }
263         }
264 
265         throw new IllegalArgumentException("Filter not found: " + filter.getClass().getName());
266     }
267 
268     /**
269      * @see IoFilterChain#remove(Class)
270      */
271     public synchronized IoFilter remove(Class<? extends IoFilter> filterType) {
272         if (filterType == null) {
273             throw new NullPointerException("filterType");
274         }
275 
276         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
277             Entry e = i.next();
278             if (filterType.isAssignableFrom(e.getFilter().getClass())) {
279                 entries.remove(i.previousIndex());
280                 return e.getFilter();
281             }
282         }
283 
284         throw new IllegalArgumentException("Filter not found: " + filterType.getName());
285     }
286 
287     public synchronized IoFilter replace(String name, IoFilter newFilter) {
288         checkBaseName(name);
289         EntryImpl e = (EntryImpl) get(name);
290         IoFilter oldFilter = e.getFilter();
291         e.setFilter(newFilter);
292         return oldFilter;
293     }
294 
295     public synchronized void replace(IoFilter oldFilter, IoFilter newFilter) {
296         for (Entry e : entries) {
297             if (e.getFilter() == oldFilter) {
298                 ((EntryImpl) e).setFilter(newFilter);
299                 return;
300             }
301         }
302         throw new IllegalArgumentException("Filter not found: "
303                 + oldFilter.getClass().getName());
304     }
305 
306     public synchronized void replace(Class<? extends IoFilter> oldFilterType,
307             IoFilter newFilter) {
308         for (Entry e : entries) {
309             if (oldFilterType.isAssignableFrom(e.getFilter().getClass())) {
310                 ((EntryImpl) e).setFilter(newFilter);
311                 return;
312             }
313         }
314         throw new IllegalArgumentException("Filter not found: "
315                 + oldFilterType.getName());
316     }
317 
318     /**
319      * @see IoFilterChain#clear()
320      */
321     public synchronized void clear() {
322         entries.clear();
323     }
324     
325     /**
326      * Clears the current list of filters and adds the specified
327      * filter mapping to this builder.  Please note that you must specify
328      * a {@link Map} implementation that iterates the filter mapping in the
329      * order of insertion such as {@link LinkedHashMap}.  Otherwise, it will
330      * throw an {@link IllegalArgumentException}.
331      */
332     public void setFilters(Map<String, ? extends IoFilter> filters) {
333         if (filters == null) {
334             throw new NullPointerException("filters");
335         }
336         
337         if (!isOrderedMap(filters)) {
338             throw new IllegalArgumentException(
339                     "filters is not an ordered map. Please try " + 
340                     LinkedHashMap.class.getName() + ".");
341         }
342 
343         filters = new LinkedHashMap<String, IoFilter>(filters);
344         for (Map.Entry<String, ? extends IoFilter> e: filters.entrySet()) {
345             if (e.getKey() == null) {
346                 throw new NullPointerException("filters contains a null key.");
347             }
348             if (e.getValue() == null) {
349                 throw new NullPointerException("filters contains a null value.");
350             }
351         }
352         
353         synchronized (this) {
354             clear();
355             for (Map.Entry<String, ? extends IoFilter> e: filters.entrySet()) {
356                 addLast(e.getKey(), e.getValue());
357             }
358         }
359     }
360     
361     @SuppressWarnings("unchecked")
362     private boolean isOrderedMap(Map map) {
363         Class<?> mapType = map.getClass();
364         if (LinkedHashMap.class.isAssignableFrom(mapType)) {
365             if (logger.isDebugEnabled()) {
366                 logger.debug(mapType.getSimpleName() + " is an ordered map.");
367             }
368             return true;
369         }
370         
371         if (logger.isDebugEnabled()) {
372             logger.debug(mapType.getName() + " is not a " + LinkedHashMap.class.getSimpleName());
373         }
374 
375         // Detect Jakarta Commons Collections OrderedMap implementations.
376         Class<?> type = mapType;
377         while (type != null) {
378             for (Class<?> i: type.getInterfaces()) {
379                 if (i.getName().endsWith("OrderedMap")) {
380                     if (logger.isDebugEnabled()) {
381                         logger.debug(
382                                 mapType.getSimpleName() +
383                                 " is an ordered map (guessed from that it " +
384                                 " implements OrderedMap interface.)");
385                     }
386                     return true;
387                 }
388             }
389             type = type.getSuperclass();
390         }
391         
392         if (logger.isDebugEnabled()) {
393             logger.debug(
394                     mapType.getName() +
395                     " doesn't implement OrderedMap interface.");
396         }
397         
398         // Last resort: try to create a new instance and test if it maintains
399         // the insertion order.
400         logger.debug(
401                 "Last resort; trying to create a new map instance with a " +
402                 "default constructor and test if insertion order is " +
403                 "maintained.");
404         
405         Map newMap;
406         try {
407             newMap = (Map) mapType.newInstance();
408         } catch (Exception e) {
409             if (logger.isDebugEnabled()) {
410                 logger.debug(
411                         "Failed to create a new map instance of '" + 
412                         mapType.getName() +"'.", e);
413             }
414             return false;
415         }
416         
417         Random rand = new Random();
418         List<String> expectedNames = new ArrayList<String>();
419         IoFilter dummyFilter = new IoFilterAdapter();
420         for (int i = 0; i < 65536; i ++) {
421             String filterName;
422             do {
423                 filterName = String.valueOf(rand.nextInt());
424             } while (newMap.containsKey(filterName));
425             
426             newMap.put(filterName, dummyFilter);
427             expectedNames.add(filterName);
428 
429             Iterator<String> it = expectedNames.iterator();
430             for (Object key: newMap.keySet()) {
431                 if (!it.next().equals(key)) {
432                     if (logger.isDebugEnabled()) {
433                         logger.debug(
434                                 "The specified map didn't pass the insertion " +
435                                 "order test after " + (i + 1) + " tries.");
436                     }
437                     return false;
438                 }
439             }
440         }
441         
442         if (logger.isDebugEnabled()) {
443             logger.debug(
444                     "The specified map passed the insertion order test.");
445         }
446         return true;
447     }
448 
449     public void buildFilterChain(IoFilterChain chain) throws Exception {
450         for (Entry e : entries) {
451             chain.addLast(e.getName(), e.getFilter());
452         }
453     }
454 
455     @Override
456     public String toString() {
457         StringBuffer buf = new StringBuffer();
458         buf.append("{ ");
459 
460         boolean empty = true;
461 
462         for (Entry e : entries) {
463             if (!empty) {
464                 buf.append(", ");
465             } else {
466                 empty = false;
467             }
468 
469             buf.append('(');
470             buf.append(e.getName());
471             buf.append(':');
472             buf.append(e.getFilter());
473             buf.append(')');
474         }
475 
476         if (empty) {
477             buf.append("empty");
478         }
479 
480         buf.append(" }");
481 
482         return buf.toString();
483     }
484 
485     private void checkBaseName(String baseName) {
486         if (baseName == null) {
487             throw new NullPointerException("baseName");
488         }
489 
490         if (!contains(baseName)) {
491             throw new IllegalArgumentException("Unknown filter name: "
492                     + baseName);
493         }
494     }
495 
496     private void register(int index, Entry e) {
497         if (contains(e.getName())) {
498             throw new IllegalArgumentException(
499                     "Other filter is using the same name: " + e.getName());
500         }
501 
502         entries.add(index, e);
503     }
504 
505     private class EntryImpl implements Entry {
506         private final String name;
507         private volatile IoFilter filter;
508 
509         private EntryImpl(String name, IoFilter filter) {
510             if (name == null) {
511                 throw new NullPointerException("name");
512             }
513             if (filter == null) {
514                 throw new NullPointerException("filter");
515             }
516 
517             this.name = name;
518             this.filter = filter;
519         }
520 
521         public String getName() {
522             return name;
523         }
524 
525         public IoFilter getFilter() {
526             return filter;
527         }
528 
529         private void setFilter(IoFilter filter) {
530             this.filter = filter;
531         }
532 
533         public NextFilter getNextFilter() {
534             throw new IllegalStateException();
535         }
536 
537         @Override
538         public String toString() {
539             return "(" + getName() + ':' + filter + ')';
540         }
541 
542         public void addAfter(String name, IoFilter filter) {
543             DefaultIoFilterChainBuilder.this.addAfter(getName(), name, filter);
544         }
545 
546         public void addBefore(String name, IoFilter filter) {
547             DefaultIoFilterChainBuilder.this.addBefore(getName(), name, filter);
548         }
549 
550         public void remove() {
551             DefaultIoFilterChainBuilder.this.remove(getName());
552         }
553 
554         public void replace(IoFilter newFilter) {
555             DefaultIoFilterChainBuilder.this.replace(getName(), newFilter);
556         }
557     }
558 }