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 = 
65          LoggerFactory.getLogger(DefaultIoFilterChainBuilder.class);
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 NullPointerException("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,
203             IoFilter filter) {
204         checkBaseName(baseName);
205 
206         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
207             Entry base = i.next();
208             if (base.getName().equals(baseName)) {
209                 register(i.previousIndex(), new EntryImpl(name, filter));
210                 break;
211             }
212         }
213     }
214 
215     /**
216      * @see IoFilterChain#addAfter(String, String, IoFilter)
217      */
218     public synchronized void addAfter(String baseName, String name,
219             IoFilter filter) {
220         checkBaseName(baseName);
221 
222         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
223             Entry base = i.next();
224             if (base.getName().equals(baseName)) {
225                 register(i.nextIndex(), new EntryImpl(name, filter));
226                 break;
227             }
228         }
229     }
230 
231     /**
232      * @see IoFilterChain#remove(String)
233      */
234     public synchronized IoFilter remove(String name) {
235         if (name == null) {
236             throw new NullPointerException("name");
237         }
238 
239         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
240             Entry e = i.next();
241             if (e.getName().equals(name)) {
242                 entries.remove(i.previousIndex());
243                 return e.getFilter();
244             }
245         }
246 
247         throw new IllegalArgumentException("Unknown filter name: " + name);
248     }
249 
250     /**
251      * @see IoFilterChain#remove(IoFilter)
252      */
253     public synchronized IoFilter remove(IoFilter filter) {
254         if (filter == null) {
255             throw new NullPointerException("filter");
256         }
257 
258         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
259             Entry e = i.next();
260             if (e.getFilter() == filter) {
261                 entries.remove(i.previousIndex());
262                 return e.getFilter();
263             }
264         }
265 
266         throw new IllegalArgumentException("Filter not found: " + filter.getClass().getName());
267     }
268 
269     /**
270      * @see IoFilterChain#remove(Class)
271      */
272     public synchronized IoFilter remove(Class<? extends IoFilter> filterType) {
273         if (filterType == null) {
274             throw new NullPointerException("filterType");
275         }
276 
277         for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) {
278             Entry e = i.next();
279             if (filterType.isAssignableFrom(e.getFilter().getClass())) {
280                 entries.remove(i.previousIndex());
281                 return e.getFilter();
282             }
283         }
284 
285         throw new IllegalArgumentException("Filter not found: " + filterType.getName());
286     }
287 
288     public synchronized IoFilter replace(String name, IoFilter newFilter) {
289         checkBaseName(name);
290         EntryImpl e = (EntryImpl)getEntry(name);
291         IoFilter oldFilter = e.getFilter();
292         e.setFilter(newFilter);
293         return oldFilter;
294     }
295 
296     public synchronized void replace(IoFilter oldFilter, IoFilter newFilter) {
297         for (Entry e : entries) {
298             if (e.getFilter() == oldFilter) {
299                 ((EntryImpl) e).setFilter(newFilter);
300                 return;
301             }
302         }
303         throw new IllegalArgumentException("Filter not found: "
304                 + oldFilter.getClass().getName());
305     }
306 
307     public synchronized void replace(Class<? extends IoFilter> oldFilterType,
308             IoFilter newFilter) {
309         for (Entry e : entries) {
310             if (oldFilterType.isAssignableFrom(e.getFilter().getClass())) {
311                 ((EntryImpl) e).setFilter(newFilter);
312                 return;
313             }
314         }
315         throw new IllegalArgumentException("Filter not found: "
316                 + oldFilterType.getName());
317     }
318 
319     /**
320      * @see IoFilterChain#clear()
321      */
322     public synchronized void clear() {
323         entries.clear();
324     }
325     
326     /**
327      * Clears the current list of filters and adds the specified
328      * filter mapping to this builder.  Please note that you must specify
329      * a {@link Map} implementation that iterates the filter mapping in the
330      * order of insertion such as {@link LinkedHashMap}.  Otherwise, it will
331      * throw an {@link IllegalArgumentException}.
332      */
333     public void setFilters(Map<String, ? extends IoFilter> filters) {
334         if (filters == null) {
335             throw new NullPointerException("filters");
336         }
337         
338         if (!isOrderedMap(filters)) {
339             throw new IllegalArgumentException(
340                     "filters is not an ordered map. Please try " + 
341                     LinkedHashMap.class.getName() + ".");
342         }
343 
344         filters = new LinkedHashMap<String, IoFilter>(filters);
345         for (Map.Entry<String, ? extends IoFilter> e: filters.entrySet()) {
346             if (e.getKey() == null) {
347                 throw new NullPointerException("filters contains a null key.");
348             }
349             if (e.getValue() == null) {
350                 throw new NullPointerException("filters contains a null value.");
351             }
352         }
353         
354         synchronized (this) {
355             clear();
356             for (Map.Entry<String, ? extends IoFilter> e: filters.entrySet()) {
357                 addLast(e.getKey(), e.getValue());
358             }
359         }
360     }
361     
362     @SuppressWarnings("unchecked")
363     private boolean isOrderedMap(Map map) {
364         Class<?> mapType = map.getClass();
365         if (LinkedHashMap.class.isAssignableFrom(mapType)) {
366             if (LOGGER.isDebugEnabled()) {
367                 LOGGER.debug(mapType.getSimpleName() + " is an ordered map.");
368             }
369             return true;
370         }
371         
372         if (LOGGER.isDebugEnabled()) {
373             LOGGER.debug(mapType.getName() + " is not a " + LinkedHashMap.class.getSimpleName());
374         }
375 
376         // Detect Jakarta Commons Collections OrderedMap implementations.
377         Class<?> type = mapType;
378         while (type != null) {
379             for (Class<?> i: type.getInterfaces()) {
380                 if (i.getName().endsWith("OrderedMap")) {
381                     if (LOGGER.isDebugEnabled()) {
382                         LOGGER.debug(
383                                 mapType.getSimpleName() +
384                                 " is an ordered map (guessed from that it " +
385                                 " implements OrderedMap interface.)");
386                     }
387                     return true;
388                 }
389             }
390             type = type.getSuperclass();
391         }
392         
393         if (LOGGER.isDebugEnabled()) {
394             LOGGER.debug(
395                     mapType.getName() +
396                     " doesn't implement OrderedMap interface.");
397         }
398         
399         // Last resort: try to create a new instance and test if it maintains
400         // the insertion order.
401         LOGGER.debug(
402                 "Last resort; trying to create a new map instance with a " +
403                 "default constructor and test if insertion order is " +
404                 "maintained.");
405         
406         Map newMap;
407         try {
408             newMap = (Map) mapType.newInstance();
409         } catch (Exception e) {
410             if (LOGGER.isDebugEnabled()) {
411                 LOGGER.debug(
412                         "Failed to create a new map instance of '" + 
413                         mapType.getName() +"'.", e);
414             }
415             return false;
416         }
417         
418         Random rand = new Random();
419         List<String> expectedNames = new ArrayList<String>();
420         IoFilter dummyFilter = new IoFilterAdapter();
421         for (int i = 0; i < 65536; i ++) {
422             String filterName;
423             do {
424                 filterName = String.valueOf(rand.nextInt());
425             } while (newMap.containsKey(filterName));
426             
427             newMap.put(filterName, dummyFilter);
428             expectedNames.add(filterName);
429 
430             Iterator<String> it = expectedNames.iterator();
431             for (Object key: newMap.keySet()) {
432                 if (!it.next().equals(key)) {
433                     if (LOGGER.isDebugEnabled()) {
434                         LOGGER.debug(
435                                 "The specified map didn't pass the insertion " +
436                                 "order test after " + (i + 1) + " tries.");
437                     }
438                     return false;
439                 }
440             }
441         }
442         
443         if (LOGGER.isDebugEnabled()) {
444             LOGGER.debug(
445                     "The specified map passed the insertion order test.");
446         }
447         return true;
448     }
449 
450     public void buildFilterChain(IoFilterChain chain) throws Exception {
451         for (Entry e : entries) {
452             chain.addLast(e.getName(), e.getFilter());
453         }
454     }
455 
456     @Override
457     public String toString() {
458         StringBuilder buf = new StringBuilder();
459         buf.append("{ ");
460 
461         boolean empty = true;
462 
463         for (Entry e : entries) {
464             if (!empty) {
465                 buf.append(", ");
466             } else {
467                 empty = false;
468             }
469 
470             buf.append('(');
471             buf.append(e.getName());
472             buf.append(':');
473             buf.append(e.getFilter());
474             buf.append(')');
475         }
476 
477         if (empty) {
478             buf.append("empty");
479         }
480 
481         buf.append(" }");
482 
483         return buf.toString();
484     }
485 
486     private void checkBaseName(String baseName) {
487         if (baseName == null) {
488             throw new NullPointerException("baseName");
489         }
490 
491         if (!contains(baseName)) {
492             throw new IllegalArgumentException("Unknown filter name: "
493                     + baseName);
494         }
495     }
496 
497     private void register(int index, Entry e) {
498         if (contains(e.getName())) {
499             throw new IllegalArgumentException(
500                     "Other filter is using the same name: " + e.getName());
501         }
502 
503         entries.add(index, e);
504     }
505 
506     private class EntryImpl implements Entry {
507         private final String name;
508         private volatile IoFilter filter;
509 
510         private EntryImpl(String name, IoFilter filter) {
511             if (name == null) {
512                 throw new NullPointerException("name");
513             }
514             if (filter == null) {
515                 throw new NullPointerException("filter");
516             }
517 
518             this.name = name;
519             this.filter = filter;
520         }
521 
522         public String getName() {
523             return name;
524         }
525 
526         public IoFilter getFilter() {
527             return filter;
528         }
529 
530         private void setFilter(IoFilter filter) {
531             this.filter = filter;
532         }
533 
534         public NextFilter getNextFilter() {
535             throw new IllegalStateException();
536         }
537 
538         @Override
539         public String toString() {
540             return "(" + getName() + ':' + filter + ')';
541         }
542 
543         public void addAfter(String name, IoFilter filter) {
544             DefaultIoFilterChainBuilder.this.addAfter(getName(), name, filter);
545         }
546 
547         public void addBefore(String name, IoFilter filter) {
548             DefaultIoFilterChainBuilder.this.addBefore(getName(), name, filter);
549         }
550 
551         public void remove() {
552             DefaultIoFilterChainBuilder.this.remove(getName());
553         }
554 
555         public void replace(IoFilter newFilter) {
556             DefaultIoFilterChainBuilder.this.replace(getName(), newFilter);
557         }
558     }
559 }