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       * @throws Exception if the initialization failed
74       */
75      protected abstract DecodingState init() throws Exception;
76  
77      /**
78       * Called once the state machine has reached its end.
79       * 
80       * @param childProducts contains the messages generated by each of the 
81       *        {@link DecodingState}s which were exposed to the received data 
82       *        during the life time of this state machine.
83       * @param out the real {@link ProtocolDecoderOutput} used by the 
84       *        {@link ProtocolCodecFilter}.
85       * @return the next state if the state machine should resume.
86       * @throws Exception if the decoding end failed
87       */
88      protected abstract DecodingState finishDecode(List<Object> childProducts, ProtocolDecoderOutput out)
89              throws Exception;
90  
91      /**
92       * Invoked to destroy this state machine once the end state has been reached
93       * or the session has been closed.
94       * 
95       * @throws Exception if the destruction failed
96       */
97      protected abstract void destroy() throws Exception;
98  
99      /**
100      * {@inheritDoc}
101      */
102     public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception {
103         DecodingState state = getCurrentState();
104 
105         final int limit = in.limit();
106         int pos = in.position();
107 
108         try {
109             for (;;) {
110                 // Wait for more data if all data is consumed.
111                 if (pos == limit) {
112                     break;
113                 }
114 
115                 DecodingState oldState = state;
116                 state = state.decode(in, childOutput);
117 
118                 // If finished, call finishDecode
119                 if (state == null) {
120                     return finishDecode(childProducts, out);
121                 }
122 
123                 int newPos = in.position();
124 
125                 // Wait for more data if nothing is consumed and state didn't change.
126                 if (newPos == pos && oldState == state) {
127                     break;
128                 }
129                 pos = newPos;
130             }
131 
132             return this;
133         } catch (Exception e) {
134             state = null;
135             throw e;
136         } finally {
137             this.currentState = state;
138 
139             // Destroy if decoding is finished or failed.
140             if (state == null) {
141                 cleanup();
142             }
143         }
144     }
145 
146     /**
147      * {@inheritDoc}
148      */
149     public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception {
150         DecodingState nextState;
151         DecodingState state = getCurrentState();
152         try {
153             for (;;) {
154                 DecodingState oldState = state;
155                 state = state.finishDecode(childOutput);
156                 if (state == null) {
157                     // Finished
158                     break;
159                 }
160 
161                 // Exit if state didn't change.
162                 if (oldState == state) {
163                     break;
164                 }
165             }
166         } catch (Exception e) {
167             state = null;
168             log.debug("Ignoring the exception caused by a closed session.", e);
169         } finally {
170             this.currentState = state;
171             nextState = finishDecode(childProducts, out);
172             if (state == null) {
173                 cleanup();
174             }
175         }
176         return nextState;
177     }
178 
179     private void cleanup() {
180         if (!initialized) {
181             throw new IllegalStateException();
182         }
183 
184         initialized = false;
185         childProducts.clear();
186         try {
187             destroy();
188         } catch (Exception e2) {
189             log.warn("Failed to destroy a decoding state machine.", e2);
190         }
191     }
192 
193     private DecodingState getCurrentState() throws Exception {
194         DecodingState state = this.currentState;
195         if (state == null) {
196             state = init();
197             initialized = true;
198         }
199         return state;
200     }
201 }