| OLD | NEW | 
| (Empty) |  | 
 |    1 // Copyright 2015 The Chromium Authors. All rights reserved. | 
 |    2 // Use of this source code is governed by a BSD-style license that can be | 
 |    3 // found in the LICENSE file. | 
 |    4  | 
 |    5 package org.chromium.net; | 
 |    6  | 
 |    7 import org.chromium.base.CalledByNative; | 
 |    8 import org.chromium.base.JNINamespace; | 
 |    9 import org.chromium.base.NativeClassQualifiedName; | 
 |   10  | 
 |   11 import java.nio.ByteBuffer; | 
 |   12 import java.util.concurrent.Executor; | 
 |   13  | 
 |   14 /** | 
 |   15  * Pass an upload body to a UrlRequest using an UploadDataProvider. | 
 |   16  */ | 
 |   17 @JNINamespace("cronet") | 
 |   18 final class CronetUploadDataStream implements UploadDataSink { | 
 |   19     // These are never changed, once a request starts. | 
 |   20     private final Executor mExecutor; | 
 |   21     private final UploadDataProvider mDataProvider; | 
 |   22     private final long mLength; | 
 |   23     private CronetUrlRequest mRequest; | 
 |   24  | 
 |   25     // Reusable read task, to reduce redundant memory allocation. | 
 |   26     private final Runnable mReadTask = new Runnable() { | 
 |   27         @Override | 
 |   28         public void run() { | 
 |   29             synchronized (mLock) { | 
 |   30                 if (mReading || mRewinding || mByteBuffer == null | 
 |   31                         || mUploadDataStreamDelegate == 0) { | 
 |   32                     throw new IllegalStateException( | 
 |   33                             "Unexpected readData call."); | 
 |   34                 } | 
 |   35                 mReading = true; | 
 |   36             } | 
 |   37             try { | 
 |   38                 mDataProvider.read(CronetUploadDataStream.this, mByteBuffer); | 
 |   39             } catch (Exception exception) { | 
 |   40                 onError(exception); | 
 |   41             } | 
 |   42         } | 
 |   43     }; | 
 |   44  | 
 |   45     // ByteBuffer created in the native code and passed to | 
 |   46     // UploadDataProvider for reading. It is only valid from the | 
 |   47     // call to mDataProvider.read until onError or onReadSucceeded. | 
 |   48     private ByteBuffer mByteBuffer = null; | 
 |   49  | 
 |   50     // Lock that protects all subsequent variables. The delegate has to be | 
 |   51     // protected to ensure safe shutdown, mReading and mRewinding are protected | 
 |   52     // to robustly detect getting read/rewind results more often than expected. | 
 |   53     private final Object mLock = new Object(); | 
 |   54  | 
 |   55     // Native adapter delegate object, owned by the CronetUploadDataStream. | 
 |   56     // It's only deleted after the native UploadDataStream object is destroyed. | 
 |   57     // All access to the delegate is synchronized, for safe usage and cleanup. | 
 |   58     private long mUploadDataStreamDelegate = 0; | 
 |   59  | 
 |   60     private boolean mReading = false; | 
 |   61     private boolean mRewinding = false; | 
 |   62     private boolean mDestroyDelegatePostponed = false; | 
 |   63  | 
 |   64     /** | 
 |   65      * Constructs a CronetUploadDataStream. | 
 |   66      * @param dataProvider the UploadDataProvider to read data from. | 
 |   67      * @param executor the Executor to execute UploadDataProvider tasks. | 
 |   68      */ | 
 |   69     public CronetUploadDataStream(UploadDataProvider dataProvider, | 
 |   70             Executor executor) { | 
 |   71         mExecutor = executor; | 
 |   72         mDataProvider = dataProvider; | 
 |   73         mLength = mDataProvider.getLength(); | 
 |   74         if (mLength < 0) { | 
 |   75             // TODO(mmenke):  Add tests and remove this line. | 
 |   76             throw new IllegalArgumentException( | 
 |   77                     "Chunked uploads not supported."); | 
 |   78         } | 
 |   79     } | 
 |   80  | 
 |   81     /** | 
 |   82      * Called by native code to make the UploadDataProvider read data into | 
 |   83      * {@code byteBuffer}. | 
 |   84      */ | 
 |   85     @SuppressWarnings("unused") | 
 |   86     @CalledByNative | 
 |   87     void readData(ByteBuffer byteBuffer) { | 
 |   88         mByteBuffer = byteBuffer; | 
 |   89         mExecutor.execute(mReadTask); | 
 |   90     } | 
 |   91  | 
 |   92     // TODO(mmenke): Consider implementing a cancel method. | 
 |   93     // currently wait for any pending read to complete. | 
 |   94  | 
 |   95     /** | 
 |   96      * Called by native code to make the UploadDataProvider rewind upload data. | 
 |   97      */ | 
 |   98     @SuppressWarnings("unused") | 
 |   99     @CalledByNative | 
 |  100     void rewind() { | 
 |  101         Runnable task = new Runnable() { | 
 |  102             @Override | 
 |  103             public void run() { | 
 |  104                 synchronized (mLock) { | 
 |  105                     if (mReading || mRewinding | 
 |  106                             || mUploadDataStreamDelegate == 0) { | 
 |  107                         throw new IllegalStateException( | 
 |  108                                 "Unexpected rewind call."); | 
 |  109                     } | 
 |  110                     mRewinding = true; | 
 |  111                 } | 
 |  112                 try { | 
 |  113                     mDataProvider.rewind(CronetUploadDataStream.this); | 
 |  114                 } catch (Exception exception) { | 
 |  115                     onError(exception); | 
 |  116                 } | 
 |  117             } | 
 |  118         }; | 
 |  119         mExecutor.execute(task); | 
 |  120     } | 
 |  121  | 
 |  122     /** | 
 |  123      * Called by native code to destroy the native adapter delegate, when the | 
 |  124      * adapter is destroyed. | 
 |  125      */ | 
 |  126     @SuppressWarnings("unused") | 
 |  127     @CalledByNative | 
 |  128     void onAdapterDestroyed() { | 
 |  129         Runnable task = new Runnable() { | 
 |  130             @Override | 
 |  131             public void run() { | 
 |  132                 destroyDelegate(); | 
 |  133             } | 
 |  134         }; | 
 |  135  | 
 |  136         mExecutor.execute(task); | 
 |  137     } | 
 |  138  | 
 |  139     /** | 
 |  140      * Helper method called when an exception occurred. This method resets | 
 |  141      * states and propagates the error to the request. | 
 |  142      */ | 
 |  143     private void onError(Exception exception) { | 
 |  144         synchronized (mLock) { | 
 |  145             if (!mReading && !mRewinding) { | 
 |  146                 throw new IllegalStateException( | 
 |  147                         "There is no read or rewind in progress."); | 
 |  148             } | 
 |  149             mReading = false; | 
 |  150             mRewinding = false; | 
 |  151             mByteBuffer = null; | 
 |  152             destroyDelegateIfPostponed(); | 
 |  153         } | 
 |  154  | 
 |  155         // Just fail the request - simpler to fail directly, and | 
 |  156         // UploadDataStream only supports failing during initialization, not | 
 |  157         // while reading. This should be safe, even if we deleted the adapter, | 
 |  158         // because in that case, the request has already been cancelled. | 
 |  159         mRequest.onUploadException(exception); | 
 |  160     } | 
 |  161  | 
 |  162     @Override | 
 |  163     public void onReadSucceeded(boolean lastChunk) { | 
 |  164         synchronized (mLock) { | 
 |  165             if (!mReading) { | 
 |  166                 throw new IllegalStateException("Non-existent read succeeded."); | 
 |  167             } | 
 |  168             if (lastChunk && mLength >= 0) { | 
 |  169                 throw new IllegalArgumentException( | 
 |  170                         "Non-chunked upload can't have last chunk"); | 
 |  171             } | 
 |  172             int bytesRead = mByteBuffer.position(); | 
 |  173  | 
 |  174             mByteBuffer = null; | 
 |  175             mReading = false; | 
 |  176  | 
 |  177             destroyDelegateIfPostponed(); | 
 |  178             // Request may been canceled already. | 
 |  179             if (mUploadDataStreamDelegate == 0) { | 
 |  180                 return; | 
 |  181             } | 
 |  182             nativeOnReadSucceeded(mUploadDataStreamDelegate, bytesRead, | 
 |  183                     lastChunk); | 
 |  184         } | 
 |  185     } | 
 |  186  | 
 |  187     @Override | 
 |  188     public void onReadError(Exception exception) { | 
 |  189         synchronized (mLock) { | 
 |  190             if (!mReading) { | 
 |  191                 throw new IllegalStateException("Non-existent read failed."); | 
 |  192             } | 
 |  193             onError(exception); | 
 |  194         } | 
 |  195     } | 
 |  196  | 
 |  197     @Override | 
 |  198     public void onRewindSucceeded() { | 
 |  199         synchronized (mLock) { | 
 |  200             if (!mRewinding) { | 
 |  201                 throw new IllegalStateException( | 
 |  202                         "Non-existent rewind succeeded."); | 
 |  203             } | 
 |  204             mRewinding = false; | 
 |  205             // Request may been canceled already. | 
 |  206             if (mUploadDataStreamDelegate == 0) { | 
 |  207                 return; | 
 |  208             } | 
 |  209             nativeOnRewindSucceeded(mUploadDataStreamDelegate); | 
 |  210         } | 
 |  211     } | 
 |  212  | 
 |  213     @Override | 
 |  214     public void onRewindError(Exception exception) { | 
 |  215         synchronized (mLock) { | 
 |  216             if (!mRewinding) { | 
 |  217                 throw new IllegalStateException("Non-existent rewind failed."); | 
 |  218             } | 
 |  219             onError(exception); | 
 |  220         } | 
 |  221     } | 
 |  222  | 
 |  223     /** | 
 |  224      * The delegate is owned by the CronetUploadDataStream, so it can be | 
 |  225      * destroyed safely when there is no pending read; however, destruction is | 
 |  226      * initiated by the destruction of the native UploadDataStream. | 
 |  227      */ | 
 |  228     private void destroyDelegate() { | 
 |  229         synchronized (mLock) { | 
 |  230             if (mReading) { | 
 |  231                 // Wait for the read to complete before destroy the delegate. | 
 |  232                 mDestroyDelegatePostponed = true; | 
 |  233                 return; | 
 |  234             } | 
 |  235             if (mUploadDataStreamDelegate == 0) { | 
 |  236                 return; | 
 |  237             } | 
 |  238             nativeDestroyDelegate(mUploadDataStreamDelegate); | 
 |  239             mUploadDataStreamDelegate = 0; | 
 |  240         } | 
 |  241     } | 
 |  242  | 
 |  243     /** | 
 |  244      * Destroy the native delegate if the destruction is postponed due to a | 
 |  245      * pending read, which has since completed. Caller needs to be on executor | 
 |  246      * thread. | 
 |  247      */ | 
 |  248     private void destroyDelegateIfPostponed() { | 
 |  249         synchronized (mLock) { | 
 |  250             if (mReading) { | 
 |  251                 throw new IllegalStateException( | 
 |  252                         "Method should not be called when read has not completed
     ."); | 
 |  253             } | 
 |  254             if (mDestroyDelegatePostponed) { | 
 |  255                 destroyDelegate(); | 
 |  256             } | 
 |  257         } | 
 |  258     } | 
 |  259  | 
 |  260     /** | 
 |  261      * Creates native objects and attaches them to the underlying request | 
 |  262      * adapter object. | 
 |  263      * TODO(mmenke):  If more types of native upload streams are needed, create | 
 |  264      * an interface with just this method, to minimize CronetURLRequest's | 
 |  265      * dependencies on each upload stream type. | 
 |  266      */ | 
 |  267     void attachToRequest(CronetUrlRequest request, long requestAdapter) { | 
 |  268         mRequest = request; | 
 |  269         mUploadDataStreamDelegate = | 
 |  270                 nativeAttachUploadDataToRequest(requestAdapter, mLength); | 
 |  271     } | 
 |  272  | 
 |  273     /** | 
 |  274      * Creates a native UploadDataStreamDelegate and UploadDataStreamAdapter | 
 |  275      * for testing. | 
 |  276      * @return the address of the native CronetUploadDataStreamAdapter object. | 
 |  277      */ | 
 |  278     public long createAdapterForTesting() { | 
 |  279         mUploadDataStreamDelegate = nativeCreateDelegateForTesting(); | 
 |  280         return nativeCreateAdapterForTesting(mLength, mUploadDataStreamDelegate)
     ; | 
 |  281     } | 
 |  282  | 
 |  283     // Native methods are implemented in upload_data_stream_adapter.cc. | 
 |  284  | 
 |  285     private native long nativeAttachUploadDataToRequest(long urlRequestAdapter, | 
 |  286             long length); | 
 |  287  | 
 |  288     private native long nativeCreateDelegateForTesting(); | 
 |  289  | 
 |  290     private native long nativeCreateAdapterForTesting(long length, | 
 |  291             long delegate); | 
 |  292  | 
 |  293     @NativeClassQualifiedName("CronetUploadDataStreamDelegate") | 
 |  294     private native void nativeOnReadSucceeded(long nativePtr, | 
 |  295             int bytesRead, boolean finalChunk); | 
 |  296  | 
 |  297     @NativeClassQualifiedName("CronetUploadDataStreamDelegate") | 
 |  298     private native void nativeOnRewindSucceeded(long nativePtr); | 
 |  299  | 
 |  300     private static native void nativeDestroyDelegate( | 
 |  301             long uploadDataStreamDelegate); | 
 |  302 } | 
| OLD | NEW |