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      public DefaultIoFilterChainBuilder(DefaultIoFilterChainBuilder filterChain) {
    79          if (filterChain == null) {
    80              throw new IllegalArgumentException("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, 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, 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 IllegalArgumentException("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 IllegalArgumentException("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 IllegalArgumentException("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) getEntry(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: " + oldFilter.getClass().getName());
    302     }
    303
    304     public synchronized void replace(Class<? extends IoFilter> oldFilterType, IoFilter newFilter) {
    305         for (Entry e : entries) {
    306             if (oldFilterType.isAssignableFrom(e.getFilter().getClass())) {
    307                 ((EntryImpl) e).setFilter(newFilter);
    308                 return;
    309             }
    310         }
    311         throw new IllegalArgumentException("Filter not found: " + oldFilterType.getName());
    312     }
    313
    314     /**
    315      * @see IoFilterChain#clear()
    316      */
    317     public synchronized void clear() {
    318         entries.clear();
    319     }
    320
    321     /**
    322      * Clears the current list of filters and adds the specified
    323      * filter mapping to this builder.  Please note that you must specify
    324      * a {@link Map} implementation that iterates the filter mapping in the
    325      * order of insertion such as {@link LinkedHashMap}.  Otherwise, it will
    326      * throw an {@link IllegalArgumentException}.
    327      */
    328     public void setFilters(Map<String, ? extends IoFilter> filters) {
    329         if (filters == null) {
    330             throw new IllegalArgumentException("filters");
    331         }
    332
    333         if (!isOrderedMap(filters)) {
    334             throw new IllegalArgumentException("filters is not an ordered map. Please try "
    335                     + LinkedHashMap.class.getName() + ".");
    336         }
    337
    338         filters = new LinkedHashMap<String, IoFilter>(filters);
    339         for (Map.Entry<String, ? extends IoFilter> e : filters.entrySet()) {
    340             if (e.getKey() == null) {
    341                 throw new IllegalArgumentException("filters contains a null key.");
    342             }
    343             if (e.getValue() == null) {
    344                 throw new IllegalArgumentException("filters contains a null value.");
    345             }
    346         }
    347
    348         synchronized (this) {
    349             clear();
    350             for (Map.Entry<String, ? extends IoFilter> e : filters.entrySet()) {
    351                 addLast(e.getKey(), e.getValue());
    352             }
    353         }
    354     }
    355
    356     @SuppressWarnings("unchecked")
    357     private boolean isOrderedMap(Map map) {
    358         Class<?> mapType = map.getClass();
    359         if (LinkedHashMap.class.isAssignableFrom(mapType)) {
    360             if (LOGGER.isDebugEnabled()) {
    361                 LOGGER.debug(mapType.getSimpleName() + " is an ordered map.");
    362             }
    363             return true;
    364         }
    365
    366         if (LOGGER.isDebugEnabled()) {
    367             LOGGER.debug(mapType.getName() + " is not a " + LinkedHashMap.class.getSimpleName());
    368         }
    369
    370         // Detect Jakarta Commons Collections OrderedMap implementations.
    371         Class<?> type = mapType;
    372         while (type != null) {
    373             for (Class<?> i : type.getInterfaces()) {
    374                 if (i.getName().endsWith("OrderedMap")) {
    375                     if (LOGGER.isDebugEnabled()) {
    376                         LOGGER.debug(mapType.getSimpleName() + " is an ordered map (guessed from that it "
    377                                 + " implements OrderedMap interface.)");
    378                     }
    379                     return true;
    380                 }
    381             }
    382             type = type.getSuperclass();
    383         }
    384
    385         if (LOGGER.isDebugEnabled()) {
    386             LOGGER.debug(mapType.getName() + " doesn't implement OrderedMap interface.");
    387         }
    388
    389         // Last resort: try to create a new instance and test if it maintains
    390         // the insertion order.
    391         LOGGER.debug("Last resort; trying to create a new map instance with a "
    392                 + "default constructor and test if insertion order is " + "maintained.");
    393
    394         Map newMap;
    395         try {
    396             newMap = (Map) mapType.newInstance();
    397         } catch (Exception e) {
    398             if (LOGGER.isDebugEnabled()) {
    399                 LOGGER.debug("Failed to create a new map instance of '" + mapType.getName() + "'.", e);
    400             }
    401             return false;
    402         }
    403
    404         Random rand = new Random();
    405         List<String> expectedNames = new ArrayList<String>();
    406         IoFilter dummyFilter = new IoFilterAdapter();
    407         for (int i = 0; i < 65536; i++) {
    408             String filterName;
    409             do {
    410                 filterName = String.valueOf(rand.nextInt());
    411             } while (newMap.containsKey(filterName));
    412
    413             newMap.put(filterName, dummyFilter);
    414             expectedNames.add(filterName);
    415
    416             Iterator<String> it = expectedNames.iterator();
    417             for (Object key : newMap.keySet()) {
    418                 if (!it.next().equals(key)) {
    419                     if (LOGGER.isDebugEnabled()) {
    420                         LOGGER.debug("The specified map didn't pass the insertion " + "order test after " + (i + 1)
    421                                 + " tries.");
    422                     }
    423                     return false;
    424                 }
    425             }
    426         }
    427
    428         if (LOGGER.isDebugEnabled()) {
    429             LOGGER.debug("The specified map passed the insertion order test.");
    430         }
    431         return true;
    432     }
    433
    434     public void buildFilterChain(IoFilterChain chain) throws Exception {
    435         for (Entry e : entries) {
    436             chain.addLast(e.getName(), e.getFilter());
    437         }
    438     }
    439
    440     @Override
    441     public String toString() {
    442         StringBuilder buf = new StringBuilder();
    443         buf.append("{ ");
    444
    445         boolean empty = true;
    446
    447         for (Entry e : entries) {
    448             if (!empty) {
    449                 buf.append(", ");
    450             } else {
    451                 empty = false;
    452             }
    453
    454             buf.append('(');
    455             buf.append(e.getName());
    456             buf.append(':');
    457             buf.append(e.getFilter());
    458             buf.append(')');
    459         }
    460
    461         if (empty) {
    462             buf.append("empty");
    463         }
    464
    465         buf.append(" }");
    466
    467         return buf.toString();
    468     }
    469
    470     private void checkBaseName(String baseName) {
    471         if (baseName == null) {
    472             throw new IllegalArgumentException("baseName");
    473         }
    474
    475         if (!contains(baseName)) {
    476             throw new IllegalArgumentException("Unknown filter name: " + baseName);
    477         }
    478     }
    479
    480     private void register(int index, Entry e) {
    481         if (contains(e.getName())) {
    482             throw new IllegalArgumentException("Other filter is using the same name: " + e.getName());
    483         }
    484
    485         entries.add(index, e);
    486     }
    487
    488     private class EntryImpl implements Entry {
    489         private final String name;
    490
    491         private volatile IoFilter filter;
    492
    493         private EntryImpl(String name, IoFilter filter) {
    494             if (name == null) {
    495                 throw new IllegalArgumentException("name");
    496             }
    497             if (filter == null) {
    498                 throw new IllegalArgumentException("filter");
    499             }
    500
    501             this.name = name;
    502             this.filter = filter;
    503         }
    504
    505         public String getName() {
    506             return name;
    507         }
    508
    509         public IoFilter getFilter() {
    510             return filter;
    511         }
    512
    513         private void setFilter(IoFilter filter) {
    514             this.filter = filter;
    515         }
    516
    517         public NextFilter getNextFilter() {
    518             throw new IllegalStateException();
    519         }
    520
    521         @Override
    522         public String toString() {
    523             return "(" + getName() + ':' + filter + ')';
    524         }
    525
    526         public void addAfter(String name, IoFilter filter) {
    527             DefaultIoFilterChainBuilder.this.addAfter(getName(), name, filter);
    528         }
    529
    530         public void addBefore(String name, IoFilter filter) {
    531             DefaultIoFilterChainBuilder.this.addBefore(getName(), name, filter);
    532         }
    533
    534         public void remove() {
    535             DefaultIoFilterChainBuilder.this.remove(getName());
    536         }
    537
    538         public void replace(IoFilter newFilter) {
    539             DefaultIoFilterChainBuilder.this.replace(getName(), newFilter);
    540         }
    541     }
    542 }