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 15:16:14 +0900 (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<Class<?>, MessageEncoder> encoders = new IdentityHashMap<Class<?>, MessageEncoder>();
    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<Class<?>> 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<Class<?>> it = messageTypes.iterator();
    173                 while (it.hasNext()) {
    174                     Class<?> type = 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 = encoders.get(type);
    194             if (encoder == null) {
    195                 encoder = findEncoder(type, new IdentityHashSet<Class<?>>());
    196             }
    197
    198             return encoder;
    199         }
    200
    201         private MessageEncoder findEncoder(Class<?> type,
    202                 Set<Class<?>> triedClasses) {
    203             MessageEncoder encoder;
    204
    205             if (triedClasses.contains(type))
    206                 return null;
    207             triedClasses.add(type);
    208
    209             encoder = encoders.get(type);
    210             if (encoder == null) {
    211                 encoder = findEncoder(type, triedClasses);
    212                 if (encoder != null)
    213                     return encoder;
    214
    215                 Class<?>[] interfaces = type.getInterfaces();
    216                 for (int i = 0; i < interfaces.length; i++) {
    217                     encoder = findEncoder(interfaces[i], triedClasses);
    218                     if (encoder != null)
    219                         return encoder;
    220                 }
    221
    222                 return null;
    223             } else
    224                 return encoder;
    225         }
    226
    227         public void dispose(IoSession session) throws Exception {
    228             DemuxingProtocolCodecFactory.this.disposeCodecResources(session);
    229         }
    230     }
    231
    232     private class ProtocolDecoderImpl extends CumulativeProtocolDecoder {
    233         private final MessageDecoder[] decoders;
    234
    235         private MessageDecoder currentDecoder;
    236
    237         protected ProtocolDecoderImpl() throws Exception {
    238             MessageDecoderFactory[] decoderFactories = DemuxingProtocolCodecFactory.this.decoderFactories;
    239             decoders = new MessageDecoder[decoderFactories.length];
    240             for (int i = decoderFactories.length - 1; i >= 0; i--) {
    241                 decoders[i] = decoderFactories[i].getDecoder();
    242             }
    243         }
    244
    245         @Override
    246         protected boolean doDecode(IoSession session, ByteBuffer in,
    247                 ProtocolDecoderOutput out) throws Exception {
    248             if (currentDecoder == null) {
    249                 MessageDecoder[] decoders = this.decoders;
    250                 int undecodables = 0;
    251                 for (int i = decoders.length - 1; i >= 0; i--) {
    252                     MessageDecoder decoder = decoders[i];
    253                     int limit = in.limit();
    254                     int pos = in.position();
    255
    256                     MessageDecoderResult result;
    257                     try {
    258                         result = decoder.decodable(session, in);
    259                     } finally {
    260                         in.position(pos);
    261                         in.limit(limit);
    262                     }
    263
    264                     if (result == MessageDecoder.OK) {
    265                         currentDecoder = decoder;
    266                         break;
    267                     } else if (result == MessageDecoder.NOT_OK) {
    268                         undecodables++;
    269                     } else if (result != MessageDecoder.NEED_DATA) {
    270                         throw new IllegalStateException(
    271                                 "Unexpected decode result (see your decodable()): "
    272                                         + result);
    273                     }
    274                 }
    275
    276                 if (undecodables == decoders.length) {
    277                     // Throw an exception if all decoders cannot decode data.
    278                     String dump = in.getHexDump();
    279                     in.position(in.limit()); // Skip data
    280                     ProtocolDecoderException e = new ProtocolDecoderException(
    281                             "No appropriate message decoder: " + dump);
    282                     e.setHexdump(dump);
    283                     throw e;
    284                 }
    285
    286                 if (currentDecoder == null) {
    287                     // Decoder is not determined yet (i.e. we need more data)
    288                     return false;
    289                 }
    290             }
    291
    292             MessageDecoderResult result = currentDecoder.decode(session, in,
    293                     out);
    294             if (result == MessageDecoder.OK) {
    295                 currentDecoder = null;
    296                 return true;
    297             } else if (result == MessageDecoder.NEED_DATA) {
    298                 return false;
    299             } else if (result == MessageDecoder.NOT_OK) {
    300                 currentDecoder = null;
    301                 throw new ProtocolDecoderException(
    302                         "Message decoder returned NOT_OK.");
    303             } else {
    304                 currentDecoder = null;
    305                 throw new IllegalStateException(
    306                         "Unexpected decode result (see your decode()): "
    307                                 + result);
    308             }
    309         }
    310
    311         @Override
    312         public void finishDecode(IoSession session, ProtocolDecoderOutput out)
    313                 throws Exception {
    314             if (currentDecoder == null) {
    315                 return;
    316             }
    317
    318             currentDecoder.finishDecode(session, out);
    319         }
    320
    321         @Override
    322         public void dispose(IoSession session) throws Exception {
    323             super.dispose(session);
    324
    325             // ProtocolEncoder.dispose() already called disposeCodec(),
    326             // so there's nothing more we need to do.
    327         }
    328     }
    329
    330     private static class SingletonMessageEncoderFactory implements
    331             MessageEncoderFactory {
    332         private final MessageEncoder encoder;
    333
    334         private SingletonMessageEncoderFactory(MessageEncoder encoder) {
    335             if (encoder == null) {
    336                 throw new NullPointerException("encoder");
    337             }
    338             this.encoder = encoder;
    339         }
    340
    341         public MessageEncoder getEncoder() {
    342             return encoder;
    343         }
    344     }
    345
    346     private static class SingletonMessageDecoderFactory implements
    347             MessageDecoderFactory {
    348         private final MessageDecoder decoder;
    349
    350         private SingletonMessageDecoderFactory(MessageDecoder decoder) {
    351             if (decoder == null) {
    352                 throw new NullPointerException("decoder");
    353             }
    354             this.decoder = decoder;
    355         }
    356
    357         public MessageDecoder getDecoder() {
    358             return decoder;
    359         }
    360     }
    361
    362     private static class DefaultConstructorMessageEncoderFactory implements
    363             MessageEncoderFactory {
    364         private final Class<?> encoderClass;
    365
    366         private DefaultConstructorMessageEncoderFactory(Class<?> encoderClass) {
    367             if (encoderClass == null) {
    368                 throw new NullPointerException("encoderClass");
    369             }
    370
    371             if (!MessageEncoder.class.isAssignableFrom(encoderClass)) {
    372                 throw new IllegalArgumentException(
    373                         "encoderClass is not assignable to MessageEncoder");
    374             }
    375             this.encoderClass = encoderClass;
    376         }
    377
    378         public MessageEncoder getEncoder() throws Exception {
    379             return (MessageEncoder) encoderClass.newInstance();
    380         }
    381     }
    382
    383     private static class DefaultConstructorMessageDecoderFactory implements
    384             MessageDecoderFactory {
    385         private final Class<?> decoderClass;
    386
    387         private DefaultConstructorMessageDecoderFactory(Class<?> decoderClass) {
    388             if (decoderClass == null) {
    389                 throw new NullPointerException("decoderClass");
    390             }
    391
    392             if (!MessageDecoder.class.isAssignableFrom(decoderClass)) {
    393                 throw new IllegalArgumentException(
    394                         "decoderClass is not assignable to MessageDecoder");
    395             }
    396             this.decoderClass = decoderClass;
    397         }
    398
    399         public MessageDecoder getDecoder() throws Exception {
    400             return (MessageDecoder) decoderClass.newInstance();
    401         }
    402     }
    403 }