All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pcloud.networking.client.PCloudAPIClient Maven / Gradle / Ivy

/*
 * Copyright (c) 2017 pCloud AG
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.pcloud.networking.client;

import com.pcloud.networking.client.internal.tls.DefaultHostnameVerifier;

import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * An implementation of a client used to make network calls
 * 

* Used to produce {@linkplain Call} and {@linkplain MultiCall} objects to make network requests to the server * * @see MultiCall * @see Call * @see Request * @see Response * @see MultiResponse * @see RequestInterceptor */ public class PCloudAPIClient { private static final long DEFAULT_KEEP_ALIVE_TIME = 60; private static final int DEFAULT_CONNECT_TIMEOUT = 15; private static final int DEFAULT_READ_TIMEOUT = 30; private static final int DEFAULT_WRITE_TIMEOUT = 30; private static final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "PCloud API Client"); } }; private int connectTimeoutMs; private int writeTimeoutMs; private int readTimeoutMs; private final SocketFactory socketFactory; private final SSLSocketFactory sslSocketFactory; private final HostnameVerifier hostnameVerifier; private final List interceptors; private final ConnectionPool connectionPool; private final ConnectionProvider connectionProvider; private final EndpointProvider endpointProvider; private final ExecutorService callExecutor; private PCloudAPIClient(Builder builder) { this.connectTimeoutMs = builder.connectTimeoutMs; this.writeTimeoutMs = builder.writeTimeoutMs; this.readTimeoutMs = builder.readTimeoutMs; this.socketFactory = builder.socketFactory != null ? builder.socketFactory : SocketFactory.getDefault(); this.sslSocketFactory = builder.sslSocketFactory != null ? builder.sslSocketFactory : (SSLSocketFactory) SSLSocketFactory.getDefault(); this.hostnameVerifier = builder.hostnameVerifier != null ? builder.hostnameVerifier : DefaultHostnameVerifier.INSTANCE; this.connectionPool = builder.connectionPool != null ? builder.connectionPool : new ConnectionPool(); this.endpointProvider = builder.endpointProvider != null ? builder.endpointProvider : EndpointProvider.DEFAULT; this.connectionProvider = new ConnectionProvider(connectionPool, endpointProvider, socketFactory, sslSocketFactory, hostnameVerifier, connectTimeoutMs, readTimeoutMs, writeTimeoutMs, false); this.callExecutor = builder.callExecutor != null ? builder.callExecutor : new ThreadPoolExecutor(0, Integer.MAX_VALUE, DEFAULT_KEEP_ALIVE_TIME, TimeUnit.SECONDS, new SynchronousQueue(), DEFAULT_THREAD_FACTORY); this.interceptors = Collections.unmodifiableList(new ArrayList<>(builder.interceptors)); } /** * Produces a new instance of a {@linkplain Call} object to make a network call *

* The returned {@linkplain Call} object will send the request to the {@link Endpoint} returned by * {@linkplain Request#endpoint()} or if null, it will send to the endpoint returned from this * {@linkplain PCloudAPIClient} instance's {@linkplain EndpointProvider} at the time of execution. * * @param request A {@linkplain Request} for this call * @return A new instance of a {@linkplain Call} object with the specified {@linkplain Request} * @throws IllegalArgumentException on a null {@linkplain Request} argument */ public Call newCall(Request request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null."); } return new RealCall(request, callExecutor, interceptors, connectionProvider); } /** * Produces a new instance of a {@linkplain MultiCall} object to make multiple network calls efficiently. *

* The returned {@linkplain MultiCall} instance will batch the provided {@linkplain Request} objects * and will send them over a single connection, reducing the round-trips and response times. *

* The batched calls will be sent to the {@linkplain Endpoint} returned from this * {@linkplain PCloudAPIClient} instance's {@linkplain EndpointProvider} at the time of execution. * * @param requests A non-null {@linkplain Collection} of {@linkplain Request} * @return A new instance of a {@linkplain MultiCall} object with the specified requests * @throws IllegalArgumentException on a null {@linkplain Collection}, * on an empty {@linkplain Collection}, * on a {@linkplain Collection} containing null elements */ public MultiCall newCall(Collection requests) { if (requests == null) { throw new IllegalArgumentException("Requests collection cannot be null."); } if (requests.isEmpty()) { throw new IllegalArgumentException("Requests collection is empty."); } for (Request request : requests) { if (request == null) { throw new IllegalArgumentException("Collection cannot contain null requests."); } } return new RealMultiCall(new ArrayList<>(requests), callExecutor, interceptors, connectionProvider, null); } /** * Produces a new instance of a {@linkplain MultiCall} object to make multiple network calls efficiently. *

* The returned {@linkplain MultiCall} instance will batch the provided {@linkplain Request} objects * and will send them over a single connection, reducing the round-trips and response times. *

* The batched calls will be sent to the provided {@linkplain Endpoint}. * * @param requests A non-null {@linkplain Collection} of {@linkplain Request} * @param endpoint A target {@linkplain Endpoint} for the {@linkplain MultiCall} * @return A new instance of a {@linkplain MultiCall} object with the specified requests * @throws IllegalArgumentException on a null {@linkplain Collection}, * on an empty {@linkplain Collection}, * on a {@linkplain Collection} containing null elements */ public MultiCall newCall(Collection requests, Endpoint endpoint) { if (requests == null) { throw new IllegalArgumentException("Requests collection cannot be null."); } if (requests.isEmpty()) { throw new IllegalArgumentException("Requests collection is empty."); } for (Request request : requests) { if (request == null) { throw new IllegalArgumentException("Collection cannot contain null requests."); } } return new RealMultiCall(new ArrayList<>(requests), callExecutor, interceptors, connectionProvider, endpoint); } /** * Create a new {@linkplain ApiChannel} for a given {@linkplain Endpoint}. *

* Same as calling {@linkplain #newChannel(Endpoint)} with the endpoint returned * by this client's {@linkplain EndpointProvider} instance. * * @see ApiChannel * @see #newChannel(Endpoint) * @see EndpointProvider * @return a non-null {@linkplain ApiChannel} instance * @throws IOException on a connection error */ public ApiChannel newChannel() throws IOException { return new RealApiChannel(connectionProvider, endpointProvider.endpoint()); } /** * Create a new {@linkplain ApiChannel} for a given {@linkplain Endpoint}. *

* The call will create and initialize an {@linkplain ApiChannel} instance to a given endpoint, * or throw an {@linkplain IOException} on error. * * @see ApiChannel * @param endpoint a target {@linkplain Endpoint} * @return a non-null {@linkplain ApiChannel} instance * @throws IOException on a connection error */ public ApiChannel newChannel(Endpoint endpoint) throws IOException { return new RealApiChannel(connectionProvider, endpoint); } /** * Returns the maximum amount of time a connection should take to establish itself in milliseconds as an int * * @return The maximum amount of time a connection should take to establish itself in milliseconds as an int */ public int connectTimeout() { return connectTimeoutMs; } /** * Returns the write timeout in milliseconds * * @return The write timeout in milliseconds */ public int writeTimeout() { return writeTimeoutMs; } /** * Returns the read timeout in milliseconds * * @return The read timeout in milliseconds */ public int readTimeout() { return readTimeoutMs; } /** * Returns the {@linkplain SocketFactory} for of this client * * @return The {@linkplain SocketFactory} for this client */ public SocketFactory socketFactory() { return socketFactory; } /** * Returns the {@linkplain SSLSocketFactory} for this client * * @return The {@linkplain SSLSocketFactory} for this client */ public SSLSocketFactory sslSocketFactory() { return sslSocketFactory; } /** * Returns the {@linkplain HostnameVerifier} for this client * * @return The {@linkplain HostnameVerifier} for this client */ public HostnameVerifier hostnameVerifier() { return hostnameVerifier; } /** * Returns the {@linkplain ConnectionPool} for this client * * @return The {@linkplain ConnectionPool} for this client */ public ConnectionPool connectionPool() { return connectionPool; } /** * Returns the {@linkplain ExecutorService} for this client * * @return The {@linkplain ExecutorService} for this client */ public ExecutorService callExecutor() { return callExecutor; } /** * Returns a {@linkplain List} of {@linkplain RequestInterceptor} for this client * * @return A {@linkplain List} of {@linkplain RequestInterceptor} for this client */ public List interceptors() { return interceptors; } /** * Get the {@linkplain EndpointProvider} set for this client instance *

* This will return the {@linkplain EndpointProvider} instance given in {@linkplain Builder#endpointProvider()} * or {@linkplain EndpointProvider#DEFAULT} if none has been set. * * @return A non-null {@linkplain EndpointProvider} object */ public EndpointProvider endpointProvider() { return endpointProvider; } /** * Shuts down the client clearing up resources such as idle connections */ public void shutdown() { callExecutor.shutdownNow(); connectionPool.evictAll(); } /** * Returns a new {@linkplain Builder} to construct the {@linkplain PCloudAPIClient} * * @return A new {@linkplain Builder} to construct the {@linkplain PCloudAPIClient} */ public Builder newBuilder() { return new Builder(this); } /** * Create a new {@linkplain Builder} instance *

* Use this method to start configuring a new {@linkplain PCloudAPIClient} instance. * * @return a new {@linkplain Builder} instance */ public static Builder newClient() { return new Builder() .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT, SECONDS) .setWriteTimeout(DEFAULT_WRITE_TIMEOUT, SECONDS) .setReadTimeout(DEFAULT_READ_TIMEOUT, SECONDS); } /** * Builds instances of the {@linkplain PCloudAPIClient} and allows different properties to be set as needed */ public static class Builder { private int connectTimeoutMs; private int writeTimeoutMs; private int readTimeoutMs; private ConnectionPool connectionPool; private EndpointProvider endpointProvider; private SocketFactory socketFactory; private SSLSocketFactory sslSocketFactory; private HostnameVerifier hostnameVerifier; private ExecutorService callExecutor; List interceptors; private Builder(PCloudAPIClient cloudAPIClient) { this.connectTimeoutMs = cloudAPIClient.connectTimeoutMs; this.writeTimeoutMs = cloudAPIClient.writeTimeoutMs; this.readTimeoutMs = cloudAPIClient.readTimeoutMs; this.connectionPool = cloudAPIClient.connectionPool; this.endpointProvider = cloudAPIClient.endpointProvider; this.socketFactory = cloudAPIClient.socketFactory; this.sslSocketFactory = cloudAPIClient.sslSocketFactory; this.hostnameVerifier = cloudAPIClient.hostnameVerifier; this.callExecutor = cloudAPIClient.callExecutor; this.interceptors = new LinkedList<>(cloudAPIClient.interceptors); } private Builder() { this.interceptors = new LinkedList<>(); } /** * Adds a new {@linkplain RequestInterceptor} * * @param interceptor A new {@linkplain RequestInterceptor} to be added to the client * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain RequestInterceptor} argument */ public Builder addInterceptor(RequestInterceptor interceptor) { if (interceptor == null) { throw new IllegalArgumentException("RequestInterceptor cannot be null."); } interceptors.add(interceptor); return this; } /** * The same as {@linkplain #addInterceptor(RequestInterceptor)} * but allows adding an entire {@linkplain List} of {@linkplain RequestInterceptor} * * @param interceptors A {@linkplain List} of {@linkplain RequestInterceptor} to be added to the client * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain Collection} or one containing null elements */ public Builder addInterceptors(Collection interceptors) { if (interceptors == null) { throw new IllegalArgumentException("RequestInterceptor collection cannot be null."); } for (RequestInterceptor r : interceptors) { addInterceptor(r); } return this; } /** * Adds a {@linkplain RequestInterceptor} to the front of the {@linkplain List} of interceptors * * @param interceptor A {@linkplain RequestInterceptor} to be added to the front of the {@linkplain List} * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain RequestInterceptor} */ public Builder addInterceptorAtFront(RequestInterceptor interceptor) { if (interceptor == null) { throw new IllegalArgumentException("RequestInterceptor cannot be null."); } interceptors.add(0, interceptor); return this; } /** * Sets the connection timeout duration * * @param timeout The maximum amount of time a connection should take to establish itself * @param timeUnit The unit in which you provided the duration argument * @return A reference to the {@linkplain Builder} * @throws IllegalArgumentException on a null {@linkplain TimeUnit}, * 0 or a negative value for the duration argument * or a duration argument greater than {@linkplain Integer#MAX_VALUE} */ public Builder setConnectTimeout(int timeout, TimeUnit timeUnit) { this.connectTimeoutMs = convertTimeValue(timeout, timeUnit); return this; } /** * Sets the write timeout duration * * @param timeout The duration for the write timeout * @param timeUnit The unit in which you specified the duration argument * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain TimeUnit}, * 0 or a negative value for the duration argument * or a duration argument greater than {@linkplain Integer#MAX_VALUE} */ public Builder setWriteTimeout(int timeout, TimeUnit timeUnit) { this.writeTimeoutMs = convertTimeValue(timeout, timeUnit); return this; } /** * Sets the read timeout duration * * @param timeout The duration for the read timeout * @param timeUnit The unit in which specified the duration argument * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain TimeUnit}, * 0 or a negative value for the duration argument * or a duration argument greater than {@linkplain Integer#MAX_VALUE} */ public Builder setReadTimeout(int timeout, TimeUnit timeUnit) { this.readTimeoutMs = convertTimeValue(timeout, timeUnit); return this; } /** * Sets a {@linkplain ConnectionPool} for the client * * @param connectionPool A {@linkplain ConnectionPool} to be set to the client * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain ConnectionPool} argument */ public Builder connectionPool(ConnectionPool connectionPool) { if (connectionPool == null) { throw new IllegalArgumentException("ConnectionPool cannot be null."); } this.connectionPool = connectionPool; return this; } /** * Sets a {@linkplain EndpointProvider} for the client * * @param endpointProvider A {@linkplain EndpointProvider} to be set to the client * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain EndpointProvider} argument */ public Builder endpointProvider(EndpointProvider endpointProvider) { if (endpointProvider == null) { throw new IllegalArgumentException("EndpointProvider cannot be null."); } this.endpointProvider = endpointProvider; return this; } /** * Sets the {@linkplain SocketFactory} for the client * * @param socketFactory The {@linkplain SocketFactory} to be set to the client * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain SocketFactory} argument */ public Builder setSocketFactory(SocketFactory socketFactory) { if (socketFactory == null) { throw new IllegalArgumentException("SocketFactory cannot be null."); } this.socketFactory = socketFactory; return this; } /** * Sets the {@linkplain SSLSocketFactory} for the client * * @param sslSocketFactory The {@linkplain SSLSocketFactory} to be set to the client * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain SSLSocketFactory} argument */ public Builder setSslSocketFactory(SSLSocketFactory sslSocketFactory) { if (sslSocketFactory == null) { throw new IllegalArgumentException("SSLSocketFactory cannot be null."); } this.sslSocketFactory = sslSocketFactory; return this; } /** * Sets the {@linkplain HostnameVerifier} for the client * * @param hostnameVerifier The {@linkplain HostnameVerifier} to be set to the client * @return A reference to the {@linkplain Builder} object * @throws IllegalArgumentException on a null {@linkplain HostnameVerifier} argument */ public Builder setHostnameVerifier(HostnameVerifier hostnameVerifier) { if (hostnameVerifier == null) { throw new IllegalArgumentException("HostnameVerifier cannot be null."); } this.hostnameVerifier = hostnameVerifier; return this; } /** * Sets a {@linkplain ExecutorService} for the client *

* If no executor is provided or this method is called with a null argument a default {@linkplain ExecutorService} will be set * * @param executorService The {@linkplain ExecutorService} to be set to the client * @return A reference to the {@linkplain Builder} object */ public Builder callExecutor(ExecutorService executorService) { this.callExecutor = executorService; return this; } /** * Creates the {@linkplain PCloudAPIClient} with all the parameters set with the {@linkplain Builder} * * @return A new instance of the {@linkplain PCloudAPIClient} with the parameters set via the {@linkplain Builder} */ public PCloudAPIClient create() { return new PCloudAPIClient(this); } private static int convertTimeValue(long timeout, TimeUnit timeUnit) { if (timeout < 0) throw new IllegalArgumentException("timeout < 0"); if (timeUnit == null) throw new NullPointerException("unit == null"); long millis = timeUnit.toMillis(timeout); if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Value too large."); if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Value too small."); return (int) millis; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy