OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / obex / javax / obex / ClientOperation.java
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.OutputStream;
38 import java.io.DataInputStream;
39 import java.io.DataOutputStream;
40 import java.io.ByteArrayOutputStream;
41
42 /**
43  * This class implements the <code>Operation</code> interface. It will read and
44  * write data via puts and gets.
45  * @hide
46  */
47 public final class ClientOperation implements Operation, BaseStream {
48
49     private ClientSession mParent;
50
51     private boolean mInputOpen;
52
53     private PrivateInputStream mPrivateInput;
54
55     private boolean mPrivateInputOpen;
56
57     private PrivateOutputStream mPrivateOutput;
58
59     private boolean mPrivateOutputOpen;
60
61     private String mExceptionMessage;
62
63     private int mMaxPacketSize;
64
65     private boolean mOperationDone;
66
67     private boolean mGetOperation;
68
69     private HeaderSet mRequestHeader;
70
71     private HeaderSet mReplyHeader;
72
73     private boolean mEndOfBodySent;
74
75     /**
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
83      */
84     public ClientOperation(int maxSize, ClientSession p, HeaderSet header, boolean type)
85             throws IOException {
86
87         mParent = p;
88         mEndOfBodySent = false;
89         mInputOpen = true;
90         mOperationDone = false;
91         mMaxPacketSize = maxSize;
92         mGetOperation = type;
93
94         mPrivateInputOpen = false;
95         mPrivateOutputOpen = false;
96         mPrivateInput = null;
97         mPrivateOutput = null;
98
99         mReplyHeader = new HeaderSet();
100
101         mRequestHeader = new HeaderSet();
102
103         int[] headerList = header.getHeaderList();
104
105         if (headerList != null) {
106
107             for (int i = 0; i < headerList.length; i++) {
108                 mRequestHeader.setHeader(headerList[i], header.getHeader(headerList[i]));
109             }
110         }
111
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);
116         }
117
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);
122
123         }
124     }
125
126     /**
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
129      * object.
130      * @throws IOException if the transaction has already ended or if an OBEX
131      *         server called this method
132      */
133     public synchronized void abort() throws IOException {
134         ensureOpen();
135         //no compatible with sun-ri
136         if ((mOperationDone) && (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE)) {
137             throw new IOException("Operation has already ended");
138         }
139
140         mExceptionMessage = "Operation aborted";
141         if ((!mOperationDone) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
142             mOperationDone = true;
143             /*
144              * Since we are not sending any headers or returning any headers then
145              * we just need to write and read the same bytes
146              */
147             mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null);
148
149             if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) {
150                 throw new IOException("Invalid response code from server");
151             }
152
153             mExceptionMessage = null;
154         }
155
156         close();
157     }
158
159     /**
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>
167      *         object
168      */
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();
174         }
175
176         return mReplyHeader.responseCode;
177     }
178
179     /**
180      * This method will always return <code>null</code>
181      * @return <code>null</code>
182      */
183     public String getEncoding() {
184         return null;
185     }
186
187     /**
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
193      */
194     public String getType() {
195         try {
196             return (String)mReplyHeader.getHeader(HeaderSet.TYPE);
197         } catch (IOException e) {
198             return null;
199         }
200     }
201
202     /**
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
205      * is returned.
206      * @return the content length of the resource that this connection's URL
207      *         references, or -1 if the content length is not known
208      */
209     public long getLength() {
210         try {
211             Long temp = (Long)mReplyHeader.getHeader(HeaderSet.LENGTH);
212
213             if (temp == null) {
214                 return -1;
215             } else {
216                 return temp.longValue();
217             }
218         } catch (IOException e) {
219             return -1;
220         }
221     }
222
223     /**
224      * Open and return an input stream for a connection.
225      * @return an input stream
226      * @throws IOException if an I/O error occurs
227      */
228     public InputStream openInputStream() throws IOException {
229
230         ensureOpen();
231
232         if (mPrivateInputOpen)
233             throw new IOException("no more input streams available");
234         if (mGetOperation) {
235             // send the GET request here
236             validateConnection();
237         } else {
238             if (mPrivateInput == null) {
239                 mPrivateInput = new PrivateInputStream(this);
240             }
241         }
242
243         mPrivateInputOpen = true;
244
245         return mPrivateInput;
246     }
247
248     /**
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
252      */
253     public DataInputStream openDataInputStream() throws IOException {
254         return new DataInputStream(openInputStream());
255     }
256
257     /**
258      * Open and return an output stream for a connection.
259      * @return an output stream
260      * @throws IOException if an I/O error occurs
261      */
262     public OutputStream openOutputStream() throws IOException {
263
264         ensureOpen();
265         ensureNotDone();
266
267         if (mPrivateOutputOpen)
268             throw new IOException("no more output streams available");
269
270         if (mPrivateOutput == null) {
271             // there are 3 bytes operation headers and 3 bytes body headers //
272             mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
273         }
274
275         mPrivateOutputOpen = true;
276
277         return mPrivateOutput;
278     }
279
280     public int getMaxPacketSize() {
281         return mMaxPacketSize - 6 - getHeaderLength();
282     }
283
284     public int getHeaderLength() {
285         // OPP may need it
286         byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
287         return headerArray.length;
288     }
289
290     /**
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
294      */
295     public DataOutputStream openDataOutputStream() throws IOException {
296         return new DataOutputStream(openOutputStream());
297     }
298
299     /**
300      * Closes the connection and ends the transaction
301      * @throws IOException if the operation has already ended or is closed
302      */
303     public void close() throws IOException {
304         mInputOpen = false;
305         mPrivateInputOpen = false;
306         mPrivateOutputOpen = false;
307         mParent.setRequestInactive();
308     }
309
310     /**
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
313      * or retrieved.
314      * @return the headers received during this <code>Operation</code>
315      * @throws IOException if this <code>Operation</code> has been closed
316      */
317     public HeaderSet getReceivedHeader() throws IOException {
318         ensureOpen();
319
320         return mReplyHeader;
321     }
322
323     /**
324      * Specifies the headers that should be sent in the next OBEX message that
325      * is sent.
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>
332      */
333     public void sendHeaders(HeaderSet headers) throws IOException {
334         ensureOpen();
335         if (mOperationDone) {
336             throw new IOException("Operation has already exchanged all data");
337         }
338
339         if (headers == null) {
340             throw new IOException("Headers may not be null");
341         }
342
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]));
347             }
348         }
349     }
350
351     /**
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
355      */
356     public void ensureNotDone() throws IOException {
357         if (mOperationDone) {
358             throw new IOException("Operation has completed");
359         }
360     }
361
362     /**
363      * Verifies that the connection is open and no exceptions should be thrown.
364      * @throws IOException if an exception needs to be thrown
365      */
366     public void ensureOpen() throws IOException {
367         mParent.ensureOpen();
368
369         if (mExceptionMessage != null) {
370             throw new IOException(mExceptionMessage);
371         }
372         if (!mInputOpen) {
373             throw new IOException("Operation has already ended");
374         }
375     }
376
377     /**
378      * Verifies that the connection is open and the proper data has been read.
379      * @throws IOException if an IO error occurs
380      */
381     private void validateConnection() throws IOException {
382         ensureOpen();
383
384         // to sure only one privateInput object exist.
385         if (mPrivateInput == null) {
386             startProcessing();
387         }
388     }
389
390     /**
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
396      */
397     private boolean sendRequest(int opCode) throws IOException {
398         boolean returnValue = false;
399         ByteArrayOutputStream out = new ByteArrayOutputStream();
400         int bodyLength = -1;
401         byte[] headerArray = ObexHelper.createHeader(mRequestHeader, true);
402         if (mPrivateOutput != null) {
403             bodyLength = mPrivateOutput.size();
404         }
405
406         /*
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
412          * the body.
413          */
414         if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) {
415             int end = 0;
416             int start = 0;
417             // split & send the headerArray in multiple packets.
418
419             while (end != headerArray.length) {
420                 //split the headerArray
421                 end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize
422                         - ObexHelper.BASE_PACKET_LENGTH);
423                 // can not split 
424                 if (end == -1) {
425                     mOperationDone = true;
426                     abort();
427                     mExceptionMessage = "Header larger then can be sent in a packet";
428                     mInputOpen = false;
429
430                     if (mPrivateInput != null) {
431                         mPrivateInput.close();
432                     }
433
434                     if (mPrivateOutput != null) {
435                         mPrivateOutput.close();
436                     }
437                     throw new IOException("OBEX Packet exceeds max packet size");
438                 }
439
440                 byte[] sendHeader = new byte[end - start];
441                 System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
442                 if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) {
443                     return false;
444                 }
445
446                 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
447                     return false;
448                 }
449
450                 start = end;
451             }
452
453             if (bodyLength > 0) {
454                 return true;
455             } else {
456                 return false;
457             }
458         } else {
459             out.write(headerArray);
460         }
461
462         if (bodyLength > 0) {
463             /*
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
467              */
468             if (bodyLength > (mMaxPacketSize - headerArray.length - 6)) {
469                 returnValue = true;
470
471                 bodyLength = mMaxPacketSize - headerArray.length - 6;
472             }
473
474             byte[] body = mPrivateOutput.readBytes(bodyLength);
475
476             /*
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)
480              */
481             if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
482                     && ((opCode & 0x80) != 0)) {
483                 out.write(0x49);
484                 mEndOfBodySent = true;
485             } else {
486                 out.write(0x48);
487             }
488
489             bodyLength += 3;
490             out.write((byte)(bodyLength >> 8));
491             out.write((byte)bodyLength);
492
493             if (body != null) {
494                 out.write(body);
495             }
496         }
497
498         if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
499             // only 0x82 or 0x83 can send 0x49
500             if ((opCode & 0x80) == 0) {
501                 out.write(0x48);
502             } else {
503                 out.write(0x49);
504                 mEndOfBodySent = true;
505
506             }
507
508             bodyLength = 3;
509             out.write((byte)(bodyLength >> 8));
510             out.write((byte)bodyLength);
511         }
512
513         if (out.size() == 0) {
514             if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) {
515                 return false;
516             }
517             return returnValue;
518         }
519         if ((out.size() > 0)
520                 && (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) {
521             return false;
522         }
523
524         // send all of the output data in 0x48, 
525         // send 0x49 with empty body
526         if ((mPrivateOutput != null) && (mPrivateOutput.size() > 0))
527             returnValue = true;
528
529         return returnValue;
530     }
531
532     /**
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
537      */
538     private synchronized void startProcessing() throws IOException {
539
540         if (mPrivateInput == null) {
541             mPrivateInput = new PrivateInputStream(this);
542         }
543         boolean more = true;
544
545         if (mGetOperation) {
546             if (!mOperationDone) {
547                 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
548                 while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
549                     more = sendRequest(0x03);
550                 }
551
552                 if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
553                     mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
554                 }
555                 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
556                     mOperationDone = true;
557                 }
558             }
559         } else {
560
561             if (!mOperationDone) {
562                 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
563                 while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
564                     more = sendRequest(0x02);
565
566                 }
567             }
568
569             if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
570                 mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput);
571             }
572
573             if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
574                 mOperationDone = true;
575             }
576         }
577     }
578
579     /**
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
584      *        output stream
585      * @throws IOException if an IO error occurs
586      */
587     public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
588             throws IOException {
589
590         if (mGetOperation) {
591             if ((inStream) && (!mOperationDone)) {
592                 // to deal with inputstream in get operation
593                 mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
594                 /*
595                   * Determine if that was not the last packet in the operation
596                   */
597                 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
598                     mOperationDone = true;
599                 }
600
601                 return true;
602
603             } else if ((!inStream) && (!mOperationDone)) {
604                 // to deal with outputstream in get operation
605
606                 if (mPrivateInput == null) {
607                     mPrivateInput = new PrivateInputStream(this);
608                 }
609                 sendRequest(0x03);
610                 return true;
611
612             } else if (mOperationDone) {
613                 return false;
614             }
615
616         } else {
617             if ((!inStream) && (!mOperationDone)) {
618                 // to deal with outputstream in put operation
619                 if (mReplyHeader.responseCode == -1) {
620                     mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
621                 }
622                 sendRequest(0x02);
623                 return true;
624             } else if ((inStream) && (!mOperationDone)) {
625                 // How to deal with inputstream  in put operation ?
626                 return false;
627
628             } else if (mOperationDone) {
629                 return false;
630             }
631
632         }
633         return false;
634     }
635
636     /**
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
641      */
642     public void streamClosed(boolean inStream) throws IOException {
643         if (!mGetOperation) {
644             if ((!inStream) && (!mOperationDone)) {
645                 // to deal with outputstream in put operation
646
647                 boolean more = true;
648
649                 if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) {
650                     byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
651                     if (headerArray.length <= 0)
652                         more = false;
653                 }
654                 // If have not sent any data so send  all now
655                 if (mReplyHeader.responseCode == -1) {
656                     mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
657                 }
658
659                 while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
660                     more = sendRequest(0x02);
661                 }
662
663                 /*
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
666                  * loop.
667                  */
668                 while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
669
670                     sendRequest(0x82);
671                 }
672                 mOperationDone = true;
673             } else if ((inStream) && (mOperationDone)) {
674                 // how to deal with input stream in put stream ?
675                 mOperationDone = true;
676             }
677         } else {
678             if ((inStream) && (!mOperationDone)) {
679
680                 // to deal with inputstream in get operation
681                 // Have not sent any data so send it all now
682
683                 if (mReplyHeader.responseCode == -1) {
684                     mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
685                 }
686
687                 while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
688                     if (!sendRequest(0x83)) {
689                         break;
690                     }
691                 }
692                 while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
693                     mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
694                 }
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.
699
700                 boolean more = true;
701
702                 if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) {
703                     byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
704                     if (headerArray.length <= 0)
705                         more = false;
706                 }
707
708                 if (mPrivateInput == null) {
709                     mPrivateInput = new PrivateInputStream(this);
710                 }
711                 if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0))
712                     more = false;
713
714                 mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
715                 while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
716                     more = sendRequest(0x03);
717                 }
718                 sendRequest(0x83);
719                 //                parent.sendRequest(0x83, null, replyHeaders, privateInput);
720                 if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
721                     mOperationDone = true;
722                 }
723             }
724         }
725     }
726 }