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.handler.chain;
021
022import java.util.ArrayList;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027
028import org.apache.mina.core.session.IoSession;
029
030/**
031 * A chain of {@link IoHandlerCommand}s.
032 *
033 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
034 */
035public class IoHandlerChain implements IoHandlerCommand {
036    private static volatile int nextId = 0;
037
038    private final int id = nextId++;
039
040    private final String NEXT_COMMAND = IoHandlerChain.class.getName() + '.' + id + ".nextCommand";
041
042    private final Map<String, Entry> name2entry = new ConcurrentHashMap<String, Entry>();
043
044    private final Entry head;
045
046    private final Entry tail;
047
048    /**
049     * Creates a new, empty chain of {@link IoHandlerCommand}s.
050     */
051    public IoHandlerChain() {
052        head = new Entry(null, null, "head", createHeadCommand());
053        tail = new Entry(head, null, "tail", createTailCommand());
054        head.nextEntry = tail;
055    }
056
057    private IoHandlerCommand createHeadCommand() {
058        return new IoHandlerCommand() {
059            public void execute(NextCommand next, IoSession session, Object message) throws Exception {
060                next.execute(session, message);
061            }
062        };
063    }
064
065    private IoHandlerCommand createTailCommand() {
066        return new IoHandlerCommand() {
067            public void execute(NextCommand next, IoSession session, Object message) throws Exception {
068                next = (NextCommand) session.getAttribute(NEXT_COMMAND);
069                if (next != null) {
070                    next.execute(session, message);
071                }
072            }
073        };
074    }
075
076    public Entry getEntry(String name) {
077        Entry e = name2entry.get(name);
078        if (e == null) {
079            return null;
080        }
081        return e;
082    }
083
084    public IoHandlerCommand get(String name) {
085        Entry e = getEntry(name);
086        if (e == null) {
087            return null;
088        }
089
090        return e.getCommand();
091    }
092
093    public NextCommand getNextCommand(String name) {
094        Entry e = getEntry(name);
095        if (e == null) {
096            return null;
097        }
098
099        return e.getNextCommand();
100    }
101
102    public synchronized void addFirst(String name, IoHandlerCommand command) {
103        checkAddable(name);
104        register(head, name, command);
105    }
106
107    public synchronized void addLast(String name, IoHandlerCommand command) {
108        checkAddable(name);
109        register(tail.prevEntry, name, command);
110    }
111
112    public synchronized void addBefore(String baseName, String name, IoHandlerCommand command) {
113        Entry baseEntry = checkOldName(baseName);
114        checkAddable(name);
115        register(baseEntry.prevEntry, name, command);
116    }
117
118    public synchronized void addAfter(String baseName, String name, IoHandlerCommand command) {
119        Entry baseEntry = checkOldName(baseName);
120        checkAddable(name);
121        register(baseEntry, name, command);
122    }
123
124    public synchronized IoHandlerCommand remove(String name) {
125        Entry entry = checkOldName(name);
126        deregister(entry);
127        return entry.getCommand();
128    }
129
130    public synchronized void clear() throws Exception {
131        Iterator<String> it = new ArrayList<String>(name2entry.keySet()).iterator();
132        while (it.hasNext()) {
133            this.remove(it.next());
134        }
135    }
136
137    private void register(Entry prevEntry, String name, IoHandlerCommand command) {
138        Entry newEntry = new Entry(prevEntry, prevEntry.nextEntry, name, command);
139        prevEntry.nextEntry.prevEntry = newEntry;
140        prevEntry.nextEntry = newEntry;
141
142        name2entry.put(name, newEntry);
143    }
144
145    private void deregister(Entry entry) {
146        Entry prevEntry = entry.prevEntry;
147        Entry nextEntry = entry.nextEntry;
148        prevEntry.nextEntry = nextEntry;
149        nextEntry.prevEntry = prevEntry;
150
151        name2entry.remove(entry.name);
152    }
153
154    /**
155     * Throws an exception when the specified filter name is not registered in this chain.
156     *
157     * @return An filter entry with the specified name.
158     */
159    private Entry checkOldName(String baseName) {
160        Entry e = name2entry.get(baseName);
161        if (e == null) {
162            throw new IllegalArgumentException("Unknown filter name:" + baseName);
163        }
164        return e;
165    }
166
167    /**
168     * Checks the specified filter name is already taken and throws an exception if already taken.
169     */
170    private void checkAddable(String name) {
171        if (name2entry.containsKey(name)) {
172            throw new IllegalArgumentException("Other filter is using the same name '" + name + "'");
173        }
174    }
175
176    public void execute(NextCommand next, IoSession session, Object message) throws Exception {
177        if (next != null) {
178            session.setAttribute(NEXT_COMMAND, next);
179        }
180
181        try {
182            callNextCommand(head, session, message);
183        } finally {
184            session.removeAttribute(NEXT_COMMAND);
185        }
186    }
187
188    private void callNextCommand(Entry entry, IoSession session, Object message) throws Exception {
189        entry.getCommand().execute(entry.getNextCommand(), session, message);
190    }
191
192    public List<Entry> getAll() {
193        List<Entry> list = new ArrayList<Entry>();
194        Entry e = head.nextEntry;
195        while (e != tail) {
196            list.add(e);
197            e = e.nextEntry;
198        }
199
200        return list;
201    }
202
203    public List<Entry> getAllReversed() {
204        List<Entry> list = new ArrayList<Entry>();
205        Entry e = tail.prevEntry;
206        while (e != head) {
207            list.add(e);
208            e = e.prevEntry;
209        }
210        return list;
211    }
212
213    public boolean contains(String name) {
214        return getEntry(name) != null;
215    }
216
217    public boolean contains(IoHandlerCommand command) {
218        Entry e = head.nextEntry;
219        while (e != tail) {
220            if (e.getCommand() == command) {
221                return true;
222            }
223            e = e.nextEntry;
224        }
225        return false;
226    }
227
228    public boolean contains(Class<? extends IoHandlerCommand> commandType) {
229        Entry e = head.nextEntry;
230        while (e != tail) {
231            if (commandType.isAssignableFrom(e.getCommand().getClass())) {
232                return true;
233            }
234            e = e.nextEntry;
235        }
236        return false;
237    }
238
239    @Override
240    public String toString() {
241        StringBuilder buf = new StringBuilder();
242        buf.append("{ ");
243
244        boolean empty = true;
245
246        Entry e = head.nextEntry;
247        while (e != tail) {
248            if (!empty) {
249                buf.append(", ");
250            } else {
251                empty = false;
252            }
253
254            buf.append('(');
255            buf.append(e.getName());
256            buf.append(':');
257            buf.append(e.getCommand());
258            buf.append(')');
259
260            e = e.nextEntry;
261        }
262
263        if (empty) {
264            buf.append("empty");
265        }
266
267        buf.append(" }");
268
269        return buf.toString();
270    }
271
272    /**
273     * Represents a name-command pair that an {@link IoHandlerChain} contains.
274     *
275     * @author <a href="http://mina.apache.org">Apache MINA Project</a>
276     */
277    public class Entry {
278        private Entry prevEntry;
279
280        private Entry nextEntry;
281
282        private final String name;
283
284        private final IoHandlerCommand command;
285
286        private final NextCommand nextCommand;
287
288        private Entry(Entry prevEntry, Entry nextEntry, String name, IoHandlerCommand command) {
289            if (command == null) {
290                throw new IllegalArgumentException("command");
291            }
292            if (name == null) {
293                throw new IllegalArgumentException("name");
294            }
295
296            this.prevEntry = prevEntry;
297            this.nextEntry = nextEntry;
298            this.name = name;
299            this.command = command;
300            this.nextCommand = new NextCommand() {
301                public void execute(IoSession session, Object message) throws Exception {
302                    Entry nextEntry = Entry.this.nextEntry;
303                    callNextCommand(nextEntry, session, message);
304                }
305            };
306        }
307
308        /**
309         * @return the name of the command.
310         */
311        public String getName() {
312            return name;
313        }
314
315        /**
316         * @return the command.
317         */
318        public IoHandlerCommand getCommand() {
319            return command;
320        }
321
322        /**
323         * @return the {@link IoHandlerCommand.NextCommand} of the command.
324         */
325        public NextCommand getNextCommand() {
326            return nextCommand;
327        }
328    }
329}