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.filter.codec.demux;
21  
22  import org.apache.mina.core.buffer.IoBuffer;
23  import org.apache.mina.core.session.AttributeKey;
24  import org.apache.mina.core.session.IoSession;
25  import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
26  import org.apache.mina.filter.codec.ProtocolDecoder;
27  import org.apache.mina.filter.codec.ProtocolDecoderException;
28  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
29  
30  /**
31   * A composite {@link ProtocolDecoder} that demultiplexes incoming {@link IoBuffer}
32   * decoding requests into an appropriate {@link MessageDecoder}.
33   * 
34   * <h2>Internal mechanism of {@link MessageDecoder} selection</h2>
35   * <p>
36   * <ol>
37   * <li>{@link DemuxingProtocolDecoder} iterates the list of candidate
38   * {@link MessageDecoder}s and calls {@link MessageDecoder#decodable(IoSession, IoBuffer)}.
39   * Initially, all registered {@link MessageDecoder}s are candidates.</li>
40   * <li>If {@link MessageDecoderResult#NOT_OK} is returned, it is removed from the candidate
41   *     list.</li>
42   * <li>If {@link MessageDecoderResult#NEED_DATA} is returned, it is retained in the candidate
43   *     list, and its {@link MessageDecoder#decodable(IoSession, IoBuffer)} will be invoked
44   *     again when more data is received.</li>
45   * <li>If {@link MessageDecoderResult#OK} is returned, {@link DemuxingProtocolDecoder}
46   *     found the right {@link MessageDecoder}.</li>
47   * <li>If there's no candidate left, an exception is raised.  Otherwise, 
48   *     {@link DemuxingProtocolDecoder} will keep iterating the candidate list.
49   * </ol>
50   * 
51   * Please note that any change of position and limit of the specified {@link IoBuffer}
52   * in {@link MessageDecoder#decodable(IoSession, IoBuffer)} will be reverted back to its
53   * original value.
54   * <p>
55   * Once a {@link MessageDecoder} is selected, {@link DemuxingProtocolDecoder} calls
56   * {@link MessageDecoder#decode(IoSession, IoBuffer, ProtocolDecoderOutput)} continuously
57   * reading its return value:
58   * <ul>
59   * <li>{@link MessageDecoderResult#NOT_OK} - protocol violation; {@link ProtocolDecoderException}
60   *                                           is raised automatically.</li>
61   * <li>{@link MessageDecoderResult#NEED_DATA} - needs more data to read the whole message;
62   *                                              {@link MessageDecoder#decode(IoSession, IoBuffer, ProtocolDecoderOutput)}
63   *                                              will be invoked again when more data is received.</li>
64   * <li>{@link MessageDecoderResult#OK} - successfully decoded a message; the candidate list will
65   *                                       be reset and the selection process will start over.</li>
66   * </ul>
67   *
68   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
69   *
70   * @see MessageDecoderFactory
71   * @see MessageDecoder
72   */
73  public class DemuxingProtocolDecoder extends CumulativeProtocolDecoder {
74  
75      private final AttributeKey STATE = new AttributeKey(getClass(), "state");
76      
77      private MessageDecoderFactory[] decoderFactories = new MessageDecoderFactory[0];
78      private static final Class<?>[] EMPTY_PARAMS = new Class[0];
79  
80      public DemuxingProtocolDecoder() {
81          // Do nothing
82      }
83  
84      public void addMessageDecoder(Class<? extends MessageDecoder> decoderClass) {
85          if (decoderClass == null) {
86              throw new IllegalArgumentException("decoderClass");
87          }
88  
89          try {
90              decoderClass.getConstructor(EMPTY_PARAMS);
91          } catch (NoSuchMethodException e) {
92              throw new IllegalArgumentException(
93                      "The specified class doesn't have a public default constructor.");
94          }
95  
96          boolean registered = false;
97          if (MessageDecoder.class.isAssignableFrom(decoderClass)) {
98              addMessageDecoder(new DefaultConstructorMessageDecoderFactory(decoderClass));
99              registered = true;
100         }
101 
102         if (!registered) {
103             throw new IllegalArgumentException(
104                     "Unregisterable type: " + decoderClass);
105         }
106     }
107 
108     public void addMessageDecoder(MessageDecoder decoder) {
109         addMessageDecoder(new SingletonMessageDecoderFactory(decoder));
110     }
111 
112     public void addMessageDecoder(MessageDecoderFactory factory) {
113         if (factory == null) {
114             throw new IllegalArgumentException("factory");
115         }
116         MessageDecoderFactory[] decoderFactories = this.decoderFactories;
117         MessageDecoderFactory[] newDecoderFactories = new MessageDecoderFactory[decoderFactories.length + 1];
118         System.arraycopy(decoderFactories, 0, newDecoderFactories, 0,
119                 decoderFactories.length);
120         newDecoderFactories[decoderFactories.length] = factory;
121         this.decoderFactories = newDecoderFactories;
122     }
123     
124     /**
125      * {@inheritDoc}
126      */
127     @Override
128     protected boolean doDecode(IoSession session, IoBuffer in,
129             ProtocolDecoderOutput out) throws Exception {
130         State state = getState(session);
131         
132         if (state.currentDecoder == null) {
133             MessageDecoder[] decoders = state.decoders;
134             int undecodables = 0;
135         
136             for (int i = decoders.length - 1; i >= 0; i--) {
137                 MessageDecoder decoder = decoders[i];
138                 int limit = in.limit();
139                 int pos = in.position();
140 
141                 MessageDecoderResult result;
142                 
143                 try {
144                     result = decoder.decodable(session, in);
145                 } finally {
146                     in.position(pos);
147                     in.limit(limit);
148                 }
149 
150                 if (result == MessageDecoder.OK) {
151                     state.currentDecoder = decoder;
152                     break;
153                 } else if (result == MessageDecoder.NOT_OK) {
154                     undecodables++;
155                 } else if (result != MessageDecoder.NEED_DATA) {
156                     throw new IllegalStateException(
157                             "Unexpected decode result (see your decodable()): "
158                                     + result);
159                 }
160             }
161 
162             if (undecodables == decoders.length) {
163                 // Throw an exception if all decoders cannot decode data.
164                 String dump = in.getHexDump();
165                 in.position(in.limit()); // Skip data
166                 ProtocolDecoderException e = new ProtocolDecoderException(
167                         "No appropriate message decoder: " + dump);
168                 e.setHexdump(dump);
169                 throw e;
170             }
171 
172             if (state.currentDecoder == null) {
173                 // Decoder is not determined yet (i.e. we need more data)
174                 return false;
175             }
176         }
177 
178         try {
179             MessageDecoderResult result = state.currentDecoder.decode(session, in,
180                     out);
181             if (result == MessageDecoder.OK) {
182                 state.currentDecoder = null;
183                 return true;
184             } else if (result == MessageDecoder.NEED_DATA) {
185                 return false;
186             } else if (result == MessageDecoder.NOT_OK) {
187                 state.currentDecoder = null;
188                 throw new ProtocolDecoderException(
189                         "Message decoder returned NOT_OK.");
190             } else {
191                 state.currentDecoder = null;
192                 throw new IllegalStateException(
193                         "Unexpected decode result (see your decode()): "
194                                 + result);
195             }
196         } catch (Exception e) {
197             state.currentDecoder = null; 
198             throw e;
199         }
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     public void finishDecode(IoSession session, ProtocolDecoderOutput out)
207             throws Exception {
208         super.finishDecode(session, out);
209         State state = getState(session);
210         MessageDecoder currentDecoder = state.currentDecoder;
211         if (currentDecoder == null) {
212             return;
213         }
214 
215         currentDecoder.finishDecode(session, out);
216     }
217 
218     /**
219      * {@inheritDoc}
220      */
221     @Override
222     public void dispose(IoSession session) throws Exception {
223         super.dispose(session);
224         session.removeAttribute(STATE);
225     }
226     
227     private State getState(IoSession session) throws Exception {
228         State state = (State) session.getAttribute(STATE);
229         
230         if (state == null) {
231             state = new State();
232             State oldState = (State) session.setAttributeIfAbsent(STATE, state);
233             
234             if (oldState != null) {
235                 state = oldState;
236             }
237         }
238         
239         return state;
240     }
241     
242     private class State {
243         private final MessageDecoder[] decoders;
244         private MessageDecoder currentDecoder;
245         
246         private State() throws Exception {
247             MessageDecoderFactory[] decoderFactories = DemuxingProtocolDecoder.this.decoderFactories;
248             decoders = new MessageDecoder[decoderFactories.length];
249             for (int i = decoderFactories.length - 1; i >= 0; i--) {
250                 decoders[i] = decoderFactories[i].getDecoder();
251             }
252         }
253     }
254 
255     private static class SingletonMessageDecoderFactory implements
256             MessageDecoderFactory {
257         private final MessageDecoder decoder;
258 
259         private SingletonMessageDecoderFactory(MessageDecoder decoder) {
260             if (decoder == null) {
261                 throw new IllegalArgumentException("decoder");
262             }
263             this.decoder = decoder;
264         }
265 
266         public MessageDecoder getDecoder() {
267             return decoder;
268         }
269     }
270 
271     private static class DefaultConstructorMessageDecoderFactory implements
272             MessageDecoderFactory {
273         private final Class<?> decoderClass;
274 
275         private DefaultConstructorMessageDecoderFactory(Class<?> decoderClass) {
276             if (decoderClass == null) {
277                 throw new IllegalArgumentException("decoderClass");
278             }
279 
280             if (!MessageDecoder.class.isAssignableFrom(decoderClass)) {
281                 throw new IllegalArgumentException(
282                         "decoderClass is not assignable to MessageDecoder");
283             }
284             this.decoderClass = decoderClass;
285         }
286 
287         public MessageDecoder getDecoder() throws Exception {
288             return (MessageDecoder) decoderClass.newInstance();
289         }
290     }
291 }