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.common;
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.common.IoFilter.NextFilter;
33  import org.apache.mina.common.IoFilterChain.Entry;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * The default implementation of {@link IoFilterChainBuilder} which is useful
39   * in most cases.  {@link DefaultIoFilterChainBuilder} has an identical interface
40   * with {@link IoFilter}; it contains a list of {@link IoFilter}s that you can
41   * modify. The {@link IoFilter}s which are added to this builder will be appended
42   * to the {@link IoFilterChain} when {@link #buildFilterChain(IoFilterChain)} is
43   * invoked.
44   * <p>
45   * However, the identical interface doesn't mean that it behaves in an exactly
46   * same way with {@link IoFilterChain}.  {@link DefaultIoFilterChainBuilder}
47   * doesn't manage the life cycle of the {@link IoFilter}s at all, and the
48   * existing {@link IoSession}s won't get affected by the changes in this builder.
49   * {@link IoFilterChainBuilder}s affect only newly created {@link IoSession}s.
50   *
51   * <pre>
52   * IoAcceptor acceptor = ...;
53   * DefaultIoFilterChainBuilder builder = acceptor.getFilterChain();
54   * builder.addLast( "myFilter", new MyFilter() );
55   * ...
56   * </pre>
57   *
58   * @author The Apache MINA Project (dev@mina.apache.org)
59   * @version $Rev: 594846 $, $Date: 2007-11-14 05:03:07 -0700 (Wed, 14 Nov 2007) $
60   */
61  public class DefaultIoFilterChainBuilder implements IoFilterChainBuilder {
62      
63      private final Logger logger = LoggerFactory.getLogger(getClass());
64      private final List<Entry> entries;
65  
66      /**
67       * Creates a new instance with an empty filter list.
68       */
69      public DefaultIoFilterChainBuilder() {
70          entries = new CopyOnWriteArrayList<Entry>();
71      }
72  
73      /**
74       * Creates a new copy of the specified {@link DefaultIoFilterChainBuilder}.
75       */
76      public DefaultIoFilterChainBuilder(DefaultIoFilterChainBuilder filterChain) {
77          if (filterChain == null) {
78              throw new NullPointerException("filterChain");
79          }
80          entries = new CopyOnWriteArrayList<Entry>(filterChain.entries);
81      }
82  
83      /**
84       * @see IoFilterChain#getEntry(String)
85       */
86      public Entry getEntry(String name) {
87          for (Entry e: entries) {
88              if (e.getName().equals(name)) {
89                  return e;
90              }
91          }
92  
93          return null;
94      }
95  
96      /**
97       * @see IoFilterChain#getEntry(IoFilter)
98       */
99      public Entry getEntry(IoFilter filter) {
100         for (Entry e: entries) {
101             if (e.getFilter() == filter) {
102                 return e;
103             }
104         }
105 
106         return null;
107     }
108 
109     /**
110      * @see IoFilterChain#getEntry(Class)
111      */
112     public Entry getEntry(Class<? extends IoFilter> filterType) {
113         for (Entry e: entries) {
114             if (filterType.isAssignableFrom(e.getFilter().getClass())) {
115                 return e;
116             }
117         }
118 
119         return null;
120     }
121 
122     /**
123      * @see IoFilterChain#get(String)
124      */
125     public IoFilter get(String name) {
126         Entry e = getEntry(name);
127         if (e == null) {
128             return null;
129         }
130 
131         return e.getFilter();
132     }
133 
134     /**
135      * @see IoFilterChain#get(Class)
136      */
137     public IoFilter get(Class<? extends IoFilter> filterType) {
138         Entry e = getEntry(filterType);
139         if (e == null) {
140             return null;
141         }
142 
143         return e.getFilter();
144     }
145 
146     /**
147      * @see IoFilterChain#getAll()
148      */
149     public List<Entry> getAll() {
150         return new ArrayList<Entry>(entries);
151     }
152 
153     /**
154      * @see IoFilterChain#getAllReversed()
155      */
156     public List<Entry> getAllReversed() {
157         List<Entry> result = getAll();
158         Collections.reverse(result);
159         return result;
160     }
161 
162     /**
163      * @see IoFilterChain#contains(String)
164      */
165     public boolean contains(String name) {
166         return getEntry(name) != null;
167     }
168 
169     /**
170      * @see IoFilterChain#contains(IoFilter)
171      */
172     public boolean contains(IoFilter filter) {
173         return getEntry(filter) != null;
174     }
175 
176     /**
177      * @see IoFilterChain#contains(Class)
178      */
179     public boolean contains(Class<? extends IoFilter> filterType) {
180         return getEntry(filterType) != null;
181     }
182 
183     /**
184      * @see IoFilterChain#addFirst(String, IoFilter)
185      */
186     public synchronized void addFirst(String name, IoFilter filter) {
187         register(0, new EntryImpl(name, filter));
188     }
189 
190     /**
191      * @see IoFilterChain#addLast(String, IoFilter)
192      */
193     public synchronized void addLast(String name, IoFilter filter) {
194         register(entries.size(), new EntryImpl(name, filter));
195     }
196 
197     /**
198      * @see IoFilterChain#addBefore(String, String, IoFilter)
199      */
200     public synchronized void addBefore(String baseName, String name,
201             IoFilter filter) {
202         checkBaseName(baseName);
203 
204         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
205             Entry base = i.next();
206             if (base.getName().equals(baseName)) {
207                 register(i.previousIndex(), new EntryImpl(name, filter));
208                 break;
209             }
210         }
211     }
212 
213     /**
214      * @see IoFilterChain#addAfter(String, String, IoFilter)
215      */
216     public synchronized void addAfter(String baseName, String name,
217             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 NullPointerException("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 NullPointerException("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 NullPointerException("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) get(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: "
302                 + oldFilter.getClass().getName());
303     }
304 
305     public synchronized void replace(Class<? extends IoFilter> oldFilterType,
306             IoFilter newFilter) {
307         for (Entry e : entries) {
308             if (oldFilterType.isAssignableFrom(e.getFilter().getClass())) {
309                 ((EntryImpl) e).setFilter(newFilter);
310                 return;
311             }
312         }
313         throw new IllegalArgumentException("Filter not found: "
314                 + oldFilterType.getName());
315     }
316 
317     /**
318      * @see IoFilterChain#clear()
319      */
320     public synchronized void clear() {
321         entries.clear();
322     }
323     
324     /**
325      * Clears the current list of filters and adds the specified
326      * filter mapping to this builder.  Please note that you must specify
327      * a {@link Map} implementation that iterates the filter mapping in the
328      * order of insertion such as {@link LinkedHashMap}.  Otherwise, it will
329      * throw an {@link IllegalArgumentException}.
330      */
331     public void setFilters(Map<String, ? extends IoFilter> filters) {
332         if (filters == null) {
333             throw new NullPointerException("filters");
334         }
335         
336         if (!isOrderedMap(filters)) {
337             throw new IllegalArgumentException(
338                     "filters is not an ordered map. Please try " + 
339                     LinkedHashMap.class.getName() + ".");
340         }
341 
342         filters = new LinkedHashMap<String, IoFilter>(filters);
343         for (Map.Entry<String, ? extends IoFilter> e: filters.entrySet()) {
344             if (e.getKey() == null) {
345                 throw new NullPointerException("filters contains a null key.");
346             }
347             if (e.getValue() == null) {
348                 throw new NullPointerException("filters contains a null value.");
349             }
350         }
351         
352         synchronized (this) {
353             clear();
354             for (Map.Entry<String, ? extends IoFilter> e: filters.entrySet()) {
355                 addLast(e.getKey(), e.getValue());
356             }
357         }
358     }
359     
360     @SuppressWarnings("unchecked")
361     private boolean isOrderedMap(Map map) {
362         Class<?> mapType = map.getClass();
363         if (LinkedHashMap.class.isAssignableFrom(mapType)) {
364             if (logger.isDebugEnabled()) {
365                 logger.debug(mapType.getSimpleName() + " is an ordered map.");
366             }
367             return true;
368         }
369         
370         if (logger.isDebugEnabled()) {
371             logger.debug(mapType.getName() + " is not a " + LinkedHashMap.class.getSimpleName());
372         }
373 
374         // Detect Jakarta Commons Collections OrderedMap implementations.
375         Class<?> type = mapType;
376         while (type != null) {
377             for (Class<?> i: type.getInterfaces()) {
378                 if (i.getName().endsWith("OrderedMap")) {
379                     if (logger.isDebugEnabled()) {
380                         logger.debug(
381                                 mapType.getSimpleName() +
382                                 " is an ordered map (guessed from that it " +
383                                 " implements OrderedMap interface.)");
384                     }
385                     return true;
386                 }
387             }
388             type = type.getSuperclass();
389         }
390         
391         if (logger.isDebugEnabled()) {
392             logger.debug(
393                     mapType.getName() +
394                     " doesn't implement OrderedMap interface.");
395         }
396         
397         // Last resort: try to create a new instance and test if it maintains
398         // the insertion order.
399         logger.debug(
400                 "Last resort; trying to create a new map instance with a " +
401                 "default constructor and test if insertion order is " +
402                 "maintained.");
403         
404         Map newMap;
405         try {
406             newMap = (Map) mapType.newInstance();
407         } catch (Exception e) {
408             if (logger.isDebugEnabled()) {
409                 logger.debug(
410                         "Failed to create a new map instance of '" + 
411                         mapType.getName() +"'.", e);
412             }
413             return false;
414         }
415         
416         Random rand = new Random();
417         List<String> expectedNames = new ArrayList<String>();
418         IoFilter dummyFilter = new IoFilterAdapter();
419         for (int i = 0; i < 65536; i ++) {
420             String filterName;
421             do {
422                 filterName = String.valueOf(rand.nextInt());
423             } while (newMap.containsKey(filterName));
424             
425             newMap.put(filterName, dummyFilter);
426             expectedNames.add(filterName);
427 
428             Iterator<String> it = expectedNames.iterator();
429             for (Object key: newMap.keySet()) {
430                 if (!it.next().equals(key)) {
431                     if (logger.isDebugEnabled()) {
432                         logger.debug(
433                                 "The specified map didn't pass the insertion " +
434                                 "order test after " + (i + 1) + " tries.");
435                     }
436                     return false;
437                 }
438             }
439         }
440         
441         if (logger.isDebugEnabled()) {
442             logger.debug(
443                     "The specified map passed the insertion order test.");
444         }
445         return true;
446     }
447 
448     public void buildFilterChain(IoFilterChain chain) throws Exception {
449         for (Entry e : entries) {
450             chain.addLast(e.getName(), e.getFilter());
451         }
452     }
453 
454     @Override
455     public String toString() {
456         StringBuffer buf = new StringBuffer();
457         buf.append("{ ");
458 
459         boolean empty = true;
460 
461         for (Entry e : entries) {
462             if (!empty) {
463                 buf.append(", ");
464             } else {
465                 empty = false;
466             }
467 
468             buf.append('(');
469             buf.append(e.getName());
470             buf.append(':');
471             buf.append(e.getFilter());
472             buf.append(')');
473         }
474 
475         if (empty) {
476             buf.append("empty");
477         }
478 
479         buf.append(" }");
480 
481         return buf.toString();
482     }
483 
484     private void checkBaseName(String baseName) {
485         if (baseName == null) {
486             throw new NullPointerException("baseName");
487         }
488 
489         if (!contains(baseName)) {
490             throw new IllegalArgumentException("Unknown filter name: "
491                     + baseName);
492         }
493     }
494 
495     private void register(int index, Entry e) {
496         if (contains(e.getName())) {
497             throw new IllegalArgumentException(
498                     "Other filter is using the same name: " + e.getName());
499         }
500 
501         entries.add(index, e);
502     }
503 
504     private class EntryImpl implements Entry {
505         private final String name;
506         private volatile IoFilter filter;
507 
508         private EntryImpl(String name, IoFilter filter) {
509             if (name == null) {
510                 throw new NullPointerException("name");
511             }
512             if (filter == null) {
513                 throw new NullPointerException("filter");
514             }
515 
516             this.name = name;
517             this.filter = filter;
518         }
519 
520         public String getName() {
521             return name;
522         }
523 
524         public IoFilter getFilter() {
525             return filter;
526         }
527 
528         private void setFilter(IoFilter filter) {
529             this.filter = filter;
530         }
531 
532         public NextFilter getNextFilter() {
533             throw new IllegalStateException();
534         }
535 
536         @Override
537         public String toString() {
538             return "(" + getName() + ':' + filter + ')';
539         }
540 
541         public void addAfter(String name, IoFilter filter) {
542             DefaultIoFilterChainBuilder.this.addAfter(getName(), name, filter);
543         }
544 
545         public void addBefore(String name, IoFilter filter) {
546             DefaultIoFilterChainBuilder.this.addBefore(getName(), name, filter);
547         }
548 
549         public void remove() {
550             DefaultIoFilterChainBuilder.this.remove(getName());
551         }
552 
553         public void replace(IoFilter newFilter) {
554             DefaultIoFilterChainBuilder.this.replace(getName(), newFilter);
555         }
556     }
557 }