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