├── jitpack.yml ├── .github ├── CODEOWNERS └── workflows │ └── codeql.yml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── contentful │ │ │ └── java │ │ │ └── cda │ │ │ ├── NestedTypeResolver.java │ │ │ ├── DeletedResource.java │ │ │ ├── SynchronousExecutor.java │ │ │ ├── rich │ │ │ ├── CDARichQuote.java │ │ │ ├── CDARichListItem.java │ │ │ ├── CDARichTable.java │ │ │ ├── CDARichParagraph.java │ │ │ ├── CDARichTableCell.java │ │ │ ├── CDARichTableRow.java │ │ │ ├── CDARichDocument.java │ │ │ ├── CDARichTableHeaderCell.java │ │ │ ├── CDARichHorizontalRule.java │ │ │ ├── CDARichOrderedList.java │ │ │ ├── CDARichUnorderedList.java │ │ │ ├── CDARichBlock.java │ │ │ ├── CDARichEmbeddedBlock.java │ │ │ ├── CDARichEmbeddedInline.java │ │ │ ├── CDARichHyperLink.java │ │ │ ├── CDARichNode.java │ │ │ ├── CDARichHeading.java │ │ │ ├── CDARichList.java │ │ │ ├── CDARichText.java │ │ │ └── CDARichMark.java │ │ │ ├── CDAType.java │ │ │ ├── CDAError.java │ │ │ ├── interceptor │ │ │ ├── UserAgentHeaderInterceptor.java │ │ │ ├── AuthorizationHeaderInterceptor.java │ │ │ ├── ErrorInterceptor.java │ │ │ ├── HeaderInterceptor.java │ │ │ └── LogInterceptor.java │ │ │ ├── CDASpace.java │ │ │ ├── Tls12Implementation.java │ │ │ ├── CDATag.java │ │ │ ├── CDAMetadata.java │ │ │ ├── Logger.java │ │ │ ├── CDAResourceNotFoundException.java │ │ │ ├── Constants.java │ │ │ ├── ArrayResource.java │ │ │ ├── SynchronizedSpace.java │ │ │ ├── CDAService.java │ │ │ ├── CDATaxonomyConcept.java │ │ │ ├── CDAArray.java │ │ │ ├── CDAContentTypeNotFoundException.java │ │ │ ├── CDAContentType.java │ │ │ ├── CDAEntry.java │ │ │ ├── ResourceDeserializer.java │ │ │ ├── Cache.java │ │ │ ├── CDALocale.java │ │ │ ├── CDAField.java │ │ │ ├── CDACallback.java │ │ │ ├── CDAResource.java │ │ │ ├── FetchQuery.java │ │ │ ├── TlsSocketFactory.java │ │ │ ├── Callbacks.java │ │ │ ├── SyncType.java │ │ │ ├── LocalizedResource.java │ │ │ └── CDAAsset.java │ └── resources │ │ └── com │ │ └── contentful │ │ └── java │ │ └── cda │ │ └── build │ │ └── GeneratedBuildParameters.java └── test │ ├── resources │ ├── errors │ │ ├── ratelimit.json │ │ ├── not_found.json │ │ └── invalid_query.json │ ├── array_empty.json │ ├── locales │ │ ├── array_empty.json │ │ ├── fetch_all_locales.json │ │ ├── space.json │ │ ├── content_types.json │ │ └── entries.json │ ├── locales_fallback │ │ ├── array_empty.json │ │ ├── space.json │ │ ├── content_types.json │ │ ├── fetch_all_locales.json │ │ └── entries.json │ ├── customs │ │ ├── space.json │ │ └── locales.json │ ├── shallow │ │ ├── space.json │ │ ├── locales.json │ │ ├── types.json │ │ ├── update.json │ │ └── initial.json │ ├── rich_text │ │ ├── sync_continued.json │ │ ├── locales.json │ │ ├── content_types.json │ │ ├── simple_text.json │ │ ├── simple_text_code.json │ │ ├── simple_text_bold.json │ │ ├── simple_text_italic.json │ │ ├── simple_text_underline.json │ │ ├── simple_horizontal_rule.json │ │ ├── simple_headline_5.json │ │ ├── simple_headline_2.json │ │ ├── simple_headline_3.json │ │ ├── simple_headline_4.json │ │ ├── simple_headline_6.json │ │ ├── simple_headline_1.json │ │ ├── simple_blockquote.json │ │ ├── simple_hyperlink.json │ │ ├── simple_text_with_link.json │ │ ├── simple_asset_block.json │ │ ├── simple_text_embedded.json │ │ └── simple_asset_hyperlink.json │ ├── demo │ │ ├── space.json │ │ ├── locales.json │ │ ├── sync_update_p1.json │ │ ├── content_types_fake.json │ │ ├── entries_nofields.json │ │ ├── entries_fake.json │ │ ├── assets_no_image.json │ │ ├── assets_jake.json │ │ ├── sync_initial_p1.json │ │ ├── sync_initial_preview_p1.json │ │ ├── sync_initial_staging_p1.json │ │ ├── links_unresolved.json │ │ └── content_types_cat.json │ ├── cda │ │ ├── locales.json │ │ ├── content_types_bar.json │ │ ├── content_types_foo.json │ │ ├── entries.json │ │ └── entries_with_errors.json │ ├── arrays │ │ ├── locales.json │ │ ├── content_types.json │ │ └── entries.json │ ├── links │ │ ├── locales.json │ │ ├── sync_empty_links.json │ │ └── content_types.json │ ├── links_invalid │ │ ├── locales.json │ │ ├── content_types.json │ │ ├── preview_space.json │ │ └── sync_initial.json │ └── content_types │ │ └── populate_cache_last_entry.json │ ├── bash │ ├── create_folders.sh │ ├── create_recordings.sh │ ├── rich_text-locales.sh │ ├── rich_text-content_types.sh │ ├── rich_text-sync_initial.sh │ ├── rich_text-sync_continued.sh │ └── rich_text-all.sh │ └── java │ └── com │ └── contentful │ └── java │ └── cda │ ├── lib │ ├── EnqueueResponse.java │ ├── Enqueue.java │ ├── TestResponse.java │ ├── TestCallback.java │ └── EnqueueResponseRule.java │ ├── SpaceTest.java │ ├── EnvironmentTest.java │ ├── CallbackTest.java │ ├── SyncTypeTest.java │ ├── ResourceFactoryUnitTest.java │ ├── LocaleTest.java │ ├── LinkTest.java │ ├── integration │ ├── IntegrationWithStagingEnvironment.java │ └── IntegrationWithMasterEnvironment.java │ ├── BaseTest.java │ └── ContentTypeTest.java ├── .gitignore ├── assets └── feature_graphic.png ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .buildscript ├── settings.xml ├── integration_tests.sh └── deploy_snapshot.sh ├── catalog-info.yaml ├── .codecov.yml ├── .travis.yml └── proguard-cda.cfg /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - oraclejdk8 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @contentful/team-developer-experience 2 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/NestedTypeResolver.java: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/errors/ratelimit.json: -------------------------------------------------------------------------------- 1 | "To many requests" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | *.iml 4 | target/ 5 | .classpath 6 | .project 7 | .settings/ 8 | -------------------------------------------------------------------------------- /src/test/bash/create_folders.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir --parents ../resources/rich_text 4 | -------------------------------------------------------------------------------- /assets/feature_graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.java/master/assets/feature_graphic.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.java/master/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip -------------------------------------------------------------------------------- /src/test/resources/array_empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 0, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [] 9 | } -------------------------------------------------------------------------------- /src/test/resources/locales/array_empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 0, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [] 9 | } -------------------------------------------------------------------------------- /src/test/resources/locales_fallback/array_empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 0, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [] 9 | } -------------------------------------------------------------------------------- /src/test/bash/create_recordings.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo $0 3 | 4 | 5 | for i in *.sh; do 6 | if [ "$(basename $0)" != "$i" ]; then 7 | echo "Executing $i" 8 | ./${i} 9 | fi 10 | done 11 | -------------------------------------------------------------------------------- /src/test/resources/errors/not_found.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Error", 4 | "id": "NotFound" 5 | }, 6 | "message": "The resource could not be found.", 7 | "requestId": "1606bf31ff38c583b132b135303ca107" 8 | } 9 | -------------------------------------------------------------------------------- /.buildscript/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sonatype-nexus-snapshots 5 | ${env.CI_DEPLOY_USERNAME} 6 | ${env.CI_DEPLOY_PASSWORD} 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/customs/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Space", 4 | "id": "fakespace" 5 | }, 6 | "name": "Fake space", 7 | "locales": [ 8 | { 9 | "code": "en-US", 10 | "default": true, 11 | "name": "English" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/test/resources/shallow/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Space", 4 | "id": "tamu5dc7eas0" 5 | }, 6 | "name": "foo", 7 | "locales": [ 8 | { 9 | "code": "en-US", 10 | "default": true, 11 | "name": "U.S. English" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/DeletedResource.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | /** Represents a deleted resource (via the Sync API). */ 4 | public class DeletedResource extends CDAResource { 5 | private static final long serialVersionUID = 5881271494348347577L; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/SynchronousExecutor.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.util.concurrent.Executor; 4 | 5 | final class SynchronousExecutor implements Executor { 6 | @Override public void execute(Runnable r) { 7 | r.run(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichQuote.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A block of nodes rendered as a direct quote. 7 | */ 8 | public class CDARichQuote extends CDARichBlock implements Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichListItem.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A block representing an item inside a list. 7 | */ 8 | public class CDARichListItem extends CDARichBlock implements Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichTable.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A paragraph of nodes, usually rendered together. 7 | */ 8 | public class CDARichTable extends CDARichBlock implements Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichParagraph.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A paragraph of nodes, usually rendered together. 7 | */ 8 | public class CDARichParagraph extends CDARichBlock implements Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichTableCell.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A paragraph of nodes, usually rendered together. 7 | */ 8 | public class CDARichTableCell extends CDARichBlock implements Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichTableRow.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A paragraph of nodes, usually rendered together. 7 | */ 8 | public class CDARichTableRow extends CDARichBlock implements Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichDocument.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * The base of a rich text field, containing all the other nodes. 7 | */ 8 | public class CDARichDocument extends CDARichBlock implements Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichTableHeaderCell.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A paragraph of nodes, usually rendered together. 7 | */ 8 | public class CDARichTableHeaderCell extends CDARichBlock implements Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAType.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | /** Constant resource types. */ 4 | public enum CDAType { 5 | ARRAY, 6 | ASSET, 7 | CONTENTTYPE, 8 | DELETEDASSET, 9 | DELETEDENTRY, 10 | ENTRY, 11 | LOCALE, 12 | SPACE, 13 | TAG, 14 | TAXONOMYCONCEPT, 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/sync_continued.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [], 6 | "nextSyncUrl": "http://cdn.contentful.com/spaces//environments/human-readable/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyY0wrkrEENlbsO2w5nCgCVENsOqWyHCp1nDhkzCgcOYLcKKwq1ewpTDisODf8OfR8KYw78SGkA-VsKYDhPCuXTCkMOaOgPCs8OJQBHDuyZXRUciXBnDt8KhwqvCpw" 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichHorizontalRule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A node representing a division, called a horizontal rule. 7 | */ 8 | public class CDARichHorizontalRule extends CDARichNode implements Serializable { 9 | /** 10 | * Construct this node. 11 | */ 12 | public CDARichHorizontalRule() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichOrderedList.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A list of elements, ordered by number. 7 | */ 8 | public class CDARichOrderedList extends CDARichList implements Serializable { 9 | /** 10 | * Create a list with numbers. 11 | */ 12 | public CDARichOrderedList() { 13 | super("1"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/demo/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Space", 4 | "id": "cfexampleapi" 5 | }, 6 | "name": "Contentful Example API", 7 | "locales": [ 8 | { 9 | "code": "en-US", 10 | "default": true, 11 | "name": "English" 12 | }, 13 | { 14 | "code": "tlh", 15 | "default": false, 16 | "name": "Klingon", 17 | "fallbackCode": "en-US" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichUnorderedList.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Representation of a block of unordered items. 7 | */ 8 | public class CDARichUnorderedList extends CDARichList implements Serializable { 9 | /** 10 | * Create a list with bullet points. 11 | */ 12 | public CDARichUnorderedList() { 13 | super("*"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/cda/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "en-US", 11 | "name": "English", 12 | "default": true, 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "2oQPjMCL9bQkylziydLh57", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/arrays/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "en-US", 11 | "name": "English", 12 | "default": true, 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "2oQPjMCL9bQkylziydLh57", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/shallow/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "en-US", 11 | "name": "English", 12 | "default": true, 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "2oQPjMCL9bQkylziydLh57", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: contentful.java 5 | description: | 6 | Java library for Contentful's Delivery and Preview APIs 7 | annotations: 8 | github.com/project-slug: contentful/contentful.java 9 | contentful.com/service-tier: "4" 10 | tags: 11 | - tier-4 12 | spec: 13 | type: library 14 | lifecycle: production 15 | owner: group:team-developer-experience 16 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "en-US", 11 | "name": "English (United States)", 12 | "default": true, 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "7fpOneSflazHm9rrNGxojE", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/lib/EnqueueResponse.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface EnqueueResponse { 11 | String fileName(); 12 | 13 | int code() default 200; 14 | 15 | String[] headers() default {}; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/com/contentful/java/cda/build/GeneratedBuildParameters.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.build; 2 | 3 | /** 4 | * Generated class containing the project version. 5 | * 6 | * This is generated on build time, so we don't have to fetch the properties on application 7 | * load. 8 | */ 9 | public final class GeneratedBuildParameters { 10 | /** 11 | * Which version of '${project.name}' is getting used? 12 | */ 13 | public static final String PROJECT_VERSION = "${project.version}"; 14 | } 15 | -------------------------------------------------------------------------------- /src/test/resources/errors/invalid_query.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Error", 4 | "id": "InvalidQuery" 5 | }, 6 | "message": "The query you sent was invalid. Probably a filter or ordering specification is not applicable to the type of a field.", 7 | "details": { 8 | "errors": [ 9 | { 10 | "name": "unknown", 11 | "path": [ 12 | "not" 13 | ], 14 | "details": "The path \"not\" is not recognized" 15 | } 16 | ] 17 | }, 18 | "requestId": "something" 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAError.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.io.Serializable; 4 | import java.util.Map; 5 | 6 | public class CDAError implements Serializable { 7 | 8 | private static final long serialVersionUID = -8417594917359895169L; 9 | 10 | private Map sys; 11 | 12 | private Map details; 13 | 14 | public Map getSys() { 15 | return sys; 16 | } 17 | 18 | public Map getDetails() { 19 | return details; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichBlock.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | /** 8 | * A collection of several nodes. 9 | */ 10 | public class CDARichBlock extends CDARichNode implements Serializable { 11 | final List content = new LinkedList<>(); 12 | 13 | /** 14 | * @return a changeable list of contents of this block 15 | */ 16 | public List getContent() { 17 | return content; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/lib/Enqueue.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Enqueue { 11 | String[] defaults() default { "demo/locales.json", "demo/content_types.json" }; 12 | 13 | String[] value() default {}; 14 | 15 | EnqueueResponse[] complex() default {}; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichEmbeddedBlock.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * This node is an inline link to a CDAEntry 7 | * 8 | * @see com.contentful.java.cda.CDAEntry 9 | */ 10 | public class CDARichEmbeddedBlock extends CDARichHyperLink implements Serializable { 11 | /** 12 | * Create a link pointing to a CDAEntry. 13 | * 14 | * @param target an entry to be pointed to. 15 | */ 16 | public CDARichEmbeddedBlock(Object target) { 17 | super(target); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichEmbeddedInline.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * This node is an inline link to a CDAEntry 7 | * 8 | * @see com.contentful.java.cda.CDAEntry 9 | */ 10 | public class CDARichEmbeddedInline extends CDARichHyperLink implements Serializable { 11 | /** 12 | * Create a link pointing to a CDAEntry. 13 | * 14 | * @param target an entry to be pointed to. 15 | */ 16 | public CDARichEmbeddedInline(Object target) { 17 | super(target); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/bash/rich_text-locales.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | folder="../resources/$(basename $(echo $0) | cut -d'-' -f1)" 4 | file="$(basename $0 | cut -d'-' -f2)" 5 | output="$(echo ${folder}/${file} | sed 's#sh#json#g')" 6 | 7 | mkdir --parent ${folder} 8 | touch ${output} 9 | 10 | curl --verbose \ 11 | -H 'Authorization: Bearer '${RICH_TEXT_DELIVERY_TOKEN} \ 12 | 'https://cdn.contentful.com/spaces/'${RICH_TEXT_SPACE_ID}'/locales' \ 13 | | sed 's/'${RICH_TEXT_SPACE_ID}'//g' \ 14 | | sed 's/'${RICH_TEXT_DELIVERY_TOKEN}'//g' \ 15 | | tee ${output} 16 | -------------------------------------------------------------------------------- /src/test/bash/rich_text-content_types.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | folder="../resources/$(basename $(echo $0) | cut -d'-' -f1)" 4 | file="$(basename $0 | cut -d'-' -f2)" 5 | output="$(echo ${folder}/${file} | sed 's#sh#json#g')" 6 | 7 | mkdir --parent ${folder} 8 | touch ${output} 9 | 10 | curl --verbose \ 11 | -H 'Authorization: Bearer '${RICH_TEXT_DELIVERY_TOKEN} \ 12 | 'https://cdn.contentful.com/spaces/'${RICH_TEXT_SPACE_ID}'/content_types' \ 13 | | sed 's/'${RICH_TEXT_SPACE_ID}'//g' \ 14 | | sed 's/'${RICH_TEXT_DELIVERY_TOKEN}'//g' \ 15 | | tee ${output} 16 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/interceptor/UserAgentHeaderInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.interceptor; 2 | 3 | /** 4 | * Interceptor to add user agent header to requests 5 | */ 6 | public class UserAgentHeaderInterceptor extends HeaderInterceptor { 7 | public static final String HEADER_NAME = "User-Agent"; 8 | 9 | /** 10 | * Create Header interceptor, saving parameters. 11 | * 12 | * @param userAgent user agent header to be send with _every_ request. 13 | */ 14 | public UserAgentHeaderInterceptor(String userAgent) { 15 | super(HEADER_NAME, userAgent); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/interceptor/AuthorizationHeaderInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.interceptor; 2 | 3 | /** 4 | * Interceptor to add authorization header to requests 5 | */ 6 | public class AuthorizationHeaderInterceptor extends HeaderInterceptor { 7 | public static final String HEADER_NAME = "Authorization"; 8 | 9 | /** 10 | * Create Header interceptor, saving parameters. 11 | * 12 | * @param token the access token to be used with *every* request. 13 | */ 14 | public AuthorizationHeaderInterceptor(String token) { 15 | super(HEADER_NAME, "Bearer " + token); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/bash/rich_text-sync_initial.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | folder="../resources/$(basename $(echo $0) | cut -d'-' -f1)" 4 | file="$(basename $0 | cut -d'-' -f2)" 5 | output="$(echo ${folder}/${file} | sed 's#sh#json#g')" 6 | 7 | mkdir --parent ${folder} 8 | touch ${output} 9 | 10 | curl --verbose \ 11 | -H 'Authorization: Bearer '${RICH_TEXT_DELIVERY_TOKEN} \ 12 | 'https://cdn.contentful.com/spaces/'${RICH_TEXT_SPACE_ID}'/environments/human-readable/sync?initial=true' \ 13 | | sed 's/'${RICH_TEXT_SPACE_ID}'//g' \ 14 | | sed 's/'${RICH_TEXT_DELIVERY_TOKEN}'//g' \ 15 | | tee ${output} 16 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichHyperLink.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * This block represents a link to a website. 7 | */ 8 | public class CDARichHyperLink extends CDARichBlock implements Serializable { 9 | Object data; 10 | 11 | /** 12 | * Create a new hyper link. 13 | * 14 | * @param target point to the target. 15 | */ 16 | public CDARichHyperLink(Object target) { 17 | this.data = target; 18 | } 19 | 20 | /** 21 | * @return the target this link points to. 22 | */ 23 | public Object getData() { 24 | return data; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.buildscript/integration_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Run JAVA specific api-coverage tests 3 | ENV='"REPO_SDK=java REPO_SLUG='$TRAVIS_REPO_SLUG' REPO_COMMIT='$TRAVIS_COMMIT'"' 4 | 5 | BODY="{ 6 | \"request\": { 7 | \"message\": \"$TRAVIS_REPO_SLUG SDK Triggered Request\", 8 | \"branch\":\"travis_experiments\", 9 | \"config\": {\"env\": $ENV} 10 | } 11 | }" 12 | 13 | # api-coverage 14 | curl -s -X POST \ 15 | -d "$BODY" \ 16 | -H "Content-Type: application/json" \ 17 | -H "Accept: application/json" \ 18 | -H "Travis-API-Version: 3" \ 19 | -H "Authorization: token $TRAVIS_TOKEN" \ 20 | 'https://api.travis-ci.com/repo/1336919/requests' 21 | 22 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "src/main/resources/com/contentful/java/cda/build/GeneratedBuildParameters.java" # ignore build resource folders 3 | 4 | coverage: 5 | precision: 1 6 | range: 70...100 7 | 8 | status: 9 | project: 10 | default: 11 | enabled: yes 12 | threshold: 1 13 | if_not_found: success 14 | if_no_updates: success 15 | patch: 16 | default: 17 | threshold: 10 18 | 19 | comment: 20 | behavior: new 21 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/SpaceTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.contentful.java.cda.lib.Enqueue; 4 | 5 | import org.junit.Test; 6 | 7 | import static com.contentful.java.cda.CDAType.SPACE; 8 | import static com.google.common.truth.Truth.assertThat; 9 | 10 | public class SpaceTest extends BaseTest { 11 | @Test 12 | @Enqueue(value = "demo/space.json", defaults = {}) 13 | public void fetchSpace() { 14 | CDASpace space = client.fetchSpace(); 15 | assertThat(space.name()).isEqualTo("Contentful Example API"); 16 | assertThat(space.id()).isEqualTo("cfexampleapi"); 17 | assertThat(space.type()).isEqualTo(SPACE); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/demo/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "en-US", 11 | "name": "English", 12 | "default": true, 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "2oQPjMCL9bQkylziydLh57", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | }, 20 | { 21 | "code": "tlh", 22 | "name": "Klingon", 23 | "default": false, 24 | "fallbackCode": "en-US", 25 | "sys": { 26 | "id": "3zpZmkZrHTIekHmXgflXaV", 27 | "type": "Locale", 28 | "version": 0 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/test/resources/demo/sync_update_p1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "type": "DeletedEntry", 9 | "id": "7qVBlCjpWE86Oseo40gAEY", 10 | "space": { 11 | "sys": { 12 | "type": "Link", 13 | "linkType": "Space", 14 | "id": "cfexampleapi" 15 | } 16 | }, 17 | "revision": 3, 18 | "createdAt": "2014-07-31T15:14:59.226Z", 19 | "updatedAt": "2014-07-31T15:14:59.226Z", 20 | "deletedAt": "2014-07-31T15:14:59.226Z" 21 | } 22 | } 23 | ], 24 | "nextPageUrl": "http://cdn.contentful.com/spaces/cfexampleapi/sync?sync_token=foo" 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDASpace.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | /** 4 | * Represents a single space. 5 | */ 6 | public class CDASpace extends CDAResource { 7 | private static final long serialVersionUID = 8920494351623297673L; 8 | String name; 9 | 10 | /** 11 | * @return name of this space. 12 | */ 13 | public String name() { 14 | return name; 15 | } 16 | 17 | /** 18 | * Create a String from this object. 19 | * 20 | * @return a String containing the id and name of this space 21 | */ 22 | @Override public String toString() { 23 | return "CDASpace{" 24 | + "id='" + id() + '\'' 25 | + ", name='" + name + '\'' 26 | + '}'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/links/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "en-US", 11 | "name": "English", 12 | "default": true, 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "2oQPjMCL9bQkylziydLh57", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | }, 20 | { 21 | "code": "space.json", 22 | "name": "Hebrew", 23 | "default": false, 24 | "fallbackCode": "en-US", 25 | "sys": { 26 | "id": "3zpZmkZrHTIekHmXgflXaV", 27 | "type": "Locale", 28 | "version": 0 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/test/resources/links_invalid/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "en-US", 11 | "name": "English", 12 | "default": true, 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "2oQPjMCL9bQkylziydLh57", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | }, 20 | { 21 | "code": "sp", 22 | "name": "Spanish", 23 | "default": false, 24 | "fallbackCode": "en-US", 25 | "sys": { 26 | "id": "3zpZmkZrHTIekHmXgflXaV", 27 | "type": "Locale", 28 | "version": 0 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/Tls12Implementation.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | /** 4 | * This enumeration stores your choices on how to enable TLS 1.2. 5 | */ 6 | public enum Tls12Implementation { 7 | /** 8 | * Use the SDK recommendation on which Tls12 implementation to use. 9 | * @see Platform.Android#needsCustomTLSSocketFactory() 10 | * @see Platform.Base#needsCustomTLSSocketFactory() 11 | */ 12 | useRecommendation, 13 | /** 14 | * Use the system provided TLS socket factory, overriding the recommendation. 15 | */ 16 | systemProvided, 17 | /** 18 | * The SDK provided TLS socket factory will be used, enabling TLS 1.2 on supported systems. 19 | */ 20 | sdkProvided 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/locales/fetch_all_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "en-US", 11 | "name": "English", 12 | "default": true, 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "2oQPjMCL9bQkylziydLh57", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | }, 20 | { 21 | "code": "tlh", 22 | "name": "Klingon", 23 | "default": false, 24 | "fallbackCode": "en-US", 25 | "sys": { 26 | "id": "3zpZmkZrHTIekHmXgflXaV", 27 | "type": "Locale", 28 | "version": 0 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDATag.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | 4 | /** 5 | * Represents a single locale. 6 | */ 7 | public class CDATag extends CDAResource { 8 | private static final long serialVersionUID = -5710267672379169622L; 9 | 10 | String name; 11 | 12 | /** 13 | * @return human readable name of this tag. 14 | */ 15 | public String name() { 16 | return name; 17 | } 18 | 19 | /** 20 | * @return a human readable string, representing the object. 21 | */ 22 | @Override public String toString() { 23 | return "CDATag { " 24 | + "attrs = " + attrs() + ", " 25 | + "id = " + id() + ", " 26 | + "name = " + name() + " " 27 | + "}"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAMetadata.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | public class CDAMetadata implements Serializable { 7 | private static final long serialVersionUID = -2852530837647649035L; 8 | private List tags; 9 | private List concepts; 10 | 11 | public List getTags() { 12 | return tags; 13 | } 14 | 15 | public List getConcepts() { 16 | return concepts; 17 | } 18 | 19 | public void setConcepts(List concepts) { 20 | this.concepts = concepts; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "CDAMetadata{}"; 26 | } 27 | } -------------------------------------------------------------------------------- /src/test/bash/rich_text-sync_continued.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | folder="../resources/$(basename $(echo $0) | cut -d'-' -f1)" 4 | file="$(basename $0 | cut -d'-' -f2)" 5 | output="$(echo ${folder}/${file} | sed 's#sh#json#g')" 6 | 7 | mkdir --parent ${folder} 8 | touch ${output} 9 | 10 | curl --verbose \ 11 | -H 'Authorization: Bearer '${RICH_TEXT_DELIVERY_TOKEN} \ 12 | 'https://cdn.contentful.com/spaces/'${RICH_TEXT_SPACE_ID}'/environments/human-readable/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyY0wrkrEENlbsO2w5nCgCVENsOqWyHCp1nDhkzCgcOYLcKKwq1ewpTDisODf8OfR8KYw78SGkA-VsKYDhPCuXTCkMOaOgPCs8OJQBHDuyZXRUciXBnDt8KhwqvCpw' \ 13 | | sed 's/'${RICH_TEXT_SPACE_ID}'//g' \ 14 | | sed 's/'${RICH_TEXT_DELIVERY_TOKEN}'//g' \ 15 | | tee ${output} 16 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichNode.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A leaf node of the rich text hierarchy. 7 | */ 8 | public class CDARichNode implements Serializable { 9 | private String nodeType; 10 | 11 | /** 12 | * Get the original node type from the API response. 13 | * @return the node type (e.g., "embedded-entry-block", "embedded-entry-inline", etc.) 14 | */ 15 | public String getNodeType() { 16 | return nodeType; 17 | } 18 | 19 | /** 20 | * Set the node type from the API response. 21 | * @param nodeType the original node type 22 | */ 23 | public void setNodeType(String nodeType) { 24 | this.nodeType = nodeType; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/demo/content_types_fake.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": [ 11 | { 12 | "id": "name", 13 | "name": "Name", 14 | "type": "Text" 15 | } 16 | ], 17 | "name": "Fake", 18 | "sys": { 19 | "space": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "Space", 23 | "id": "cfexampleapi" 24 | } 25 | }, 26 | "type": "ContentType", 27 | "id": "fake", 28 | "revision": 2, 29 | "createdAt": "2013-06-27T22:46:12.852Z", 30 | "updatedAt": "2013-09-02T13:14:47.863Z" 31 | } 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/Logger.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | /** 4 | * Custom logger interface, used for logging network traffic. 5 | */ 6 | public interface Logger { 7 | /** 8 | * Determine the level of logging 9 | */ 10 | enum Level { 11 | /** 12 | * Do not log anything. 13 | */ 14 | NONE, 15 | 16 | /** 17 | * Do basic logging, not all requests will be logged. 18 | */ 19 | BASIC, 20 | 21 | /** 22 | * Log all requests. 23 | */ 24 | FULL 25 | } 26 | 27 | /** 28 | * Abstract method to be implemented by client. 29 | *

30 | * This method gets called, once an event needs to 31 | * 32 | * @param message to be delivered to the logger. 33 | */ 34 | void log(String message); 35 | } 36 | -------------------------------------------------------------------------------- /src/test/resources/locales/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Space", 4 | "id": "7dh3w86is8ls" 5 | }, 6 | "name": "Fallback Locales", 7 | "locales": [ 8 | { 9 | "code": "default", 10 | "default": true, 11 | "name": "default locale", 12 | "fallbackCode": null 13 | }, 14 | { 15 | "code": "first", 16 | "default": false, 17 | "name": "first locale", 18 | "fallbackCode": "inbetween" 19 | }, 20 | { 21 | "code": "inbetween", 22 | "default": false, 23 | "name": "inbetween locale", 24 | "fallbackCode": "default" 25 | }, 26 | { 27 | "code": "null", 28 | "default": false, 29 | "name": "locale with fallback set to null", 30 | "fallbackCode": null 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/locales_fallback/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Space", 4 | "id": "7dh3w86is8ls" 5 | }, 6 | "name": "Fallback Locales", 7 | "locales": [ 8 | { 9 | "code": "default", 10 | "default": true, 11 | "name": "default locale", 12 | "fallbackCode": null 13 | }, 14 | { 15 | "code": "first", 16 | "default": false, 17 | "name": "first locale", 18 | "fallbackCode": "inbetween" 19 | }, 20 | { 21 | "code": "inbetween", 22 | "default": false, 23 | "name": "inbetween locale", 24 | "fallbackCode": "default" 25 | }, 26 | { 27 | "code": "null", 28 | "default": false, 29 | "name": "locale with fallback set to null", 30 | "fallbackCode": null 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichHeading.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Defines a headline of the text. 7 | *

8 | * Can have an arbitrary level assigned, but useful probably between 1 and 6. 9 | */ 10 | public class CDARichHeading extends CDARichBlock implements Serializable { 11 | private final int level; 12 | 13 | /** 14 | * Create a heading block, describing a level elements deep nested heading. 15 | * 16 | * @param level a number indicating the level of this heading. 17 | */ 18 | public CDARichHeading(int level) { 19 | this.level = level; 20 | } 21 | 22 | /** 23 | * @return the current nesting level of this heading. 24 | */ 25 | public int getLevel() { 26 | return level; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "CodeQL Scan for GitHub Actions Workflows" 3 | 4 | on: 5 | push: 6 | branches: [master] 7 | paths: [".github/workflows/**"] 8 | pull_request: 9 | branches: [master] 10 | paths: [".github/workflows/**"] 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze GitHub Actions workflows 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Initialize CodeQL 25 | uses: github/codeql-action/init@v3 26 | with: 27 | languages: actions 28 | 29 | - name: Run CodeQL Analysis 30 | uses: github/codeql-action/analyze@v3 31 | with: 32 | category: actions 33 | -------------------------------------------------------------------------------- /src/test/resources/demo/entries_nofields.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "cfexampleapi" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "cat" 24 | } 25 | }, 26 | "id": "foo", 27 | "revision": 5, 28 | "createdAt": "2013-06-27T22:46:19.513Z", 29 | "updatedAt": "2013-09-04T09:19:39.027Z", 30 | "locale": "en-US" 31 | } 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /src/test/resources/cda/content_types_bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "name": "bar", 11 | "fields": [ 12 | { 13 | "name": "name", 14 | "id": "name", 15 | "type": "Text" 16 | } 17 | ], 18 | "displayField": "name", 19 | "sys": { 20 | "space": { 21 | "sys": { 22 | "type": "Link", 23 | "linkType": "Space", 24 | "id": "amjnj7ldpank" 25 | } 26 | }, 27 | "type": "ContentType", 28 | "id": "3lYaFZKDgQCUwWy6uEoQYi", 29 | "revision": 1, 30 | "createdAt": "2015-07-06T14:55:25.655Z", 31 | "updatedAt": "2015-07-06T14:55:25.655Z" 32 | } 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /src/test/resources/cda/content_types_foo.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "name": "foo", 11 | "fields": [ 12 | { 13 | "name": "link", 14 | "id": "link", 15 | "type": "Link", 16 | "linkType": "Entry" 17 | } 18 | ], 19 | "sys": { 20 | "space": { 21 | "sys": { 22 | "type": "Link", 23 | "linkType": "Space", 24 | "id": "amjnj7ldpank" 25 | } 26 | }, 27 | "type": "ContentType", 28 | "id": "3XN5y2aye44m0Yioko6K6k", 29 | "revision": 1, 30 | "createdAt": "2015-07-06T14:55:11.166Z", 31 | "updatedAt": "2015-07-06T14:55:11.166Z" 32 | } 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /src/test/resources/demo/entries_fake.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": { 11 | }, 12 | "sys": { 13 | "space": { 14 | "sys": { 15 | "type": "Link", 16 | "linkType": "Space", 17 | "id": "cfexampleapi" 18 | } 19 | }, 20 | "type": "Entry", 21 | "contentType": { 22 | "sys": { 23 | "type": "Link", 24 | "linkType": "ContentType", 25 | "id": "foo" 26 | } 27 | }, 28 | "id": "bar", 29 | "revision": 8, 30 | "createdAt": "2013-06-27T22:46:20.171Z", 31 | "updatedAt": "2013-11-18T15:58:02.018Z", 32 | "locale": "en-US" 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.util.Locale; 4 | 5 | /** 6 | * RuntimeException indicating a resource was not found on Contentful. 7 | */ 8 | public class CDAResourceNotFoundException extends RuntimeException { 9 | 10 | private static final long serialVersionUID = -7419778969492055048L; 11 | 12 | /** 13 | * Create a new exception 14 | * 15 | * @param resourceType the type of the resource not found (Entry,Asset,Space) 16 | * @param resourceId the actual id of the resource not found. 17 | */ 18 | public CDAResourceNotFoundException( 19 | Class resourceType, 20 | String resourceId) { 21 | super(String.format( 22 | Locale.getDefault(), "Could not find id '%s' of type '%s'.", 23 | resourceId, 24 | resourceType.getSimpleName()) 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/lib/TestResponse.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.lib; 2 | 3 | import okhttp3.Headers; 4 | 5 | public class TestResponse { 6 | private final int code; 7 | private final String fileName; 8 | private final Headers headers; 9 | 10 | TestResponse(int code, String fileName, String[] headers) { 11 | this.code = code; 12 | this.fileName = fileName; 13 | this.headers = createHeaders(headers); 14 | } 15 | 16 | private Headers createHeaders(String[] headers) { 17 | final Headers.Builder builder = new Headers.Builder(); 18 | for (final String line : headers) { 19 | builder.add(line); 20 | } 21 | return builder.build(); 22 | } 23 | 24 | public int getCode() { 25 | return code; 26 | } 27 | 28 | public String getFileName() { 29 | return fileName; 30 | } 31 | 32 | public Headers headers() { 33 | return headers; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/Constants.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.util.Locale; 4 | 5 | /** 6 | * This class holds specific constants, used throughout the sdk, not accessible by mere mortals. 7 | */ 8 | class Constants { 9 | static final Locale LOCALE = Locale.US; 10 | 11 | static final String SCHEME = "https"; 12 | 13 | static final String ENDPOINT_PREVIEW = SCHEME + "://preview.contentful.com/"; 14 | 15 | static final String ENDPOINT_PROD = SCHEME + "://cdn.contentful.com/"; 16 | 17 | static final String PATH_ASSETS = "assets"; 18 | 19 | static final String PATH_CONTENT_TYPES = "content_types"; 20 | 21 | static final String PATH_ENTRIES = "entries"; 22 | 23 | static final String PATH_LOCALES = "locales"; 24 | 25 | static final String PATH_TAGS = "tags"; 26 | 27 | static final String PATH_TAXONOMY_CONCEPTS = "taxonomy/concepts"; 28 | 29 | static final String DEFAULT_ENVIRONMENT = "master"; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/ArrayResource.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * An abstraction of CDAResources combined into one array. 8 | * 9 | * @see CDAResource 10 | */ 11 | public abstract class ArrayResource extends CDAResource { 12 | private static final long serialVersionUID = -2702554830040250962L; 13 | List items; 14 | 15 | Map assets; 16 | 17 | Map entries; 18 | 19 | /** 20 | * @return items in this resource. 21 | */ 22 | public List items() { 23 | return items; 24 | } 25 | 26 | /** 27 | * @return assets mapped by asset id (includes linked resources). 28 | */ 29 | public Map assets() { 30 | return assets; 31 | } 32 | 33 | /** 34 | * @return entries mapped by entry id (includes linked resources). 35 | */ 36 | public Map entries() { 37 | return entries; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/lib/TestCallback.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.lib; 2 | 3 | import com.contentful.java.cda.CDACallback; 4 | import com.contentful.java.cda.CDAResource; 5 | 6 | import java.util.concurrent.CountDownLatch; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class TestCallback extends CDACallback { 10 | private T result; 11 | 12 | private Throwable error; 13 | 14 | final CountDownLatch latch = new CountDownLatch(1); 15 | 16 | @Override protected void onSuccess(T result) { 17 | this.result = result; 18 | latch.countDown(); 19 | } 20 | 21 | @Override protected void onFailure(Throwable error) { 22 | this.error = error; 23 | latch.countDown(); 24 | } 25 | 26 | public TestCallback await() throws InterruptedException { 27 | latch.await(1, TimeUnit.SECONDS); 28 | return this; 29 | } 30 | 31 | public T result() { 32 | return result; 33 | } 34 | 35 | public Throwable error() { 36 | return error; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/resources/demo/assets_no_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 6, 6 | "skip": 0, 7 | "limit": 1, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "arqlnkt58eul" 16 | } 17 | }, 18 | "id": "6OpsQmtnl6uYyeUmkOiYYq", 19 | "type": "Asset", 20 | "createdAt": "2016-07-29T14:04:28.343Z", 21 | "updatedAt": "2016-07-29T14:04:28.343Z", 22 | "revision": 1, 23 | "locale": "en-US" 24 | }, 25 | "fields": { 26 | "title": "budah", 27 | "file": { 28 | "url": "//assets.contentful.com/arqlnkt58eul/6OpsQmtnl6uYyeUmkOiYYq/e4156f9d089bd539fd482554945517e4/budah.obj", 29 | "details": { 30 | "size": 932240 31 | }, 32 | "fileName": "budah-small.obj", 33 | "contentType": "application/x-tgif" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/SynchronizedSpace.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * Represents results for synchronization via the Sync API. 7 | */ 8 | public class SynchronizedSpace extends ArrayResource { 9 | private static final long serialVersionUID = 8618757744312417604L; 10 | String nextPageUrl; 11 | 12 | String nextSyncUrl; 13 | 14 | Set deletedAssets; 15 | 16 | Set deletedEntries; 17 | 18 | /** 19 | * @return url to the next sync. 20 | */ 21 | public String nextSyncUrl() { 22 | return nextSyncUrl; 23 | } 24 | 25 | /** 26 | * @return url of next page, containing more data. 27 | */ 28 | String nextPageUrl() { 29 | return nextPageUrl; 30 | } 31 | 32 | /** 33 | * @return deleted asset ids. 34 | */ 35 | public Set deletedAssets() { 36 | return deletedAssets; 37 | } 38 | 39 | /** 40 | * @return deleted entry ids. 41 | */ 42 | public Set deletedEntries() { 43 | return deletedEntries; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/resources/demo/assets_jake.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": { 11 | "title": "Jake", 12 | "file": { 13 | "fileName": "jake.png", 14 | "contentType": "image/png", 15 | "details": { 16 | "image": { 17 | "width": 100, 18 | "height": 161 19 | }, 20 | "size": 20480 21 | }, 22 | "url": "//images.contentful.com/cfexampleapi/4hlteQAXS8iS0YCMU6QMWg/2a4d826144f014109364ccf5c891d2dd/jake.png" 23 | } 24 | }, 25 | "sys": { 26 | "space": { 27 | "sys": { 28 | "type": "Link", 29 | "linkType": "Space", 30 | "id": "cfexampleapi" 31 | } 32 | }, 33 | "type": "Asset", 34 | "id": "jake", 35 | "revision": 2, 36 | "createdAt": "2013-09-02T14:56:34.260Z", 37 | "updatedAt": "2013-09-02T15:22:39.466Z", 38 | "locale": "en-US" 39 | } 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /src/test/resources/demo/sync_initial_p1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "cfexampleapi" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "1t9IbcfdCk6m04uISSsaIK" 21 | } 22 | }, 23 | "id": "5ETMRzkl9KM4omyMwKAOki", 24 | "revision": 3, 25 | "createdAt": "2014-02-21T13:42:57.752Z", 26 | "updatedAt": "2014-08-23T14:42:35.207Z" 27 | }, 28 | "fields": { 29 | "name": { 30 | "en-US": "London" 31 | }, 32 | "center": { 33 | "en-US": { 34 | "lat": 51.508515, 35 | "lon": -0.12548719999995228 36 | } 37 | } 38 | } 39 | } 40 | ], 41 | "nextPageUrl": "http://cdn.contentful.com/spaces/cfexampleapi/sync?sync_token=foo" 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichList.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Parent class for all list classes 7 | */ 8 | public class CDARichList extends CDARichBlock implements Serializable { 9 | final CharSequence decoration; 10 | 11 | /** 12 | * Create a list of the given symbols per nesting level 13 | * 14 | * @param decoration a symbol to be added for differentiation. Can be [1,A,a,I,i] for 15 | * prefixing each node with an arabic number (1., 2., …), a capitalized letter 16 | * (A., B., …), a lowercase letter (a., b., …) or roman numerals in capital 17 | * (I, II, …) or non capitalized form (i, ii, …). Alternatively unordered 18 | * symbols can be used: `*` for bullets, `-` for dashes and `⭐` for stars etc. 19 | */ 20 | public CDARichList(CharSequence decoration) { 21 | this.decoration = decoration; 22 | } 23 | 24 | /** 25 | * @return decoration for this list. 26 | */ 27 | public CharSequence getDecoration() { 28 | return decoration; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/links/sync_empty_links.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "75oea9kejr52" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "2s9novBkP2G0oasUaG8kCs" 21 | } 22 | }, 23 | "id": "3vyEoAvlkk8yE4a8gCCkiu", 24 | "revision": 1, 25 | "createdAt": "2015-07-10T11:56:10.469Z", 26 | "updatedAt": "2015-07-10T11:56:10.469Z" 27 | }, 28 | "fields": { 29 | "entry": { 30 | "en-US": null 31 | }, 32 | "asset": { 33 | "en-US": null 34 | }, 35 | "entries": { 36 | "en-US": [] 37 | }, 38 | "assets": { 39 | "en-US": [] 40 | }, 41 | "symbols": { 42 | "en-US": [] 43 | } 44 | } 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /src/test/resources/demo/sync_initial_preview_p1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "cfexampleapi" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "1t9IbcfdCk6m04uISSsaIK" 21 | } 22 | }, 23 | "id": "5ETMRzkl9KM4omyMwKAOki", 24 | "revision": 3, 25 | "createdAt": "2014-02-21T13:42:57.752Z", 26 | "updatedAt": "2014-08-23T14:42:35.207Z", 27 | "locale": "en-US" 28 | }, 29 | "fields": { 30 | "name": { 31 | "en-US": "London" 32 | }, 33 | "center": { 34 | "en-US": { 35 | "lat": 51.508515, 36 | "lon": -0.12548719999995228 37 | } 38 | } 39 | } 40 | } 41 | ], 42 | "nextPageUrl": "http://cdn.contentful.com/spaces/cfexampleapi/sync?sync_token=foo" 43 | } 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: java 3 | 4 | dist: trusty 5 | 6 | jdk: 7 | - oraclejdk8 8 | - openjdk8 9 | 10 | script: 11 | - mvn jacoco:prepare-agent test jacoco:report 12 | 13 | after_success: 14 | - .buildscript/deploy_snapshot.sh 15 | - .buildscript/integration_tests.sh 16 | 17 | env: 18 | global: 19 | - secure: ZkYu76rxDUUgoUSIz12CReNZHd2c5O2LBWCHnuQpqfJfcCXBr1J24F7WftiFXH+9Od43qHdF4hQ9Vy0vWEHSJ1hxZnCIy5yYsvqV4GJ94DIJhvNqmo48QPkA3tFUsqd48rEeBXMUsON30m89KDp1se2+l3UJaX+R27RUi47nraU= 20 | - secure: jKXD1hYVgJypaQHRSm4gshxMhB+T+UibaLDLCSfGWSaGzogDsWnPXf9qqG3jtozKHyH0BkANaaO6rRgNQNfbaklm4jopL1bdRkd47JO/nibNwuVcG07TQyCPQR6jv/WoRAkxXOGdMSwwS2+puM7FJqLarbzY7uBEJtNsZn5JT48= 21 | - secure: k2FkpJUg0FxfTeTadPc4xDA7xPmheZg6+u4DLn7kRP3bfHNG8lP57pYq/b1iDN+3VBXOkQtGttUyS1EExUubmeoQk4JwOUuePv6Ru24Mi5YoEdlkBzu2KZXV0o7aZZookYdsQTOQ/Jd576sn3oiXbAtW6n93h7h6SRCFIAG/nmc= 22 | 23 | 24 | branches: 25 | except: 26 | - gh-pages 27 | 28 | notifications: 29 | slack: 30 | - secure: Wq9lfTl+SSlfe+4OSLIINsUJZKEN0jYC07nI6PL1Sv4wZCUHmkvXNvsDj+0QLYkKkdEao2Yg3CLHSU5GdvZ2eQpY1mut6i+DxUHnICDQp6liS73UfW3KZIw7zkAMrlXYgVKtIUCUVjZFVyZ7AOxdt8Z6y5oKuXQiGC6h8vmi2Qk= 31 | -------------------------------------------------------------------------------- /src/test/resources/locales/content_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "7dh3w86is8ls" 16 | } 17 | }, 18 | "id": "sampleType", 19 | "type": "ContentType", 20 | "createdAt": "2016-06-21T08:53:55.767Z", 21 | "updatedAt": "2016-06-21T13:16:34.507Z", 22 | "revision": 5 23 | }, 24 | "displayField": "title", 25 | "name": "Sample Type", 26 | "description": "af → zu-ZA →en-US*\nne-NP → NULL\nbs_BA⁰ → en-US*\n\n(→if not found, look at the following locale)\n(* = default locale)\n(⁰ = can include empty elements)\n(NULL = No default)", 27 | "fields": [ 28 | { 29 | "id": "title", 30 | "name": "title", 31 | "type": "Symbol", 32 | "localized": true, 33 | "required": true, 34 | "disabled": false, 35 | "omitted": false 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/test/resources/locales_fallback/content_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "7dh3w86is8ls" 16 | } 17 | }, 18 | "id": "sampleType", 19 | "type": "ContentType", 20 | "createdAt": "2016-06-21T08:53:55.767Z", 21 | "updatedAt": "2016-06-21T13:16:34.507Z", 22 | "revision": 5 23 | }, 24 | "displayField": "title", 25 | "name": "Sample Type", 26 | "description": "af → zu-ZA →en-US*\nne-NP → NULL\nbs_BA⁰ → en-US*\n\n(→if not found, look at the following locale)\n(* = default locale)\n(⁰ = can include empty elements)\n(NULL = No default)", 27 | "fields": [ 28 | { 29 | "id": "title", 30 | "name": "title", 31 | "type": "Symbol", 32 | "localized": true, 33 | "required": true, 34 | "disabled": false, 35 | "omitted": false 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAService.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import io.reactivex.rxjava3.core.Flowable; 4 | import retrofit2.Response; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import retrofit2.http.QueryMap; 9 | 10 | import java.util.Map; 11 | 12 | interface CDAService { 13 | @GET("spaces/{space}") 14 | Flowable> space( 15 | @Path("space") String space); 16 | 17 | @GET("spaces/{space}/environments/{environment}/{type}") 18 | Flowable> array( 19 | @Path("space") String space, 20 | @Path("environment") String environment, 21 | @Path("type") String type, 22 | @QueryMap Map query); 23 | 24 | @GET("spaces/{space}/environments/{environment}/sync") 25 | Flowable> sync( 26 | @Path("space") String space, 27 | @Path("environment") String environment, 28 | @Query("initial") Boolean initial, 29 | @Query("sync_token") String syncToken, 30 | @Query("type") String type, 31 | @Query("content_type") String contentType, 32 | @Query("limit") Integer limit); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDATaxonomyConcept.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.Map; 6 | 7 | public class CDATaxonomyConcept extends CDAResource { 8 | private static final long serialVersionUID = -2852530837647669036L; 9 | 10 | @SerializedName("prefLabel") 11 | protected Map prefLabel; 12 | 13 | /** 14 | * @return the preferred label map containing locale-to-label mappings. 15 | */ 16 | public Map prefLabel() { 17 | return prefLabel; 18 | } 19 | 20 | /** 21 | * Get the preferred label for a specific locale. 22 | * @param locale the locale code (e.g., "en-US") 23 | * @return the label for the specified locale, or null if not found 24 | */ 25 | public String getPrefLabel(String locale) { 26 | return prefLabel != null ? prefLabel.get(locale) : null; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "CDATaxonomyConcept{" 32 | + "attrs=" + attrs + '\'' 33 | + ", prefLabel=" + prefLabel 34 | + '}'; 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichText.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | /** 8 | * A leaf element of the rich text node graph: Render a given text with the given markings. 9 | */ 10 | public class CDARichText extends CDARichNode implements Serializable { 11 | private final List marks = new ArrayList<>(); 12 | private final CharSequence text; 13 | 14 | /** 15 | * Create a text with the given marks 16 | * 17 | * @param text the text to be displayed 18 | * @param marks the marks to be used if any 19 | */ 20 | public CDARichText(CharSequence text, List marks) { 21 | if (text == null) { 22 | text = ""; 23 | } 24 | 25 | this.marks.addAll(marks); 26 | this.text = text; 27 | } 28 | 29 | /** 30 | * @return the text of this node. 31 | */ 32 | public CharSequence getText() { 33 | return text; 34 | } 35 | 36 | /** 37 | * @return the marks of this text. 38 | * 39 | * @see CDARichMark.CDARichMarkBold 40 | */ 41 | public List getMarks() { 42 | return marks; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/resources/shallow/types.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "name": "foo", 11 | "fields": [ 12 | { 13 | "name": "name", 14 | "id": "name", 15 | "type": "Symbol" 16 | }, 17 | { 18 | "name": "image", 19 | "id": "image", 20 | "type": "Link", 21 | "linkType": "Asset" 22 | }, 23 | { 24 | "name": "array", 25 | "id": "array", 26 | "type": "Array", 27 | "items": { 28 | "type": "Link", 29 | "linkType": "Asset" 30 | } 31 | } 32 | ], 33 | "description": "", 34 | "displayField": "name", 35 | "sys": { 36 | "space": { 37 | "sys": { 38 | "type": "Link", 39 | "linkType": "Space", 40 | "id": "tamu5dc7eas0" 41 | } 42 | }, 43 | "type": "ContentType", 44 | "id": "15hKDLsTB28isKMAuaQy4A", 45 | "revision": 2, 46 | "createdAt": "2015-07-14T12:18:11.976Z", 47 | "updatedAt": "2015-07-14T12:57:51.262Z" 48 | } 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /proguard-cda.cfg: -------------------------------------------------------------------------------- 1 | # contentful.java 2 | -keep class com.contentful.java.cda.** { *; } 3 | 4 | # RxJava 5 | -dontwarn sun.misc.** 6 | 7 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { 8 | long producerIndex; 9 | long consumerIndex; 10 | } 11 | 12 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { 13 | rx.internal.util.atomic.LinkedQueueNode producerNode; 14 | } 15 | 16 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { 17 | rx.internal.util.atomic.LinkedQueueNode consumerNode; 18 | } 19 | 20 | # OkHttp 21 | -keepattributes Signature 22 | -keepattributes *Annotation* 23 | -keep class okhttp3.** { *; } 24 | -keep interface okhttp3.** { *; } 25 | -dontwarn okhttp3.** 26 | 27 | # Retrofit 28 | -dontwarn retrofit2.** 29 | -keep class retrofit2.** { *; } 30 | -keepclasseswithmembers class * { 31 | @retrofit2.http.* ; 32 | } 33 | 34 | # Okio 35 | -dontwarn java.nio.file.* 36 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 37 | -dontwarn okio.** 38 | 39 | # Gson 40 | -keepattributes EnclosingMethod 41 | -keep class com.google.gson.stream.** { *; } 42 | 43 | # Android warnings 44 | -dontwarn android.support.v4.** 45 | 46 | # Kotlin 47 | -dontwarn org.junit.** 48 | -------------------------------------------------------------------------------- /src/test/bash/rich_text-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | folder="../resources/$(basename $(echo $0) | cut -d'-' -f1)" 4 | file="$(basename $0 | cut -d'-' -f2)" 5 | 6 | mkdir --parent ${folder} 7 | 8 | for id in $(curl \ 9 | --silent \ 10 | -H "Authorization: Bearer ${RICH_TEXT_DELIVERY_TOKEN}" \ 11 | "https://cdn.contentful.com/spaces/${RICH_TEXT_SPACE_ID}/environments/human-readable/entries?content_type=rich&fields.name\[match\]=simple&select=sys.id" \ 12 | | grep id | cut -d'"' -f4); do 13 | name="$(curl \ 14 | --silent \ 15 | -H "Authorization: Bearer ${RICH_TEXT_DELIVERY_TOKEN}" \ 16 | "https://cdn.contentful.com/spaces/${RICH_TEXT_SPACE_ID}/environments/human-readable/entries?content_type=rich&sys.id=${id}&select=fields.name" \ 17 | | grep name | cut -d'"' -f4)" 18 | output="$(echo ${folder}/${name}.json)" 19 | echo ${name} 20 | 21 | curl --silent \ 22 | -H "Authorization: Bearer ${RICH_TEXT_DELIVERY_TOKEN}" \ 23 | "https://cdn.contentful.com/spaces/${RICH_TEXT_SPACE_ID}/environments/human-readable/entries?sys.id=${id}" \ 24 | | sed 's/'${RICH_TEXT_SPACE_ID}'//g' \ 25 | | sed 's/'${RICH_TEXT_DELIVERY_TOKEN}'//g' \ 26 | | tee ${output} 27 | done 28 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/interceptor/ErrorInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.interceptor; 2 | 3 | import com.contentful.java.cda.CDAHttpException; 4 | 5 | import java.io.IOException; 6 | 7 | import okhttp3.Interceptor; 8 | import okhttp3.Request; 9 | import okhttp3.Response; 10 | 11 | /** 12 | * This interceptor will only be used for throwing an exception, once the server returns an error. 13 | */ 14 | public class ErrorInterceptor implements Interceptor { 15 | private final boolean logSensitiveData; 16 | 17 | public ErrorInterceptor(boolean logSensitiveData) { 18 | this.logSensitiveData = logSensitiveData; 19 | } 20 | 21 | 22 | /** 23 | * Intercepts chain to check for unsuccessful requests. 24 | * 25 | * @param chain provided by the framework to check 26 | * @return the response if no error occurred 27 | * @throws IOException will get thrown if response code is unsuccessful 28 | */ 29 | @Override public Response intercept(Chain chain) throws IOException { 30 | final Request request = chain.request(); 31 | final Response response = chain.proceed(request); 32 | 33 | if (!response.isSuccessful()) { 34 | throw new CDAHttpException(request, response, logSensitiveData); 35 | } 36 | 37 | return response; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/resources/locales_fallback/fetch_all_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 4, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "default", 11 | "default": true, 12 | "name": "default locale", 13 | "fallbackCode": null, 14 | "sys": { 15 | "id": "2oQPjMCL9bQkylziydLh57", 16 | "type": "Locale", 17 | "version": 1 18 | } 19 | }, 20 | { 21 | "code": "first", 22 | "default": false, 23 | "name": "first locale", 24 | "fallbackCode": "inbetween", 25 | "sys": { 26 | "id": "2oQPjMCL9bQkylziydLh57", 27 | "type": "Locale", 28 | "version": 1 29 | } 30 | }, 31 | { 32 | "code": "inbetween", 33 | "default": false, 34 | "name": "inbetween locale", 35 | "fallbackCode": "default", 36 | "sys": { 37 | "id": "2oQPjMCL9bQkylziydLh57", 38 | "type": "Locale", 39 | "version": 1 40 | } 41 | }, 42 | { 43 | "code": "null", 44 | "default": false, 45 | "name": "locale with fallback set to null", 46 | "fallbackCode": null, 47 | "sys": { 48 | "id": "2oQPjMCL9bQkylziydLh57", 49 | "type": "Locale", 50 | "version": 1 51 | } 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAArray.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Collection of CDA resources. 9 | */ 10 | public class CDAArray extends ArrayResource { 11 | private static final long serialVersionUID = 6596224363025698245L; 12 | int total; 13 | 14 | int skip; 15 | 16 | int limit; 17 | 18 | Includes includes; 19 | 20 | private List errors; 21 | 22 | /** 23 | * @return total number of resources (linked excluded). 24 | */ 25 | public int total() { 26 | return total; 27 | } 28 | 29 | /** 30 | * @return number of resources to be skipped. 31 | */ 32 | public int skip() { 33 | return skip; 34 | } 35 | 36 | /** 37 | * @return limit attribute. How many max resources got requested? 38 | */ 39 | public int limit() { 40 | return limit; 41 | } 42 | 43 | /** 44 | * @return a list of errors if any present 45 | */ 46 | public List getErrors() { 47 | return errors; 48 | } 49 | 50 | public void setErrors(List errors) { 51 | this.errors = errors; 52 | } 53 | 54 | static class Includes { 55 | @SerializedName("Asset") List assets; 56 | 57 | @SerializedName("Entry") List entries; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/resources/demo/sync_initial_staging_p1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "cfexampleapi" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "1t9IbcfdCk6m04uISSsaIK" 21 | } 22 | }, 23 | "id": "5ETMRzkl9KM4omyMwKAOki", 24 | "revision": 3, 25 | "createdAt": "2014-02-21T13:42:57.752Z", 26 | "updatedAt": "2014-08-23T14:42:35.207Z", 27 | "environment": { 28 | "sys": { 29 | "id": "staging", 30 | "type": "Link", 31 | "linkType": "Environment" 32 | } 33 | }, 34 | "locale": "en-US" 35 | }, 36 | "fields": { 37 | "name": { 38 | "en-US": "London" 39 | }, 40 | "center": { 41 | "en-US": { 42 | "lat": 51.508515, 43 | "lon": -0.12548719999995228 44 | } 45 | } 46 | } 47 | } 48 | ], 49 | "nextPageUrl": "http://cdn.contentful.com/spaces/cfexampleapi/sync?sync_token=foo" 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAContentTypeNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import static java.lang.String.format; 4 | import static java.util.Locale.getDefault; 5 | 6 | /** 7 | * RuntimeException indicating an entry linking to a non existing Content Type. 8 | *

9 | * A content type must be requestable from an entry in order to link the includes. 10 | */ 11 | public class CDAContentTypeNotFoundException extends RuntimeException { 12 | 13 | private static final long serialVersionUID = -5839900656195732862L; 14 | 15 | /** 16 | * Create a new exception. 17 | * 18 | * @param resourceId the actual id of the resource requested. 19 | * @param resourceType the type of the resource not found. 20 | * @param resourceTypeId the id of the content type not found. 21 | * @param throwable the cause of this exception. 22 | */ 23 | public CDAContentTypeNotFoundException( 24 | String resourceId, 25 | Class resourceType, 26 | String resourceTypeId, 27 | Throwable throwable) { 28 | super(format( 29 | getDefault(), 30 | "Could not find content type '%s' for resource with id '%s' of type '%s'.", 31 | resourceTypeId, 32 | resourceId, 33 | resourceType.getSimpleName()), 34 | throwable 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.buildscript/deploy_snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. 4 | # 5 | # Adapted from https://coderwall.com/p/9b_lfq and 6 | # http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ 7 | 8 | SLUG="contentful/contentful.java" 9 | JDK="oraclejdk8" 10 | BRANCH="master" 11 | 12 | set -e 13 | 14 | if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then 15 | echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." 16 | elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then 17 | echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." 18 | elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 19 | echo "Skipping snapshot deployment: was pull request." 20 | elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then 21 | echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." 22 | else 23 | echo "Deploying snapshot to maven ..." 24 | mvn clean source:jar javadoc:jar deploy --settings=".buildscript/settings.xml" -Dmaven.test.skip=true 25 | echo "Snapshot deployed to maven!" 26 | fi 27 | 28 | echo "Deploying snapshot to jitpack ..." 29 | curl --verbose --location "https://jitpack.io/com/github/contentful/contentful.java/${TRAVIS_BRANCH}-SNAPSHOT/" 30 | echo "Snapshot deployed to jitpack!" 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAContentType.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Represents a single content type. 7 | */ 8 | public class CDAContentType extends CDAResource { 9 | private static final long serialVersionUID = 7901798878659781364L; 10 | protected List fields; 11 | 12 | String name; 13 | 14 | String displayField; 15 | 16 | String description; 17 | 18 | /** 19 | * @return list of fields. 20 | */ 21 | public List fields() { 22 | return fields; 23 | } 24 | 25 | /** 26 | * @return name of this content type. 27 | */ 28 | public String name() { 29 | return name; 30 | } 31 | 32 | /** 33 | * @return field to be used for displaying. 34 | */ 35 | public String displayField() { 36 | return displayField; 37 | } 38 | 39 | /** 40 | * @return description of this content type. 41 | */ 42 | public String description() { 43 | return description; 44 | } 45 | 46 | /** 47 | * Convert this object into a human readable string. 48 | * 49 | * @return a string, containing id, name and description of this type. 50 | */ 51 | @Override public String toString() { 52 | return "CDAContentType{" 53 | + "id='" + id() + '\'' 54 | + ", name='" + name + '\'' 55 | + ", description='" + description + '\'' 56 | + '}'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/resources/demo/links_unresolved.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": { 11 | "name": "Happy Cat", 12 | "bestFriend": { 13 | "sys": { 14 | "type": "Link", 15 | "linkType": "Entry", 16 | "id": "nyancat" 17 | } 18 | }, 19 | "likes": [ 20 | "cheezburger" 21 | ], 22 | "color": "gray", 23 | "birthday": "2003-10-28T23:00:00+00:00", 24 | "lives": 1, 25 | "image": { 26 | "sys": { 27 | "type": "Link", 28 | "linkType": "Asset", 29 | "id": "happycat" 30 | } 31 | } 32 | }, 33 | "sys": { 34 | "space": { 35 | "sys": { 36 | "type": "Link", 37 | "linkType": "Space", 38 | "id": "cfexampleapi" 39 | } 40 | }, 41 | "type": "Entry", 42 | "contentType": { 43 | "sys": { 44 | "type": "Link", 45 | "linkType": "ContentType", 46 | "id": "cat" 47 | } 48 | }, 49 | "id": "happycat", 50 | "revision": 8, 51 | "createdAt": "2013-06-27T22:46:20.171Z", 52 | "updatedAt": "2013-11-18T15:58:02.018Z", 53 | "locale": "en-US" 54 | } 55 | } 56 | ] 57 | } -------------------------------------------------------------------------------- /src/test/resources/rich_text/content_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "rich", 19 | "type": "ContentType", 20 | "createdAt": "2018-10-17T13:19:01.984Z", 21 | "updatedAt": "2018-10-17T13:32:19.221Z", 22 | "environment": { 23 | "sys": { 24 | "id": "master", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 3 30 | }, 31 | "displayField": "name", 32 | "name": "Rich", 33 | "description": "", 34 | "fields": [ 35 | { 36 | "id": "name", 37 | "name": "name", 38 | "type": "Symbol", 39 | "localized": false, 40 | "required": false, 41 | "disabled": false, 42 | "omitted": false 43 | }, 44 | { 45 | "id": "rich", 46 | "name": "rich", 47 | "type": "RichText", 48 | "localized": false, 49 | "required": false, 50 | "disabled": false, 51 | "omitted": false 52 | } 53 | ] 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/test/resources/customs/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "name": "English (United States)", 11 | "code": "en-US", 12 | "fallbackCode": null, 13 | "default": true, 14 | "contentManagementApi": true, 15 | "contentDeliveryApi": true, 16 | "optional": false, 17 | "sys": { 18 | "type": "Locale", 19 | "id": "1g7GsNqzbuJyDbi4kkn8AE", 20 | "version": 1, 21 | "space": { 22 | "sys": { 23 | "type": "Link", 24 | "linkType": "Space", 25 | "id": "wr6gvsml8b51" 26 | } 27 | }, 28 | "environment": { 29 | "sys": { 30 | "type": "Link", 31 | "linkType": "Environment", 32 | "id": "master" 33 | } 34 | }, 35 | "createdBy": { 36 | "sys": { 37 | "type": "Link", 38 | "linkType": "User", 39 | "id": "7uJNojWP0gbuP7Pplz7Syo" 40 | } 41 | }, 42 | "createdAt": "2019-05-14T14:40:37Z", 43 | "updatedBy": { 44 | "sys": { 45 | "type": "Link", 46 | "linkType": "User", 47 | "id": "7uJNojWP0gbuP7Pplz7Syo" 48 | } 49 | }, 50 | "updatedAt": "2019-05-14T14:40:37Z" 51 | } 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/EnvironmentTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.contentful.java.cda.lib.Enqueue; 4 | 5 | import org.junit.Test; 6 | 7 | import okhttp3.mockwebserver.RecordedRequest; 8 | 9 | import static com.google.common.truth.Truth.assertThat; 10 | 11 | public class EnvironmentTest extends BaseTest { 12 | 13 | @Test @Enqueue("demo/entries.json") 14 | public void creatingDefaultClientTalksToMaster() throws InterruptedException { 15 | 16 | createBuilder() 17 | .setSpace(DEFAULT_SPACE) 18 | .build() 19 | .fetch(CDAEntry.class) 20 | .all(); 21 | 22 | assertThat(server.getRequestCount()).isEqualTo(3); 23 | 24 | final RecordedRequest recordedRequest = server.takeRequest(); 25 | assertThat(recordedRequest.getRequestUrl().toString()).contains("/environments/master/"); 26 | } 27 | 28 | @Test @Enqueue("demo/entries.json") 29 | public void settingEnvironmentUpdatesRequestURI() throws InterruptedException { 30 | createBuilder() 31 | .setSpace(DEFAULT_SPACE) 32 | .setEnvironment(STAGING_ENVIRONMENT) 33 | .build() 34 | .fetch(CDAEntry.class) 35 | .all(); 36 | 37 | assertThat(server.getRequestCount()).isEqualTo(3); 38 | 39 | final RecordedRequest recordedRequest = server.takeRequest(); 40 | assertThat(recordedRequest.getRequestUrl().toString()).contains("/environments/" + STAGING_ENVIRONMENT + "/"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAEntry.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * The class represents a basic entry in the space. 7 | */ 8 | public class CDAEntry extends LocalizedResource { 9 | private static final long serialVersionUID = 5902790363045498307L; 10 | protected CDAContentType contentType; 11 | @SerializedName("metadata") 12 | private CDAMetadata metadata; 13 | 14 | /** 15 | * @return the metadata set. 16 | */ 17 | public CDAMetadata metadata() { 18 | return metadata; 19 | } 20 | 21 | /** 22 | * @return the contentType set. 23 | */ 24 | public CDAContentType contentType() { 25 | return contentType; 26 | } 27 | 28 | /** 29 | * Set the contentType of this entry. 30 | * @param contentType the type to be set. 31 | */ 32 | protected void setContentType(CDAContentType contentType) { 33 | this.contentType = contentType; 34 | } 35 | 36 | public CDAMetadata getMetadata() { 37 | return metadata; 38 | } 39 | 40 | public void setMetadata(CDAMetadata metadata) { 41 | this.metadata = metadata; 42 | } 43 | 44 | /** 45 | * Create a human readable string of this object. 46 | * @return a string, containing the id of this content type. 47 | */ 48 | @Override public String toString() { 49 | return "CDAEntry{" 50 | + "id='" + id() + '\'' 51 | + "metadata='" + metadata() + '\'' 52 | + '}'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/ResourceDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.google.gson.JsonDeserializationContext; 4 | import com.google.gson.JsonDeserializer; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParseException; 7 | 8 | import java.lang.reflect.Type; 9 | import java.util.Collections; 10 | 11 | import static com.contentful.java.cda.CDAType.ASSET; 12 | import static com.contentful.java.cda.CDAType.ENTRY; 13 | import static com.contentful.java.cda.Constants.LOCALE; 14 | import static com.contentful.java.cda.Util.classForType; 15 | 16 | final class ResourceDeserializer implements JsonDeserializer { 17 | @Override public CDAResource deserialize(JsonElement json, Type classType, 18 | JsonDeserializationContext context) throws JsonParseException { 19 | CDAType cdaType = extractType(json); 20 | CDAResource result = context.deserialize(json, classForType(cdaType)); 21 | if (ASSET.equals(cdaType) || ENTRY.equals(cdaType)) { 22 | LocalizedResource localized = (LocalizedResource) result; 23 | if (localized.fields == null) { 24 | localized.fields = Collections.emptyMap(); 25 | } 26 | } 27 | return result; 28 | } 29 | 30 | private CDAType extractType(JsonElement json) { 31 | String type = json.getAsJsonObject().get("sys").getAsJsonObject() 32 | .get("type") 33 | .getAsString(); 34 | 35 | return CDAType.valueOf(type.toUpperCase(LOCALE)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/Cache.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | class Cache { 7 | private List locales; 8 | 9 | protected CDALocale defaultLocale; 10 | 11 | private Map types; 12 | 13 | private final Object localesLock = new Object(); 14 | 15 | private final Object typesLock = new Object(); 16 | 17 | List locales() { 18 | return locales; 19 | } 20 | 21 | protected CDALocale defaultLocale() { 22 | return defaultLocale; 23 | } 24 | 25 | void setLocales(List locales) { 26 | synchronized (localesLock) { 27 | this.locales = locales; 28 | 29 | updateDefaultLocale(); 30 | } 31 | } 32 | 33 | void updateDefaultLocale() { 34 | if (this.locales != null) { 35 | for (final CDALocale locale : this.locales) { 36 | if (locale.isDefaultLocale()) { 37 | this.defaultLocale = locale; 38 | } 39 | } 40 | } 41 | } 42 | 43 | Map types() { 44 | synchronized (typesLock) { 45 | return types; 46 | } 47 | } 48 | 49 | void setTypes(Map types) { 50 | synchronized (typesLock) { 51 | this.types = types; 52 | } 53 | } 54 | 55 | void clear() { 56 | synchronized (localesLock) { 57 | locales = null; 58 | defaultLocale = null; 59 | } 60 | 61 | synchronized (typesLock) { 62 | types = null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDALocale.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Represents a single locale. 7 | */ 8 | public class CDALocale extends CDAResource { 9 | private static final long serialVersionUID = -5710267672379169621L; 10 | String code; 11 | 12 | String name; 13 | 14 | @SerializedName("fallbackCode") 15 | String fallbackLocaleCode; 16 | 17 | @SerializedName("default") 18 | boolean defaultLocale; 19 | 20 | /** 21 | * @return code of this locale. ('en-US' or similar). 22 | */ 23 | public String code() { 24 | return code; 25 | } 26 | 27 | /** 28 | * @return human readable name of this locale. 29 | */ 30 | public String name() { 31 | return name; 32 | } 33 | 34 | /** 35 | * @return the code of a locale to be used for falling back. 36 | */ 37 | public String fallbackLocaleCode() { 38 | return fallbackLocaleCode; 39 | } 40 | 41 | /** 42 | * @return true if this is the default locale. 43 | */ 44 | public boolean isDefaultLocale() { 45 | return defaultLocale; 46 | } 47 | 48 | /** 49 | * @return a human readable string, representing the object. 50 | */ 51 | @Override public String toString() { 52 | return "CDALocale { " + super.toString() + " " 53 | + "code = " + code() + ", " 54 | + "defaultLocale = " + isDefaultLocale() + ", " 55 | + "fallbackLocaleCode = " + fallbackLocaleCode() + ", " 56 | + "name = " + name() + " " 57 | + "}"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_text.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "ybxKk5Avzqam6KymAOgIG", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:26.926Z", 21 | "updatedAt": "2018-10-17T13:32:26.926Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 1, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_text", 41 | "rich": { 42 | "content": [ 43 | { 44 | "data": {}, 45 | "content": [ 46 | { 47 | "marks": [], 48 | "value": "This is some simple text", 49 | "nodeType": "text" 50 | } 51 | ], 52 | "nodeType": "paragraph" 53 | } 54 | ], 55 | "nodeType": "document" 56 | } 57 | } 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/interceptor/HeaderInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.interceptor; 2 | 3 | import java.io.IOException; 4 | 5 | import okhttp3.Interceptor; 6 | import okhttp3.Request; 7 | import okhttp3.Response; 8 | 9 | /** 10 | * This class adds custom headers to all requests it intercepts. 11 | */ 12 | public class HeaderInterceptor implements Interceptor { 13 | private final String name; 14 | private final String value; 15 | 16 | /** 17 | * Create an arbitrary header adding interceptor. 18 | * 19 | * @param name of the header to be used. 20 | * @param value value of the new header. 21 | */ 22 | public HeaderInterceptor(String name, String value) { 23 | this.name = name; 24 | this.value = value; 25 | } 26 | 27 | /** 28 | * Method called by framework, to enrich current request chain with the header information 29 | * requested. 30 | * 31 | * @param chain the execution chain for the request. 32 | * @return the response received. 33 | * @throws IOException in case of failure down the line. 34 | */ 35 | @Override public Response intercept(Chain chain) throws IOException { 36 | final Request request = chain.request(); 37 | 38 | return chain.proceed(request.newBuilder() 39 | .addHeader(name, value) 40 | .build()); 41 | } 42 | 43 | /** 44 | * @return the name of this header. 45 | */ 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | /** 51 | * @return the value of this header. 52 | */ 53 | public String getValue() { 54 | return value; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/resources/arrays/content_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "name": "parent", 11 | "fields": [ 12 | { 13 | "name": "child", 14 | "id": "child", 15 | "type": "Link", 16 | "linkType": "Entry", 17 | "localized": false 18 | } 19 | ], 20 | "description": "", 21 | "sys": { 22 | "space": { 23 | "sys": { 24 | "type": "Link", 25 | "linkType": "Space", 26 | "id": "dc9j5zkjikxm" 27 | } 28 | }, 29 | "type": "ContentType", 30 | "id": "Jm9AuzgH8OyocaMQSMwKC", 31 | "revision": 1, 32 | "createdAt": "2015-10-22T20:52:53.122Z", 33 | "updatedAt": "2015-10-22T20:52:53.122Z" 34 | } 35 | }, 36 | { 37 | "name": "child", 38 | "fields": [ 39 | { 40 | "name": "name", 41 | "id": "name", 42 | "type": "Symbol", 43 | "localized": false 44 | } 45 | ], 46 | "description": "", 47 | "displayField": "name", 48 | "sys": { 49 | "space": { 50 | "sys": { 51 | "type": "Link", 52 | "linkType": "Space", 53 | "id": "dc9j5zkjikxm" 54 | } 55 | }, 56 | "type": "ContentType", 57 | "id": "71tYS0GavSkUMsokUWmAW4", 58 | "revision": 1, 59 | "createdAt": "2015-10-22T20:53:20.595Z", 60 | "updatedAt": "2015-10-22T20:53:20.595Z" 61 | } 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/test/resources/links_invalid/content_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "name": "child", 11 | "fields": [ 12 | { 13 | "name": "name", 14 | "id": "name", 15 | "type": "Symbol", 16 | "localized": false 17 | } 18 | ], 19 | "description": "", 20 | "displayField": "name", 21 | "sys": { 22 | "space": { 23 | "sys": { 24 | "type": "Link", 25 | "linkType": "Space", 26 | "id": "dc9j5zkjikxm" 27 | } 28 | }, 29 | "type": "ContentType", 30 | "id": "71tYS0GavSkUMsokUWmAW4", 31 | "revision": 1, 32 | "createdAt": "2015-10-22T20:53:20.595Z", 33 | "updatedAt": "2015-10-22T20:53:20.595Z" 34 | } 35 | }, 36 | { 37 | "name": "parent", 38 | "fields": [ 39 | { 40 | "name": "child", 41 | "id": "child", 42 | "type": "Link", 43 | "linkType": "Entry", 44 | "localized": true 45 | } 46 | ], 47 | "description": "", 48 | "sys": { 49 | "space": { 50 | "sys": { 51 | "type": "Link", 52 | "linkType": "Space", 53 | "id": "dc9j5zkjikxm" 54 | } 55 | }, 56 | "type": "ContentType", 57 | "id": "59TAIUBW5OaOSS4WgE8M4s", 58 | "revision": 1, 59 | "createdAt": "2015-10-28T17:47:44.061Z", 60 | "updatedAt": "2015-10-28T17:47:44.061Z" 61 | } 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/test/resources/shallow/update.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "tamu5dc7eas0" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "15hKDLsTB28isKMAuaQy4A" 21 | } 22 | }, 23 | "id": "2k5aHpfw7m0waMKYksC2Ww", 24 | "revision": 3, 25 | "createdAt": "2015-07-14T12:58:05.066Z", 26 | "updatedAt": "2015-07-14T12:59:11.589Z" 27 | }, 28 | "fields": { 29 | "name": { 30 | "en-US": "foo" 31 | }, 32 | "image": { 33 | "en-US": { 34 | "sys": { 35 | "type": "Link", 36 | "linkType": "Asset", 37 | "id": "m5pDQF5IRiIicMmg2ukmQ" 38 | } 39 | } 40 | }, 41 | "array": { 42 | "en-US": [ 43 | { 44 | "sys": { 45 | "type": "Link", 46 | "linkType": "Asset", 47 | "id": "m5pDQF5IRiIicMmg2ukmQ" 48 | } 49 | } 50 | ] 51 | } 52 | } 53 | } 54 | ], 55 | "nextSyncUrl": "https://cdn.contentful.com/spaces/tamu5dc7eas0/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyYRw4xSScOWb8KVfyrDs8K7RgHCnsOLwpjDu8KMw6VsCcKnQcO9w6zDlAxqRXhJNsKZBsOsw7LCv8KQw4vCoENqw7rDtGhGMsOCw4xuEcKkw4JrAMOww7HCp8OLYy_CpsKcKQ" 56 | } -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_text_code.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "6VkkamQ5zO4qWIugKkWowK", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:33.090Z", 21 | "updatedAt": "2019-01-04T14:05:26.322Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 3, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_text_code", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [ 50 | { 51 | "type": "code" 52 | } 53 | ], 54 | "value": "This is code", 55 | "nodeType": "text" 56 | } 57 | ], 58 | "nodeType": "paragraph" 59 | } 60 | ], 61 | "nodeType": "document" 62 | } 63 | } 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_text_bold.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "6VTkaiufQck4wyMwCu4u4E", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:31.957Z", 21 | "updatedAt": "2019-01-04T14:09:56.326Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 2, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_text_bold", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [ 50 | { 51 | "type": "bold" 52 | } 53 | ], 54 | "value": "This is bold text", 55 | "nodeType": "text" 56 | } 57 | ], 58 | "nodeType": "paragraph" 59 | } 60 | ], 61 | "nodeType": "document" 62 | } 63 | } 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_text_italic.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "1eIWMi8RNEO0ii4OsAUeow", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:33.252Z", 21 | "updatedAt": "2019-01-04T14:09:14.741Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 2, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_text_italic", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [ 50 | { 51 | "type": "italic" 52 | } 53 | ], 54 | "value": "This is italic text", 55 | "nodeType": "text" 56 | } 57 | ], 58 | "nodeType": "paragraph" 59 | } 60 | ], 61 | "nodeType": "document" 62 | } 63 | } 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAField.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | /** Represents a single content type field. */ 8 | public class CDAField implements Serializable { 9 | private static final long serialVersionUID = -2852530837647669035L; 10 | String name; 11 | 12 | protected String id; 13 | 14 | protected String type; 15 | 16 | String linkType; 17 | 18 | boolean disabled; 19 | 20 | boolean required; 21 | 22 | boolean localized; 23 | 24 | Map items; 25 | 26 | List> validations; 27 | 28 | /** @return name of this content type. */ 29 | public String name() { 30 | return name; 31 | } 32 | 33 | /** @return the id of this object. */ 34 | public String id() { 35 | return id; 36 | } 37 | 38 | /** @return the type. **/ 39 | public String type() { 40 | return type; 41 | } 42 | 43 | /** @return the link type. **/ 44 | public String linkType() { 45 | return linkType; 46 | } 47 | 48 | /** @return true if this object is disabled. */ 49 | public boolean isDisabled() { 50 | return disabled; 51 | } 52 | 53 | /** @return true if this object is required. */ 54 | public boolean isRequired() { 55 | return required; 56 | } 57 | 58 | /** @return true if this object is localized. */ 59 | public boolean isLocalized() { 60 | return localized; 61 | } 62 | 63 | /** @return a map of items, this field contains. */ 64 | public Map items() { 65 | return items; 66 | } 67 | 68 | /** @return a map of validations, defined on this object. */ 69 | public List> validations() { 70 | return validations; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_text_underline.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "42c9Fb0T0As26K0W0E48U2", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:32.829Z", 21 | "updatedAt": "2019-01-04T15:07:25.350Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 3, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_text_underline", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [ 50 | { 51 | "type": "underline" 52 | } 53 | ], 54 | "value": "This is some underlined text", 55 | "nodeType": "text" 56 | } 57 | ], 58 | "nodeType": "paragraph" 59 | } 60 | ], 61 | "nodeType": "document" 62 | } 63 | } 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDACallback.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import io.reactivex.rxjava3.disposables.Disposable; 4 | 5 | /** 6 | * Communicates responses from a server or offline requests. One and only one method will be 7 | * invoked in response to a given request. 8 | * 9 | * @param expected response type. 10 | */ 11 | public abstract class CDACallback { 12 | private final Object lock = new Object(); 13 | 14 | private boolean cancelled; 15 | 16 | private Disposable disposable; 17 | 18 | /** 19 | * Successful response. 20 | * 21 | * @param result the result of this operation. 22 | */ 23 | protected abstract void onSuccess(T result); 24 | 25 | /** 26 | * Invoked when a network or unexpected exception occurred during the HTTP request. 27 | * 28 | * @param error the error occurred. 29 | */ 30 | protected void onFailure(Throwable error) { 31 | throw new IllegalStateException(error); 32 | } 33 | 34 | /** 35 | * @return true in case {@link #cancel()} was called. 36 | */ 37 | public boolean isCancelled() { 38 | synchronized (lock) { 39 | return cancelled; 40 | } 41 | } 42 | 43 | /** 44 | * Cancels the subscription for this callback, onFailure()/onSuccess() methods will not be 45 | * called. 46 | */ 47 | public void cancel() { 48 | synchronized (lock) { 49 | cancelled = true; 50 | unsubscribe(); 51 | } 52 | } 53 | 54 | void setSubscription(Disposable disposable) { 55 | synchronized (lock) { 56 | this.disposable = disposable; 57 | } 58 | } 59 | 60 | void unsubscribe() { 61 | synchronized (lock) { 62 | if (disposable != null) { 63 | disposable.dispose(); 64 | disposable = null; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/resources/cda/entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "amjnj7ldpank" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "3XN5y2aye44m0Yioko6K6k" 24 | } 25 | }, 26 | "id": "3UpazZmO8g8iI0iWAMmGMS", 27 | "revision": 1, 28 | "createdAt": "2015-07-06T14:55:51.686Z", 29 | "updatedAt": "2015-07-06T14:55:51.686Z", 30 | "locale": "en-US" 31 | }, 32 | "fields": { 33 | "link": { 34 | "sys": { 35 | "type": "Link", 36 | "linkType": "Entry", 37 | "id": "1y5CUd7lwMQaI2micsAWIO" 38 | } 39 | } 40 | } 41 | }, 42 | { 43 | "sys": { 44 | "space": { 45 | "sys": { 46 | "type": "Link", 47 | "linkType": "Space", 48 | "id": "amjnj7ldpank" 49 | } 50 | }, 51 | "type": "Entry", 52 | "contentType": { 53 | "sys": { 54 | "type": "Link", 55 | "linkType": "ContentType", 56 | "id": "3lYaFZKDgQCUwWy6uEoQYi" 57 | } 58 | }, 59 | "id": "1y5CUd7lwMQaI2micsAWIO", 60 | "revision": 2, 61 | "createdAt": "2015-07-06T14:55:41.548Z", 62 | "updatedAt": "2015-07-07T12:37:23.236Z", 63 | "locale": "en-US" 64 | }, 65 | "fields": { 66 | "name": "bar" 67 | } 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/lib/EnqueueResponseRule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.lib; 2 | 3 | import com.contentful.java.cda.BaseTest; 4 | 5 | import org.junit.rules.MethodRule; 6 | import org.junit.runners.model.FrameworkMethod; 7 | import org.junit.runners.model.Statement; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.List; 12 | 13 | public class EnqueueResponseRule implements MethodRule { 14 | @Override public Statement apply(Statement statement, FrameworkMethod method, Object o) { 15 | Enqueue enqueue = method.getAnnotation(Enqueue.class); 16 | if (enqueue != null) { 17 | if (!(o instanceof BaseTest)) { 18 | throw new RuntimeException("Test class must extend " 19 | + BaseTest.class.getName() 20 | + "when using @" 21 | + Enqueue.class.getSimpleName()); 22 | } 23 | List responses = new ArrayList<>(enqueueToTestResponse(enqueue)); 24 | ((BaseTest) o).setResponseQueue(responses); 25 | } 26 | return statement; 27 | } 28 | 29 | private Collection enqueueToTestResponse(Enqueue enqueue) { 30 | final List responses 31 | = new ArrayList<>( 32 | enqueue.complex().length 33 | + enqueue.value().length); 34 | 35 | for (final EnqueueResponse response : enqueue.complex()) { 36 | responses.add(new TestResponse(response.code(), response.fileName(), response.headers())); 37 | } 38 | 39 | for (final String response : enqueue.defaults()) { 40 | responses.add(new TestResponse(200, response, new String[]{})); 41 | } 42 | 43 | for (final String response : enqueue.value()) { 44 | responses.add(new TestResponse(200, response, new String[]{})); 45 | } 46 | 47 | return responses; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/resources/content_types/populate_cache_last_entry.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "6kcg7gal4146" 16 | } 17 | }, 18 | "id": "151", 19 | "type": "Entry", 20 | "createdAt": "2017-08-09T09:43:01.771Z", 21 | "updatedAt": "2017-08-09T09:43:22.844Z", 22 | "createdBy": { 23 | "sys": { 24 | "type": "Link", 25 | "linkType": "User", 26 | "id": "7uJNojWP0gbuP7Pplz7Syo" 27 | } 28 | }, 29 | "updatedBy": { 30 | "sys": { 31 | "type": "Link", 32 | "linkType": "User", 33 | "id": "7uJNojWP0gbuP7Pplz7Syo" 34 | } 35 | }, 36 | "publishedCounter": 1, 37 | "version": 18, 38 | "publishedBy": { 39 | "sys": { 40 | "type": "Link", 41 | "linkType": "User", 42 | "id": "7uJNojWP0gbuP7Pplz7Syo" 43 | } 44 | }, 45 | "publishedVersion": 17, 46 | "firstPublishedAt": "2017-08-09T09:43:22.808Z", 47 | "publishedAt": "2017-08-09T09:43:22.808Z", 48 | "contentType": { 49 | "sys": { 50 | "type": "Link", 51 | "linkType": "ContentType", 52 | "id": "151" 53 | } 54 | } 55 | }, 56 | "fields": { 57 | "first": { 58 | "en-US": "this is the end!" 59 | }, 60 | "second": { 61 | "en-US": "Some more text" 62 | }, 63 | "third": { 64 | "en-US": "last" 65 | } 66 | } 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_horizontal_rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "2pcmZPg5HS4Ca0gme600IW", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:34.679Z", 21 | "updatedAt": "2018-10-17T13:32:34.679Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 1, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_horizontal_rule", 41 | "rich": { 42 | "content": [ 43 | { 44 | "data": {}, 45 | "content": [ 46 | { 47 | "marks": [], 48 | "value": "", 49 | "nodeType": "text" 50 | } 51 | ], 52 | "nodeType": "hr" 53 | }, 54 | { 55 | "data": {}, 56 | "content": [ 57 | { 58 | "marks": [], 59 | "value": "", 60 | "nodeType": "text" 61 | } 62 | ], 63 | "nodeType": "paragraph" 64 | } 65 | ], 66 | "nodeType": "document" 67 | } 68 | } 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/CallbackTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.contentful.java.cda.lib.Enqueue; 4 | import com.contentful.java.cda.lib.TestCallback; 5 | 6 | import org.junit.Test; 7 | 8 | import java.util.concurrent.CountDownLatch; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import okhttp3.mockwebserver.MockResponse; 12 | 13 | import static com.google.common.truth.Truth.assertThat; 14 | import static org.junit.Assert.fail; 15 | 16 | public class CallbackTest extends BaseTest { 17 | @Test 18 | @Enqueue("demo/entries_nyancat.json") 19 | public void fetchEntryAsync() throws InterruptedException { 20 | assertCallback( 21 | client.fetch(CDAEntry.class).one("nyancat", new TestCallback<>()) 22 | .await()); 23 | } 24 | 25 | @Test 26 | public void onFailure() throws InterruptedException { 27 | server.enqueue(new MockResponse().setStatus("404")); 28 | try { 29 | client.fetchSpace(new TestCallback<>()).await(); 30 | } catch (Exception e) { 31 | assertThat(e.getMessage()).isEqualTo("Cannot log to a null logger. Please set either logLevel to None, or do set a Logger"); 32 | throw e; 33 | } 34 | } 35 | 36 | @Test 37 | @Enqueue("array_empty.json") 38 | public void cancel() throws InterruptedException { 39 | CDACallback callback = new CDACallback() { 40 | @Override protected void onSuccess(CDAArray result) { 41 | fail("Callback should not be invoked."); 42 | } 43 | 44 | @Override protected void onFailure(Throwable error) { 45 | fail("Callback should not be invoked."); 46 | } 47 | }; 48 | 49 | CountDownLatch latch = new CountDownLatch(1); 50 | callback.cancel(); 51 | client.fetch(CDAEntry.class).all(callback); 52 | assertThat(callback.isCancelled()).isTrue(); 53 | latch.await(1, TimeUnit.SECONDS); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAResource.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | import java.util.Map; 7 | 8 | import static com.contentful.java.cda.Constants.LOCALE; 9 | 10 | /** 11 | * This class represents a basic resource, serving as a base class 12 | * for CDAContentType, CDASpace and, indirectly, for CDAEntry. 13 | */ 14 | public abstract class CDAResource implements Serializable { 15 | private static final long serialVersionUID = -160701290783423915L; 16 | @SerializedName("sys") 17 | protected Map attrs; 18 | 19 | /** 20 | * @return a string representing this object's id. 21 | */ 22 | public String id() { 23 | return getAttribute("id"); 24 | } 25 | 26 | /** 27 | * @return the type of this resource. 28 | */ 29 | public CDAType type() { 30 | String type = getAttribute("type"); 31 | return CDAType.valueOf(type.toUpperCase(LOCALE)); 32 | } 33 | 34 | /** 35 | * @return all of the attributes, this object holds. 36 | */ 37 | public Map attrs() { 38 | return attrs; 39 | } 40 | 41 | /** 42 | * Retrieve a specific attribute of this resource. 43 | * 44 | * @param key a string key associated with the value to be retrieved. 45 | * @param the type of the attribute to be retrieved. 46 | * @return the actual value of the attribute, or null, if there the key was not found. 47 | */ 48 | @SuppressWarnings("unchecked") 49 | public T getAttribute(String key) { 50 | return (T) attrs.get(key); 51 | } 52 | 53 | 54 | /** 55 | * @return a human readable string, representing the object. 56 | */ 57 | @Override public String toString() { 58 | return "CDAResource { " 59 | + "attrs = " + attrs() + ", " 60 | + "id = " + id() + ", " 61 | + "type = " + type() + " " 62 | + "}"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/resources/arrays/entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "dc9j5zkjikxm" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "Jm9AuzgH8OyocaMQSMwKC" 24 | } 25 | }, 26 | "id": "7Avw18DWveMI60a0WWwyCi", 27 | "revision": 1, 28 | "createdAt": "2015-10-22T20:54:34.968Z", 29 | "updatedAt": "2015-10-22T20:54:34.968Z", 30 | "locale": "en-US" 31 | }, 32 | "fields": { 33 | "child": { 34 | "sys": { 35 | "type": "Link", 36 | "linkType": "Entry", 37 | "id": "phJLkn1SHmUimqUWuEOeG" 38 | } 39 | } 40 | } 41 | } 42 | ], 43 | "includes": { 44 | "Entry": [ 45 | { 46 | "sys": { 47 | "space": { 48 | "sys": { 49 | "type": "Link", 50 | "linkType": "Space", 51 | "id": "dc9j5zkjikxm" 52 | } 53 | }, 54 | "type": "Entry", 55 | "contentType": { 56 | "sys": { 57 | "type": "Link", 58 | "linkType": "ContentType", 59 | "id": "71tYS0GavSkUMsokUWmAW4" 60 | } 61 | }, 62 | "id": "phJLkn1SHmUimqUWuEOeG", 63 | "revision": 1, 64 | "createdAt": "2015-10-22T20:54:26.244Z", 65 | "updatedAt": "2015-10-22T20:54:26.244Z", 66 | "locale": "en-US" 67 | }, 68 | "fields": { 69 | "name": "child" 70 | } 71 | } 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/SyncTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import org.junit.Test; 4 | 5 | import static com.google.common.truth.Truth.assertThat; 6 | 7 | public class SyncTypeTest { 8 | 9 | @Test 10 | public void testEntries() { 11 | final SyncType subject = SyncType.allEntries(); 12 | 13 | assertThat(subject).isNotNull(); 14 | assertThat(subject.getName()).isEqualTo("Entry"); 15 | assertThat(subject.getContentType()).isNull(); 16 | } 17 | 18 | @Test 19 | public void testAssets() { 20 | final SyncType subject = SyncType.allAssets(); 21 | 22 | assertThat(subject).isNotNull(); 23 | assertThat(subject.getName()).isEqualTo("Asset"); 24 | assertThat(subject.getContentType()).isNull(); 25 | } 26 | 27 | @Test 28 | public void testDeleted() { 29 | final SyncType subject = SyncType.onlyDeletion(); 30 | 31 | assertThat(subject).isNotNull(); 32 | assertThat(subject.getName()).isEqualTo("Deletion"); 33 | assertThat(subject.getContentType()).isNull(); 34 | } 35 | 36 | @Test 37 | public void testDeletedEntries() { 38 | final SyncType subject = SyncType.onlyDeletedEntries(); 39 | 40 | assertThat(subject).isNotNull(); 41 | assertThat(subject.getName()).isEqualTo("DeletedEntry"); 42 | assertThat(subject.getContentType()).isNull(); 43 | } 44 | 45 | @Test 46 | public void testDeletedAssets() { 47 | final SyncType subject = SyncType.onlyDeletedAssets(); 48 | 49 | assertThat(subject).isNotNull(); 50 | assertThat(subject.getName()).isEqualTo("DeletedAsset"); 51 | assertThat(subject.getContentType()).isNull(); 52 | } 53 | 54 | @Test 55 | public void testEntriesWithType() { 56 | final SyncType subject = SyncType.onlyEntriesOfType("cat"); 57 | 58 | assertThat(subject).isNotNull(); 59 | assertThat(subject.getName()).isEqualTo("Entry"); 60 | assertThat(subject.getContentType()).isEqualTo("cat"); 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_headline_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "52TyxOtKLCQu6mu8aACYKa", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:27.618Z", 21 | "updatedAt": "2019-01-04T14:13:18.764Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 2, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_headline_5", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "Headline of level 5.", 51 | "nodeType": "text" 52 | } 53 | ], 54 | "nodeType": "heading-5" 55 | }, 56 | { 57 | "data": {}, 58 | "content": [ 59 | { 60 | "data": {}, 61 | "marks": [], 62 | "value": "", 63 | "nodeType": "text" 64 | } 65 | ], 66 | "nodeType": "paragraph" 67 | } 68 | ], 69 | "nodeType": "document" 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/ResourceFactoryUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.MockitoJUnitRunner; 8 | 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.concurrent.Executor; 12 | 13 | import retrofit2.Response; 14 | 15 | import static com.google.common.truth.Truth.assertThat; 16 | import static org.mockito.Mockito.mock; 17 | import static org.mockito.Mockito.when; 18 | 19 | @RunWith(MockitoJUnitRunner.class) 20 | public class ResourceFactoryUnitTest { 21 | 22 | CDAClient client; 23 | 24 | @Mock 25 | public Cache cache; 26 | 27 | @Before public void setup() { 28 | when(cache.defaultLocale()).thenReturn(mock(CDALocale.class)); 29 | 30 | client = new CDAClient( 31 | cache, 32 | mock(Executor.class), 33 | mock(CDAService.class), 34 | mock(CDAClient.Builder.class)); 35 | } 36 | 37 | @Test 38 | public void testPreserveOrderOfElements() { 39 | final CDAArray inputArray = new CDAArray(); 40 | inputArray.items = new ArrayList<>(); 41 | for (int i = 0; i < 23; ++i) { 42 | inputArray.items().add(createCdaEntry(String.format("%03d", i))); 43 | } 44 | 45 | final Response inputArrayResponse = Response.success(inputArray); 46 | final CDAArray outputArray = ResourceFactory.array(inputArrayResponse, client); 47 | 48 | assertThat(outputArray.entries().values()).containsAllIn(inputArray.items); 49 | } 50 | 51 | private CDAEntry createCdaEntry(String id) { 52 | final CDAEntry entry = new CDAEntry(); 53 | entry.fields = new HashMap<>(); 54 | entry.attrs = new HashMap<>(); 55 | entry.attrs.put("type", CDAType.ENTRY.name()); 56 | entry.attrs.put("id", id); 57 | entry.setContentType(new CDAContentType()); 58 | entry.contentType().fields = new ArrayList<>(); 59 | return entry; 60 | } 61 | } -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_headline_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "66zfk6zIgoGGOa6kGeWqoi", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:29.327Z", 21 | "updatedAt": "2019-01-04T14:11:42.603Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 2, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_headline_2", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "This is headline level 2", 51 | "nodeType": "text" 52 | } 53 | ], 54 | "nodeType": "heading-2" 55 | }, 56 | { 57 | "data": {}, 58 | "content": [ 59 | { 60 | "data": {}, 61 | "marks": [], 62 | "value": "", 63 | "nodeType": "text" 64 | } 65 | ], 66 | "nodeType": "paragraph" 67 | } 68 | ], 69 | "nodeType": "document" 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_headline_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "2PuzK2bTpCIuQA6gc6a4sq", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:28.884Z", 21 | "updatedAt": "2019-01-04T14:12:04.434Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 2, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_headline_3", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "This is heading level 3", 51 | "nodeType": "text" 52 | } 53 | ], 54 | "nodeType": "heading-3" 55 | }, 56 | { 57 | "data": {}, 58 | "content": [ 59 | { 60 | "data": {}, 61 | "marks": [], 62 | "value": "", 63 | "nodeType": "text" 64 | } 65 | ], 66 | "nodeType": "paragraph" 67 | } 68 | ], 69 | "nodeType": "document" 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_headline_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "4TDbGOxMCsikyccqyMUWAc", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:28.296Z", 21 | "updatedAt": "2019-01-04T14:12:54.867Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 2, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_headline_4", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "Heading 4 looks like this", 51 | "nodeType": "text" 52 | } 53 | ], 54 | "nodeType": "heading-4" 55 | }, 56 | { 57 | "data": {}, 58 | "content": [ 59 | { 60 | "data": {}, 61 | "marks": [], 62 | "value": "", 63 | "nodeType": "text" 64 | } 65 | ], 66 | "nodeType": "paragraph" 67 | } 68 | ], 69 | "nodeType": "document" 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_headline_6.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "2s3r0fFqosWGCSWIcWoyiU", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:30.025Z", 21 | "updatedAt": "2019-01-04T14:12:32.002Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 2, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_headline_6", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "This is heading is level 6.", 51 | "nodeType": "text" 52 | } 53 | ], 54 | "nodeType": "heading-6" 55 | }, 56 | { 57 | "data": {}, 58 | "content": [ 59 | { 60 | "data": {}, 61 | "marks": [], 62 | "value": "", 63 | "nodeType": "text" 64 | } 65 | ], 66 | "nodeType": "paragraph" 67 | } 68 | ], 69 | "nodeType": "document" 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_headline_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "4vg1cRFHtKaAgWYaAEUu6K", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:30.252Z", 21 | "updatedAt": "2019-01-04T14:06:02.180Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 3, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_headline_1", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "This is a level one headline", 51 | "nodeType": "text" 52 | } 53 | ], 54 | "nodeType": "heading-1" 55 | }, 56 | { 57 | "data": {}, 58 | "content": [ 59 | { 60 | "data": {}, 61 | "marks": [], 62 | "value": "", 63 | "nodeType": "text" 64 | } 65 | ], 66 | "nodeType": "paragraph" 67 | } 68 | ], 69 | "nodeType": "document" 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/test/resources/links_invalid/preview_space.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "dc9j5zkjikxm" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "59TAIUBW5OaOSS4WgE8M4s" 21 | } 22 | }, 23 | "id": "2PovwSgHUce0AsokOswsqA", 24 | "revision": 1, 25 | "createdAt": "2015-12-22T17:48:13.629Z", 26 | "updatedAt": "2015-12-22T17:48:13.629Z", 27 | "locale": "en-US" 28 | }, 29 | "fields": { 30 | "child": { 31 | "en-US": { 32 | "sys": { 33 | "type": "Link", 34 | "linkType": "Entry", 35 | "id": "INVALID" 36 | } 37 | }, 38 | "es": { 39 | "sys": { 40 | "type": "Link", 41 | "linkType": "Entry", 42 | "id": "51oWCPywfKyAs62gk66GUk" 43 | } 44 | } 45 | } 46 | } 47 | }, 48 | { 49 | "sys": { 50 | "space": { 51 | "sys": { 52 | "type": "Link", 53 | "linkType": "Space", 54 | "id": "dc9j5zkjikxm" 55 | } 56 | }, 57 | "type": "Entry", 58 | "contentType": { 59 | "sys": { 60 | "type": "Link", 61 | "linkType": "ContentType", 62 | "id": "71tYS0GavSkUMsokUWmAW4" 63 | } 64 | }, 65 | "id": "51oWCPywfKyAs62gk66GUk", 66 | "revision": 1, 67 | "createdAt": "2015-12-22T17:47:56.133Z", 68 | "updatedAt": "2015-12-22T17:47:56.133Z", 69 | "locale": "en-US" 70 | }, 71 | "fields": { 72 | "name": { 73 | "en-US": "foo" 74 | } 75 | } 76 | } 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/LocaleTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.contentful.java.cda.lib.Enqueue; 4 | 5 | import org.junit.Test; 6 | 7 | import static com.google.common.truth.Truth.assertThat; 8 | 9 | public class LocaleTest extends BaseTest { 10 | @Test 11 | @Enqueue("locales/array_empty.json") 12 | public void fetchEmptyLocales() { 13 | final CDAArray all = client.fetch(CDALocale.class).all(); 14 | 15 | assertThat(all.total).isEqualTo(0); 16 | assertThat(all.limit).isEqualTo(1000); 17 | } 18 | 19 | @Test(expected = CDAResourceNotFoundException.class) 20 | @Enqueue("errors/not_found.json") 21 | public void fetchOneNonExistingThrows() { 22 | client.fetch(CDALocale.class).one("foo"); 23 | } 24 | 25 | @Test 26 | @Enqueue("locales/fetch_all_locales.json") 27 | public void fetchOne() { 28 | final CDALocale one = client.fetch(CDALocale.class).one("2oQPjMCL9bQkylziydLh57"); 29 | assertThat(one.toString()).isEqualTo("CDALocale { CDAResource { attrs = {" + 30 | "id=2oQPjMCL9bQkylziydLh57, type=Locale, version=1.0}, id = 2oQPjMCL9bQkylziydLh57, " + 31 | "type = LOCALE } code = en-US, defaultLocale = true, fallbackLocaleCode = null, " + 32 | "name = English }"); 33 | } 34 | 35 | @Test 36 | @Enqueue("locales/fetch_all_locales.json") 37 | public void fetchAllLocales() { 38 | final CDAArray found = client.fetch(CDALocale.class).all(); 39 | 40 | assertThat(found.items().size()).isEqualTo(2); 41 | 42 | final CDALocale first = (CDALocale) found.items().get(0); 43 | assertThat(first.code()).isEqualTo("en-US"); 44 | assertThat(first.name()).isEqualTo("English"); 45 | assertThat(first.fallbackLocaleCode()).isNull(); 46 | assertThat(first.isDefaultLocale()).isTrue(); 47 | 48 | final CDALocale second = (CDALocale) found.items().get(1); 49 | assertThat(second.code()).isEqualTo("tlh"); 50 | assertThat(second.name()).isEqualTo("Klingon"); 51 | assertThat(second.fallbackLocaleCode()).isEqualTo("en-US"); 52 | assertThat(second.isDefaultLocale()).isFalse(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/interceptor/LogInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.interceptor; 2 | 3 | import com.contentful.java.cda.Logger; 4 | import okhttp3.Interceptor; 5 | import okhttp3.Request; 6 | import okhttp3.Response; 7 | 8 | import java.io.IOException; 9 | 10 | //BEGIN TO LONG CODE LINES 11 | /** 12 | * Simple interceptor to log a request and its response. 13 | * 14 | * @see https://github.com/square/okhttp/wiki/Interceptors 15 | */ 16 | //END TO LONG CODE LINES 17 | public class LogInterceptor implements Interceptor { 18 | private static final double NANOS_PER_SECOND = 1000000d; 19 | private final Logger logger; 20 | 21 | /** 22 | * Creates a LogInterceptor, taking a logger to be logged to, once a request comes in. 23 | * 24 | * @param logger a nonnull logger to be used. 25 | * @throws IllegalArgumentException if a null logger was given. 26 | */ 27 | public LogInterceptor(Logger logger) { 28 | if (logger != null) { 29 | this.logger = logger; 30 | } else { 31 | throw new IllegalArgumentException("Logger cannot be null for interception ..."); 32 | } 33 | } 34 | 35 | /** 36 | * Log the incoming request. 37 | *

38 | * Once a request gets triggered in okhttp3, this interceptor gets called. 39 | * 40 | * @param chain the chain of interceptor, provided by the okhttp3. 41 | * @return the response of the chain. 42 | * @throws IOException in case of failure down the line. 43 | */ 44 | @Override public Response intercept(Chain chain) throws IOException { 45 | Request request = chain.request(); 46 | 47 | long t1 = System.nanoTime(); 48 | logger.log(String.format("Sending request %s on %s%n%s", 49 | request.url(), chain.connection(), request.headers())); 50 | 51 | Response response = chain.proceed(request); 52 | 53 | long t2 = System.nanoTime(); 54 | logger.log(String.format("Received response for %s in %.1fms%n%s", 55 | response.request().url(), (t2 - t1) / NANOS_PER_SECOND, response.headers())); 56 | 57 | return response; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_blockquote.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "3f6f7Q6FhCIka0OqEuWoMc", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:33.981Z", 21 | "updatedAt": "2019-01-04T14:08:01.334Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 3, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_blockquote", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "content": [ 50 | { 51 | "data": {}, 52 | "marks": [], 53 | "value": "This is a blockquote", 54 | "nodeType": "text" 55 | } 56 | ], 57 | "nodeType": "paragraph" 58 | } 59 | ], 60 | "nodeType": "blockquote" 61 | }, 62 | { 63 | "data": {}, 64 | "content": [ 65 | { 66 | "data": {}, 67 | "marks": [], 68 | "value": "", 69 | "nodeType": "text" 70 | } 71 | ], 72 | "nodeType": "paragraph" 73 | } 74 | ], 75 | "nodeType": "document" 76 | } 77 | } 78 | } 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /src/test/resources/links_invalid/sync_initial.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "dc9j5zkjikxm" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "59TAIUBW5OaOSS4WgE8M4s" 21 | } 22 | }, 23 | "id": "2PovwSgHUce0AsokOswsqA", 24 | "revision": 1, 25 | "createdAt": "2015-10-28T17:48:13.629Z", 26 | "updatedAt": "2015-10-28T17:48:13.629Z" 27 | }, 28 | "fields": { 29 | "child": { 30 | "en-US": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "Entry", 34 | "id": "INVALID" 35 | } 36 | }, 37 | "es": { 38 | "sys": { 39 | "type": "Link", 40 | "linkType": "Entry", 41 | "id": "51oWCPywfKyAs62gk66GUk" 42 | } 43 | } 44 | } 45 | } 46 | }, 47 | { 48 | "sys": { 49 | "space": { 50 | "sys": { 51 | "type": "Link", 52 | "linkType": "Space", 53 | "id": "dc9j5zkjikxm" 54 | } 55 | }, 56 | "type": "Entry", 57 | "contentType": { 58 | "sys": { 59 | "type": "Link", 60 | "linkType": "ContentType", 61 | "id": "71tYS0GavSkUMsokUWmAW4" 62 | } 63 | }, 64 | "id": "51oWCPywfKyAs62gk66GUk", 65 | "revision": 1, 66 | "createdAt": "2015-10-28T17:47:56.133Z", 67 | "updatedAt": "2015-10-28T17:47:56.133Z" 68 | }, 69 | "fields": { 70 | "name": { 71 | "en-US": "foo" 72 | } 73 | } 74 | } 75 | ], 76 | "nextSyncUrl": "https://cdn.contentful.com/spaces/dc9j5zkjikxm/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyYXJj7CgcO3PzMvSsO7MHrCgQNTwo0mNcKzwqRmMMKWfcKdwrzCug9iLSZwwptrw4fDjUrDicKJwqkywr_DlRYwchTDscOSw4spXTLCpX5twpvCmMKewqEZwo7Cm8Oz" 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/FetchQuery.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | /** 4 | * Represents a query to the Delivery API which may be invoked synchronously or asynchronously 5 | * with a callback. 6 | */ 7 | public class FetchQuery extends AbsQuery> { 8 | /** 9 | * Create a FetchQuery for the given type, using the client. 10 | * 11 | * @param type is the ContentType class to be queried for. 12 | * @param client a valid client to be used for the actual data retrieval. 13 | */ 14 | public FetchQuery(Class type, CDAClient client) { 15 | super(type, client); 16 | } 17 | 18 | /** 19 | * Fetch and return a resource matching the given {@code id}. 20 | * 21 | * @param id resource id. 22 | * @return result resource, null if it does not exist. 23 | */ 24 | public T one(String id) { 25 | try { 26 | return baseQuery().one(id).blockingFirst(); 27 | } catch (NullPointerException e) { 28 | throw new CDAResourceNotFoundException(type, id); 29 | } 30 | } 31 | 32 | /** 33 | * Async fetch resource matching the given {@code id}. 34 | * 35 | * @param id resource id. 36 | * @param callback callback. 37 | * @param callback type. 38 | * @return the given {@code callback} instance. 39 | */ 40 | @SuppressWarnings("unchecked") 41 | public > C one(String id, C callback) { 42 | return (C) Callbacks.subscribeAsync(baseQuery().one(id), callback, client); 43 | } 44 | 45 | /** 46 | * Fetch and return all resources matching the type of this query. 47 | * 48 | * @return {@link CDAArray} result. 49 | */ 50 | public CDAArray all() { 51 | return baseQuery().all().blockingFirst(); 52 | } 53 | 54 | /** 55 | * Async fetch all resources matching the type of this query. 56 | * 57 | * @param callback callback. 58 | * @param callback type. 59 | * @return the given {@code callback} instance. 60 | */ 61 | @SuppressWarnings("unchecked") 62 | public > C all(C callback) { 63 | return (C) Callbacks.subscribeAsync(baseQuery().all(), callback, client); 64 | } 65 | 66 | private ObserveQuery baseQuery() { 67 | return client.observe(type).where(params); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_hyperlink.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "19QAcMqhFWUIi4EE4GQ00a", 19 | "type": "Entry", 20 | "createdAt": "2018-10-31T10:23:22.650Z", 21 | "updatedAt": "2018-10-31T10:23:22.650Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 1, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_hyperlink", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "", 51 | "nodeType": "text" 52 | }, 53 | { 54 | "data": { 55 | "uri": "https://www.example.com/" 56 | }, 57 | "content": [ 58 | { 59 | "data": {}, 60 | "marks": [], 61 | "value": "Regular hyperlink to example.com", 62 | "nodeType": "text" 63 | } 64 | ], 65 | "nodeType": "hyperlink" 66 | }, 67 | { 68 | "data": {}, 69 | "marks": [], 70 | "value": "", 71 | "nodeType": "text" 72 | } 73 | ], 74 | "nodeType": "paragraph" 75 | } 76 | ], 77 | "nodeType": "document" 78 | } 79 | } 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_text_with_link.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "104F4Lbe2gUKuo42yGygsQ", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:33.642Z", 21 | "updatedAt": "2019-01-04T14:08:56.346Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 3, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_text_with_link", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "", 51 | "nodeType": "text" 52 | }, 53 | { 54 | "data": { 55 | "uri": "https://www.contentful.com" 56 | }, 57 | "content": [ 58 | { 59 | "data": {}, 60 | "marks": [], 61 | "value": "This is a text with linking to contentful.com", 62 | "nodeType": "text" 63 | } 64 | ], 65 | "nodeType": "hyperlink" 66 | }, 67 | { 68 | "data": {}, 69 | "marks": [], 70 | "value": "", 71 | "nodeType": "text" 72 | } 73 | ], 74 | "nodeType": "paragraph" 75 | } 76 | ], 77 | "nodeType": "document" 78 | } 79 | } 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /src/test/resources/cda/entries_with_errors.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "amjnj7ldpank" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "3XN5y2aye44m0Yioko6K6k" 24 | } 25 | }, 26 | "id": "3UpazZmO8g8iI0iWAMmGMS", 27 | "revision": 1, 28 | "createdAt": "2015-07-06T14:55:51.686Z", 29 | "updatedAt": "2015-07-06T14:55:51.686Z", 30 | "locale": "en-US" 31 | }, 32 | "fields": { 33 | "link": { 34 | "sys": { 35 | "type": "Link", 36 | "linkType": "Entry", 37 | "id": "1y5CUd7lwMQaI2micsAWIO" 38 | } 39 | } 40 | } 41 | }, 42 | { 43 | "sys": { 44 | "space": { 45 | "sys": { 46 | "type": "Link", 47 | "linkType": "Space", 48 | "id": "amjnj7ldpank" 49 | } 50 | }, 51 | "type": "Entry", 52 | "contentType": { 53 | "sys": { 54 | "type": "Link", 55 | "linkType": "ContentType", 56 | "id": "3lYaFZKDgQCUwWy6uEoQYi" 57 | } 58 | }, 59 | "id": "1y5CUd7lwMQaI2micsAWIO", 60 | "revision": 2, 61 | "createdAt": "2015-07-06T14:55:41.548Z", 62 | "updatedAt": "2015-07-07T12:37:23.236Z", 63 | "locale": "en-US" 64 | }, 65 | "fields": { 66 | "name": "bar" 67 | } 68 | } 69 | ], 70 | "errors": [ 71 | { 72 | "sys": { 73 | "id": "notResolvable", 74 | "type": "error" 75 | }, 76 | "details": { 77 | "type": "Link", 78 | "linkType": "ContentType", 79 | "id": "3UpazZmO8g8iI0iWAMmGMS" 80 | } 81 | }, 82 | { 83 | "sys": { 84 | "id": "notResolvable", 85 | "type": "error" 86 | }, 87 | "details": { 88 | "type": "Link", 89 | "linkType": "ContentType", 90 | "id": "1y5CUd7lwMQaI2micsAWIO" 91 | } 92 | } 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /src/test/resources/links/content_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "name": "container", 11 | "fields": [ 12 | { 13 | "name": "entry", 14 | "id": "entry", 15 | "type": "Link", 16 | "linkType": "Entry", 17 | "localized": true 18 | }, 19 | { 20 | "name": "asset", 21 | "id": "asset", 22 | "type": "Link", 23 | "linkType": "Asset", 24 | "localized": true 25 | }, 26 | { 27 | "name": "entries", 28 | "id": "entries", 29 | "type": "Array", 30 | "items": { 31 | "type": "Link", 32 | "linkType": "Entry" 33 | }, 34 | "localized": true 35 | }, 36 | { 37 | "name": "assets", 38 | "id": "assets", 39 | "type": "Array", 40 | "items": { 41 | "type": "Link", 42 | "linkType": "Asset" 43 | }, 44 | "localized": true 45 | }, 46 | { 47 | "name": "symbols", 48 | "id": "symbols", 49 | "type": "Array", 50 | "items": { 51 | "type": "Symbol" 52 | } 53 | } 54 | ], 55 | "sys": { 56 | "space": { 57 | "sys": { 58 | "type": "Link", 59 | "linkType": "Space", 60 | "id": "75oea9kejr52" 61 | } 62 | }, 63 | "type": "ContentType", 64 | "id": "2s9novBkP2G0oasUaG8kCs", 65 | "revision": 1, 66 | "createdAt": "2015-07-10T11:34:04.806Z", 67 | "updatedAt": "2015-07-10T11:34:04.806Z" 68 | } 69 | }, 70 | { 71 | "name": "child", 72 | "fields": [ 73 | { 74 | "name": "foo", 75 | "id": "foo", 76 | "type": "Text" 77 | } 78 | ], 79 | "displayField": "foo", 80 | "sys": { 81 | "space": { 82 | "sys": { 83 | "type": "Link", 84 | "linkType": "Space", 85 | "id": "75oea9kejr52" 86 | } 87 | }, 88 | "type": "ContentType", 89 | "id": "3V0QxGs0ucuwuuIEyGk8E0", 90 | "revision": 1, 91 | "createdAt": "2015-07-10T11:49:35.887Z", 92 | "updatedAt": "2015-07-10T11:49:35.887Z" 93 | } 94 | } 95 | ] 96 | } -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/LinkTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.contentful.java.cda.lib.Enqueue; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.List; 8 | 9 | import static com.google.common.truth.Truth.assertThat; 10 | 11 | public class LinkTest extends BaseTest { 12 | @Test @Enqueue("demo/links_unresolved.json") public void unresolvedLinkIsNull() { 13 | CDAArray array = client.fetch(CDAEntry.class).all(); 14 | assertThat(array.items()).hasSize(1); 15 | assertThat(array.entries()).hasSize(1); 16 | 17 | CDAEntry entry = array.entries().get("happycat"); 18 | assertThat(entry).isNotNull(); 19 | assertThat(entry.getField("bestFriend")).isNull(); 20 | } 21 | 22 | @SuppressWarnings("unchecked") 23 | @Test @Enqueue(defaults = { 24 | "links/locales.json", 25 | "links/content_types.json" 26 | }, value = { 27 | "links/entries.json" 28 | }) public void arrays() { 29 | CDAArray array = client.fetch(CDAEntry.class).all(); 30 | assertThat(array.total()).isEqualTo(4); 31 | assertThat(array.items()).hasSize(4); 32 | assertThat(array.assets()).hasSize(2); 33 | assertThat(array.entries()).hasSize(4); 34 | 35 | CDAEntry container = array.entries().get("3vyEoAvlkk8yE4a8gCCkiu"); 36 | assertThat(container).isNotNull(); 37 | assertThat(container.getField("asset")).isInstanceOf(CDAAsset.class); 38 | assertThat(container.getField("entry")).isInstanceOf(CDAEntry.class); 39 | 40 | List assets = container.getField("assets"); 41 | assertThat(assets).isNotNull(); 42 | assertThat(assets).hasSize(2); 43 | assertThat(assets.get(0).id()).isEqualTo("3xkzMDqRTaoIeKkUYwiIUw"); 44 | assertThat(assets.get(1).id()).isEqualTo("5WHMX35TkQg08sK0agoMw"); 45 | 46 | List entries = container.getField("entries"); 47 | assertThat(entries).isNotNull(); 48 | assertThat(entries).hasSize(3); 49 | assertThat(entries.get(0).id()).isEqualTo("4NvEw8RaUUkSa2uEEogAeG"); 50 | assertThat(entries.get(1).id()).isEqualTo("3XNpMBumdOsWuYUs0wsgMS"); 51 | assertThat(entries.get(2).id()).isEqualTo("4kDCK9U7OgQiieIqi6ScWA"); 52 | 53 | assertThat((List) container.getField("symbols")).containsExactly("a", "b", "c"); 54 | } 55 | 56 | @Test @Enqueue(defaults = { 57 | "links/locales.json", 58 | "links/content_types.json" 59 | }, value = { 60 | "links/sync_empty_links.json" 61 | }) public void testEmptyLinks() { 62 | client.sync().fetch(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/rich/CDARichMark.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.rich; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * How to draw a given text. 7 | *

8 | * Subclasses are used for further differentiation. 9 | */ 10 | public class CDARichMark implements Serializable { 11 | 12 | public CDARichMark(String type) { 13 | this.type = type; 14 | } 15 | 16 | /** 17 | * @return the type of the marker. 18 | */ 19 | public String getType() { 20 | return type; 21 | } 22 | 23 | protected final String type; 24 | 25 | /** 26 | * A bold mark of a rich text. 27 | */ 28 | public static class CDARichMarkBold extends CDARichMark { 29 | 30 | public CDARichMarkBold() { 31 | super("bold"); 32 | } 33 | 34 | } 35 | 36 | /** 37 | * Declares the text as being displayed in italics. 38 | */ 39 | public static class CDARichMarkItalic extends CDARichMark { 40 | 41 | public CDARichMarkItalic() { 42 | super("italic"); 43 | } 44 | 45 | } 46 | 47 | /** 48 | * Marker for making the rich text displayed as underline. 49 | */ 50 | public static class CDARichMarkUnderline extends CDARichMark { 51 | 52 | public CDARichMarkUnderline() { 53 | super("underline"); 54 | } 55 | 56 | } 57 | 58 | /** 59 | * Marker for making the rich text displayed as superscript. 60 | */ 61 | public static class CDARichMarkSuperscript extends CDARichMark { 62 | 63 | public CDARichMarkSuperscript() { 64 | super("superscript"); 65 | } 66 | 67 | } 68 | 69 | /** 70 | * Marker for making the rich text displayed as subscript. 71 | */ 72 | public static class CDARichMarkSubscript extends CDARichMark { 73 | 74 | public CDARichMarkSubscript() { 75 | super("subscript"); 76 | } 77 | 78 | } 79 | 80 | /** 81 | * The text marked by this marker should be represented by Code. 82 | */ 83 | public static class CDARichMarkCode extends CDARichMark { 84 | 85 | public CDARichMarkCode() { 86 | super("code"); 87 | } 88 | 89 | } 90 | 91 | /** 92 | * Any custom marker for a given rich text. 93 | */ 94 | public static class CDARichMarkCustom extends CDARichMark { 95 | /** 96 | * Create a custom marker using the given type. 97 | * 98 | * @param type which type should this marker have? 99 | */ 100 | public CDARichMarkCustom(String type) { 101 | super(type); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/TlsSocketFactory.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import javax.net.ssl.SSLContext; 4 | import javax.net.ssl.SSLSocket; 5 | import javax.net.ssl.SSLSocketFactory; 6 | import java.io.IOException; 7 | import java.net.InetAddress; 8 | import java.net.Socket; 9 | import java.security.KeyManagementException; 10 | import java.security.NoSuchAlgorithmException; 11 | 12 | /** 13 | * Internal class for enabling TLS 1.2 support on https connections, starting from Android 4.4 14 | *

15 | * {@see https://developer.android.com/reference/javax/net/ssl/SSLSocket.html} 16 | */ 17 | final class TlsSocketFactory extends SSLSocketFactory { 18 | 19 | private static final String[] PROTOCOLS_TLS_1_2_ONLY = {"TLSv1.2"}; 20 | 21 | private final SSLSocketFactory delegate; 22 | 23 | TlsSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { 24 | SSLContext context = SSLContext.getInstance("TLS"); 25 | context.init(null, null, null); 26 | delegate = context.getSocketFactory(); 27 | } 28 | 29 | @Override 30 | public String[] getDefaultCipherSuites() { 31 | return delegate.getDefaultCipherSuites(); 32 | } 33 | 34 | @Override 35 | public String[] getSupportedCipherSuites() { 36 | return delegate.getSupportedCipherSuites(); 37 | } 38 | 39 | @Override 40 | public Socket createSocket(Socket s, String host, int port, boolean autoClose) 41 | throws IOException { 42 | return enableTlsOnSocket(delegate.createSocket(s, host, port, autoClose)); 43 | } 44 | 45 | @Override 46 | public Socket createSocket(String host, int port) throws IOException { 47 | return enableTlsOnSocket(delegate.createSocket(host, port)); 48 | } 49 | 50 | @Override 51 | public Socket createSocket(String host, int port, InetAddress localHost, int localPort) 52 | throws IOException { 53 | return enableTlsOnSocket(delegate.createSocket(host, port, localHost, localPort)); 54 | } 55 | 56 | @Override 57 | public Socket createSocket(InetAddress host, int port) throws IOException { 58 | return enableTlsOnSocket(delegate.createSocket(host, port)); 59 | } 60 | 61 | @Override 62 | public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) 63 | throws IOException { 64 | return enableTlsOnSocket(delegate.createSocket(address, port, localAddress, localPort)); 65 | } 66 | 67 | private Socket enableTlsOnSocket(Socket socket) { 68 | if (socket instanceof SSLSocket) { 69 | ((SSLSocket) socket).setEnabledProtocols(PROTOCOLS_TLS_1_2_ONLY); 70 | } 71 | return socket; 72 | } 73 | } -------------------------------------------------------------------------------- /src/test/resources/shallow/initial.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "tamu5dc7eas0" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "15hKDLsTB28isKMAuaQy4A" 21 | } 22 | }, 23 | "id": "2k5aHpfw7m0waMKYksC2Ww", 24 | "revision": 2, 25 | "createdAt": "2015-07-14T12:58:05.066Z", 26 | "updatedAt": "2015-07-14T12:58:43.661Z" 27 | }, 28 | "fields": { 29 | "name": { 30 | "en-US": "foo2" 31 | }, 32 | "image": { 33 | "en-US": { 34 | "sys": { 35 | "type": "Link", 36 | "linkType": "Asset", 37 | "id": "m5pDQF5IRiIicMmg2ukmQ" 38 | } 39 | } 40 | }, 41 | "array": { 42 | "en-US": [ 43 | { 44 | "sys": { 45 | "type": "Link", 46 | "linkType": "Asset", 47 | "id": "m5pDQF5IRiIicMmg2ukmQ" 48 | } 49 | } 50 | ] 51 | } 52 | } 53 | }, 54 | { 55 | "sys": { 56 | "space": { 57 | "sys": { 58 | "type": "Link", 59 | "linkType": "Space", 60 | "id": "tamu5dc7eas0" 61 | } 62 | }, 63 | "type": "Asset", 64 | "id": "m5pDQF5IRiIicMmg2ukmQ", 65 | "revision": 1, 66 | "createdAt": "2015-07-14T12:18:44.894Z", 67 | "updatedAt": "2015-07-14T12:18:44.894Z" 68 | }, 69 | "fields": { 70 | "title": { 71 | "en-US": "bar" 72 | }, 73 | "file": { 74 | "en-US": { 75 | "fileName": "snap.jpg", 76 | "contentType": "image/jpeg", 77 | "details": { 78 | "image": { 79 | "width": 400, 80 | "height": 280 81 | }, 82 | "size": 21747 83 | }, 84 | "url": "//images.contentful.com/tamu5dc7eas0/m5pDQF5IRiIicMmg2ukmQ/7ae98075b1709c20527bd081779d130f/snap.jpg" 85 | } 86 | } 87 | } 88 | } 89 | ], 90 | "nextSyncUrl": "https://cdn.contentful.com/spaces/tamu5dc7eas0/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdybCkwbDvVoewpt2YSMdwqJLwoPDiV3DpzQpwrbDuxQzNTFPw57Ds2t1w5bCohlXw5jCoMO1woDDrcOxLMOTw6zChMK2I8OOwq7CjDRMInIqw58xw4pBwqJ_BScaWcKM" 91 | } -------------------------------------------------------------------------------- /src/test/resources/demo/content_types_cat.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "metadata": { 9 | "tags": [ 10 | { 11 | "sys": { 12 | "type": "Link", 13 | "linkType": "Tag", 14 | "id": "nyCampaign" 15 | } 16 | } 17 | ] 18 | }, 19 | "items": [ 20 | { 21 | "fields": [ 22 | { 23 | "id": "name", 24 | "name": "Name", 25 | "type": "Text", 26 | "required": true, 27 | "localized": true 28 | }, 29 | { 30 | "id": "likes", 31 | "name": "Likes", 32 | "type": "Array", 33 | "required": false, 34 | "localized": false, 35 | "items": { 36 | "type": "Symbol" 37 | } 38 | }, 39 | { 40 | "id": "color", 41 | "name": "Color", 42 | "type": "Symbol", 43 | "required": false, 44 | "localized": false, 45 | "validations": [ 46 | { 47 | "in": [ 48 | "rainbow", 49 | "pink" 50 | ] 51 | } 52 | ] 53 | }, 54 | { 55 | "id": "bestFriend", 56 | "name": "Best Friend", 57 | "type": "Link", 58 | "required": false, 59 | "localized": false, 60 | "linkType": "Entry" 61 | }, 62 | { 63 | "id": "birthday", 64 | "name": "Birthday", 65 | "type": "Date", 66 | "required": false, 67 | "localized": false 68 | }, 69 | { 70 | "id": "lifes", 71 | "name": "Lifes left", 72 | "type": "Integer", 73 | "required": false, 74 | "localized": false, 75 | "disabled": true 76 | }, 77 | { 78 | "id": "lives", 79 | "name": "Lives left", 80 | "type": "Integer", 81 | "required": false, 82 | "localized": false 83 | }, 84 | { 85 | "id": "image", 86 | "name": "Image", 87 | "required": false, 88 | "localized": false, 89 | "type": "Link", 90 | "linkType": "Asset" 91 | } 92 | ], 93 | "name": "Cat", 94 | "displayField": "name", 95 | "description": "Meow.", 96 | "sys": { 97 | "space": { 98 | "sys": { 99 | "type": "Link", 100 | "linkType": "Space", 101 | "id": "cfexampleapi" 102 | } 103 | }, 104 | "type": "ContentType", 105 | "id": "cat", 106 | "revision": 2, 107 | "createdAt": "2013-06-27T22:46:12.852Z", 108 | "updatedAt": "2013-09-02T13:14:47.863Z" 109 | } 110 | } 111 | ] 112 | } -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/integration/IntegrationWithStagingEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.integration; 2 | 3 | import com.contentful.java.cda.CDAArray; 4 | import com.contentful.java.cda.CDAClient; 5 | import com.contentful.java.cda.CDAEntry; 6 | import com.contentful.java.cda.CDAHttpException; 7 | import com.contentful.java.cda.SynchronizedSpace; 8 | 9 | import org.junit.Test; 10 | 11 | import static com.contentful.java.cda.QueryOperation.Exists; 12 | import static com.contentful.java.cda.QueryOperation.IsEarlierOrAt; 13 | import static com.contentful.java.cda.QueryOperation.IsEarlierThan; 14 | import static com.contentful.java.cda.SyncType.onlyDeletedAssets; 15 | import static com.google.common.truth.Truth.assertThat; 16 | 17 | public class IntegrationWithStagingEnvironment extends IntegrationWithMasterEnvironment { 18 | public void setUp() { 19 | client = CDAClient.builder() 20 | .setSpace("5s4tdjmyjfpl") 21 | .setToken("16bc99120b1e0b33cc16323324baab4dbe9350c0d7a2b222ed8d7d5328e95bf3") 22 | .setEnvironment("staging") 23 | .build(); 24 | } 25 | 26 | 27 | // cannot fetch space on non master token 28 | @Test(expected = CDAHttpException.class) 29 | @Override public void fetchSpace() { 30 | client.fetchSpace(); 31 | } 32 | 33 | @Override 34 | public void fetchEntriesInRange() { 35 | CDAArray found = client.fetch(CDAEntry.class) 36 | .withContentType("cat") 37 | .where("fields.birthday", IsEarlierThan, "1980-01-01") 38 | .all(); 39 | 40 | assertThat(found.items().size()).isEqualTo(1); 41 | CDAEntry nyancat = (CDAEntry) found.items().get(0); 42 | assertThat(nyancat.getField("name")).isEqualTo("Staged Garfield"); 43 | } 44 | 45 | //"/spaces/{space_id}/entries?content_type={content_type}&{attribute}%5Blte%5D={value}", 46 | @Override 47 | public void fetchEntriesEarlierOrAt() { 48 | CDAArray found = client.fetch(CDAEntry.class) 49 | .withContentType("cat") 50 | .where("fields.birthday", IsEarlierOrAt, "1979-06-18T23:00:00") 51 | .all(); 52 | 53 | assertThat(found.items().size()).isEqualTo(1); 54 | CDAEntry cat = (CDAEntry) found.items().get(0); 55 | assertThat(cat.getField("name")).isEqualTo("Staged Garfield"); 56 | } 57 | 58 | @Override 59 | public void fetchWithExistsQueryOnFields() { 60 | CDAArray found = client.fetch(CDAEntry.class) 61 | .withContentType("cat") 62 | .where("fields.bestFriend", Exists, false) // entries without id 63 | .all(); 64 | 65 | assertThat(found.items()).hasSize(1); 66 | assertThat(found.entries().get("garfield").getField("name")).isEqualTo("Staged Garfield"); 67 | } 68 | 69 | @Override public void syncTypeOfDeletedAssets() { 70 | final SynchronizedSpace space = client.sync(onlyDeletedAssets()).fetch(); 71 | 72 | assertThat(space.nextSyncUrl()).isNotEmpty(); 73 | assertThat(space.items()).hasSize(0); 74 | assertThat(space.assets()).hasSize(0); 75 | assertThat(space.deletedEntries()).hasSize(0); 76 | assertThat(space.deletedAssets()).hasSize(0); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/Callbacks.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import io.reactivex.rxjava3.core.Flowable; 4 | import io.reactivex.rxjava3.flowables.ConnectableFlowable; 5 | import io.reactivex.rxjava3.functions.Consumer; 6 | import io.reactivex.rxjava3.schedulers.Schedulers; 7 | 8 | final class Callbacks { 9 | private Callbacks() { 10 | throw new AssertionError(); 11 | } 12 | 13 | static CDACallback subscribeAsync( 14 | Flowable flowable, CDACallback callback, CDAClient client) { 15 | ConnectableFlowable connectable = flowable.observeOn(Schedulers.io()).publish(); 16 | 17 | callback.setSubscription(connectable.subscribe( 18 | new SuccessAction<>(callback, client), 19 | new FailureAction(callback, client))); 20 | 21 | connectable.connect(); 22 | 23 | return callback; 24 | } 25 | 26 | abstract static class BaseAction implements Consumer { 27 | protected final CDACallback callback; 28 | 29 | protected final CDAClient client; 30 | 31 | BaseAction(CDACallback callback, CDAClient client) { 32 | this.callback = callback; 33 | this.client = client; 34 | } 35 | 36 | @Override public void accept(E e) { 37 | if (!callback.isCancelled()) { 38 | doCall(e); 39 | } 40 | callback.unsubscribe(); 41 | } 42 | 43 | protected abstract void doCall(E e); 44 | 45 | protected void execute(Runnable r) { 46 | client.callbackExecutor.execute(r); 47 | } 48 | } 49 | 50 | static class SuccessAction extends BaseAction { 51 | SuccessAction(CDACallback callback, CDAClient client) { 52 | super(callback, client); 53 | } 54 | 55 | @SuppressWarnings("unchecked") 56 | @Override 57 | protected void doCall(E e) { 58 | execute(new SuccessRunnable<>(e, (CDACallback) callback)); 59 | } 60 | } 61 | 62 | static class FailureAction extends BaseAction { 63 | FailureAction(CDACallback callback, CDAClient client) { 64 | super(callback, client); 65 | } 66 | 67 | @Override protected void doCall(Throwable t) { 68 | execute(new FailureRunnable(t, callback)); 69 | } 70 | } 71 | 72 | static class SuccessRunnable implements Runnable { 73 | private final E result; 74 | 75 | private final CDACallback callback; 76 | 77 | SuccessRunnable(E result, CDACallback callback) { 78 | this.result = result; 79 | this.callback = callback; 80 | } 81 | 82 | @Override public void run() { 83 | if (!callback.isCancelled()) { 84 | callback.onSuccess(result); 85 | } 86 | } 87 | } 88 | 89 | static class FailureRunnable implements Runnable { 90 | private final Throwable throwable; 91 | 92 | private final CDACallback callback; 93 | 94 | FailureRunnable(Throwable throwable, CDACallback callback) { 95 | this.throwable = throwable; 96 | this.callback = callback; 97 | } 98 | 99 | @Override public void run() { 100 | if (!callback.isCancelled()) { 101 | callback.onFailure(throwable); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/SyncType.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import static com.contentful.java.cda.SyncType.Type.Asset; 4 | import static com.contentful.java.cda.SyncType.Type.DeletedAsset; 5 | import static com.contentful.java.cda.SyncType.Type.DeletedEntry; 6 | import static com.contentful.java.cda.SyncType.Type.Deletion; 7 | import static com.contentful.java.cda.SyncType.Type.Entry; 8 | 9 | /** 10 | * The type of syncing. 11 | *

12 | * If you want to specify to sync only specific parts of content, please use this class in 13 | * conjunction with {@link CDAClient#sync(SyncType)} and one of the static methods defined here. 14 | */ 15 | public class SyncType { 16 | /** 17 | * What should be synced? 18 | */ 19 | public enum Type { 20 | /** 21 | * Only include new and changed assets. 22 | */ 23 | Asset, 24 | /** 25 | * Only include new and changed entries. 26 | */ 27 | Entry, 28 | /** 29 | * Only include deletions of assets and entries. 30 | */ 31 | Deletion, 32 | /** 33 | * Only include deletions of assets. 34 | */ 35 | DeletedAsset, 36 | /** 37 | * Only include deletions of entries. 38 | */ 39 | DeletedEntry 40 | } 41 | 42 | final Type type; 43 | final String contentType; 44 | 45 | SyncType(Type type, String contentType) { 46 | this.type = type; 47 | this.contentType = contentType; 48 | } 49 | 50 | /** 51 | * @return a type syncing all assets. 52 | */ 53 | public static SyncType allAssets() { 54 | return new SyncType(Asset, null); 55 | } 56 | 57 | /** 58 | * @return a type syncing all entries. 59 | */ 60 | public static SyncType allEntries() { 61 | return new SyncType(Entry, null); 62 | } 63 | 64 | //BEGIN TO LONG CODE LINES 65 | 66 | /** 67 | * Sync all entries of a specific content type. 68 | * 69 | * @param type which content type to sync. 70 | * @return a type syncing all entries of a specific type. 71 | * @see Contentful Docs 72 | */ 73 | //END TO LONG CODE LINES 74 | public static SyncType onlyEntriesOfType(String type) { 75 | return new SyncType(Entry, type); 76 | } 77 | 78 | /** 79 | * @return a type syncing all deleted resources, including assets and entries. 80 | */ 81 | public static SyncType onlyDeletion() { 82 | return new SyncType(Deletion, null); 83 | } 84 | 85 | /** 86 | * @return a type syncing all deleted assets. 87 | */ 88 | public static SyncType onlyDeletedAssets() { 89 | return new SyncType(DeletedAsset, null); 90 | } 91 | 92 | /** 93 | * @return a type syncing all deleted entries. 94 | */ 95 | public static SyncType onlyDeletedEntries() { 96 | return new SyncType(DeletedEntry, null); 97 | } 98 | 99 | /** 100 | * @return a string representation of the type's name 101 | */ 102 | public String getName() { 103 | return type.name(); 104 | } 105 | 106 | /** 107 | * @return the content type name if present. 108 | */ 109 | public String getContentType() { 110 | return contentType; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_asset_block.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "7JzNqtv2LesWiQUoMigCQI", 19 | "type": "Entry", 20 | "createdAt": "2018-10-31T11:00:56.771Z", 21 | "updatedAt": "2018-10-31T11:00:56.771Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 1, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_asset_block", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": { 46 | "target": { 47 | "sys": { 48 | "id": "47Q1142uaI6SuK8UoGeeQy", 49 | "type": "Link", 50 | "linkType": "Asset" 51 | } 52 | } 53 | }, 54 | "content": [], 55 | "nodeType": "embedded-asset-block" 56 | }, 57 | { 58 | "data": {}, 59 | "content": [ 60 | { 61 | "data": {}, 62 | "marks": [], 63 | "value": "", 64 | "nodeType": "text" 65 | } 66 | ], 67 | "nodeType": "paragraph" 68 | } 69 | ], 70 | "nodeType": "document" 71 | } 72 | } 73 | } 74 | ], 75 | "includes": { 76 | "Asset": [ 77 | { 78 | "sys": { 79 | "space": { 80 | "sys": { 81 | "type": "Link", 82 | "linkType": "Space", 83 | "id": "" 84 | } 85 | }, 86 | "id": "47Q1142uaI6SuK8UoGeeQy", 87 | "type": "Asset", 88 | "createdAt": "2018-10-31T10:21:34.784Z", 89 | "updatedAt": "2018-10-31T10:21:34.784Z", 90 | "environment": { 91 | "sys": { 92 | "id": "human-readable", 93 | "type": "Link", 94 | "linkType": "Environment" 95 | } 96 | }, 97 | "revision": 1, 98 | "locale": "en-US" 99 | }, 100 | "fields": { 101 | "title": "cat", 102 | "file": { 103 | "url": "//images.ctfassets.net//47Q1142uaI6SuK8UoGeeQy/4136a21284ad31498f2cbb5996329396/Kittyply_edit1.jpg", 104 | "details": { 105 | "size": 1009523, 106 | "image": { 107 | "width": 1728, 108 | "height": 1152 109 | } 110 | }, 111 | "fileName": "Kittyply_edit1.jpg", 112 | "contentType": "image/jpeg" 113 | } 114 | } 115 | } 116 | ] 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/resources/locales/entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 5, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "7dh3w86is8ls" 16 | } 17 | }, 18 | "id": "no-null", 19 | "type": "Entry", 20 | "createdAt": "2016-07-04T15:58:21.612Z", 21 | "updatedAt": "2016-07-04T15:58:21.612Z", 22 | "revision": 1, 23 | "contentType": { 24 | "sys": { 25 | "type": "Link", 26 | "linkType": "ContentType", 27 | "id": "sampleType" 28 | } 29 | } 30 | }, 31 | "fields": { 32 | "title": { 33 | "first": "first", 34 | "default": "no-null", 35 | "inbetween": "inbetween" 36 | } 37 | } 38 | }, 39 | { 40 | "sys": { 41 | "space": { 42 | "sys": { 43 | "type": "Link", 44 | "linkType": "Space", 45 | "id": "7dh3w86is8ls" 46 | } 47 | }, 48 | "id": "no-inbetween", 49 | "type": "Entry", 50 | "createdAt": "2016-07-04T15:58:22.577Z", 51 | "updatedAt": "2016-07-04T15:58:22.577Z", 52 | "revision": 1, 53 | "contentType": { 54 | "sys": { 55 | "type": "Link", 56 | "linkType": "ContentType", 57 | "id": "sampleType" 58 | } 59 | } 60 | }, 61 | "fields": { 62 | "title": { 63 | "first": "first", 64 | "default": "no-inbetween", 65 | "null": "null" 66 | } 67 | } 68 | }, 69 | { 70 | "sys": { 71 | "space": { 72 | "sys": { 73 | "type": "Link", 74 | "linkType": "Space", 75 | "id": "7dh3w86is8ls" 76 | } 77 | }, 78 | "id": "no-first-and-no-inbetween", 79 | "type": "Entry", 80 | "createdAt": "2016-07-04T16:10:28.180Z", 81 | "updatedAt": "2016-07-05T13:22:00.251Z", 82 | "revision": 2, 83 | "contentType": { 84 | "sys": { 85 | "type": "Link", 86 | "linkType": "ContentType", 87 | "id": "sampleType" 88 | } 89 | } 90 | }, 91 | "fields": { 92 | "title": { 93 | "default": "no-first-and-no-inbetween", 94 | "null": "null" 95 | } 96 | } 97 | }, 98 | { 99 | "sys": { 100 | "space": { 101 | "sys": { 102 | "type": "Link", 103 | "linkType": "Space", 104 | "id": "7dh3w86is8ls" 105 | } 106 | }, 107 | "id": "no-first", 108 | "type": "Entry", 109 | "createdAt": "2016-07-04T15:58:19.645Z", 110 | "updatedAt": "2016-07-04T15:58:19.645Z", 111 | "revision": 1, 112 | "contentType": { 113 | "sys": { 114 | "type": "Link", 115 | "linkType": "ContentType", 116 | "id": "sampleType" 117 | } 118 | } 119 | }, 120 | "fields": { 121 | "title": { 122 | "default": "no-first", 123 | "null": "null", 124 | "inbetween": "inbetween" 125 | } 126 | } 127 | } 128 | ] 129 | } 130 | -------------------------------------------------------------------------------- /src/test/resources/locales_fallback/entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 5, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "7dh3w86is8ls" 16 | } 17 | }, 18 | "id": "no-null", 19 | "type": "Entry", 20 | "createdAt": "2016-07-04T15:58:21.612Z", 21 | "updatedAt": "2016-07-04T15:58:21.612Z", 22 | "revision": 1, 23 | "contentType": { 24 | "sys": { 25 | "type": "Link", 26 | "linkType": "ContentType", 27 | "id": "sampleType" 28 | } 29 | } 30 | }, 31 | "fields": { 32 | "title": { 33 | "first": "first", 34 | "default": "no-null", 35 | "inbetween": "inbetween" 36 | } 37 | } 38 | }, 39 | { 40 | "sys": { 41 | "space": { 42 | "sys": { 43 | "type": "Link", 44 | "linkType": "Space", 45 | "id": "7dh3w86is8ls" 46 | } 47 | }, 48 | "id": "no-inbetween", 49 | "type": "Entry", 50 | "createdAt": "2016-07-04T15:58:22.577Z", 51 | "updatedAt": "2016-07-04T15:58:22.577Z", 52 | "revision": 1, 53 | "contentType": { 54 | "sys": { 55 | "type": "Link", 56 | "linkType": "ContentType", 57 | "id": "sampleType" 58 | } 59 | } 60 | }, 61 | "fields": { 62 | "title": { 63 | "first": "first", 64 | "default": "no-inbetween", 65 | "null": "null" 66 | } 67 | } 68 | }, 69 | { 70 | "sys": { 71 | "space": { 72 | "sys": { 73 | "type": "Link", 74 | "linkType": "Space", 75 | "id": "7dh3w86is8ls" 76 | } 77 | }, 78 | "id": "no-first-and-no-inbetween", 79 | "type": "Entry", 80 | "createdAt": "2016-07-04T16:10:28.180Z", 81 | "updatedAt": "2016-07-05T13:22:00.251Z", 82 | "revision": 2, 83 | "contentType": { 84 | "sys": { 85 | "type": "Link", 86 | "linkType": "ContentType", 87 | "id": "sampleType" 88 | } 89 | } 90 | }, 91 | "fields": { 92 | "title": { 93 | "default": "no-first-and-no-inbetween", 94 | "null": "null" 95 | } 96 | } 97 | }, 98 | { 99 | "sys": { 100 | "space": { 101 | "sys": { 102 | "type": "Link", 103 | "linkType": "Space", 104 | "id": "7dh3w86is8ls" 105 | } 106 | }, 107 | "id": "no-first", 108 | "type": "Entry", 109 | "createdAt": "2016-07-04T15:58:19.645Z", 110 | "updatedAt": "2016-07-04T15:58:19.645Z", 111 | "revision": 1, 112 | "contentType": { 113 | "sys": { 114 | "type": "Link", 115 | "linkType": "ContentType", 116 | "id": "sampleType" 117 | } 118 | } 119 | }, 120 | "fields": { 121 | "title": { 122 | "default": "no-first", 123 | "null": "null", 124 | "inbetween": "inbetween" 125 | } 126 | } 127 | } 128 | ] 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.contentful.java.cda.lib.EnqueueResponseRule; 4 | import com.contentful.java.cda.lib.TestCallback; 5 | import com.contentful.java.cda.lib.TestResponse; 6 | 7 | import org.junit.After; 8 | import org.junit.Before; 9 | import org.junit.Rule; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.net.URL; 14 | import java.util.List; 15 | import java.util.logging.LogManager; 16 | 17 | import okhttp3.mockwebserver.MockResponse; 18 | import okhttp3.mockwebserver.MockWebServer; 19 | 20 | import static com.contentful.java.cda.Util.checkNotNull; 21 | import static com.google.common.truth.Truth.assertThat; 22 | import static java.nio.charset.Charset.defaultCharset; 23 | import static org.apache.commons.io.FileUtils.readFileToString; 24 | 25 | public class BaseTest { 26 | public static final String DEFAULT_TOKEN = "test_token"; 27 | public static final String DEFAULT_SPACE = "test_space"; 28 | public static final String STAGING_ENVIRONMENT = "staging"; 29 | 30 | CDAClient client; 31 | 32 | MockWebServer server; 33 | 34 | List responseQueue; 35 | 36 | @Rule public EnqueueResponseRule enqueueResponse = new EnqueueResponseRule(); 37 | 38 | @Before public void setUp() throws IOException { 39 | LogManager.getLogManager().reset(); 40 | server = createServer(); 41 | server.start(); 42 | 43 | client = createClient(); 44 | 45 | if (responseQueue != null) { 46 | for (TestResponse response : responseQueue) { 47 | enqueue(response); 48 | } 49 | } 50 | } 51 | 52 | @After public void tearDown() throws IOException { 53 | server.shutdown(); 54 | } 55 | 56 | protected CDAClient createClient() { 57 | return createBuilder() 58 | .build(); 59 | } 60 | 61 | protected CDAClient createPreviewClient() { 62 | return createBuilder() 63 | .preview() 64 | .setEndpoint(serverUrl()) 65 | .build(); 66 | } 67 | 68 | protected CDAClient.Builder createBuilder() { 69 | return CDAClient.builder() 70 | .setSpace(DEFAULT_SPACE) 71 | .setToken(DEFAULT_TOKEN) 72 | .setEndpoint(serverUrl()); 73 | } 74 | 75 | protected String serverUrl() { 76 | return "http://" + server.getHostName() + ":" + server.getPort(); 77 | } 78 | 79 | protected MockWebServer createServer() { 80 | return new MockWebServer(); 81 | } 82 | 83 | protected void enqueue(TestResponse response) throws IOException { 84 | URL resource = getClass().getClassLoader().getResource(response.getFileName()); 85 | checkNotNull(resource, "File not found: " + response.getFileName()); 86 | final MockResponse mock = new MockResponse() 87 | .setResponseCode(response.getCode()) 88 | .setBody(readFileToString(new File(resource.getFile()), defaultCharset())); 89 | 90 | if (response.headers().size() > 0) { 91 | mock.setHeaders(response.headers()); 92 | } 93 | 94 | server.enqueue(mock); 95 | } 96 | 97 | public BaseTest setResponseQueue(List responseQueue) { 98 | this.responseQueue = responseQueue; 99 | return this; 100 | } 101 | 102 | protected T assertCallback(TestCallback callback) { 103 | assertThat(callback.error()).isNull(); 104 | assertThat(callback.result()).isNotNull(); 105 | return callback.result(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/integration/IntegrationWithMasterEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda.integration; 2 | 3 | import com.contentful.java.cda.CDAAsset; 4 | import com.contentful.java.cda.CDAClient; 5 | import com.contentful.java.cda.CDAEntry; 6 | import com.contentful.java.cda.CDALocale; 7 | import com.contentful.java.cda.CDASpace; 8 | import com.contentful.java.cda.LocalizedResource; 9 | import com.contentful.java.cda.SynchronizedSpace; 10 | 11 | import java.util.List; 12 | 13 | import static com.contentful.java.cda.CDAType.SPACE; 14 | import static com.contentful.java.cda.SyncType.onlyDeletedAssets; 15 | import static com.google.common.truth.Truth.assertThat; 16 | 17 | public class IntegrationWithMasterEnvironment extends Integration { 18 | @Override public void setUp() { 19 | client = CDAClient.builder() 20 | .setSpace("5s4tdjmyjfpl") 21 | .setToken("84017d3a5da6d3ae9c733c6c210c55eebc3da033730b4e5093a6e6aa099b4995") 22 | .setEnvironment("master") 23 | .build(); 24 | } 25 | 26 | // space id and so on changed on master 27 | @Override public void fetchSpace() { 28 | CDASpace space = client.fetchSpace(); 29 | assertThat(space.name()).isEqualTo("Contentful Example API with En"); 30 | assertThat(space.id()).isEqualTo("5s4tdjmyjfpl"); 31 | assertThat(space.type()).isEqualTo(SPACE); 32 | } 33 | 34 | // asset url changed between master and staging 35 | @Override public void fetchSpecificAsset() { 36 | CDAAsset entry = client.fetch(CDAAsset.class).one("nyancat"); 37 | 38 | assertThat(entry.url()).isEqualTo("//images.ctfassets.net/5s4tdjmyjfpl/nyancat/" + 39 | "28850673d35cacb94192832b5f5c1960/Nyan_cat_250px_frame.png"); 40 | } 41 | 42 | 43 | // locales have different ids 44 | @Override public void fetchOneLocale() { 45 | final CDALocale found = client.fetch(CDALocale.class).one("4pPeIa89F7KD3G1q47eViY"); 46 | 47 | assertThat(found.code()).isEqualTo("en-US"); 48 | assertThat(found.name()).isEqualTo("English"); 49 | assertThat(found.fallbackLocaleCode()).isNull(); 50 | assertThat(found.isDefaultLocale()).isTrue(); 51 | } 52 | 53 | @Override void assertNyanCat(CDAEntry entry) { 54 | assertThat(entry.id()).isEqualTo("nyancat"); 55 | assertThat(entry.getField("name")).isEqualTo("Nyan Cat"); 56 | assertThat(entry.getField("color")).isEqualTo("rainbow"); 57 | assertThat(entry.getField("birthday")).isEqualTo("2011-04-04T22:00+00:00"); 58 | assertThat(entry.getField("lives")).isEqualTo(1337.0); 59 | 60 | List likes = entry.getField("likes"); 61 | assertThat(likes).containsExactly("rainbows", "fish"); 62 | 63 | Object bestFriend = entry.getField("bestFriend"); 64 | assertThat(bestFriend).isInstanceOf(CDAEntry.class); 65 | assertThat(entry).isSameAs(((CDAEntry) bestFriend).getField("bestFriend")); 66 | 67 | // Localization 68 | final LocalizedResource.Localizer localized = entry.localize("tlh"); 69 | assertThat(localized.getField("color")).isEqualTo("rainbow"); 70 | assertThat(localized.getField("non-existing-does-not-throw")).isNull(); 71 | } 72 | 73 | @Override public void syncTypeOfDeletedAssets() { 74 | final SynchronizedSpace space = client.sync(onlyDeletedAssets()).fetch(); 75 | 76 | assertThat(space.nextSyncUrl()).isNotEmpty(); 77 | assertThat(space.items()).hasSize(0); 78 | assertThat(space.assets()).hasSize(0); 79 | assertThat(space.deletedEntries()).hasSize(0); 80 | assertThat(space.deletedAssets()).hasSize(0); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/LocalizedResource.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Represents a resource which may contain field values for multiple locales. 7 | */ 8 | public abstract class LocalizedResource extends CDAResource { 9 | private static final long serialVersionUID = 5713028146014748949L; 10 | 11 | public class Localizer { 12 | private final String locale; 13 | 14 | Localizer(String locale) { 15 | this.locale = locale; 16 | } 17 | 18 | /** 19 | * Extracts a field from the fields set of the active locale, result type is inferred. 20 | * 21 | * @param key field key. 22 | * @param type. 23 | * @return field value, null if it doesn't exist. 24 | */ 25 | @SuppressWarnings("unchecked") 26 | public T getField(String key) { 27 | final Map value = (Map) fields.get(key); 28 | if (value == null) { 29 | return null; 30 | } 31 | 32 | return getFieldForFallbackLocale(value, locale); 33 | } 34 | 35 | T getFieldForFallbackLocale(Map value, String locale) { 36 | if (locale == null) { 37 | return null; 38 | } 39 | 40 | final T localized = value.get(locale); 41 | if (localized != null) { 42 | return localized; 43 | } else { 44 | return getFieldForFallbackLocale(value, fallbackLocaleMap.get(locale)); 45 | } 46 | } 47 | } 48 | 49 | String defaultLocale; 50 | 51 | Map fallbackLocaleMap; 52 | 53 | Map fields; 54 | 55 | Map rawFields; 56 | 57 | /** 58 | * Creates an object to be used for returning different field in one locale. 59 | * 60 | * @param locale pointing to a locale in the environment. 61 | * @return localizer to localize the fields. 62 | */ 63 | public Localizer localize(String locale) { 64 | return new Localizer(locale); 65 | } 66 | 67 | /** 68 | * Get a field using the environments default locale. 69 | * 70 | * @param key field key. 71 | * @param type. 72 | * @return field value, null if it doesn't exist. 73 | * @see #localize(String) 74 | */ 75 | public T getField(String key) { 76 | return localize(defaultLocale).getField(key); 77 | } 78 | 79 | /** 80 | * Extracts a field from the fields set of the active locale, result type is inferred. 81 | * 82 | * @param locale locale to be used. 83 | * @param key field key. 84 | * @param type. 85 | * @return field value, null if it doesn't exist. 86 | */ 87 | public T getField(String locale, String key) { 88 | return localize(locale).getField(key); 89 | } 90 | 91 | /** 92 | * Internal method for updating contents of a field. 93 | *

94 | * This method is used by the SDK to generate objects based on raw fields. 95 | * 96 | * Do not use this field to update data on Contentful. Take a look at the CMA-SDK for that. 97 | * 98 | * @param locale locale to be updated. 99 | * @param key the key of the field to be updated. 100 | * @param value the value of the field to be used. 101 | */ 102 | @SuppressWarnings("unchecked") 103 | public void setField(String locale, String key, Object value) { 104 | ((Map) fields.get(key)).put(locale, value); 105 | } 106 | 107 | /** 108 | * @return raw unprocessed fields. 109 | */ 110 | public Map rawFields() { 111 | return rawFields; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/java/cda/ContentTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.contentful.java.cda.lib.Enqueue; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import static com.google.common.truth.Truth.assertThat; 12 | 13 | public class ContentTypeTest extends BaseTest { 14 | @Test 15 | @Enqueue("demo/content_types_cat.json") 16 | public void fetchContentType() { 17 | CDAContentType cat = client.fetch(CDAContentType.class).one("cat"); 18 | assertThat(cat.name()).isEqualTo("Cat"); 19 | assertThat(cat.displayField()).isEqualTo("name"); 20 | assertThat(cat.description()).isEqualTo("Meow."); 21 | assertThat(cat.fields()).hasSize(8); 22 | } 23 | 24 | @Test 25 | @Enqueue({"demo/content_types_cat.json", "demo/content_types_fake.json"}) 26 | public void manuallyFetchedContentTypeIsCached() { 27 | client.fetch(CDAContentType.class).one("cat"); 28 | assertThat(client.cache.types()).hasSize(5); 29 | assertThat(client.cache.types()).doesNotContainKey("fake"); 30 | 31 | CDAContentType fake = client.fetch(CDAContentType.class).one("fake"); 32 | assertThat(client.cache.types()).hasSize(6); 33 | assertThat(client.cache.types().get(fake.id())).isSameAs(fake); 34 | } 35 | 36 | @Test 37 | @Enqueue(defaults = { 38 | "cda/locales.json" 39 | }, value = { 40 | "cda/content_types_foo.json", 41 | "cda/entries.json", 42 | "cda/content_types_bar.json" 43 | }) 44 | public void missingContentTypeIsFetchedAndCached() { 45 | assertThat(client.cache.types()).isNull(); 46 | CDAArray array = client.fetch(CDAEntry.class).all(); 47 | CDAEntry foo = array.entries().get("3UpazZmO8g8iI0iWAMmGMS"); 48 | assertThat(foo).isNotNull(); 49 | 50 | CDAEntry bar = foo.getField("link"); 51 | assertThat(bar).isNotNull(); 52 | assertThat(bar.getField("name")).isEqualTo("bar"); 53 | 54 | assertThat(client.cache.types()).containsKey("3lYaFZKDgQCUwWy6uEoQYi"); 55 | } 56 | 57 | @Test(expected = CDAContentTypeNotFoundException.class) 58 | @Enqueue({ 59 | "demo/entries_fake.json", 60 | "array_empty.json" 61 | }) 62 | public void badTypeMappingThrows() { 63 | try { 64 | client.fetch(CDAEntry.class).all(); 65 | } catch (CDAContentTypeNotFoundException e) { 66 | final String message = "Could not find content type 'foo' " + 67 | "for resource with id 'bar' of type 'CDAEntry'."; 68 | assertThat(e.getMessage()).isEqualTo(message); 69 | throw e; 70 | } 71 | } 72 | 73 | @Test 74 | @Enqueue("demo/content_types_cat.json") 75 | public void fetchFieldValidations() { 76 | CDAContentType catContentType = client.fetch(CDAContentType.class).one("cat"); 77 | assertThat(catContentType).isNotNull(); 78 | CDAField colorField = findFieldById(catContentType.fields(), "color"); 79 | assertThat(colorField).isNotNull(); 80 | assertThat(colorField.validations()).hasSize(1); 81 | Map colorValidation = colorField.validations().get(0); 82 | assertThat(colorValidation).containsKey("in"); 83 | @SuppressWarnings("unchecked") 84 | List allowedColorValues = (List) colorValidation.get("in"); 85 | assertThat(allowedColorValues).isNotNull(); 86 | assertThat(allowedColorValues).hasSize(2); 87 | assertThat(allowedColorValues).containsExactlyElementsIn(Arrays.asList("rainbow", "pink")); 88 | } 89 | 90 | private CDAField findFieldById(List fields, String id) { 91 | for (CDAField field : fields) { 92 | if (id.equals(field.id())) { 93 | return field; 94 | } 95 | } 96 | return null; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/java/cda/CDAAsset.java: -------------------------------------------------------------------------------- 1 | package com.contentful.java.cda; 2 | 3 | import com.contentful.java.cda.image.ImageOption; 4 | 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Represents a single asset. 10 | */ 11 | public class CDAAsset extends LocalizedResource { 12 | 13 | private static final long serialVersionUID = -4645571481643616657L; 14 | private CDAMetadata metadata; 15 | /** 16 | * @return the metadata set. 17 | */ 18 | public CDAMetadata metadata() { 19 | return metadata; 20 | } 21 | 22 | /** 23 | * @return title of this asset. 24 | */ 25 | public String title() { 26 | return getField("title"); 27 | } 28 | 29 | /** 30 | * @return url to the file of this asset. 31 | */ 32 | public String url() { 33 | return fileField("url"); 34 | } 35 | 36 | /** 37 | * Returns a url with the given image manipulation. 38 | *

39 | * If the asset is not pointing to an image (as identified by its mimetype) the {@link #url()} is 40 | * returned. Same happens if the options are empty or non existing, then the url gets returned. 41 | *

42 | * In an error case (for instance, using the same option twice), the last option with the same 43 | * operation will be used. 44 | * 45 | * @param options to manipulate the image the returned url will be pointing to. 46 | * @return an url reflecting all the options given. 47 | * @throws IllegalArgumentException if no options are given. 48 | * @throws IllegalArgumentException if no mimetype was set on asset. 49 | * @throws IllegalArgumentException if mimetype was not an image. 50 | * @see ImageOption 51 | * @see #url() 52 | */ 53 | public String urlForImageWith(ImageOption... options) { 54 | if (options == null || options.length == 0) { 55 | throw new IllegalArgumentException("Do not use empty options argument. " 56 | + "If you want to manipulate the url by hand, please use `CDAAsset.url()` instead."); 57 | } 58 | 59 | final String mimeType = mimeType(); 60 | if (mimeType == null || !mimeType.startsWith("image")) { 61 | throw new IllegalStateException("Asset does not have an image mime type."); 62 | } 63 | 64 | final Map mappedOptions 65 | = new LinkedHashMap<>(options.length); 66 | 67 | for (final ImageOption option : options) { 68 | mappedOptions.put(option.getOperation(), option); 69 | } 70 | 71 | String url = url(); 72 | for (final ImageOption option : mappedOptions.values()) { 73 | url = option.apply(url); 74 | } 75 | 76 | return url; 77 | } 78 | 79 | /** 80 | * @return mime-type of this asset. 81 | */ 82 | public String mimeType() { 83 | return fileField("contentType"); 84 | } 85 | 86 | /** 87 | * Helper method to extract a field from the {@code file} map. 88 | * 89 | * @param key the key who's value to be returned. 90 | * @param the type of this field. 91 | * @return field of this file. 92 | */ 93 | @SuppressWarnings("unchecked") 94 | public T fileField(String key) { 95 | T result = null; 96 | Map file = getField("file"); 97 | if (file != null) { 98 | result = (T) file.get(key); 99 | } 100 | return result; 101 | } 102 | 103 | /** 104 | * Return a string, showing the id and title. 105 | * 106 | * @return a human readable string 107 | */ 108 | @Override public String toString() { 109 | return "CDAAsset{" 110 | + "id='" + id() + '\'' 111 | + ", title='" + title() + '\'' 112 | + ", metadata='" + metadata() + '\'' 113 | + '}'; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_text_embedded.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "2oVOBFJuE0ym8Yqqi2CS4i", 19 | "type": "Entry", 20 | "createdAt": "2018-10-17T13:32:29.635Z", 21 | "updatedAt": "2018-10-17T13:32:29.635Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 1, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_text_embedded", 41 | "rich": { 42 | "content": [ 43 | { 44 | "data": { 45 | "target": { 46 | "sys": { 47 | "id": "ybxKk5Avzqam6KymAOgIG", 48 | "type": "Link", 49 | "linkType": "Entry" 50 | } 51 | } 52 | }, 53 | "content": [ 54 | { 55 | "marks": [], 56 | "value": "", 57 | "nodeType": "text" 58 | } 59 | ], 60 | "nodeType": "embedded-entry-block" 61 | }, 62 | { 63 | "data": {}, 64 | "content": [ 65 | { 66 | "marks": [], 67 | "value": "", 68 | "nodeType": "text" 69 | } 70 | ], 71 | "nodeType": "paragraph" 72 | } 73 | ], 74 | "nodeType": "document" 75 | } 76 | } 77 | } 78 | ], 79 | "includes": { 80 | "Entry": [ 81 | { 82 | "sys": { 83 | "space": { 84 | "sys": { 85 | "type": "Link", 86 | "linkType": "Space", 87 | "id": "" 88 | } 89 | }, 90 | "id": "ybxKk5Avzqam6KymAOgIG", 91 | "type": "Entry", 92 | "createdAt": "2018-10-17T13:32:26.926Z", 93 | "updatedAt": "2018-10-17T13:32:26.926Z", 94 | "environment": { 95 | "sys": { 96 | "id": "human-readable", 97 | "type": "Link", 98 | "linkType": "Environment" 99 | } 100 | }, 101 | "revision": 1, 102 | "contentType": { 103 | "sys": { 104 | "type": "Link", 105 | "linkType": "ContentType", 106 | "id": "rich" 107 | } 108 | }, 109 | "locale": "en-US" 110 | }, 111 | "fields": { 112 | "name": "simple_text", 113 | "rich": { 114 | "content": [ 115 | { 116 | "data": {}, 117 | "content": [ 118 | { 119 | "marks": [], 120 | "value": "This is some simple text", 121 | "nodeType": "text" 122 | } 123 | ], 124 | "nodeType": "paragraph" 125 | } 126 | ], 127 | "nodeType": "document" 128 | } 129 | } 130 | } 131 | ] 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/resources/rich_text/simple_asset_hyperlink.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "" 16 | } 17 | }, 18 | "id": "3hSV7a95dSiMGcgmSGsi6I", 19 | "type": "Entry", 20 | "createdAt": "2018-10-31T10:22:06.907Z", 21 | "updatedAt": "2018-10-31T10:22:06.907Z", 22 | "environment": { 23 | "sys": { 24 | "id": "human-readable", 25 | "type": "Link", 26 | "linkType": "Environment" 27 | } 28 | }, 29 | "revision": 1, 30 | "contentType": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "ContentType", 34 | "id": "rich" 35 | } 36 | }, 37 | "locale": "en-US" 38 | }, 39 | "fields": { 40 | "name": "simple_asset_hyperlink", 41 | "rich": { 42 | "data": {}, 43 | "content": [ 44 | { 45 | "data": {}, 46 | "content": [ 47 | { 48 | "data": {}, 49 | "marks": [], 50 | "value": "", 51 | "nodeType": "text" 52 | }, 53 | { 54 | "data": { 55 | "target": { 56 | "sys": { 57 | "id": "47Q1142uaI6SuK8UoGeeQy", 58 | "type": "Link", 59 | "linkType": "Asset" 60 | } 61 | } 62 | }, 63 | "content": [ 64 | { 65 | "data": {}, 66 | "marks": [], 67 | "value": "Asset hyperlink to cat image", 68 | "nodeType": "text" 69 | } 70 | ], 71 | "nodeType": "asset-hyperlink" 72 | }, 73 | { 74 | "data": {}, 75 | "marks": [], 76 | "value": "", 77 | "nodeType": "text" 78 | } 79 | ], 80 | "nodeType": "paragraph" 81 | } 82 | ], 83 | "nodeType": "document" 84 | } 85 | } 86 | } 87 | ], 88 | "includes": { 89 | "Asset": [ 90 | { 91 | "sys": { 92 | "space": { 93 | "sys": { 94 | "type": "Link", 95 | "linkType": "Space", 96 | "id": "" 97 | } 98 | }, 99 | "id": "47Q1142uaI6SuK8UoGeeQy", 100 | "type": "Asset", 101 | "createdAt": "2018-10-31T10:21:34.784Z", 102 | "updatedAt": "2018-10-31T10:21:34.784Z", 103 | "environment": { 104 | "sys": { 105 | "id": "human-readable", 106 | "type": "Link", 107 | "linkType": "Environment" 108 | } 109 | }, 110 | "revision": 1, 111 | "locale": "en-US" 112 | }, 113 | "fields": { 114 | "title": "cat", 115 | "file": { 116 | "url": "//images.ctfassets.net//47Q1142uaI6SuK8UoGeeQy/4136a21284ad31498f2cbb5996329396/Kittyply_edit1.jpg", 117 | "details": { 118 | "size": 1009523, 119 | "image": { 120 | "width": 1728, 121 | "height": 1152 122 | } 123 | }, 124 | "fileName": "Kittyply_edit1.jpg", 125 | "contentType": "image/jpeg" 126 | } 127 | } 128 | } 129 | ] 130 | } 131 | } 132 | --------------------------------------------------------------------------------