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