2 * Copyright (c) 2008-2009, Motorola, Inc.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.io.DataInputStream;
39 import java.io.DataOutputStream;
40 import java.io.ByteArrayOutputStream;
43 * This class implements the <code>Operation</code> interface. It will read and
44 * write data via puts and gets.
47 public final class ClientOperation implements Operation, BaseStream {
49 private ClientSession mParent;
51 private boolean mInputOpen;
53 private PrivateInputStream mPrivateInput;
55 private boolean mPrivateInputOpen;
57 private PrivateOutputStream mPrivateOutput;
59 private boolean mPrivateOutputOpen;
61 private String mExceptionMessage;
63 private int mMaxPacketSize;
65 private boolean mOperationDone;
67 private boolean mGetOperation;
69 private HeaderSet mRequestHeader;
71 private HeaderSet mReplyHeader;
73 private boolean mEndOfBodySent;
76 * Creates new OperationImpl to read and write data to a server
77 * @param maxSize the maximum packet size
78 * @param p the parent to this object
79 * @param type <code>true</code> if this is a get request;
80 * <code>false</code. if this is a put request
81 * @param header the header to set in the initial request
82 * @throws IOException if the an IO error occurred
84 public ClientOperation(int maxSize, ClientSession p, HeaderSet header, boolean type)
88 mEndOfBodySent = false;
90 mOperationDone = false;
91 mMaxPacketSize = maxSize;
94 mPrivateInputOpen = false;
95 mPrivateOutputOpen = false;
97 mPrivateOutput = null;
99 mReplyHeader = new HeaderSet();
101 mRequestHeader = new HeaderSet();
103 int[] headerList = header.getHeaderList();
105 if (headerList != null) {
107 for (int i = 0; i < headerList.length; i++) {
108 mRequestHeader.setHeader(headerList[i], header.getHeader(headerList[i]));
112 if ((header).mAuthChall != null) {
113 mRequestHeader.mAuthChall = new byte[(header).mAuthChall.length];
114 System.arraycopy((header).mAuthChall, 0, mRequestHeader.mAuthChall, 0,
115 (header).mAuthChall.length);
118 if ((header).mAuthResp != null) {
119 mRequestHeader.mAuthResp = new byte[(header).mAuthResp.length];
120 System.arraycopy((header).mAuthResp, 0, mRequestHeader.mAuthResp, 0,
121 (header).mAuthResp.length);
127 * Sends an ABORT message to the server. By calling this method, the
128 * corresponding input and output streams will be closed along with this
130 * @throws IOException if the transaction has already ended or if an OBEX
131 * server called this method
133 public synchronized void abort() throws IOException {
135 //no compatible with sun-ri
136 if ((mOperationDone) && (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE)) {
137 throw new IOException("Operation has already ended");
140 mExceptionMessage = "Operation aborted";
141 if ((!mOperationDone) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
142 mOperationDone = true;
144 * Since we are not sending any headers or returning any headers then
145 * we just need to write and read the same bytes
147 mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null);
149 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) {
150 throw new IOException("Invalid response code from server");
153 mExceptionMessage = null;
160 * Retrieves the response code retrieved from the server. Response codes are
161 * defined in the <code>ResponseCodes</code> interface.
162 * @return the response code retrieved from the server
163 * @throws IOException if an error occurred in the transport layer during
164 * the transaction; if this method is called on a
165 * <code>HeaderSet</code> object created by calling
166 * <code>createHeaderSet</code> in a <code>ClientSession</code>
169 public synchronized int getResponseCode() throws IOException {
170 //avoid dup validateConnection
171 if ((mReplyHeader.responseCode == -1)
172 || (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
173 validateConnection();
176 return mReplyHeader.responseCode;
180 * This method will always return <code>null</code>
181 * @return <code>null</code>
183 public String getEncoding() {
188 * Returns the type of content that the resource connected to is providing.
189 * E.g. if the connection is via HTTP, then the value of the content-type
190 * header field is returned.
191 * @return the content type of the resource that the URL references, or
192 * <code>null</code> if not known
194 public String getType() {
196 return (String)mReplyHeader.getHeader(HeaderSet.TYPE);
197 } catch (IOException e) {
203 * Returns the length of the content which is being provided. E.g. if the
204 * connection is via HTTP, then the value of the content-length header field
206 * @return the content length of the resource that this connection's URL
207 * references, or -1 if the content length is not known
209 public long getLength() {
211 Long temp = (Long)mReplyHeader.getHeader(HeaderSet.LENGTH);
216 return temp.longValue();
218 } catch (IOException e) {
224 * Open and return an input stream for a connection.
225 * @return an input stream
226 * @throws IOException if an I/O error occurs
228 public InputStream openInputStream() throws IOException {
232 if (mPrivateInputOpen)
233 throw new IOException("no more input streams available");
235 // send the GET request here
236 validateConnection();
238 if (mPrivateInput == null) {
239 mPrivateInput = new PrivateInputStream(this);
243 mPrivateInputOpen = true;
245 return mPrivateInput;
249 * Open and return a data input stream for a connection.
250 * @return an input stream
251 * @throws IOException if an I/O error occurs
253 public DataInputStream openDataInputStream() throws IOException {
254 return new DataInputStream(openInputStream());
258 * Open and return an output stream for a connection.
259 * @return an output stream
260 * @throws IOException if an I/O error occurs
262 public OutputStream openOutputStream() throws IOException {
267 if (mPrivateOutputOpen)
268 throw new IOException("no more output streams available");
270 if (mPrivateOutput == null) {
271 // there are 3 bytes operation headers and 3 bytes body headers //
272 mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
275 mPrivateOutputOpen = true;
277 return mPrivateOutput;
280 public int getMaxPacketSize() {
281 return mMaxPacketSize - 6 - getHeaderLength();
284 public int getHeaderLength() {
286 byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
287 return headerArray.length;
291 * Open and return a data output stream for a connection.
292 * @return an output stream
293 * @throws IOException if an I/O error occurs
295 public DataOutputStream openDataOutputStream() throws IOException {
296 return new DataOutputStream(openOutputStream());
300 * Closes the connection and ends the transaction
301 * @throws IOException if the operation has already ended or is closed
303 public void close() throws IOException {
305 mPrivateInputOpen = false;
306 mPrivateOutputOpen = false;
307 mParent.setRequestInactive();
311 * Returns the headers that have been received during the operation.
312 * Modifying the object returned has no effect on the headers that are sent
314 * @return the headers received during this <code>Operation</code>
315 * @throws IOException if this <code>Operation</code> has been closed
317 public HeaderSet getReceivedHeader() throws IOException {
324 * Specifies the headers that should be sent in the next OBEX message that
326 * @param headers the headers to send in the next message
327 * @throws IOException if this <code>Operation</code> has been closed or the
328 * transaction has ended and no further messages will be exchanged
329 * @throws IllegalArgumentException if <code>headers</code> was not created
330 * by a call to <code>ServerRequestHandler.createHeaderSet()</code>
331 * @throws NullPointerException if <code>headers</code> is <code>null</code>
333 public void sendHeaders(HeaderSet headers) throws IOException {
335 if (mOperationDone) {
336 throw new IOException("Operation has already exchanged all data");
339 if (headers == null) {
340 throw new IOException("Headers may not be null");
343 int[] headerList = headers.getHeaderList();
344 if (headerList != null) {
345 for (int i = 0; i < headerList.length; i++) {
346 mRequestHeader.setHeader(headerList[i], headers.getHeader(headerList[i]));
352 * Verifies that additional information may be sent. In other words, the
353 * operation is not done.
354 * @throws IOException if the operation is completed
356 public void ensureNotDone() throws IOException {
357 if (mOperationDone) {
358 throw new IOException("Operation has completed");
363 * Verifies that the connection is open and no exceptions should be thrown.
364 * @throws IOException if an exception needs to be thrown
366 public void ensureOpen() throws IOException {
367 mParent.ensureOpen();
369 if (mExceptionMessage != null) {
370 throw new IOException(mExceptionMessage);
373 throw new IOException("Operation has already ended");
378 * Verifies that the connection is open and the proper data has been read.
379 * @throws IOException if an IO error occurs
381 private void validateConnection() throws IOException {
384 // to sure only one privateInput object exist.
385 if (mPrivateInput == null) {
391 * Sends a request to the client of the specified type
392 * @param opCode the request code to send to the client
393 * @return <code>true</code> if there is more data to send;
394 * <code>false</code> if there is no more data to send
395 * @throws IOException if an IO error occurs
397 private boolean sendRequest(int opCode) throws IOException {
398 boolean returnValue = false;
399 ByteArrayOutputStream out = new ByteArrayOutputStream();
401 byte[] headerArray = ObexHelper.createHeader(mRequestHeader, true);
402 if (mPrivateOutput != null) {
403 bodyLength = mPrivateOutput.size();
407 * Determine if there is space to add a body request. At present
408 * this method checks to see if there is room for at least a 17
409 * byte body header. This number needs to be at least 6 so that
410 * there is room for the header ID and length and the reply ID and
411 * length, but it is a waste of resources if we can't send much of
414 if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) {
417 // split & send the headerArray in multiple packets.
419 while (end != headerArray.length) {
420 //split the headerArray
421 end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize
422 - ObexHelper.BASE_PACKET_LENGTH);
425 mOperationDone = true;
427 mExceptionMessage = "Header larger then can be sent in a packet";
430 if (mPrivateInput != null) {
431 mPrivateInput.close();
434 if (mPrivateOutput != null) {
435 mPrivateOutput.close();
437 throw new IOException("OBEX Packet exceeds max packet size");
440 byte[] sendHeader = new byte[end - start];
441 System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
442 if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) {
446 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
453 if (bodyLength > 0) {
459 out.write(headerArray);
462 if (bodyLength > 0) {
464 * Determine if we can send the whole body or just part of
465 * the body. Remember that there is the 3 bytes for the
466 * response message and 3 bytes for the header ID and length
468 if (bodyLength > (mMaxPacketSize - headerArray.length - 6)) {
471 bodyLength = mMaxPacketSize - headerArray.length - 6;
474 byte[] body = mPrivateOutput.readBytes(bodyLength);
477 * Since this is a put request if the final bit is set or
478 * the output stream is closed we need to send the 0x49
479 * (End of Body) otherwise, we need to send 0x48 (Body)
481 if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
482 && ((opCode & 0x80) != 0)) {
484 mEndOfBodySent = true;
490 out.write((byte)(bodyLength >> 8));
491 out.write((byte)bodyLength);
498 if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
499 // only 0x82 or 0x83 can send 0x49
500 if ((opCode & 0x80) == 0) {
504 mEndOfBodySent = true;
509 out.write((byte)(bodyLength >> 8));
510 out.write((byte)bodyLength);
513 if (out.size() == 0) {
514 if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) {
520 && (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) {
524 // send all of the output data in 0x48,
525 // send 0x49 with empty body
526 if ((mPrivateOutput != null) && (mPrivateOutput.size() > 0))
533 * This method starts the processing thread results. It will send the
534 * initial request. If the response takes more then one packet, a thread
535 * will be started to handle additional requests
536 * @throws IOException if an IO error occurs
538 private synchronized void startProcessing() throws IOException {
540 if (mPrivateInput == null) {
541 mPrivateInput = new PrivateInputStream(this);
546 if (!mOperationDone) {
547 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
548 while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
549 more = sendRequest(0x03);
552 if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
553 mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
555 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
556 mOperationDone = true;
561 if (!mOperationDone) {
562 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
563 while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
564 more = sendRequest(0x02);
569 if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
570 mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput);
573 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
574 mOperationDone = true;
580 * Continues the operation since there is no data to read.
581 * @param sendEmpty <code>true</code> if the operation should send an empty
582 * packet or not send anything if there is no data to send
583 * @param inStream <code>true</code> if the stream is input stream or is
585 * @throws IOException if an IO error occurs
587 public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
591 if ((inStream) && (!mOperationDone)) {
592 // to deal with inputstream in get operation
593 mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
595 * Determine if that was not the last packet in the operation
597 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
598 mOperationDone = true;
603 } else if ((!inStream) && (!mOperationDone)) {
604 // to deal with outputstream in get operation
606 if (mPrivateInput == null) {
607 mPrivateInput = new PrivateInputStream(this);
612 } else if (mOperationDone) {
617 if ((!inStream) && (!mOperationDone)) {
618 // to deal with outputstream in put operation
619 if (mReplyHeader.responseCode == -1) {
620 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
624 } else if ((inStream) && (!mOperationDone)) {
625 // How to deal with inputstream in put operation ?
628 } else if (mOperationDone) {
637 * Called when the output or input stream is closed.
638 * @param inStream <code>true</code> if the input stream is closed;
639 * <code>false</code> if the output stream is closed
640 * @throws IOException if an IO error occurs
642 public void streamClosed(boolean inStream) throws IOException {
643 if (!mGetOperation) {
644 if ((!inStream) && (!mOperationDone)) {
645 // to deal with outputstream in put operation
649 if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) {
650 byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
651 if (headerArray.length <= 0)
654 // If have not sent any data so send all now
655 if (mReplyHeader.responseCode == -1) {
656 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
659 while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
660 more = sendRequest(0x02);
664 * According to the IrOBEX specification, after the final put, you
665 * only have a single reply to send. so we don't need the while
668 while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
672 mOperationDone = true;
673 } else if ((inStream) && (mOperationDone)) {
674 // how to deal with input stream in put stream ?
675 mOperationDone = true;
678 if ((inStream) && (!mOperationDone)) {
680 // to deal with inputstream in get operation
681 // Have not sent any data so send it all now
683 if (mReplyHeader.responseCode == -1) {
684 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
687 while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
688 if (!sendRequest(0x83)) {
692 while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
693 mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
695 mOperationDone = true;
696 } else if ((!inStream) && (!mOperationDone)) {
697 // to deal with outputstream in get operation
698 // part of the data may have been sent in continueOperation.
702 if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) {
703 byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
704 if (headerArray.length <= 0)
708 if (mPrivateInput == null) {
709 mPrivateInput = new PrivateInputStream(this);
711 if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0))
714 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
715 while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
716 more = sendRequest(0x03);
719 // parent.sendRequest(0x83, null, replyHeaders, privateInput);
720 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
721 mOperationDone = true;