001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.core.filterchain;
021
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.Iterator;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.ListIterator;
028import java.util.Map;
029import java.util.Random;
030import java.util.concurrent.CopyOnWriteArrayList;
031
032import org.apache.mina.core.filterchain.IoFilter.NextFilter;
033import org.apache.mina.core.filterchain.IoFilterChain.Entry;
034import org.apache.mina.core.session.IoSession;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * The default implementation of {@link IoFilterChainBuilder} which is useful
040 * in most cases.  {@link DefaultIoFilterChainBuilder} has an identical interface
041 * with {@link IoFilter}; it contains a list of {@link IoFilter}s that you can
042 * modify. The {@link IoFilter}s which are added to this builder will be appended
043 * to the {@link IoFilterChain} when {@link #buildFilterChain(IoFilterChain)} is
044 * invoked.
045 * <p>
046 * However, the identical interface doesn't mean that it behaves in an exactly
047 * same way with {@link IoFilterChain}.  {@link DefaultIoFilterChainBuilder}
048 * doesn't manage the life cycle of the {@link IoFilter}s at all, and the
049 * existing {@link IoSession}s won't get affected by the changes in this builder.
050 * {@link IoFilterChainBuilder}s affect only newly created {@link IoSession}s.
051 *
052 * <pre>
053 * IoAcceptor acceptor = ...;
054 * DefaultIoFilterChainBuilder builder = acceptor.getFilterChain();
055 * builder.addLast( "myFilter", new MyFilter() );
056 * ...
057 * </pre>
058 *
059 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
060 * @org.apache.xbean.XBean
061 */
062public class DefaultIoFilterChainBuilder implements IoFilterChainBuilder {
063
064    private final static Logger LOGGER = LoggerFactory.getLogger(DefaultIoFilterChainBuilder.class);
065
066    private final List<Entry> entries;
067
068    /**
069     * Creates a new instance with an empty filter list.
070     */
071    public DefaultIoFilterChainBuilder() {
072        entries = new CopyOnWriteArrayList<Entry>();
073    }
074
075    /**
076     * Creates a new copy of the specified {@link DefaultIoFilterChainBuilder}.
077     * 
078     * @param filterChain The FilterChain we will copy
079     */
080    public DefaultIoFilterChainBuilder(DefaultIoFilterChainBuilder filterChain) {
081        if (filterChain == null) {
082            throw new IllegalArgumentException("filterChain");
083        }
084        entries = new CopyOnWriteArrayList<Entry>(filterChain.entries);
085    }
086
087    /**
088     * @see IoFilterChain#getEntry(String)
089     * 
090     * @param name The Filter's name we are looking for
091     * @return The found Entry
092     */
093    public Entry getEntry(String name) {
094        for (Entry e : entries) {
095            if (e.getName().equals(name)) {
096                return e;
097            }
098        }
099
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}