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 java.util.IdentityHashMap;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.mina.common.ByteBuffer;
28  import org.apache.mina.common.IoSession;
29  import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
30  import org.apache.mina.filter.codec.ProtocolCodecFactory;
31  import org.apache.mina.filter.codec.ProtocolDecoder;
32  import org.apache.mina.filter.codec.ProtocolDecoderException;
33  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
34  import org.apache.mina.filter.codec.ProtocolEncoder;
35  import org.apache.mina.filter.codec.ProtocolEncoderException;
36  import org.apache.mina.filter.codec.ProtocolEncoderOutput;
37  import org.apache.mina.util.IdentityHashSet;
38  
39  /**
40   * A composite {@link ProtocolCodecFactory} that consists of multiple
41   * {@link MessageEncoder}s and {@link MessageDecoder}s.
42   * {@link ProtocolEncoder} and {@link ProtocolDecoder} this factory
43   * returns demultiplex incoming messages and buffers to
44   * appropriate {@link MessageEncoder}s and {@link MessageDecoder}s. 
45   * 
46   * <h2>Disposing resources acquired by {@link MessageEncoder} and {@link MessageDecoder}</h2>
47   * <p>
48   * Make your {@link MessageEncoder} and {@link MessageDecoder} to put all
49   * resources that need to be released as a session attribute.  {@link #disposeCodecResources(IoSession)}
50   * method will be invoked when a session is closed.  Override {@link #disposeCodecResources(IoSession)}
51   * to release the resources you've put as an attribute.
52   * <p>
53   * We didn't provide any <tt>dispose</tt> method for {@link MessageEncoder} and {@link MessageDecoder}
54   * because they can give you a big performance penalty in case you have a lot of
55   * message types to handle.
56   * 
57   * @author The Apache Directory Project (mina-dev@directory.apache.org)
58   * @version $Rev: 612026 $, $Date: 2008-01-15 07:16:14 +0100 (Tue, 15 Jan 2008) $
59   * 
60   * @see MessageEncoder
61   * @see MessageDecoder
62   */
63  public class DemuxingProtocolCodecFactory implements ProtocolCodecFactory {
64      private MessageDecoderFactory[] decoderFactories = new MessageDecoderFactory[0];
65  
66      private MessageEncoderFactory[] encoderFactories = new MessageEncoderFactory[0];
67  
68      private static final Class[] EMPTY_PARAMS = new Class[0];
69  
70      public DemuxingProtocolCodecFactory() {
71      }
72  
73      public void register(Class encoderOrDecoderClass) {
74          if (encoderOrDecoderClass == null) {
75              throw new NullPointerException("encoderOrDecoderClass");
76          }
77  
78          try {
79              encoderOrDecoderClass.getConstructor(EMPTY_PARAMS);
80          } catch (NoSuchMethodException e) {
81              throw new IllegalArgumentException(
82                      "The specifiec class doesn't have a public default constructor.");
83          }
84  
85          boolean registered = false;
86          if (MessageEncoder.class.isAssignableFrom(encoderOrDecoderClass)) {
87              register(new DefaultConstructorMessageEncoderFactory(
88                      encoderOrDecoderClass));
89              registered = true;
90          }
91  
92          if (MessageDecoder.class.isAssignableFrom(encoderOrDecoderClass)) {
93              register(new DefaultConstructorMessageDecoderFactory(
94                      encoderOrDecoderClass));
95              registered = true;
96          }
97  
98          if (!registered) {
99              throw new IllegalArgumentException("Unregisterable type: "
100                     + encoderOrDecoderClass);
101         }
102     }
103 
104     public void register(MessageEncoder encoder) {
105         register(new SingletonMessageEncoderFactory(encoder));
106     }
107 
108     public void register(MessageEncoderFactory factory) {
109         if (factory == null) {
110             throw new NullPointerException("factory");
111         }
112         MessageEncoderFactory[] encoderFactories = this.encoderFactories;
113         MessageEncoderFactory[] newEncoderFactories = new MessageEncoderFactory[encoderFactories.length + 1];
114         System.arraycopy(encoderFactories, 0, newEncoderFactories, 0,
115                 encoderFactories.length);
116         newEncoderFactories[encoderFactories.length] = factory;
117         this.encoderFactories = newEncoderFactories;
118     }
119 
120     public void register(MessageDecoder decoder) {
121         register(new SingletonMessageDecoderFactory(decoder));
122     }
123 
124     public void register(MessageDecoderFactory factory) {
125         if (factory == null) {
126             throw new NullPointerException("factory");
127         }
128         MessageDecoderFactory[] decoderFactories = this.decoderFactories;
129         MessageDecoderFactory[] newDecoderFactories = new MessageDecoderFactory[decoderFactories.length + 1];
130         System.arraycopy(decoderFactories, 0, newDecoderFactories, 0,
131                 decoderFactories.length);
132         newDecoderFactories[decoderFactories.length] = factory;
133         this.decoderFactories = newDecoderFactories;
134     }
135 
136     public ProtocolEncoder getEncoder() throws Exception {
137         return new ProtocolEncoderImpl();
138     }
139 
140     public ProtocolDecoder getDecoder() throws Exception {
141         return new ProtocolDecoderImpl();
142     }
143 
144     /**
145      * Implement this method to release all resources acquired to perform
146      * encoding and decoding messages for the specified <tt>session</tt>.
147      * By default, this method does nothing.
148      * 
149      * @param session the session that requires resource deallocation now
150      */
151     protected void disposeCodecResources(IoSession session) {
152         // Do nothing by default; let users implement it as they want.
153 
154         // This statement is just to avoid compiler warning.  Please ignore. 
155         session.getTransportType();
156     }
157 
158     private class ProtocolEncoderImpl implements ProtocolEncoder {
159         private final Map encoders = new IdentityHashMap();
160 
161         private ProtocolEncoderImpl() throws Exception {
162             MessageEncoderFactory[] encoderFactories = DemuxingProtocolCodecFactory.this.encoderFactories;
163             for (int i = encoderFactories.length - 1; i >= 0; i--) {
164                 MessageEncoder encoder = encoderFactories[i].getEncoder();
165                 Set messageTypes = encoder.getMessageTypes();
166                 if (messageTypes == null) {
167                     throw new IllegalStateException(encoder.getClass()
168                             .getName()
169                             + "#getMessageTypes() may not return null.");
170                 }
171 
172                 Iterator it = messageTypes.iterator();
173                 while (it.hasNext()) {
174                     Class type = (Class) it.next();
175                     encoders.put(type, encoder);
176                 }
177             }
178         }
179 
180         public void encode(IoSession session, Object message,
181                 ProtocolEncoderOutput out) throws Exception {
182             Class type = message.getClass();
183             MessageEncoder encoder = findEncoder(type);
184             if (encoder == null) {
185                 throw new ProtocolEncoderException("Unexpected message type: "
186                         + type);
187             }
188 
189             encoder.encode(session, message, out);
190         }
191 
192         private MessageEncoder findEncoder(Class type) {
193             MessageEncoder encoder = (MessageEncoder) encoders.get(type);
194             if (encoder == null) {
195                 encoder = findEncoder(type, new IdentityHashSet());
196             }
197 
198             return encoder;
199         }
200 
201         private MessageEncoder findEncoder(Class type, Set triedClasses) {
202             MessageEncoder encoder;
203 
204             if (triedClasses.contains(type))
205                 return null;
206             triedClasses.add(type);
207 
208             encoder = (MessageEncoder) encoders.get(type);
209             if (encoder == null) {
210                 encoder = findEncoder(type, triedClasses);
211                 if (encoder != null)
212                     return encoder;
213 
214                 Class[] interfaces = type.getInterfaces();
215                 for (int i = 0; i < interfaces.length; i++) {
216                     encoder = findEncoder(interfaces[i], triedClasses);
217                     if (encoder != null)
218                         return encoder;
219                 }
220 
221                 return null;
222             } else
223                 return encoder;
224         }
225 
226         public void dispose(IoSession session) throws Exception {
227             DemuxingProtocolCodecFactory.this.disposeCodecResources(session);
228         }
229     }
230 
231     private class ProtocolDecoderImpl extends CumulativeProtocolDecoder {
232         private final MessageDecoder[] decoders;
233 
234         private MessageDecoder currentDecoder;
235 
236         protected ProtocolDecoderImpl() throws Exception {
237             MessageDecoderFactory[] decoderFactories = DemuxingProtocolCodecFactory.this.decoderFactories;
238             decoders = new MessageDecoder[decoderFactories.length];
239             for (int i = decoderFactories.length - 1; i >= 0; i--) {
240                 decoders[i] = decoderFactories[i].getDecoder();
241             }
242         }
243 
244         protected boolean doDecode(IoSession session, ByteBuffer in,
245                 ProtocolDecoderOutput out) throws Exception {
246             if (currentDecoder == null) {
247                 MessageDecoder[] decoders = this.decoders;
248                 int undecodables = 0;
249                 for (int i = decoders.length - 1; i >= 0; i--) {
250                     MessageDecoder decoder = decoders[i];
251                     int limit = in.limit();
252                     int pos = in.position();
253 
254                     MessageDecoderResult result;
255                     try {
256                         result = decoder.decodable(session, in);
257                     } finally {
258                         in.position(pos);
259                         in.limit(limit);
260                     }
261 
262                     if (result == MessageDecoder.OK) {
263                         currentDecoder = decoder;
264                         break;
265                     } else if (result == MessageDecoder.NOT_OK) {
266                         undecodables++;
267                     } else if (result != MessageDecoder.NEED_DATA) {
268                         throw new IllegalStateException(
269                                 "Unexpected decode result (see your decodable()): "
270                                         + result);
271                     }
272                 }
273 
274                 if (undecodables == decoders.length) {
275                     // Throw an exception if all decoders cannot decode data.
276                     String dump = in.getHexDump();
277                     in.position(in.limit()); // Skip data
278                     ProtocolDecoderException e = new ProtocolDecoderException(
279                             "No appropriate message decoder: " + dump);
280                     e.setHexdump(dump);
281                     throw e;
282                 }
283 
284                 if (currentDecoder == null) {
285                     // Decoder is not determined yet (i.e. we need more data)
286                     return false;
287                 }
288             }
289 
290             MessageDecoderResult result = currentDecoder.decode(session, in,
291                     out);
292             if (result == MessageDecoder.OK) {
293                 currentDecoder = null;
294                 return true;
295             } else if (result == MessageDecoder.NEED_DATA) {
296                 return false;
297             } else if (result == MessageDecoder.NOT_OK) {
298                 currentDecoder = null;
299                 throw new ProtocolDecoderException(
300                         "Message decoder returned NOT_OK.");
301             } else {
302                 currentDecoder = null;
303                 throw new IllegalStateException(
304                         "Unexpected decode result (see your decode()): "
305                                 + result);
306             }
307         }
308 
309         public void finishDecode(IoSession session, ProtocolDecoderOutput out)
310                 throws Exception {
311             if (currentDecoder == null) {
312                 return;
313             }
314 
315             currentDecoder.finishDecode(session, out);
316         }
317 
318         public void dispose(IoSession session) throws Exception {
319             super.dispose(session);
320 
321             // ProtocolEncoder.dispose() already called disposeCodec(),
322             // so there's nothing more we need to do.
323         }
324     }
325 
326     private static class SingletonMessageEncoderFactory implements
327             MessageEncoderFactory {
328         private final MessageEncoder encoder;
329 
330         private SingletonMessageEncoderFactory(MessageEncoder encoder) {
331             if (encoder == null) {
332                 throw new NullPointerException("encoder");
333             }
334             this.encoder = encoder;
335         }
336 
337         public MessageEncoder getEncoder() {
338             return encoder;
339         }
340     }
341 
342     private static class SingletonMessageDecoderFactory implements
343             MessageDecoderFactory {
344         private final MessageDecoder decoder;
345 
346         private SingletonMessageDecoderFactory(MessageDecoder decoder) {
347             if (decoder == null) {
348                 throw new NullPointerException("decoder");
349             }
350             this.decoder = decoder;
351         }
352 
353         public MessageDecoder getDecoder() {
354             return decoder;
355         }
356     }
357 
358     private static class DefaultConstructorMessageEncoderFactory implements
359             MessageEncoderFactory {
360         private final Class encoderClass;
361 
362         private DefaultConstructorMessageEncoderFactory(Class encoderClass) {
363             if (encoderClass == null) {
364                 throw new NullPointerException("encoderClass");
365             }
366 
367             if (!MessageEncoder.class.isAssignableFrom(encoderClass)) {
368                 throw new IllegalArgumentException(
369                         "encoderClass is not assignable to MessageEncoder");
370             }
371             this.encoderClass = encoderClass;
372         }
373 
374         public MessageEncoder getEncoder() throws Exception {
375             return (MessageEncoder) encoderClass.newInstance();
376         }
377     }
378 
379     private static class DefaultConstructorMessageDecoderFactory implements
380             MessageDecoderFactory {
381         private final Class decoderClass;
382 
383         private DefaultConstructorMessageDecoderFactory(Class decoderClass) {
384             if (decoderClass == null) {
385                 throw new NullPointerException("decoderClass");
386             }
387 
388             if (!MessageDecoder.class.isAssignableFrom(decoderClass)) {
389                 throw new IllegalArgumentException(
390                         "decoderClass is not assignable to MessageDecoder");
391             }
392             this.decoderClass = decoderClass;
393         }
394 
395         public MessageDecoder getDecoder() throws Exception {
396             return (MessageDecoder) decoderClass.newInstance();
397         }
398     }
399 }