diff --git a/check_code b/check_code index 9f165cd07..d6ad49d97 100755 --- a/check_code +++ b/check_code @@ -53,6 +53,7 @@ LAYERS = [ 'sample', 'client.webApiHttpClient', 'client.okHttpClient', + 'client.webApiUrlConnectionClient', 'client', 'client.webApiClients', 'client.structures', diff --git a/core/src/main/java/com/backblaze/b2/client/B2AccountAuthorizerV5AuthImpl.java b/core/src/main/java/com/backblaze/b2/client/B2AccountAuthorizerV5AuthImpl.java new file mode 100644 index 000000000..0391cbe7f --- /dev/null +++ b/core/src/main/java/com/backblaze/b2/client/B2AccountAuthorizerV5AuthImpl.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017, Backblaze Inc. All Rights Reserved. + * License https://www.backblaze.com/using_b2_code.html + */ +package com.backblaze.b2.client; + +import com.backblaze.b2.client.exceptions.B2Exception; +import com.backblaze.b2.client.structures.B2AccountAuthorization; +import com.backblaze.b2.client.structures.B2Allowed; +import com.backblaze.b2.client.structures.B2AuthorizeAccountRequest; +import com.backblaze.b2.util.B2Preconditions; + +/** + * B2AccountAuthorizerV5AuthImpl is used when the user has authenticated + * with B1. + */ +public class B2AccountAuthorizerV5AuthImpl implements B2AccountAuthorizer { + private final B2AccountAuthorization b2AccountAuthorization; + + @Override + public B2AccountAuthorization authorize(B2StorageClientWebifier webifier) throws B2Exception { + return b2AccountAuthorization; + } + + public B2AccountAuthorizerV5AuthImpl(B2AccountAuthorization b2AccountAuthorization){ + this.b2AccountAuthorization = b2AccountAuthorization; + } +} diff --git a/core/src/main/java/com/backblaze/b2/client/B2StorageClientFactory.java b/core/src/main/java/com/backblaze/b2/client/B2StorageClientFactory.java index e5a3cf40d..e1734b1b0 100644 --- a/core/src/main/java/com/backblaze/b2/client/B2StorageClientFactory.java +++ b/core/src/main/java/com/backblaze/b2/client/B2StorageClientFactory.java @@ -6,6 +6,8 @@ import com.backblaze.b2.client.credentialsSources.B2Credentials; import com.backblaze.b2.client.credentialsSources.B2CredentialsFromEnvironmentSource; +import com.backblaze.b2.client.structures.B2AccountAuthorization; +import com.backblaze.b2.client.structures.B2Allowed; /** * Implementations of B2StorageClientFactory can create a B2StorageClient from a B2ClientConfig. @@ -56,4 +58,20 @@ default B2StorageClient create(String userAgent) { final B2Credentials credentials = B2CredentialsFromEnvironmentSource.build().getCredentials(); return create(credentials.getApplicationKeyId(), credentials.getApplicationKey(), userAgent); } + + /** + * If we have authenticated via other means, we can provide the complete authorization + * @param b2AccountAuthorization complete auth info normally returned by authorize_account + * @param userAgent the user agent to use when performing http requests. + * @return a new B2StorageClient or throws a RuntimeException if it can't make one. + */ + + default B2StorageClient create(B2AccountAuthorization b2AccountAuthorization, + String userAgent){ + final B2AccountAuthorizer accountAuthorizer = new B2AccountAuthorizerV5AuthImpl(b2AccountAuthorization); + final B2ClientConfig config = B2ClientConfig + .builder( accountAuthorizer, userAgent) + .build(); + return create(config); + } } diff --git a/core/src/main/java/com/backblaze/b2/client/B2StorageClientFactoryPathBasedImpl.java b/core/src/main/java/com/backblaze/b2/client/B2StorageClientFactoryPathBasedImpl.java index 65de5e5b8..209e74814 100644 --- a/core/src/main/java/com/backblaze/b2/client/B2StorageClientFactoryPathBasedImpl.java +++ b/core/src/main/java/com/backblaze/b2/client/B2StorageClientFactoryPathBasedImpl.java @@ -41,13 +41,15 @@ public class B2StorageClientFactoryPathBasedImpl implements B2StorageClientFacto private B2StorageClientFactory factory; B2StorageClientFactoryPathBasedImpl() { - // register the Apache HttpClient-based implementation: registerClass("com.backblaze.b2.client.webApiHttpClient.B2StorageHttpClientFactory"); // register the okhttp-based implementation: registerClass("com.backblaze.b2.client.okHttpClient.B2StorageOkHttpClientFactory"); + // register the UrlConnection-based implementation: + registerClass("com.backblaze.b2.client.webApiUrlConnectionClient.B2WebApiUrlConnectionClientFactory"); + } /** diff --git a/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2StorageUrlConnectionClientBuilder.java b/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2StorageUrlConnectionClientBuilder.java new file mode 100644 index 000000000..f168200fd --- /dev/null +++ b/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2StorageUrlConnectionClientBuilder.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019, Backblaze Inc. All Rights Reserved. + * License https://www.backblaze.com/using_b2_code.html + */ +package com.backblaze.b2.client.webApiUrlConnectionClient; + +import com.backblaze.b2.client.B2ClientConfig; +import com.backblaze.b2.client.B2DefaultRetryPolicy; +import com.backblaze.b2.client.B2RetryPolicy; +import com.backblaze.b2.client.B2Sdk; +import com.backblaze.b2.client.B2StorageClient; +import com.backblaze.b2.client.B2StorageClientImpl; +import com.backblaze.b2.client.B2StorageClientWebifier; +import com.backblaze.b2.client.B2StorageClientWebifierImpl; +import com.backblaze.b2.client.webApiClients.B2WebApiClient; + +import java.util.function.Supplier; + +public class B2StorageUrlConnectionClientBuilder { + private static final String DEFAULT_MASTER_URL = "https://api.backblazeb2.com/"; + private final B2ClientConfig config; + private B2WebApiClient webApiClient; + private Supplier retryPolicySupplier; + + @SuppressWarnings("WeakerAccess") + public static B2StorageUrlConnectionClientBuilder builder(B2ClientConfig config) { + return new B2StorageUrlConnectionClientBuilder(config); + } + + private B2StorageUrlConnectionClientBuilder(B2ClientConfig config) { + this.config = config; + } + + public B2StorageClient build() { + final B2WebApiClient webApiClient = (this.webApiClient != null) ? + this.webApiClient : + new B2WebApiUrlConnectionClientImpl(); + final B2StorageClientWebifier webifier = new B2StorageClientWebifierImpl( + webApiClient, + config.getUserAgent() + " " + B2Sdk.getName() + "/" + B2Sdk.getVersion(), + (config.getMasterUrl() == null) ? DEFAULT_MASTER_URL : config.getMasterUrl(), + config.getTestModeOrNull()); + final Supplier retryPolicySupplier = (this.retryPolicySupplier != null) ? + this.retryPolicySupplier : + B2DefaultRetryPolicy.supplier(); + return new B2StorageClientImpl( + webifier, + config, + retryPolicySupplier); + } + + @SuppressWarnings("unused") + public B2StorageUrlConnectionClientBuilder setRetryPolicySupplier(Supplier retryPolicySupplier) { + this.retryPolicySupplier = retryPolicySupplier; + return this; + } +} diff --git a/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientFactory.java b/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientFactory.java new file mode 100644 index 000000000..2cbc291a0 --- /dev/null +++ b/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientFactory.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019, Backblaze Inc. All Rights Reserved. + * License https://www.backblaze.com/using_b2_code.html + */ +package com.backblaze.b2.client.webApiUrlConnectionClient; + + +import com.backblaze.b2.client.B2ClientConfig; +import com.backblaze.b2.client.B2StorageClient; +import com.backblaze.b2.client.B2StorageClientFactory; + +/** + * Simple factory for the UrlConnection-based B2StorageClient. + * + * THREAD-SAFE. + */ +public class B2WebApiUrlConnectionClientFactory implements B2StorageClientFactory { + + @Override + public B2StorageClient create(B2ClientConfig config) { + return B2StorageUrlConnectionClientBuilder.builder(config).build(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientImpl.java b/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientImpl.java new file mode 100644 index 000000000..d4b4c14ab --- /dev/null +++ b/core/src/main/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientImpl.java @@ -0,0 +1,351 @@ +/* + * Copyright 2019, Backblaze Inc. All Rights Reserved. + * License https://www.backblaze.com/using_b2_code.html + */ +package com.backblaze.b2.client.webApiUrlConnectionClient; + +import com.backblaze.b2.client.contentHandlers.B2ContentSink; +import com.backblaze.b2.client.contentSources.B2Headers; +import com.backblaze.b2.client.contentSources.B2HeadersImpl; +import com.backblaze.b2.client.exceptions.B2ConnectFailedException; +import com.backblaze.b2.client.exceptions.B2Exception; +import com.backblaze.b2.client.exceptions.B2LocalException; +import com.backblaze.b2.client.exceptions.B2NetworkException; +import com.backblaze.b2.client.exceptions.B2NetworkTimeoutException; +import com.backblaze.b2.client.structures.B2ErrorStructure; +import com.backblaze.b2.client.webApiClients.B2WebApiClient; +import com.backblaze.b2.json.B2Json; +import com.backblaze.b2.json.B2JsonException; +import com.backblaze.b2.json.B2JsonOptions; +import com.backblaze.b2.util.B2IoUtils; +import com.backblaze.b2.util.B2StringUtil; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +import static com.backblaze.b2.util.B2DateTimeUtil.ONE_SECOND_IN_MILLIS; + +// XXX: make sure all the code is getting used. +// XXX: connect and read (and post?) socket timeouts? + +public class B2WebApiUrlConnectionClientImpl implements B2WebApiClient { + private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 5; + private static final int DEFAULT_SOCKET_TIMEOUT_SECONDS = 20; + + private final B2Json bzJson = B2Json.get(); + + /** + * POSTs to a web service that takes JSON and returns JSON. + * + * @param url the url to post to + * @param headersOrNull if non-null, some headers to include in the post + * @param request the object to be converted to json (with B2Json) and posted as the body of the request + * @param responseClass the class to convert the response body into (with B2Json) + * @param the class + * @return the response object (converted from json by B2Json) + * @throws B2Exception if there's any trouble + */ + public ResponseType postJsonReturnJson( + String url, + B2Headers headersOrNull, + Object request, + Class responseClass) throws B2Exception { + final String responseString = postJsonAndReturnString(url, headersOrNull, request); + try { + return bzJson.fromJson(responseString, responseClass, B2JsonOptions.DEFAULT_AND_ALLOW_EXTRA_FIELDS); + } catch (B2JsonException e) { + throw new B2LocalException("parsing_failed", "can't convert response from json: " + e.getMessage(), e); + } + } + + /** + * POSTs to a web service that takes content and returns JSON. + * + * @param url the url to post to + * @param headersOrNull if non-null, some headers to include in the POST + * @param contentStream the content to post as the body of the request + * @param contentLength the number of bytes in the content to post + * @param responseClass the class to convert the response body into (with B2Json) + * @param the class + * @return the response object (converted from json by B2Json) + * @throws B2Exception if there's any trouble + */ + public ResponseType postDataReturnJson( + String url, + B2Headers headersOrNull, + InputStream contentStream, + long contentLength, + Class responseClass) throws B2Exception { + try { + String responseJson = postAndReturnString(url, headersOrNull, contentLength, contentStream); + return B2Json.get().fromJson(responseJson, responseClass, B2JsonOptions.DEFAULT_AND_ALLOW_EXTRA_FIELDS); + } catch (B2JsonException e) { + throw new B2LocalException("parsing_failed", "can't convert response from json: " + e.getMessage(), e); + } + } + + /** + * GETs from a web service that returns content. + * + * @param urlString the url to post to + * @param headersOrNull if non-null, some headers to include in the GET + * @param handler the object which will be called with the response's headers and content + * @throws B2Exception if there's any trouble + */ + public void getContent(String urlString, + B2Headers headersOrNull, + B2ContentSink handler) throws B2Exception { + try { + final HttpURLConnection httpCxn = openHttpConnection(urlString); + configureHttpCxn(httpCxn, 0L, headersOrNull); + + final int statusCode = httpCxn.getResponseCode(); + if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_PARTIAL) { + handler.readContent(makeB2Headers(httpCxn), httpCxn.getInputStream()); + } else { + throw extractExceptionFromErrorResponse(statusCode, httpCxn); + } + } catch (IOException e) { + throw translateToB2Exception(e, urlString); + } + } + + /** + * HEADSs to a web service that returns content, and returns the headers. + * + * @param urlString the url to head to + * @param headersOrNull the headers, if any. + * @return the headers of the response. + * @throws B2Exception if there's any trouble + */ + public B2Headers head(String urlString, B2Headers headersOrNull) throws B2Exception { + try { + final HttpURLConnection httpCxn = openHttpConnection(urlString); + + // set method & say there will be input + httpCxn.setRequestMethod("HEAD"); + httpCxn.setDoInput(true); + + configureHttpCxn(httpCxn, 0L, headersOrNull); + + final int statusCode = httpCxn.getResponseCode(); + if (statusCode == HttpURLConnection.HTTP_OK) { + return makeB2Headers(httpCxn); + } else { + throw extractExceptionFromErrorResponse(statusCode, httpCxn); + } + } catch (IOException e) { + throw translateToB2Exception(e, urlString); + } + } + + /** + * Adds headers to the given http connection, including some we always add + * and including the ones in headersOrNull if it's not null. + * @param httpCxn the cxn to add the headers to + * @param contentLength the Content-Length + * @param headersOrNull if non-null, the headers to add to the connection. + */ + private void configureHttpCxn(HttpURLConnection httpCxn, long contentLength, B2Headers headersOrNull) { + // android tries to get the server to gzip responses. that makes the + // Content-Length not contain the size of the object being requested + // from B2. until we figure out whether that's safe, let's disable + // that behavior. see: + // https://android-developers.googleblog.com/2011/09/androids-http-clients.html + // https://developer.android.com/about/versions/marshmallow/android-6.0-changes + httpCxn.setRequestProperty("Accept-Encoding", "identity"); + + if (contentLength > 0) { + // this prevents us from buffering the content before posting it. + // it also keeps the httpCxn from being able to handle redirects automatically, + // but we're ok with that. AND, i think it sets the Content-Length header. + httpCxn.setFixedLengthStreamingMode(contentLength); + } + + // disable caches (XXX: revisit?) + httpCxn.setUseCaches(false); + + // set some timeouts + httpCxn.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_SECONDS * (int) ONE_SECOND_IN_MILLIS); + httpCxn.setConnectTimeout(DEFAULT_SOCKET_TIMEOUT_SECONDS * (int) ONE_SECOND_IN_MILLIS); + + + // set headers provided by caller. + if (headersOrNull != null) { + for (String name : headersOrNull.getNames()) { + httpCxn.setRequestProperty(name, headersOrNull.getValueOrNull(name)); + } + } + } + + private B2Headers makeB2Headers(HttpURLConnection httpCxn) { + final B2HeadersImpl.Builder builder = B2HeadersImpl.builder(); + for (Map.Entry> entry : httpCxn.getHeaderFields().entrySet()) { + // there's an entry with the key null which contains the HTTP response status line! + final String name = entry.getKey(); + if (name != null) { + final StringJoiner joiner = new StringJoiner(","); + entry.getValue().forEach(joiner::add); + builder.set(name, joiner.toString()); + } + } + return builder.build(); + } + + /** + * Closes this object and its underlying resources. + * This is overridden from AutoCloseable to declare that it can't throw any exception. + */ + @Override + public void close() { + } + + + private String postJsonAndReturnString(String url, + B2Headers headersOrNull, + Object request) throws B2Exception { + try { + final byte[] bytesToPost = bzJson.toJsonUtf8Bytes(request); + final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytesToPost); + return postAndReturnString(url, headersOrNull, bytesToPost.length, inputStream); + } catch (B2JsonException e) { + throw new B2LocalException("bad_argument", "failed to convert object to json: " + e, e); + } + } + + /** + * POSTs to a web service that returns content, and returns the content + * as a single string. + * + * @param urlString the url to post to + * @param headersOrNull the headers, if any. + * @param contentLength the Content-Length + * @param inputStream the data to post. + * @return the body of the response. + * @throws B2Exception if there's any trouble + */ + private String postAndReturnString(String urlString, B2Headers headersOrNull, long contentLength, InputStream inputStream) + throws B2Exception { + + try { + final HttpURLConnection httpCxn = openHttpConnection(urlString); + + // set method & say there will be input + httpCxn.setRequestMethod("POST"); + httpCxn.setDoInput(true); + httpCxn.setDoOutput(true); + + configureHttpCxn(httpCxn, contentLength, headersOrNull); + + try (OutputStream outputStream = httpCxn.getOutputStream()) { + B2IoUtils.copy(inputStream, outputStream); + outputStream.flush(); + } + + final int statusCode = httpCxn.getResponseCode(); + if (statusCode == HttpURLConnection.HTTP_OK) { + return streamToString(httpCxn.getInputStream()); + } else { + throw extractExceptionFromErrorResponse(statusCode, httpCxn); + } + } catch (IOException e) { + throw translateToB2Exception(e, urlString); + } + } + + private String streamToString(InputStream inputStream) throws IOException { + final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + B2IoUtils.copy(inputStream, byteStream); + return byteStream.toString(B2StringUtil.UTF8); + } + + private HttpURLConnection openHttpConnection(String urlString) throws IOException, B2LocalException { + HttpURLConnection httpCxn; + final URL url = new URL(urlString); + final URLConnection urlCxn = url.openConnection(); + + if (!(urlCxn instanceof HttpURLConnection)) { + throw new B2LocalException("bad_url", "url isn't http-ish? (" + url + ")", null); + } + httpCxn = (HttpURLConnection) urlCxn; + return httpCxn; + } + + private B2Exception extractExceptionFromErrorResponse(int statusCode, HttpURLConnection urlCxn) { + String responseText = null; + + // Try B2 error structure + try { + responseText = streamToString(urlCxn.getErrorStream()); + B2ErrorStructure err = B2Json.get().fromJson(responseText, B2ErrorStructure.class); + return B2Exception.create(err.code, err.status, getRetryAfterSecondsOrNull(urlCxn), err.message); + } + catch (Throwable t) { + // we can't parse the response as a B2 JSON error structure. + // so use the default. + final String errMsg = (responseText != null) ? + responseText : + "" + t.getMessage(); // make sure this isn't null. + return new B2Exception("unknown", statusCode, getRetryAfterSecondsOrNull(urlCxn), errMsg); + } + } + + + private B2Exception translateToB2Exception(IOException e, String url) { + if (e instanceof MalformedURLException) { + // from HTTP Components + return new B2LocalException("local", "bad url: " + url, e); + } + if (e instanceof ConnectException) { + // java.net base class for HttpHostConnectException. + return new B2ConnectFailedException("connect_failed", null, "failed to connect for " + url, e); + } + if (e instanceof UnknownHostException) { + return new B2ConnectFailedException("unknown_host", null, "unknown host for " + url, e); + } + if (e instanceof SocketTimeoutException) { + return new B2NetworkTimeoutException("socket_timeout", null, "socket timed out talking to " + url, e); + } + if (e instanceof SocketException) { + return new B2NetworkException("socket_exception", null, "socket exception talking to " + url, e); + } + + return new B2NetworkException("io_exception", null, e + " talking to " + url, e); + } + /** + * If there's a Retry-After header and it has a delay-seconds formatted value, + * this returns it. (to be clear, if there's an HTTP-date value, we ignore it + * and keep looking for one with delay-seconds format.) + * + * @param httpCxn the http connection which should have a response. + * @return the delay-seconds from a Retry-After header, if any. otherwise, null. + */ + private Integer getRetryAfterSecondsOrNull(HttpURLConnection httpCxn) { + // https://tools.ietf.org/html/rfc7231#section-7.1.3 + final List valuesOrNull = httpCxn.getHeaderFields().get(B2Headers.RETRY_AFTER); + if (valuesOrNull != null) { + for (String value : valuesOrNull) { + try { + return Integer.parseInt(value, 10); + } catch (IllegalArgumentException e) { + // continue. + } + } + } + return null; + } +} diff --git a/core/src/test/java/com/backblaze/b2/client/B2StorageClientFactoryPathBasedImplTest.java b/core/src/test/java/com/backblaze/b2/client/B2StorageClientFactoryPathBasedImplTest.java index 15b074c7c..455a3ce7e 100644 --- a/core/src/test/java/com/backblaze/b2/client/B2StorageClientFactoryPathBasedImplTest.java +++ b/core/src/test/java/com/backblaze/b2/client/B2StorageClientFactoryPathBasedImplTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockingDetails; @@ -18,11 +19,10 @@ public class B2StorageClientFactoryPathBasedImplTest { @Test - public void testDefaultFactory_failsBecauseTestEnvironmentDoesntIncludeHttpClientJars() { + public void testDefaultFactory_succeedsTestEnvironmentIncludesUrlBasedImplementation() { final B2StorageClientFactory factory = B2StorageClientFactory.createDefaultFactory(); - thrown.expectMessage("can't find any of the registered classes."); - factory.create("appKeyId", "appKey", "userAgent"); + assertNotNull(factory.create("appKeyId", "appKey", "userAgent")); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/test/java/com/backblaze/b2/client/webApiClients/B2WebApiUrlConnectionClientFactoryTest.java b/core/src/test/java/com/backblaze/b2/client/webApiClients/B2WebApiUrlConnectionClientFactoryTest.java new file mode 100644 index 000000000..c2296083a --- /dev/null +++ b/core/src/test/java/com/backblaze/b2/client/webApiClients/B2WebApiUrlConnectionClientFactoryTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019, Backblaze Inc. All Rights Reserved. + * License https://www.backblaze.com/using_b2_code.html + */ +package com.backblaze.b2.client.webApiClients; + +import com.backblaze.b2.client.B2StorageClientFactory; +import com.backblaze.b2.client.B2StorageClientFactoryPathBasedImpl; +import com.backblaze.b2.client.webApiUrlConnectionClient.B2WebApiUrlConnectionClientFactory; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class B2WebApiUrlConnectionClientFactoryTest { + @Test + public void testCreate() { + // this is mostly to keep B2StorageHttpClientFactory from being unused. + final B2WebApiUrlConnectionClientFactory factory = new B2WebApiUrlConnectionClientFactory(); + assertNotNull(factory); + } + + @Test + public void testDefaultFactory_succeedsBecauseTestEnvironmentIncludesUrlConnectionBasedImplementation() { + final B2StorageClientFactory factory = B2StorageClientFactory.createDefaultFactory(); + assertTrue(factory instanceof B2StorageClientFactoryPathBasedImpl); + + assertNotNull(factory.create("appKeyId", "appKey", "userAgent")); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientImplTest.java b/core/src/test/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientImplTest.java new file mode 100644 index 000000000..109074ae9 --- /dev/null +++ b/core/src/test/java/com/backblaze/b2/client/webApiUrlConnectionClient/B2WebApiUrlConnectionClientImplTest.java @@ -0,0 +1,17 @@ +/* + * Copyright 2017, Backblaze Inc. All Rights Reserved. + * License https://www.backblaze.com/using_b2_code.html + */ +package com.backblaze.b2.client.webApiUrlConnectionClient; + +import com.backblaze.b2.client.webApiUrlConnectionClient.B2WebApiUrlConnectionClientImpl; +import org.junit.Test; + +public class B2WebApiUrlConnectionClientImplTest { + @Test + public void testForCoverageOnly() { + // just be sure the class isn't unused. + // that's a pretty weak test! + new B2WebApiUrlConnectionClientImpl(); + } +} \ No newline at end of file diff --git a/samples/src/main/java/com/backblaze/b2/sample/B2Sample.java b/samples/src/main/java/com/backblaze/b2/sample/B2Sample.java index cc85743eb..b448c7371 100644 --- a/samples/src/main/java/com/backblaze/b2/sample/B2Sample.java +++ b/samples/src/main/java/com/backblaze/b2/sample/B2Sample.java @@ -420,8 +420,9 @@ private static byte[] makeLargeFileBytes() { */ + @SuppressWarnings("SameParameterValue") private static File makeHugeFile(String fileNamePrefix) throws IOException { - final long fileSize = (long)Integer.MAX_VALUE + 1000*1000; + final long fileSize = 1000 * 1000 ;//(long)Integer.MAX_VALUE + 1000*1000; final byte[] bytes = {0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF}; final File tempFile = File.createTempFile(fileNamePrefix, ".tmp"); final RandomAccessFile randomAccessFile= new RandomAccessFile(tempFile, "rw");