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.statemachine;
    21
    22  import java.util.ArrayList;
    23  import java.util.List;
    24
    25  import org.apache.mina.core.buffer.IoBuffer;
    26  import org.apache.mina.core.filterchain.IoFilter.NextFilter;
    27  import org.apache.mina.core.session.IoSession;
    28  import org.apache.mina.filter.codec.ProtocolCodecFilter;
    29  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
    30  import org.slf4j.Logger;
    31  import org.slf4j.LoggerFactory;
    32
    33  /**
    34   * Abstract base class for decoder state machines. Calls {@link #init()} to
    35   * get the start {@link DecodingState} of the state machine. Calls
    36   * {@link #destroy()} when the state machine has reached its end state or when
    37   * the session is closed.
    38   * <p>
    39   * NOTE: The {@link ProtocolDecoderOutput} used by this class when calling
    40   * {@link DecodingState#decode(IoBuffer, ProtocolDecoderOutput)} buffers decoded
    41   * messages in a {@link List}. Once the state machine has reached its end state
    42   * this class will call {@link #finishDecode(List, ProtocolDecoderOutput)}. The
    43   * implementation will have to take care of writing the decoded messages to the
    44   * real {@link ProtocolDecoderOutput} used by the configured
    45   * {@link ProtocolCodecFilter}.
    46   * </p>
    47   *
    48   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    49   */
    50  public abstract class DecodingStateMachine implements DecodingState {
    51      private final Logger log = LoggerFactory.getLogger(DecodingStateMachine.class);
    52
    53      private final List<Object> childProducts = new ArrayList<Object>();
    54
    55      private final ProtocolDecoderOutput childOutput = new ProtocolDecoderOutput() {
    56          public void flush(NextFilter nextFilter, IoSession session) {
    57              // Do nothing
    58          }
    59
    60          public void write(Object message) {
    61              childProducts.add(message);
    62          }
    63      };
    64
    65      private DecodingState currentState;
    66
    67      private boolean initialized;
    68
    69      /**
    70       * Invoked to initialize this state machine.
    71       *
    72       * @return the start {@link DecodingState}.
    73       */
    74      protected abstract DecodingState init() throws Exception;
    75
    76      /**
    77       * Called once the state machine has reached its end.
    78       *
    79       * @param childProducts contains the messages generated by each of the
    80       *        {@link DecodingState}s which were exposed to the received data
    81       *        during the life time of this state machine.
    82       * @param out the real {@link ProtocolDecoderOutput} used by the
    83       *        {@link ProtocolCodecFilter}.
    84       * @return the next state if the state machine should resume.
    85       */
    86      protected abstract DecodingState finishDecode(List<Object> childProducts, ProtocolDecoderOutput out)
    87              throws Exception;
    88
    89      /**
    90       * Invoked to destroy this state machine once the end state has been reached
    91       * or the session has been closed.
    92       */
    93      protected abstract void destroy() throws Exception;
    94
    95      /**
    96       * {@inheritDoc}
    97       */
    98      public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception {
    99          DecodingState state = getCurrentState();
    100
    101         final int limit = in.limit();
    102         int pos = in.position();
    103
    104         try {
    105             for (;;) {
    106                 // Wait for more data if all data is consumed.
    107                 if (pos == limit) {
    108                     break;
    109                 }
    110
    111                 DecodingState oldState = state;
    112                 state = state.decode(in, childOutput);
    113
    114                 // If finished, call finishDecode
    115                 if (state == null) {
    116                     return finishDecode(childProducts, out);
    117                 }
    118
    119                 int newPos = in.position();
    120
    121                 // Wait for more data if nothing is consumed and state didn't change.
    122                 if (newPos == pos && oldState == state) {
    123                     break;
    124                 }
    125                 pos = newPos;
    126             }
    127
    128             return this;
    129         } catch (Exception e) {
    130             state = null;
    131             throw e;
    132         } finally {
    133             this.currentState = state;
    134
    135             // Destroy if decoding is finished or failed.
    136             if (state == null) {
    137                 cleanup();
    138             }
    139         }
    140     }
    141
    142     /**
    143      * {@inheritDoc}
    144      */
    145     public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception {
    146         DecodingState nextState;
    147         DecodingState state = getCurrentState();
    148         try {
    149             for (;;) {
    150                 DecodingState oldState = state;
    151                 state = state.finishDecode(childOutput);
    152                 if (state == null) {
    153                     // Finished
    154                     break;
    155                 }
    156
    157                 // Exit if state didn't change.
    158                 if (oldState == state) {
    159                     break;
    160                 }
    161             }
    162         } catch (Exception e) {
    163             state = null;
    164             log.debug("Ignoring the exception caused by a closed session.", e);
    165         } finally {
    166             this.currentState = state;
    167             nextState = finishDecode(childProducts, out);
    168             if (state == null) {
    169                 cleanup();
    170             }
    171         }
    172         return nextState;
    173     }
    174
    175     private void cleanup() {
    176         if (!initialized) {
    177             throw new IllegalStateException();
    178         }
    179
    180         initialized = false;
    181         childProducts.clear();
    182         try {
    183             destroy();
    184         } catch (Exception e2) {
    185             log.warn("Failed to destroy a decoding state machine.", e2);
    186         }
    187     }
    188
    189     private DecodingState getCurrentState() throws Exception {
    190         DecodingState state = this.currentState;
    191         if (state == null) {
    192             state = init();
    193             initialized = true;
    194         }
    195         return state;
    196     }
    197 }