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: 575233 $, $Date: 2007-09-13 18:23:33 +0900 (Thu, 13 Sep 2007) $
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                     throw new ProtocolDecoderException(
279                             "No appropriate message decoder: " + dump);
280                 }
281 
282                 if (currentDecoder == null) {
283                     // Decoder is not determined yet (i.e. we need more data)
284                     return false;
285                 }
286             }
287 
288             MessageDecoderResult result = currentDecoder.decode(session, in,
289                     out);
290             if (result == MessageDecoder.OK) {
291                 currentDecoder = null;
292                 return true;
293             } else if (result == MessageDecoder.NEED_DATA) {
294                 return false;
295             } else if (result == MessageDecoder.NOT_OK) {
296                 currentDecoder = null;
297                 throw new ProtocolDecoderException(
298                         "Message decoder returned NOT_OK.");
299             } else {
300                 currentDecoder = null;
301                 throw new IllegalStateException(
302                         "Unexpected decode result (see your decode()): "
303                                 + result);
304             }
305         }
306 
307         public void finishDecode(IoSession session, ProtocolDecoderOutput out)
308                 throws Exception {
309             if (currentDecoder == null) {
310                 return;
311             }
312 
313             currentDecoder.finishDecode(session, out);
314         }
315 
316         public void dispose(IoSession session) throws Exception {
317             super.dispose(session);
318 
319             // ProtocolEncoder.dispose() already called disposeCodec(),
320             // so there's nothing more we need to do.
321         }
322     }
323 
324     private static class SingletonMessageEncoderFactory implements
325             MessageEncoderFactory {
326         private final MessageEncoder encoder;
327 
328         private SingletonMessageEncoderFactory(MessageEncoder encoder) {
329             if (encoder == null) {
330                 throw new NullPointerException("encoder");
331             }
332             this.encoder = encoder;
333         }
334 
335         public MessageEncoder getEncoder() {
336             return encoder;
337         }
338     }
339 
340     private static class SingletonMessageDecoderFactory implements
341             MessageDecoderFactory {
342         private final MessageDecoder decoder;
343 
344         private SingletonMessageDecoderFactory(MessageDecoder decoder) {
345             if (decoder == null) {
346                 throw new NullPointerException("decoder");
347             }
348             this.decoder = decoder;
349         }
350 
351         public MessageDecoder getDecoder() {
352             return decoder;
353         }
354     }
355 
356     private static class DefaultConstructorMessageEncoderFactory implements
357             MessageEncoderFactory {
358         private final Class encoderClass;
359 
360         private DefaultConstructorMessageEncoderFactory(Class encoderClass) {
361             if (encoderClass == null) {
362                 throw new NullPointerException("encoderClass");
363             }
364 
365             if (!MessageEncoder.class.isAssignableFrom(encoderClass)) {
366                 throw new IllegalArgumentException(
367                         "encoderClass is not assignable to MessageEncoder");
368             }
369             this.encoderClass = encoderClass;
370         }
371 
372         public MessageEncoder getEncoder() throws Exception {
373             return (MessageEncoder) encoderClass.newInstance();
374         }
375     }
376 
377     private static class DefaultConstructorMessageDecoderFactory implements
378             MessageDecoderFactory {
379         private final Class decoderClass;
380 
381         private DefaultConstructorMessageDecoderFactory(Class decoderClass) {
382             if (decoderClass == null) {
383                 throw new NullPointerException("decoderClass");
384             }
385 
386             if (!MessageDecoder.class.isAssignableFrom(decoderClass)) {
387                 throw new IllegalArgumentException(
388                         "decoderClass is not assignable to MessageDecoder");
389             }
390             this.decoderClass = decoderClass;
391         }
392 
393         public MessageDecoder getDecoder() throws Exception {
394             return (MessageDecoder) decoderClass.newInstance();
395         }
396     }
397 }