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