From aede0f894d4060379aa34308a7f8d18ba0050000 Mon Sep 17 00:00:00 2001 From: bachinger Date: Fri, 10 Mar 2017 11:38:51 -0800 Subject: [PATCH] Propagate updates of default header fields of the HttpDataSource.BaseFactory to HttpDataSource instances. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=149780233 --- .../ext/cronet/CronetDataSourceTest.java | 3 +- .../ext/cronet/CronetDataSource.java | 54 +++--- .../ext/cronet/CronetDataSourceFactory.java | 6 +- .../ext/okhttp/OkHttpDataSource.java | 34 ++-- .../ext/okhttp/OkHttpDataSourceFactory.java | 7 +- .../upstream/DefaultDataSource.java | 2 +- .../upstream/DefaultHttpDataSource.java | 38 +++-- .../DefaultHttpDataSourceFactory.java | 5 +- .../exoplayer2/upstream/HttpDataSource.java | 155 ++++++++++++++---- 9 files changed, 204 insertions(+), 100 deletions(-) diff --git a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java index 31def44d36..246e23e172 100644 --- a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java +++ b/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java @@ -118,7 +118,8 @@ public final class CronetDataSourceTest { TEST_CONNECT_TIMEOUT_MS, TEST_READ_TIMEOUT_MS, true, // resetTimeoutOnRedirects - mockClock)); + mockClock, + null)); when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true); when(mockCronetEngine.newUrlRequestBuilder( anyString(), any(UrlRequest.Callback.class), any(Executor.class))) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index f6202c6e1e..4f15a6eabc 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.ByteBuffer; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -98,7 +97,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou private final int connectTimeoutMs; private final int readTimeoutMs; private final boolean resetTimeoutOnRedirects; - private final Map requestProperties; + private final RequestProperties defaultRequestProperties; + private final RequestProperties requestProperties; private final ConditionVariable operation; private final Clock clock; @@ -136,7 +136,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou public CronetDataSource(CronetEngine cronetEngine, Executor executor, Predicate contentTypePredicate, TransferListener listener) { this(cronetEngine, executor, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS, - DEFAULT_READ_TIMEOUT_MILLIS, false); + DEFAULT_READ_TIMEOUT_MILLIS, false, null); } /** @@ -149,17 +149,20 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou * @param connectTimeoutMs The connection timeout, in milliseconds. * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param defaultRequestProperties The default request properties to be used. */ public CronetDataSource(CronetEngine cronetEngine, Executor executor, Predicate contentTypePredicate, TransferListener listener, - int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects) { + int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, + RequestProperties defaultRequestProperties) { this(cronetEngine, executor, contentTypePredicate, listener, connectTimeoutMs, - readTimeoutMs, resetTimeoutOnRedirects, new SystemClock()); + readTimeoutMs, resetTimeoutOnRedirects, new SystemClock(), defaultRequestProperties); } /* package */ CronetDataSource(CronetEngine cronetEngine, Executor executor, Predicate contentTypePredicate, TransferListener listener, - int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, Clock clock) { + int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, Clock clock, + RequestProperties defaultRequestProperties) { this.cronetEngine = Assertions.checkNotNull(cronetEngine); this.executor = Assertions.checkNotNull(executor); this.contentTypePredicate = contentTypePredicate; @@ -168,7 +171,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou this.readTimeoutMs = readTimeoutMs; this.resetTimeoutOnRedirects = resetTimeoutOnRedirects; this.clock = Assertions.checkNotNull(clock); - requestProperties = new HashMap<>(); + this.defaultRequestProperties = defaultRequestProperties; + requestProperties = new RequestProperties(); operation = new ConditionVariable(); } @@ -176,23 +180,17 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou @Override public void setRequestProperty(String name, String value) { - synchronized (requestProperties) { - requestProperties.put(name, value); - } + requestProperties.set(name, value); } @Override public void clearRequestProperty(String name) { - synchronized (requestProperties) { - requestProperties.remove(name); - } + requestProperties.remove(name); } @Override public void clearAllRequestProperties() { - synchronized (requestProperties) { - requestProperties.clear(); - } + requestProperties.clear(); } @Override @@ -421,16 +419,24 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(dataSpec.uri.toString(), this, executor); // Set the headers. - synchronized (requestProperties) { - if (dataSpec.postBody != null && dataSpec.postBody.length != 0 - && !requestProperties.containsKey(CONTENT_TYPE)) { - throw new OpenException("POST request with non-empty body must set Content-Type", dataSpec, - Status.IDLE); - } - for (Entry headerEntry : requestProperties.entrySet()) { - requestBuilder.addHeader(headerEntry.getKey(), headerEntry.getValue()); + boolean isContentTypeHeaderSet = false; + if (defaultRequestProperties != null) { + for (Entry headerEntry : defaultRequestProperties.getSnapshot().entrySet()) { + String key = headerEntry.getKey(); + isContentTypeHeaderSet = isContentTypeHeaderSet || CONTENT_TYPE.equals(key); + requestBuilder.addHeader(key, headerEntry.getValue()); } } + Map requestPropertiesSnapshot = requestProperties.getSnapshot(); + for (Entry headerEntry : requestPropertiesSnapshot.entrySet()) { + String key = headerEntry.getKey(); + isContentTypeHeaderSet = isContentTypeHeaderSet || CONTENT_TYPE.equals(key); + requestBuilder.addHeader(key, headerEntry.getValue()); + } + if (dataSpec.postBody != null && dataSpec.postBody.length != 0 && !isContentTypeHeaderSet) { + throw new OpenException("POST request with non-empty body must set Content-Type", dataSpec, + Status.IDLE); + } // Set the Range header. if (currentDataSpec.position != 0 || currentDataSpec.length != C.LENGTH_UNSET) { StringBuilder rangeValue = new StringBuilder(); diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java index 3df901ce59..db560305a7 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.ext.cronet; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory; import com.google.android.exoplayer2.upstream.HttpDataSource.Factory; import com.google.android.exoplayer2.upstream.TransferListener; @@ -68,9 +69,10 @@ public final class CronetDataSourceFactory extends BaseFactory { } @Override - protected CronetDataSource createDataSourceInternal() { + protected CronetDataSource createDataSourceInternal(HttpDataSource.RequestProperties + defaultRequestProperties) { return new CronetDataSource(cronetEngine, executor, contentTypePredicate, transferListener, - connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects); + connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects, null, defaultRequestProperties); } } diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 90a4728933..47850c0637 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -27,7 +27,6 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -51,7 +50,8 @@ public class OkHttpDataSource implements HttpDataSource { private final Predicate contentTypePredicate; private final TransferListener listener; private final CacheControl cacheControl; - private final HashMap requestProperties; + private final RequestProperties defaultRequestProperties; + private final RequestProperties requestProperties; private DataSpec dataSpec; private Response response; @@ -87,7 +87,7 @@ public class OkHttpDataSource implements HttpDataSource { */ public OkHttpDataSource(Call.Factory callFactory, String userAgent, Predicate contentTypePredicate, TransferListener listener) { - this(callFactory, userAgent, contentTypePredicate, listener, null); + this(callFactory, userAgent, contentTypePredicate, listener, null, null); } /** @@ -99,16 +99,19 @@ public class OkHttpDataSource implements HttpDataSource { * {@link #open(DataSpec)}. * @param listener An optional listener. * @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header. + * @param defaultRequestProperties The optional default {@link RequestProperties} to be sent to + * the server as HTTP headers on every request. */ public OkHttpDataSource(Call.Factory callFactory, String userAgent, Predicate contentTypePredicate, TransferListener listener, - CacheControl cacheControl) { + CacheControl cacheControl, RequestProperties defaultRequestProperties) { this.callFactory = Assertions.checkNotNull(callFactory); this.userAgent = Assertions.checkNotEmpty(userAgent); this.contentTypePredicate = contentTypePredicate; this.listener = listener; this.cacheControl = cacheControl; - this.requestProperties = new HashMap<>(); + this.defaultRequestProperties = defaultRequestProperties; + this.requestProperties = new RequestProperties(); } @Override @@ -125,24 +128,18 @@ public class OkHttpDataSource implements HttpDataSource { public void setRequestProperty(String name, String value) { Assertions.checkNotNull(name); Assertions.checkNotNull(value); - synchronized (requestProperties) { - requestProperties.put(name, value); - } + requestProperties.set(name, value); } @Override public void clearRequestProperty(String name) { Assertions.checkNotNull(name); - synchronized (requestProperties) { - requestProperties.remove(name); - } + requestProperties.remove(name); } @Override public void clearAllRequestProperties() { - synchronized (requestProperties) { - requestProperties.clear(); - } + requestProperties.clear(); } @Override @@ -268,11 +265,14 @@ public class OkHttpDataSource implements HttpDataSource { if (cacheControl != null) { builder.cacheControl(cacheControl); } - synchronized (requestProperties) { - for (Map.Entry property : requestProperties.entrySet()) { - builder.addHeader(property.getKey(), property.getValue()); + if (defaultRequestProperties != null) { + for (Map.Entry property : defaultRequestProperties.getSnapshot().entrySet()) { + builder.header(property.getKey(), property.getValue()); } } + for (Map.Entry property : requestProperties.getSnapshot().entrySet()) { + builder.header(property.getKey(), property.getValue()); + } if (!(position == 0 && length == C.LENGTH_UNSET)) { String rangeRequest = "bytes=" + position + "-"; if (length != C.LENGTH_UNSET) { diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java index 8cbe295fa4..5228065db1 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.ext.okhttp; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory; import com.google.android.exoplayer2.upstream.HttpDataSource.Factory; import com.google.android.exoplayer2.upstream.TransferListener; @@ -59,8 +60,10 @@ public final class OkHttpDataSourceFactory extends BaseFactory { } @Override - protected OkHttpDataSource createDataSourceInternal() { - return new OkHttpDataSource(callFactory, userAgent, null, listener, cacheControl); + protected OkHttpDataSource createDataSourceInternal( + HttpDataSource.RequestProperties defaultRequestProperties) { + return new OkHttpDataSource(callFactory, userAgent, null, listener, cacheControl, + defaultRequestProperties); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java index ae6f1e0691..9d13383a56 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java @@ -81,7 +81,7 @@ public final class DefaultDataSource implements DataSource { boolean allowCrossProtocolRedirects) { this(context, listener, new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis, - readTimeoutMillis, allowCrossProtocolRedirects)); + readTimeoutMillis, allowCrossProtocolRedirects, null)); } /** diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index ca0fda9399..599cdddeb9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -32,7 +32,6 @@ import java.net.HttpURLConnection; import java.net.NoRouteToHostException; import java.net.ProtocolException; import java.net.URL; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -44,8 +43,8 @@ import java.util.regex.Pattern; *

* By default this implementation will not follow cross-protocol redirects (i.e. redirects from * HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the - * {@link #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean)} - * constructor and passing {@code true} as the final argument. + * {@link #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean, + * RequestProperties)} constructor and passing {@code true} as the second last argument. */ public class DefaultHttpDataSource implements HttpDataSource { @@ -70,7 +69,8 @@ public class DefaultHttpDataSource implements HttpDataSource { private final int readTimeoutMillis; private final String userAgent; private final Predicate contentTypePredicate; - private final HashMap requestProperties; + private final RequestProperties defaultRequestProperties; + private final RequestProperties requestProperties; private final TransferListener listener; private DataSpec dataSpec; @@ -121,7 +121,8 @@ public class DefaultHttpDataSource implements HttpDataSource { public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate, TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis) { - this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false); + this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false, + null); } /** @@ -137,17 +138,21 @@ public class DefaultHttpDataSource implements HttpDataSource { * as an infinite timeout. Pass {@link #DEFAULT_READ_TIMEOUT_MILLIS} to use the default value. * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP * to HTTPS and vice versa) are enabled. + * @param defaultRequestProperties The default request properties to be sent to the server as + * HTTP headers or {@code null} if not required. */ public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate, TransferListener listener, int connectTimeoutMillis, - int readTimeoutMillis, boolean allowCrossProtocolRedirects) { + int readTimeoutMillis, boolean allowCrossProtocolRedirects, + RequestProperties defaultRequestProperties) { this.userAgent = Assertions.checkNotEmpty(userAgent); this.contentTypePredicate = contentTypePredicate; this.listener = listener; - this.requestProperties = new HashMap<>(); + this.requestProperties = new RequestProperties(); this.connectTimeoutMillis = connectTimeoutMillis; this.readTimeoutMillis = readTimeoutMillis; this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; + this.defaultRequestProperties = defaultRequestProperties; } @Override @@ -164,24 +169,18 @@ public class DefaultHttpDataSource implements HttpDataSource { public void setRequestProperty(String name, String value) { Assertions.checkNotNull(name); Assertions.checkNotNull(value); - synchronized (requestProperties) { - requestProperties.put(name, value); - } + requestProperties.set(name, value); } @Override public void clearRequestProperty(String name) { Assertions.checkNotNull(name); - synchronized (requestProperties) { - requestProperties.remove(name); - } + requestProperties.remove(name); } @Override public void clearAllRequestProperties() { - synchronized (requestProperties) { - requestProperties.clear(); - } + requestProperties.clear(); } @Override @@ -394,11 +393,14 @@ public class DefaultHttpDataSource implements HttpDataSource { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(connectTimeoutMillis); connection.setReadTimeout(readTimeoutMillis); - synchronized (requestProperties) { - for (Map.Entry property : requestProperties.entrySet()) { + if (defaultRequestProperties != null) { + for (Map.Entry property : defaultRequestProperties.getSnapshot().entrySet()) { connection.setRequestProperty(property.getKey(), property.getValue()); } } + for (Map.Entry property : requestProperties.getSnapshot().entrySet()) { + connection.setRequestProperty(property.getKey(), property.getValue()); + } if (!(position == 0 && length == C.LENGTH_UNSET)) { String rangeRequest = "bytes=" + position + "-"; if (length != C.LENGTH_UNSET) { diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java index 615eb4df97..7679307c5a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java @@ -76,9 +76,10 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory { } @Override - protected DefaultHttpDataSource createDataSourceInternal() { + protected DefaultHttpDataSource createDataSourceInternal( + HttpDataSource.RequestProperties defaultRequestProperties) { return new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis, - readTimeoutMillis, allowCrossProtocolRedirects); + readTimeoutMillis, allowCrossProtocolRedirects, getDefaultRequestProperties()); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index 1f88828f28..73572d999f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -17,12 +17,12 @@ package com.google.android.exoplayer2.upstream; import android.support.annotation.IntDef; import android.text.TextUtils; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,84 +41,173 @@ public interface HttpDataSource extends DataSource { HttpDataSource createDataSource(); /** - * Sets a default request header for {@link HttpDataSource} instances subsequently created by - * the factory. Previously created instances are not affected. + * Gets the default request properties used by all {@link HttpDataSource}s created by the + * factory. Changes to the properties will be reflected in any future requests made by + * {@link HttpDataSource}s created by the factory. * + * @return The default request properties of the factory. + */ + RequestProperties getDefaultRequestProperties(); + + /** + * Sets a default request header for {@link HttpDataSource} instances created by the factory. + * + * @deprecated Use {@link #getDefaultRequestProperties} instead. * @param name The name of the header field. * @param value The value of the field. */ + @Deprecated void setDefaultRequestProperty(String name, String value); /** - * Clears a default request header for {@link HttpDataSource} instances subsequently created by - * the factory. Previously created instances are not affected. + * Clears a default request header for {@link HttpDataSource} instances created by the factory. * + * @deprecated Use {@link #getDefaultRequestProperties} instead. * @param name The name of the header field. */ + @Deprecated void clearDefaultRequestProperty(String name); /** - * Clears all default request header for all {@link HttpDataSource} instances subsequently - * created by the factory. Previously created instances are not affected. + * Clears all default request headers for all {@link HttpDataSource} instances created by the + * factory. + * + * @deprecated Use {@link #getDefaultRequestProperties} instead. */ + @Deprecated void clearAllDefaultRequestProperties(); } + /** + * Stores HTTP request properties (aka HTTP headers) and provides methods to modify the headers + * in a thread safe way to avoid the potential of creating snapshots of an inconsistent or + * unintended state. + */ + final class RequestProperties { + + private final Map requestProperties; + private Map requestPropertiesSnapshot; + + public RequestProperties() { + requestProperties = new HashMap<>(); + } + + /** + * Sets the specified property {@code value} for the specified {@code name}. If a property for + * this name previously existed, the old value is replaced by the specified value. + * + * @param name The name of the request property. + * @param value The value of the request property. + */ + public synchronized void set(String name, String value) { + requestPropertiesSnapshot = null; + requestProperties.put(name, value); + } + + /** + * Sets the keys and values contained in the map. If a property previously existed, the old + * value is replaced by the specified value. If a property previously existed and is not in the + * map, the property is left unchanged. + * + * @param properties The request properties. + */ + public synchronized void set(Map properties) { + requestPropertiesSnapshot = null; + requestProperties.putAll(properties); + } + + /** + * Removes all properties previously existing and sets the keys and values of the map. + * + * @param properties The request properties. + */ + public synchronized void clearAndSet(Map properties) { + requestPropertiesSnapshot = null; + requestProperties.clear(); + requestProperties.putAll(properties); + } + + /** + * Removes a request property by name. + * + * @param name The name of the request property to remove. + */ + public synchronized void remove(String name) { + requestPropertiesSnapshot = null; + requestProperties.remove(name); + } + + /** + * Clears all request properties. + */ + public synchronized void clear() { + requestPropertiesSnapshot = null; + requestProperties.clear(); + } + + /** + * Gets a snapshot of the request properties. + * + * @return A snapshot of the request properties. + */ + public synchronized Map getSnapshot() { + if (requestPropertiesSnapshot == null) { + requestPropertiesSnapshot = Collections.unmodifiableMap(new HashMap<>(requestProperties)); + } + return requestPropertiesSnapshot; + } + + } + /** * Base implementation of {@link Factory} that sets default request properties. */ abstract class BaseFactory implements Factory { - private final HashMap requestProperties; + private final RequestProperties defaultRequestProperties; public BaseFactory() { - requestProperties = new HashMap<>(); + defaultRequestProperties = new RequestProperties(); } @Override public final HttpDataSource createDataSource() { - HttpDataSource dataSource = createDataSourceInternal(); - synchronized (requestProperties) { - for (Map.Entry property : requestProperties.entrySet()) { - dataSource.setRequestProperty(property.getKey(), property.getValue()); - } - } - return dataSource; + return createDataSourceInternal(defaultRequestProperties); } + @Override + public RequestProperties getDefaultRequestProperties() { + return defaultRequestProperties; + } + + @Deprecated @Override public final void setDefaultRequestProperty(String name, String value) { - Assertions.checkNotNull(name); - Assertions.checkNotNull(value); - synchronized (requestProperties) { - requestProperties.put(name, value); - } + defaultRequestProperties.set(name, value); } + @Deprecated @Override public final void clearDefaultRequestProperty(String name) { - Assertions.checkNotNull(name); - synchronized (requestProperties) { - requestProperties.remove(name); - } + defaultRequestProperties.remove(name); } + @Deprecated @Override public final void clearAllDefaultRequestProperties() { - synchronized (requestProperties) { - requestProperties.clear(); - } + defaultRequestProperties.clear(); } /** - * Called by {@link #createDataSource()} to create a {@link HttpDataSource} instance without - * default request properties set. Default request properties will be set by - * {@link #createDataSource()} before the instance is returned. + * Called by {@link #createDataSource()} to create a {@link HttpDataSource} instance. * - * @return A {@link HttpDataSource} instance without default request properties set. + * @param defaultRequestProperties The default {@code RequestProperties} to be used by the + * {@link HttpDataSource} instance. + * @return A {@link HttpDataSource} instance. */ - protected abstract HttpDataSource createDataSourceInternal(); + protected abstract HttpDataSource createDataSourceInternal(RequestProperties + defaultRequestProperties); }