Home | History | Annotate | Download | only in obex
      1 /*
      2  * Copyright (c) 2008-2009, Motorola, Inc.
      3  *
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions are met:
      8  *
      9  * - Redistributions of source code must retain the above copyright notice,
     10  * this list of conditions and the following disclaimer.
     11  *
     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.
     15  *
     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.
     19  *
     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.
     31  */
     32 
     33 package javax.obex;
     34 
     35 import java.io.IOException;
     36 import java.io.InputStream;
     37 import java.io.DataInputStream;
     38 import java.io.OutputStream;
     39 import java.io.DataOutputStream;
     40 import java.io.ByteArrayOutputStream;
     41 
     42 /**
     43  * This class implements the Operation interface for server side connections.
     44  * <P>
     45  * <STRONG>Request Codes</STRONG> There are four different request codes that
     46  * are in this class. 0x02 is a PUT request that signals that the request is not
     47  * complete and requires an additional OBEX packet. 0x82 is a PUT request that
     48  * says that request is complete. In this case, the server can begin sending the
     49  * response. The 0x03 is a GET request that signals that the request is not
     50  * finished. When the server receives a 0x83, the client is signaling the server
     51  * that it is done with its request. TODO: Extend the ClientOperation and reuse
     52  * the methods defined TODO: in that class.
     53  * @hide
     54  */
     55 public final class ServerOperation implements Operation, BaseStream {
     56 
     57     public boolean isAborted;
     58 
     59     public HeaderSet requestHeader;
     60 
     61     public HeaderSet replyHeader;
     62 
     63     public boolean finalBitSet;
     64 
     65     private InputStream mInput;
     66 
     67     private ServerSession mParent;
     68 
     69     private int mMaxPacketLength;
     70 
     71     private int mResponseSize;
     72 
     73     private boolean mClosed;
     74 
     75     private boolean mGetOperation;
     76 
     77     private PrivateInputStream mPrivateInput;
     78 
     79     private PrivateOutputStream mPrivateOutput;
     80 
     81     private boolean mPrivateOutputOpen;
     82 
     83     private String mExceptionString;
     84 
     85     private ServerRequestHandler mListener;
     86 
     87     private boolean mRequestFinished;
     88 
     89     private boolean mHasBody;
     90 
     91     /**
     92      * Creates new ServerOperation
     93      * @param p the parent that created this object
     94      * @param in the input stream to read from
     95      * @param out the output stream to write to
     96      * @param request the initial request that was received from the client
     97      * @param maxSize the max packet size that the client will accept
     98      * @param listen the listener that is responding to the request
     99      * @throws IOException if an IO error occurs
    100      */
    101     public ServerOperation(ServerSession p, InputStream in, int request, int maxSize,
    102             ServerRequestHandler listen) throws IOException {
    103 
    104         isAborted = false;
    105         mParent = p;
    106         mInput = in;
    107         mMaxPacketLength = maxSize;
    108         mClosed = false;
    109         requestHeader = new HeaderSet();
    110         replyHeader = new HeaderSet();
    111         mPrivateInput = new PrivateInputStream(this);
    112         mResponseSize = 3;
    113         mListener = listen;
    114         mRequestFinished = false;
    115         mPrivateOutputOpen = false;
    116         mHasBody = false;
    117         int bytesReceived;
    118 
    119         /*
    120          * Determine if this is a PUT request
    121          */
    122         if ((request == 0x02) || (request == 0x82)) {
    123             /*
    124              * It is a PUT request.
    125              */
    126             mGetOperation = false;
    127 
    128             /*
    129              * Determine if the final bit is set
    130              */
    131             if ((request & 0x80) == 0) {
    132                 finalBitSet = false;
    133             } else {
    134                 finalBitSet = true;
    135                 mRequestFinished = true;
    136             }
    137         } else if ((request == 0x03) || (request == 0x83)) {
    138             /*
    139              * It is a GET request.
    140              */
    141             mGetOperation = true;
    142 
    143             // For Get request, final bit set is decided by server side logic
    144             finalBitSet = false;
    145 
    146             if (request == 0x83) {
    147                 mRequestFinished = true;
    148             }
    149         } else {
    150             throw new IOException("ServerOperation can not handle such request");
    151         }
    152 
    153         int length = in.read();
    154         length = (length << 8) + in.read();
    155 
    156         /*
    157          * Determine if the packet length is larger than this device can receive
    158          */
    159         if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
    160             mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
    161             throw new IOException("Packet received was too large");
    162         }
    163 
    164         /*
    165          * Determine if any headers were sent in the initial request
    166          */
    167         if (length > 3) {
    168             byte[] data = new byte[length - 3];
    169             bytesReceived = in.read(data);
    170 
    171             while (bytesReceived != data.length) {
    172                 bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
    173             }
    174 
    175             byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
    176 
    177             if (body != null) {
    178                 mHasBody = true;
    179             }
    180 
    181             if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
    182                 mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID));
    183             } else {
    184                 mListener.setConnectionId(1);
    185             }
    186 
    187             if (requestHeader.mAuthResp != null) {
    188                 if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
    189                     mExceptionString = "Authentication Failed";
    190                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
    191                     mClosed = true;
    192                     requestHeader.mAuthResp = null;
    193                     return;
    194                 }
    195             }
    196 
    197             if (requestHeader.mAuthChall != null) {
    198                 mParent.handleAuthChall(requestHeader);
    199                 // send the  authResp to the client
    200                 replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
    201                 System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
    202                         replyHeader.mAuthResp.length);
    203                 requestHeader.mAuthResp = null;
    204                 requestHeader.mAuthChall = null;
    205 
    206             }
    207 
    208             if (body != null) {
    209                 mPrivateInput.writeBytes(body, 1);
    210             } else {
    211                 while ((!mGetOperation) && (!finalBitSet)) {
    212                     sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    213                     if (mPrivateInput.available() > 0) {
    214                         break;
    215                     }
    216                 }
    217             }
    218         }
    219 
    220         while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) {
    221             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    222             if (mPrivateInput.available() > 0) {
    223                 break;
    224             }
    225         }
    226 
    227         // wait for get request finished !!!!
    228         while (mGetOperation && !mRequestFinished) {
    229             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    230         }
    231     }
    232 
    233     public boolean isValidBody() {
    234         return mHasBody;
    235     }
    236 
    237     /**
    238      * Determines if the operation should continue or should wait. If it should
    239      * continue, this method will continue the operation.
    240      * @param sendEmpty if <code>true</code> then this will continue the
    241      *        operation even if no headers will be sent; if <code>false</code>
    242      *        then this method will only continue the operation if there are
    243      *        headers to send
    244      * @param inStream if<code>true</code> the stream is input stream, otherwise
    245      *        output stream
    246      * @return <code>true</code> if the operation was completed;
    247      *         <code>false</code> if no operation took place
    248      */
    249     public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
    250             throws IOException {
    251         if (!mGetOperation) {
    252             if (!finalBitSet) {
    253                 if (sendEmpty) {
    254                     sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    255                     return true;
    256                 } else {
    257                     if ((mResponseSize > 3) || (mPrivateOutput.size() > 0)) {
    258                         sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    259                         return true;
    260                     } else {
    261                         return false;
    262                     }
    263                 }
    264             } else {
    265                 return false;
    266             }
    267         } else {
    268             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    269             return true;
    270         }
    271     }
    272 
    273     /**
    274      * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it
    275      * will wait for a response from the client before ending.
    276      * @param type the response code to send back to the client
    277      * @return <code>true</code> if the final bit was not set on the reply;
    278      *         <code>false</code> if no reply was received because the operation
    279      *         ended, an abort was received, or the final bit was set in the
    280      *         reply
    281      * @throws IOException if an IO error occurs
    282      */
    283     public synchronized boolean sendReply(int type) throws IOException {
    284         ByteArrayOutputStream out = new ByteArrayOutputStream();
    285         int bytesReceived;
    286 
    287         long id = mListener.getConnectionId();
    288         if (id == -1) {
    289             replyHeader.mConnectionID = null;
    290         } else {
    291             replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
    292         }
    293 
    294         byte[] headerArray = ObexHelper.createHeader(replyHeader, true);
    295         int bodyLength = -1;
    296         int orginalBodyLength = -1;
    297 
    298         if (mPrivateOutput != null) {
    299             bodyLength = mPrivateOutput.size();
    300             orginalBodyLength = bodyLength;
    301         }
    302 
    303         if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketLength) {
    304 
    305             int end = 0;
    306             int start = 0;
    307 
    308             while (end != headerArray.length) {
    309                 end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketLength
    310                         - ObexHelper.BASE_PACKET_LENGTH);
    311                 if (end == -1) {
    312 
    313                     mClosed = true;
    314 
    315                     if (mPrivateInput != null) {
    316                         mPrivateInput.close();
    317                     }
    318 
    319                     if (mPrivateOutput != null) {
    320                         mPrivateOutput.close();
    321                     }
    322                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
    323                     throw new IOException("OBEX Packet exceeds max packet size");
    324                 }
    325                 byte[] sendHeader = new byte[end - start];
    326                 System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
    327 
    328                 mParent.sendResponse(type, sendHeader);
    329                 start = end;
    330             }
    331 
    332             if (bodyLength > 0) {
    333                 return true;
    334             } else {
    335                 return false;
    336             }
    337 
    338         } else {
    339             out.write(headerArray);
    340         }
    341 
    342         // For Get operation: if response code is OBEX_HTTP_OK, then this is the
    343         // last packet; so set finalBitSet to true.
    344         if (mGetOperation && type == ResponseCodes.OBEX_HTTP_OK) {
    345             finalBitSet = true;
    346         }
    347 
    348         if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
    349             if (bodyLength > 0) {
    350                 /*
    351                  * Determine if I can send the whole body or just part of
    352                  * the body.  Remember that there is the 3 bytes for the
    353                  * response message and 3 bytes for the header ID and length
    354                  */
    355                 if (bodyLength > (mMaxPacketLength - headerArray.length - 6)) {
    356                     bodyLength = mMaxPacketLength - headerArray.length - 6;
    357                 }
    358 
    359                 byte[] body = mPrivateOutput.readBytes(bodyLength);
    360 
    361                 /*
    362                  * Since this is a put request if the final bit is set or
    363                  * the output stream is closed we need to send the 0x49
    364                  * (End of Body) otherwise, we need to send 0x48 (Body)
    365                  */
    366                 if ((finalBitSet) || (mPrivateOutput.isClosed())) {
    367                     out.write(0x49);
    368                 } else {
    369                     out.write(0x48);
    370                 }
    371 
    372                 bodyLength += 3;
    373                 out.write((byte)(bodyLength >> 8));
    374                 out.write((byte)bodyLength);
    375                 out.write(body);
    376             }
    377         }
    378 
    379         if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
    380             out.write(0x49);
    381             orginalBodyLength = 3;
    382             out.write((byte)(orginalBodyLength >> 8));
    383             out.write((byte)orginalBodyLength);
    384 
    385         }
    386 
    387         mResponseSize = 3;
    388         mParent.sendResponse(type, out.toByteArray());
    389 
    390         if (type == ResponseCodes.OBEX_HTTP_CONTINUE) {
    391             int headerID = mInput.read();
    392             int length = mInput.read();
    393             length = (length << 8) + mInput.read();
    394             if ((headerID != ObexHelper.OBEX_OPCODE_PUT)
    395                     && (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL)
    396                     && (headerID != ObexHelper.OBEX_OPCODE_GET)
    397                     && (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
    398 
    399                 if (length > 3) {
    400                     byte[] temp = new byte[length - 3];
    401                     // First three bytes already read, compensating for this
    402                     bytesReceived = mInput.read(temp);
    403 
    404                     while (bytesReceived != temp.length) {
    405                         bytesReceived += mInput.read(temp, bytesReceived,
    406                                 temp.length - bytesReceived);
    407                     }
    408                 }
    409 
    410                 /*
    411                  * Determine if an ABORT was sent as the reply
    412                  */
    413                 if (headerID == ObexHelper.OBEX_OPCODE_ABORT) {
    414                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
    415                     mClosed = true;
    416                     isAborted = true;
    417                     mExceptionString = "Abort Received";
    418                     throw new IOException("Abort Received");
    419                 } else {
    420                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
    421                     mClosed = true;
    422                     mExceptionString = "Bad Request Received";
    423                     throw new IOException("Bad Request Received");
    424                 }
    425             } else {
    426 
    427                 if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
    428                     finalBitSet = true;
    429                 } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
    430                     mRequestFinished = true;
    431                 }
    432 
    433                 /*
    434                  * Determine if the packet length is larger then this device can receive
    435                  */
    436                 if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
    437                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
    438                     throw new IOException("Packet received was too large");
    439                 }
    440 
    441                 /*
    442                  * Determine if any headers were sent in the initial request
    443                  */
    444                 if (length > 3) {
    445                     byte[] data = new byte[length - 3];
    446                     bytesReceived = mInput.read(data);
    447 
    448                     while (bytesReceived != data.length) {
    449                         bytesReceived += mInput.read(data, bytesReceived, data.length
    450                                 - bytesReceived);
    451                     }
    452                     byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
    453                     if (body != null) {
    454                         mHasBody = true;
    455                     }
    456                     if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
    457                         mListener.setConnectionId(ObexHelper
    458                                 .convertToLong(requestHeader.mConnectionID));
    459                     } else {
    460                         mListener.setConnectionId(1);
    461                     }
    462 
    463                     if (requestHeader.mAuthResp != null) {
    464                         if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
    465                             mExceptionString = "Authentication Failed";
    466                             mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
    467                             mClosed = true;
    468                             requestHeader.mAuthResp = null;
    469                             return false;
    470                         }
    471                         requestHeader.mAuthResp = null;
    472                     }
    473 
    474                     if (requestHeader.mAuthChall != null) {
    475                         mParent.handleAuthChall(requestHeader);
    476                         // send the auhtResp to the client
    477                         replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
    478                         System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
    479                                 replyHeader.mAuthResp.length);
    480                         requestHeader.mAuthResp = null;
    481                         requestHeader.mAuthChall = null;
    482                     }
    483 
    484                     if (body != null) {
    485                         mPrivateInput.writeBytes(body, 1);
    486                     }
    487                 }
    488             }
    489             return true;
    490         } else {
    491             return false;
    492         }
    493     }
    494 
    495     /**
    496      * Sends an ABORT message to the server. By calling this method, the
    497      * corresponding input and output streams will be closed along with this
    498      * object.
    499      * @throws IOException if the transaction has already ended or if an OBEX
    500      *         server called this method
    501      */
    502     public void abort() throws IOException {
    503         throw new IOException("Called from a server");
    504     }
    505 
    506     /**
    507      * Returns the headers that have been received during the operation.
    508      * Modifying the object returned has no effect on the headers that are sent
    509      * or retrieved.
    510      * @return the headers received during this <code>Operation</code>
    511      * @throws IOException if this <code>Operation</code> has been closed
    512      */
    513     public HeaderSet getReceivedHeader() throws IOException {
    514         ensureOpen();
    515         return requestHeader;
    516     }
    517 
    518     /**
    519      * Specifies the headers that should be sent in the next OBEX message that
    520      * is sent.
    521      * @param headers the headers to send in the next message
    522      * @throws IOException if this <code>Operation</code> has been closed or the
    523      *         transaction has ended and no further messages will be exchanged
    524      * @throws IllegalArgumentException if <code>headers</code> was not created
    525      *         by a call to <code>ServerRequestHandler.createHeaderSet()</code>
    526      */
    527     public void sendHeaders(HeaderSet headers) throws IOException {
    528         ensureOpen();
    529 
    530         if (headers == null) {
    531             throw new IOException("Headers may not be null");
    532         }
    533 
    534         int[] headerList = headers.getHeaderList();
    535         if (headerList != null) {
    536             for (int i = 0; i < headerList.length; i++) {
    537                 replyHeader.setHeader(headerList[i], headers.getHeader(headerList[i]));
    538             }
    539 
    540         }
    541     }
    542 
    543     /**
    544      * Retrieves the response code retrieved from the server. Response codes are
    545      * defined in the <code>ResponseCodes</code> interface.
    546      * @return the response code retrieved from the server
    547      * @throws IOException if an error occurred in the transport layer during
    548      *         the transaction; if this method is called on a
    549      *         <code>HeaderSet</code> object created by calling
    550      *         <code>createHeaderSet</code> in a <code>ClientSession</code>
    551      *         object; if this is called from a server
    552      */
    553     public int getResponseCode() throws IOException {
    554         throw new IOException("Called from a server");
    555     }
    556 
    557     /**
    558      * Always returns <code>null</code>
    559      * @return <code>null</code>
    560      */
    561     public String getEncoding() {
    562         return null;
    563     }
    564 
    565     /**
    566      * Returns the type of content that the resource connected to is providing.
    567      * E.g. if the connection is via HTTP, then the value of the content-type
    568      * header field is returned.
    569      * @return the content type of the resource that the URL references, or
    570      *         <code>null</code> if not known
    571      */
    572     public String getType() {
    573         try {
    574             return (String)requestHeader.getHeader(HeaderSet.TYPE);
    575         } catch (IOException e) {
    576             return null;
    577         }
    578     }
    579 
    580     /**
    581      * Returns the length of the content which is being provided. E.g. if the
    582      * connection is via HTTP, then the value of the content-length header field
    583      * is returned.
    584      * @return the content length of the resource that this connection's URL
    585      *         references, or -1 if the content length is not known
    586      */
    587     public long getLength() {
    588         try {
    589             Long temp = (Long)requestHeader.getHeader(HeaderSet.LENGTH);
    590 
    591             if (temp == null) {
    592                 return -1;
    593             } else {
    594                 return temp.longValue();
    595             }
    596         } catch (IOException e) {
    597             return -1;
    598         }
    599     }
    600 
    601     public int getMaxPacketSize() {
    602         return mMaxPacketLength - 6 - getHeaderLength();
    603     }
    604 
    605     public int getHeaderLength() {
    606         long id = mListener.getConnectionId();
    607         if (id == -1) {
    608             replyHeader.mConnectionID = null;
    609         } else {
    610             replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
    611         }
    612 
    613         byte[] headerArray = ObexHelper.createHeader(replyHeader, false);
    614 
    615         return headerArray.length;
    616     }
    617 
    618     /**
    619      * Open and return an input stream for a connection.
    620      * @return an input stream
    621      * @throws IOException if an I/O error occurs
    622      */
    623     public InputStream openInputStream() throws IOException {
    624         ensureOpen();
    625         return mPrivateInput;
    626     }
    627 
    628     /**
    629      * Open and return a data input stream for a connection.
    630      * @return an input stream
    631      * @throws IOException if an I/O error occurs
    632      */
    633     public DataInputStream openDataInputStream() throws IOException {
    634         return new DataInputStream(openInputStream());
    635     }
    636 
    637     /**
    638      * Open and return an output stream for a connection.
    639      * @return an output stream
    640      * @throws IOException if an I/O error occurs
    641      */
    642     public OutputStream openOutputStream() throws IOException {
    643         ensureOpen();
    644 
    645         if (mPrivateOutputOpen) {
    646             throw new IOException("no more input streams available, stream already opened");
    647         }
    648 
    649         if (!mRequestFinished) {
    650             throw new IOException("no  output streams available ,request not finished");
    651         }
    652 
    653         if (mPrivateOutput == null) {
    654             mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
    655         }
    656         mPrivateOutputOpen = true;
    657         return mPrivateOutput;
    658     }
    659 
    660     /**
    661      * Open and return a data output stream for a connection.
    662      * @return an output stream
    663      * @throws IOException if an I/O error occurs
    664      */
    665     public DataOutputStream openDataOutputStream() throws IOException {
    666         return new DataOutputStream(openOutputStream());
    667     }
    668 
    669     /**
    670      * Closes the connection and ends the transaction
    671      * @throws IOException if the operation has already ended or is closed
    672      */
    673     public void close() throws IOException {
    674         ensureOpen();
    675         mClosed = true;
    676     }
    677 
    678     /**
    679      * Verifies that the connection is open and no exceptions should be thrown.
    680      * @throws IOException if an exception needs to be thrown
    681      */
    682     public void ensureOpen() throws IOException {
    683         if (mExceptionString != null) {
    684             throw new IOException(mExceptionString);
    685         }
    686         if (mClosed) {
    687             throw new IOException("Operation has already ended");
    688         }
    689     }
    690 
    691     /**
    692      * Verifies that additional information may be sent. In other words, the
    693      * operation is not done.
    694      * <P>
    695      * Included to implement the BaseStream interface only. It does not do
    696      * anything on the server side since the operation of the Operation object
    697      * is not done until after the handler returns from its method.
    698      * @throws IOException if the operation is completed
    699      */
    700     public void ensureNotDone() throws IOException {
    701     }
    702 
    703     /**
    704      * Called when the output or input stream is closed. It does not do anything
    705      * on the server side since the operation of the Operation object is not
    706      * done until after the handler returns from its method.
    707      * @param inStream <code>true</code> if the input stream is closed;
    708      *        <code>false</code> if the output stream is closed
    709      * @throws IOException if an IO error occurs
    710      */
    711     public void streamClosed(boolean inStream) throws IOException {
    712 
    713     }
    714 }
    715