com.contentful.java.cda.CDAClient Maven / Gradle / Ivy
package com.contentful.java.cda;
//BEGIN TO LONG CODE LINES
import com.contentful.java.cda.interceptor.AuthorizationHeaderInterceptor;
import com.contentful.java.cda.interceptor.ContentfulUserAgentHeaderInterceptor;
import com.contentful.java.cda.interceptor.ContentfulUserAgentHeaderInterceptor.Section;
import com.contentful.java.cda.interceptor.ContentfulUserAgentHeaderInterceptor.Section.OperatingSystem;
import com.contentful.java.cda.interceptor.ContentfulUserAgentHeaderInterceptor.Section.Version;
import com.contentful.java.cda.interceptor.ErrorInterceptor;
import com.contentful.java.cda.interceptor.LogInterceptor;
import com.contentful.java.cda.interceptor.UserAgentHeaderInterceptor;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import io.reactivex.Flowable;
import io.reactivex.functions.Function;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import static com.contentful.java.cda.Constants.ENDPOINT_PROD;
import static com.contentful.java.cda.Constants.PATH_CONTENT_TYPES;
import static com.contentful.java.cda.Tls12Implementation.useRecommendation;
import static com.contentful.java.cda.Util.checkNotNull;
import static com.contentful.java.cda.build.GeneratedBuildParameters.PROJECT_VERSION;
import static com.contentful.java.cda.interceptor.ContentfulUserAgentHeaderInterceptor.Section.os;
import static com.contentful.java.cda.interceptor.ContentfulUserAgentHeaderInterceptor.Section.platform;
import static com.contentful.java.cda.interceptor.ContentfulUserAgentHeaderInterceptor.Section.sdk;
import static javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm;
//END TO LONG CODE LINES
/**
* Client to be used when requesting information from the Delivery API. Every client is associated
* with exactly one Space, but there is no limit to the concurrent number of clients existing at
* any one time. Avoid creating multiple clients for the same Space. Use {@link #builder()}
* to create a new client instance.
*/
public class CDAClient {
private static final int CONTENT_TYPE_LIMIT_MAX = 1000;
final String spaceId;
final String token;
final CDAService service;
final Cache cache;
final Executor callbackExecutor;
final boolean preview;
private CDAClient(Builder builder) {
this(new Cache(),
Platform.get().callbackExecutor(),
createService(builder),
builder);
validate(builder);
}
CDAClient(Cache cache, Executor executor, CDAService service, Builder builder) {
this.cache = cache;
this.callbackExecutor = executor;
this.service = service;
this.spaceId = builder.space;
this.token = builder.token;
this.preview = builder.preview;
}
private void validate(Builder builder) {
checkNotNull(builder.space, "Space ID must be provided.");
if (builder.callFactory == null) {
checkNotNull(builder.token, "A token must be provided, if no call factory is specified.");
}
}
private static CDAService createService(Builder clientBuilder) {
String endpoint = clientBuilder.endpoint;
if (endpoint == null) {
endpoint = ENDPOINT_PROD;
}
Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(ResourceFactory.GSON))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.callFactory(clientBuilder.createOrGetCallFactory(clientBuilder))
.baseUrl(endpoint);
return retrofitBuilder.build().create(CDAService.class);
}
/**
* Returns a {@link FetchQuery} for a given {@code type}, which can be used to fulfill the
* request synchronously or asynchronously when a callback is provided.
*
* @param type resource type.
* @param resource type.
* @return query instance.
*/
public FetchQuery fetch(Class type) {
return new FetchQuery(type, this);
}
/**
* Returns an {@link ObserveQuery} for a given {@code type}, which can be used to return
* an {@link Flowable} that fetches the desired resources.
*
* @param type resource type.
* @param resource type.
* @return query instance.
*/
public ObserveQuery observe(Class type) {
return new ObserveQuery(type, this);
}
/**
* Populate the content type cache with _all_ available content types.
*
* This method will run through all the content types, saving them in the process and also takes
* care of paging.
*
* This method is synchronous.
*
* @return the number of content types cached.
*/
public int populateContentTypeCache() {
return observeContentTypeCachePopulation().blockingFirst();
}
/**
* Populate the content type cache with _all_ available content types.
*
* This method is synchronous.
*
* @param limit the number of content types per page.
* @return the number of content types cached.
* @throws IllegalArgumentException if limit is less or equal to 0.
* @throws IllegalArgumentException if limit is more then 1_000.
* @see #populateContentTypeCache()
*/
public int populateContentTypeCache(int limit) {
if (limit > CONTENT_TYPE_LIMIT_MAX) {
throw new IllegalArgumentException("Content types per page limit cannot be more then 1000.");
}
if (limit <= 0) {
throw new IllegalArgumentException("Content types per page limit cannot be "
+ "less or equal to 0.");
}
return observeContentTypeCachePopulation(limit).blockingFirst();
}
/**
* Populate the content type cache with _all_ available content types.
*
* This method will run through all the content types, saving them in the process and also takes
* care of paging.
*
* This method is asynchronous and needs to be subscribed to.
*
* @return the flowable representing the asynchronous call.
*/
public Flowable observeContentTypeCachePopulation() {
return observeContentTypeCachePopulation(CONTENT_TYPE_LIMIT_MAX);
}
/**
* Populate the content type cache with _all_ available content types.
*
* This method will run through all the content types, saving them in the process and also takes
* care of paging.
*
* This method is asynchronous and needs to be subscribed to.
*
* @param limit the number of content types per page.
* @return the flowable representing the asynchronous call.
* @throws IllegalArgumentException if limit is less or equal to 0.
* @throws IllegalArgumentException if limit is more then 1_000.
*/
public Flowable observeContentTypeCachePopulation(final int limit) {
if (limit > CONTENT_TYPE_LIMIT_MAX) {
throw new IllegalArgumentException("Content types per page limit cannot be more then 1000.");
}
if (limit <= 0) {
throw new IllegalArgumentException("Content types per page limit cannot be "
+ "less or equal to 0.");
}
return
observe(CDAContentType.class)
.orderBy("sys.id")
.limit(limit)
.all()
.map(
new Function() {
@Override public CDAArray apply(CDAArray array) throws Exception {
if (array.skip() + array.limit() < array.total()) {
return nextPage(array);
} else {
return array;
}
}
private CDAArray nextPage(CDAArray array) {
final CDAArray nextArray = observe(CDAContentType.class)
.orderBy("sys.id")
.limit(limit)
.skip(array.skip + limit)
.all()
.map(this)
.blockingFirst();
array.skip = nextArray.skip;
array.items.addAll(nextArray.items);
array.assets.putAll(nextArray.assets);
array.entries.putAll(nextArray.entries);
return array;
}
}
)
.map(
new Function() {
@Override public Integer apply(CDAArray array) throws Exception {
for (CDAResource resource : array.items) {
if (resource instanceof CDAContentType) {
cache.types().put(resource.id(), (CDAContentType) resource);
} else {
throw new IllegalStateException(
"Requesting a list of content types should not return "
+ "any other type.");
}
}
return array.total;
}
}
);
}
/**
* Returns a {@link SyncQuery} for initial synchronization via the Sync API.
*
* @return query instance.
*/
public SyncQuery sync() {
return sync(null, null);
}
/**
* Returns a {@link SyncQuery} for synchronization with the provided {@code syncToken} via
* the Sync API.
*
* If called from a {@link #preview} client, this will always do an initial sync.
*
* @param syncToken sync token.
* @return query instance.
*/
public SyncQuery sync(String syncToken) {
return sync(syncToken, null);
}
/**
* Returns a {@link SyncQuery} for synchronization with an existing space.
*
* If called from a {@link #preview} client, this will always do an initial sync.
*
* @param synchronizedSpace space to sync.
* @return query instance.
*/
public SyncQuery sync(SynchronizedSpace synchronizedSpace) {
return sync(null, synchronizedSpace);
}
private SyncQuery sync(String syncToken, SynchronizedSpace synchronizedSpace) {
if (preview) {
syncToken = null;
synchronizedSpace = null;
}
SyncQuery.Builder builder = SyncQuery.builder().setClient(this);
if (synchronizedSpace != null) {
builder.setSpace(synchronizedSpace);
}
if (syncToken != null) {
builder.setSyncToken(syncToken);
}
return builder.build();
}
/**
* @return the space for this client (synchronously).
*/
public CDASpace fetchSpace() {
return observeSpace().blockingFirst();
}
/**
* Asynchronously fetch the space.
*
* @param the type of the callback to be used.
* @param callback the value of the callback to be called back.
* @return the space for this client (asynchronously).
*/
@SuppressWarnings("unchecked")
public > C fetchSpace(C callback) {
return (C) Callbacks.subscribeAsync(observeSpace(), callback, this);
}
/**
* @return an {@link Flowable} that fetches the space for this client.
*/
public Flowable observeSpace() {
return cacheSpace(true);
}
/**
* Caching
*/
Flowable cacheAll(final boolean invalidate) {
return cacheSpace(invalidate)
.flatMap(new Function>>() {
@Override public Flowable