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