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;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  
25  import org.apache.mina.common.ByteBuffer;
26  import org.apache.mina.common.IoFilterAdapter;
27  import org.apache.mina.common.IoSession;
28  import org.apache.mina.common.WriteFuture;
29  import org.apache.mina.util.Queue;
30  
31  /**
32   * Filter implementation which makes it possible to write {@link InputStream}
33   * objects directly using {@link IoSession#write(Object)}. When an 
34   * {@link InputStream} is written to a session this filter will read the bytes
35   * from the stream into {@link ByteBuffer} objects and write those buffers
36   * to the next filter. When end of stream has been reached this filter will
37   * call {@link NextFilter#messageSent(IoSession, Object)} using the original
38   * {@link InputStream} written to the session and notifies 
39   * {@link org.apache.mina.common.WriteFuture} on the 
40   * original {@link org.apache.mina.common.IoFilter.WriteRequest}.
41   * <p>
42   * This filter will ignore written messages which aren't {@link InputStream}
43   * instances. Such messages will be passed to the next filter directly.
44   * </p>
45   * <p>
46   * NOTE: this filter does not close the stream after all data from stream
47   * has been written. The {@link org.apache.mina.common.IoHandler} should take
48   * care of that in its 
49   * {@link org.apache.mina.common.IoHandler#messageSent(IoSession, Object)} 
50   * callback.
51   * </p>
52   * 
53   * @author The Apache Directory Project (mina-dev@directory.apache.org)
54   * @version $Rev: 555855 $, $Date: 2007-07-13 12:19:00 +0900 (Fri, 13 Jul 2007) $
55   */
56  public class StreamWriteFilter extends IoFilterAdapter {
57      /**
58       * The default buffer size this filter uses for writing.
59       */
60      public static final int DEFAULT_STREAM_BUFFER_SIZE = 4096;
61  
62      /**
63       * The attribute name used when binding the {@link InputStream} to the session.
64       */
65      public static final String CURRENT_STREAM = StreamWriteFilter.class
66              .getName()
67              + ".stream";
68  
69      protected static final String WRITE_REQUEST_QUEUE = StreamWriteFilter.class
70              .getName()
71              + ".queue";
72  
73      protected static final String INITIAL_WRITE_FUTURE = StreamWriteFilter.class
74              .getName()
75              + ".future";
76  
77      private int writeBufferSize = DEFAULT_STREAM_BUFFER_SIZE;
78  
79      public void filterWrite(NextFilter nextFilter, IoSession session,
80              WriteRequest writeRequest) throws Exception {
81          // If we're already processing a stream we need to queue the WriteRequest.
82          if (session.getAttribute(CURRENT_STREAM) != null) {
83              Queue queue = (Queue) session.getAttribute(WRITE_REQUEST_QUEUE);
84              if (queue == null) {
85                  queue = new Queue();
86                  session.setAttribute(WRITE_REQUEST_QUEUE, queue);
87              }
88              queue.push(writeRequest);
89              return;
90          }
91  
92          Object message = writeRequest.getMessage();
93  
94          if (message instanceof InputStream) {
95  
96              InputStream inputStream = (InputStream) message;
97  
98              ByteBuffer byteBuffer = getNextByteBuffer(inputStream);
99              if (byteBuffer == null) {
100                 // End of stream reached.
101                 writeRequest.getFuture().setWritten(true);
102                 nextFilter.messageSent(session, message);
103             } else {
104                 session.setAttribute(CURRENT_STREAM, inputStream);
105                 session.setAttribute(INITIAL_WRITE_FUTURE, writeRequest
106                         .getFuture());
107 
108                 nextFilter.filterWrite(session, new WriteRequest(byteBuffer));
109             }
110 
111         } else {
112             nextFilter.filterWrite(session, writeRequest);
113         }
114     }
115 
116     public void messageSent(NextFilter nextFilter, IoSession session,
117             Object message) throws Exception {
118         InputStream inputStream = (InputStream) session
119                 .getAttribute(CURRENT_STREAM);
120 
121         if (inputStream == null) {
122             nextFilter.messageSent(session, message);
123         } else {
124             ByteBuffer byteBuffer = getNextByteBuffer(inputStream);
125 
126             if (byteBuffer == null) {
127                 // End of stream reached.
128                 session.removeAttribute(CURRENT_STREAM);
129                 WriteFuture writeFuture = (WriteFuture) session
130                         .removeAttribute(INITIAL_WRITE_FUTURE);
131 
132                 // Write queued WriteRequests.
133                 Queue queue = (Queue) session
134                         .removeAttribute(WRITE_REQUEST_QUEUE);
135                 if (queue != null) {
136                     WriteRequest wr = (WriteRequest) queue.pop();
137                     while (wr != null) {
138                         filterWrite(nextFilter, session, wr);
139                         wr = (WriteRequest) queue.pop();
140                     }
141                 }
142 
143                 writeFuture.setWritten(true);
144                 nextFilter.messageSent(session, inputStream);
145             } else {
146                 nextFilter.filterWrite(session, new WriteRequest(byteBuffer));
147             }
148         }
149     }
150 
151     private ByteBuffer getNextByteBuffer(InputStream is) throws IOException {
152         byte[] bytes = new byte[writeBufferSize];
153 
154         int off = 0;
155         int n = 0;
156         while (off < bytes.length
157                 && (n = is.read(bytes, off, bytes.length - off)) != -1) {
158             off += n;
159         }
160 
161         if (n == -1 && off == 0) {
162             return null;
163         }
164 
165         ByteBuffer buffer = ByteBuffer.wrap(bytes, 0, off);
166 
167         return buffer;
168     }
169 
170     /**
171      * Returns the size of the write buffer in bytes. Data will be read from the 
172      * stream in chunks of this size and then written to the next filter.
173      * 
174      * @return the write buffer size.
175      */
176     public int getWriteBufferSize() {
177         return writeBufferSize;
178     }
179 
180     /**
181      * Sets the size of the write buffer in bytes. Data will be read from the 
182      * stream in chunks of this size and then written to the next filter.
183      * 
184      * @throws IllegalArgumentException if the specified size is &lt; 1.
185      */
186     public void setWriteBufferSize(int writeBufferSize) {
187         if (writeBufferSize < 1) {
188             throw new IllegalArgumentException(
189                     "writeBufferSize must be at least 1");
190         }
191         this.writeBufferSize = writeBufferSize;
192     }
193 
194 }