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
    79      private static final Class<?>[] EMPTY_PARAMS = new Class[0];
    80
    81      public DemuxingProtocolDecoder() {
    82          // Do nothing
    83      }
    84
    85      public void addMessageDecoder(Class<? extends MessageDecoder> decoderClass) {
    86          if (decoderClass == null) {
    87              throw new IllegalArgumentException("decoderClass");
    88          }
    89
    90          try {
    91              decoderClass.getConstructor(EMPTY_PARAMS);
    92          } catch (NoSuchMethodException e) {
    93              throw new IllegalArgumentException("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("Unregisterable type: " + decoderClass);
    104         }
    105     }
    106
    107     public void addMessageDecoder(MessageDecoder decoder) {
    108         addMessageDecoder(new SingletonMessageDecoderFactory(decoder));
    109     }
    110
    111     public void addMessageDecoder(MessageDecoderFactory factory) {
    112         if (factory == null) {
    113             throw new IllegalArgumentException("factory");
    114         }
    115         MessageDecoderFactory[] decoderFactories = this.decoderFactories;
    116         MessageDecoderFactory[] newDecoderFactories = new MessageDecoderFactory[decoderFactories.length + 1];
    117         System.arraycopy(decoderFactories, 0, newDecoderFactories, 0, decoderFactories.length);
    118         newDecoderFactories[decoderFactories.length] = factory;
    119         this.decoderFactories = newDecoderFactories;
    120     }
    121
    122     /**
    123      * {@inheritDoc}
    124      */
    125     @Override
    126     protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
    127         State state = getState(session);
    128
    129         if (state.currentDecoder == null) {
    130             MessageDecoder[] decoders = state.decoders;
    131             int undecodables = 0;
    132
    133             for (int i = decoders.length - 1; i >= 0; i--) {
    134                 MessageDecoder decoder = decoders[i];
    135                 int limit = in.limit();
    136                 int pos = in.position();
    137
    138                 MessageDecoderResult result;
    139
    140                 try {
    141                     result = decoder.decodable(session, in);
    142                 } finally {
    143                     in.position(pos);
    144                     in.limit(limit);
    145                 }
    146
    147                 if (result == MessageDecoder.OK) {
    148                     state.currentDecoder = decoder;
    149                     break;
    150                 } else if (result == MessageDecoder.NOT_OK) {
    151                     undecodables++;
    152                 } else if (result != MessageDecoder.NEED_DATA) {
    153                     throw new IllegalStateException("Unexpected decode result (see your decodable()): " + result);
    154                 }
    155             }
    156
    157             if (undecodables == decoders.length) {
    158                 // Throw an exception if all decoders cannot decode data.
    159                 String dump = in.getHexDump();
    160                 in.position(in.limit()); // Skip data
    161                 ProtocolDecoderException e = new ProtocolDecoderException("No appropriate message decoder: " + dump);
    162                 e.setHexdump(dump);
    163                 throw e;
    164             }
    165
    166             if (state.currentDecoder == null) {
    167                 // Decoder is not determined yet (i.e. we need more data)
    168                 return false;
    169             }
    170         }
    171
    172         try {
    173             MessageDecoderResult result = state.currentDecoder.decode(session, in, out);
    174             if (result == MessageDecoder.OK) {
    175                 state.currentDecoder = null;
    176                 return true;
    177             } else if (result == MessageDecoder.NEED_DATA) {
    178                 return false;
    179             } else if (result == MessageDecoder.NOT_OK) {
    180                 state.currentDecoder = null;
    181                 throw new ProtocolDecoderException("Message decoder returned NOT_OK.");
    182             } else {
    183                 state.currentDecoder = null;
    184                 throw new IllegalStateException("Unexpected decode result (see your decode()): " + result);
    185             }
    186         } catch (Exception e) {
    187             state.currentDecoder = null;
    188             throw e;
    189         }
    190     }
    191
    192     /**
    193      * {@inheritDoc}
    194      */
    195     @Override
    196     public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
    197         super.finishDecode(session, out);
    198         State state = getState(session);
    199         MessageDecoder currentDecoder = state.currentDecoder;
    200         if (currentDecoder == null) {
    201             return;
    202         }
    203
    204         currentDecoder.finishDecode(session, out);
    205     }
    206
    207     /**
    208      * {@inheritDoc}
    209      */
    210     @Override
    211     public void dispose(IoSession session) throws Exception {
    212         super.dispose(session);
    213         session.removeAttribute(STATE);
    214     }
    215
    216     private State getState(IoSession session) throws Exception {
    217         State state = (State) session.getAttribute(STATE);
    218
    219         if (state == null) {
    220             state = new State();
    221             State oldState = (State) session.setAttributeIfAbsent(STATE, state);
    222
    223             if (oldState != null) {
    224                 state = oldState;
    225             }
    226         }
    227
    228         return state;
    229     }
    230
    231     private class State {
    232         private final MessageDecoder[] decoders;
    233
    234         private MessageDecoder currentDecoder;
    235
    236         private State() throws Exception {
    237             MessageDecoderFactory[] decoderFactories = DemuxingProtocolDecoder.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
    245     private static class SingletonMessageDecoderFactory implements MessageDecoderFactory {
    246         private final MessageDecoder decoder;
    247
    248         private SingletonMessageDecoderFactory(MessageDecoder decoder) {
    249             if (decoder == null) {
    250                 throw new IllegalArgumentException("decoder");
    251             }
    252             this.decoder = decoder;
    253         }
    254
    255         public MessageDecoder getDecoder() {
    256             return decoder;
    257         }
    258     }
    259
    260     private static class DefaultConstructorMessageDecoderFactory implements MessageDecoderFactory {
    261         private final Class<?> decoderClass;
    262
    263         private DefaultConstructorMessageDecoderFactory(Class<?> decoderClass) {
    264             if (decoderClass == null) {
    265                 throw new IllegalArgumentException("decoderClass");
    266             }
    267
    268             if (!MessageDecoder.class.isAssignableFrom(decoderClass)) {
    269                 throw new IllegalArgumentException("decoderClass is not assignable to MessageDecoder");
    270             }
    271             this.decoderClass = decoderClass;
    272         }
    273
    274         public MessageDecoder getDecoder() throws Exception {
    275             return (MessageDecoder) decoderClass.newInstance();
    276         }
    277     }
    278 }