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.buffer;
21  
22  import java.io.BufferedOutputStream;
23  import java.util.concurrent.ConcurrentHashMap;
24  
25  import org.apache.mina.core.buffer.IoBuffer;
26  import org.apache.mina.core.filterchain.IoFilter;
27  import org.apache.mina.core.filterchain.IoFilterAdapter;
28  import org.apache.mina.core.session.IoSession;
29  import org.apache.mina.core.write.DefaultWriteRequest;
30  import org.apache.mina.core.write.WriteRequest;
31  import org.apache.mina.filter.codec.ProtocolCodecFilter;
32  import org.apache.mina.util.LazyInitializedCacheMap;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  /**
37   * An {@link IoFilter} implementation used to buffer outgoing {@link WriteRequest} almost 
38   * like what {@link BufferedOutputStream} does. Using this filter allows to be less dependent 
39   * from network latency. It is also useful when a session is generating very small messages 
40   * too frequently and consequently generating unnecessary traffic overhead.
41   * 
42   * Please note that it should always be placed before the {@link ProtocolCodecFilter} 
43   * as it only handles {@link WriteRequest}'s carrying {@link IoBuffer} objects.
44   * 
45   * @author The Apache MINA Project (dev@mina.apache.org)
46   * @version $Rev: 689337 $, $Date: 2008-08-27 04:18:17 +0200 (Wed, 27 Aug 2008) $
47   * @since MINA 2.0.0-M2
48   * @org.apache.xbean.XBean
49   */
50  public final class BufferedWriteFilter extends IoFilterAdapter {
51      private final Logger logger = LoggerFactory
52              .getLogger(BufferedWriteFilter.class);
53  
54      /**
55       * Default buffer size value in bytes.
56       */
57      public final static int DEFAULT_BUFFER_SIZE = 8192;
58  
59      /**
60       * The buffer size allocated for each new session's buffer.
61       */
62      private int bufferSize = DEFAULT_BUFFER_SIZE;
63  
64      /**
65       * The map that matches an {@link IoSession} and it's {@link IoBuffer}
66       * buffer.
67       */
68      private final LazyInitializedCacheMap<IoSession, IoBuffer> buffersMap;
69  
70      /**
71       * Default constructor. Sets buffer size to {@link #DEFAULT_BUFFER_SIZE}
72       * bytes. Uses a default instance of {@link ConcurrentHashMap}.
73       */
74      public BufferedWriteFilter() {
75          this(DEFAULT_BUFFER_SIZE, null);
76      }
77  
78      /**
79       * Constructor which sets buffer size to <code>bufferSize</code>.Uses a default 
80       * instance of {@link ConcurrentHashMap}.
81       * 
82       * @param bufferSize the new buffer size
83       */
84      public BufferedWriteFilter(int bufferSize) {
85          this(bufferSize, null);
86      }
87  
88      /**
89       * Constructor which sets buffer size to <code>bufferSize</code>. If 
90       * <code>buffersMap</code> is null then a default instance of {@link ConcurrentHashMap} 
91       * is created else the provided instance is used.
92       * 
93       * @param bufferSize the new buffer size
94       * @param buffersMap the map to use for storing each session buffer 
95       */
96      public BufferedWriteFilter(int bufferSize,
97              LazyInitializedCacheMap<IoSession, IoBuffer> buffersMap) {
98          super();
99          this.bufferSize = bufferSize;
100         if (buffersMap == null) {
101             this.buffersMap = new LazyInitializedCacheMap<IoSession, IoBuffer>();
102         } else {
103             this.buffersMap = buffersMap;
104         }
105     }
106 
107     /**
108      * Returns buffer size.
109      */
110     public int getBufferSize() {
111         return bufferSize;
112     }
113 
114     /**
115      * Sets the buffer size but only for the newly created buffers.
116      * 
117      * @param bufferSize the new buffer size
118      */
119     public void setBufferSize(int bufferSize) {
120         this.bufferSize = bufferSize;
121     }
122 
123     /**
124      * {@inheritDoc}
125      * 
126      * @throws Exception if <code>writeRequest.message</code> isn't an
127      *                   {@link IoBuffer} instance.
128      */
129     @Override
130     public void filterWrite(NextFilter nextFilter, IoSession session,
131             WriteRequest writeRequest) throws Exception {
132 
133         Object data = writeRequest.getMessage();
134 
135         if (data instanceof IoBuffer) {
136             write(session, (IoBuffer) data);
137         } else {
138             throw new IllegalArgumentException(
139                     "This filter should only buffer IoBuffer objects");
140         }
141     }
142 
143     /**
144      * Writes an {@link IoBuffer} to the session's buffer.
145      * 
146      * @param session the session to which a write is requested
147      * @param data the data to buffer
148      */
149     private void write(IoSession session, IoBuffer data) {
150         IoBuffer dest = buffersMap.putIfAbsent(session,
151                 new IoBufferLazyInitializer(bufferSize));
152 
153         write(session, data, dest);
154     }
155 
156     /**
157      * Writes <code>data</code> {@link IoBuffer} to the <code>buf</code>
158      * {@link IoBuffer} which buffers write requests for the
159      * <code>session</code> {@ link IoSession} until buffer is full 
160      * or manually flushed.
161      * 
162      * @param session the session where buffer will be written
163      * @param data the data to buffer
164      * @param buf the buffer where data will be temporarily written 
165      */
166     private void write(IoSession session, IoBuffer data, IoBuffer buf) {
167         try {
168             int len = data.remaining();
169             if (len >= buf.capacity()) {
170                 /*
171                  * If the request length exceeds the size of the output buffer,
172                  * flush the output buffer and then write the data directly.
173                  */
174                 NextFilter nextFilter = session.getFilterChain().getNextFilter(
175                         this);
176                 internalFlush(nextFilter, session, buf);
177                 nextFilter.filterWrite(session, new DefaultWriteRequest(data));
178                 return;
179             }
180             if (len > (buf.limit() - buf.position())) {
181                 internalFlush(session.getFilterChain().getNextFilter(this),
182                         session, buf);
183             }
184             synchronized (buf) {
185                 buf.put(data);
186             }
187         } catch (Throwable e) {
188             session.getFilterChain().fireExceptionCaught(e);
189         }
190     }
191 
192     /**
193      * Internal method that actually flushes the buffered data.
194      * 
195      * @param nextFilter the {@link NextFilter} of this filter
196      * @param session the session where buffer will be written
197      * @param buf the data to write
198      * @throws Exception if a write operation fails
199      */
200     private void internalFlush(NextFilter nextFilter, IoSession session,
201             IoBuffer buf) throws Exception {
202         IoBuffer tmp = null;
203         synchronized (buf) {
204             buf.flip();
205             tmp = buf.duplicate();
206             buf.clear();
207         }
208         logger.debug("Flushing buffer: {}", tmp);
209         nextFilter.filterWrite(session, new DefaultWriteRequest(tmp));
210     }
211 
212     /**
213      * Flushes the buffered data.
214      * 
215      * @param session the session where buffer will be written
216      */
217     public void flush(IoSession session) {
218         try {
219             internalFlush(session.getFilterChain().getNextFilter(this),
220                     session, buffersMap.get(session));
221         } catch (Throwable e) {
222             session.getFilterChain().fireExceptionCaught(e);
223         }
224     }
225 
226     /**
227      * Internal method that actually frees the {@link IoBuffer} that contains
228      * the buffered data that has not been flushed.
229      * 
230      * @param session the session we operate on
231      */
232     private void free(IoSession session) {
233         IoBuffer buf = buffersMap.remove(session);
234         if (buf != null) {
235             buf.free();
236         }
237     }
238 
239     /**
240      * {@inheritDoc}
241      */
242     @Override
243     public void exceptionCaught(NextFilter nextFilter, IoSession session,
244             Throwable cause) throws Exception {
245         free(session);
246     }
247 
248     /**
249      * {@inheritDoc}
250      */
251     @Override
252     public void sessionClosed(NextFilter nextFilter, IoSession session)
253             throws Exception {
254         free(session);
255     }
256 }