
com.contentful.java.cda.CDAClient Maven / Gradle / Ivy
Show all versions of java-sdk Show documentation
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 io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.functions.Function;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import org.reactivestreams.Publisher;
import retrofit2.Converter;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
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.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
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.Constants.PATH_LOCALES;
import static com.contentful.java.cda.ResourceFactory.fromArrayToItems;
import static com.contentful.java.cda.ResourceFactory.fromResponse;
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 environmentId;
final String token;
final CDAService service;
final Cache cache;
final Executor callbackExecutor;
final boolean preview;
final boolean logSensitiveData;
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.environmentId = builder.environment;
this.token = builder.token;
this.preview = builder.preview;
this.logSensitiveData = builder.logSensitiveData;
}
private void validate(Builder builder) {
checkNotNull(builder.space, "Space ID must be provided.");
checkNotNull(builder.environment, "Environment ID must not be null.");
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(clientBuilder.createOrGetConverterFactory(clientBuilder))
.addCallAdapterFactory(RxJava3CallAdapterFactory.createSynchronous())
.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 fulfil the
* request synchronously or asynchronously when a callback is provided.
*
* @param type resource type. This can be either a {@link CDALocale}, a {@link CDAEntry},
* a {@link CDAAsset}, or a {@link CDAContentType}
* @param type for avoiding casting on calling side.
* @return A query to call {@link FetchQuery#all()} or {@link FetchQuery#one(String)} on it.
* @see #fetchSpace()
*/
public FetchQuery fetch(Class type) {
return new FetchQuery<>(type, this);
}
/**
* Returns a {@link TransformQuery} to transform the default contentful response into a specific
* custom model type.
*
*
* @param An annotated custom {@link TransformQuery.ContentfulEntryModel} model.
* @return a query for async calls to Contentful transforming the response to custom types
* @see TransformQuery.ContentfulEntryModel
* @see TransformQuery.ContentfulField
* @see TransformQuery.ContentfulSystemField
*/
public TransformQuery observeAndTransform(Class type) {
return new TransformQuery<>(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. This can be either a {@link CDALocale}, a {@link CDAEntry},
* a {@link CDAAsset}, or a {@link CDAContentType}
* @param type for avoiding casting on calling side.
* @return A query to call {@link ObserveQuery#all()} or {@link ObserveQuery#one(String)} on it.
* @see #observeSpace()
*/
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) {
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) {
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 type the type to be sync'ed.
* @return query instance.
*/
public SyncQuery sync(SyncType type) {
return sync(null, null, type);
}
public boolean shouldLogSensitiveData() {
return logSensitiveData;
}
/**
* 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) {
return sync(syncToken, synchronizedSpace, null);
}
private SyncQuery sync(String syncToken, SynchronizedSpace synchronizedSpace,
SyncType type) {
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);
}
if (type != null) {
builder.setType(type);
}
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 service.space(spaceId).map(new Function, CDASpace>() {
@Override
public CDASpace apply(Response response) throws Exception {
return ResourceFactory.fromResponse(response);
}
});
}
/**
* Caching
*/
Flowable cacheAll(final boolean invalidate) {
return cacheLocales(invalidate)
.flatMap(new Function, Publisher extends Map>>() {
@Override
public Publisher extends Map> apply(List locales) {
return CDAClient.this.cacheTypes(invalidate);
}
})
.map(new Function