diff --git a/.travis.yml b/.travis.yml index 2967a8c..cb98ca0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ script: - "sbt test it:test unidoc" - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then sbt "gitPublish target/javaunidoc https://$GH_TOKEN:x-oauth-basic@github.com/$TRAVIS_REPO_SLUG.git - javadoc sphere-oss automation@commercetools.de"; fi\ + javadoc sphere-oss automation@commercetools.de"; fi jdk: - oraclejdk8 diff --git a/cms-contentful/it/com/commercetools/sunrise/cms/contentful/ContentfulCmsServiceIT.java b/cms-contentful/it/com/commercetools/sunrise/cms/contentful/ContentfulCmsServiceIT.java index 7d2f9ed..e1fed3b 100644 --- a/cms-contentful/it/com/commercetools/sunrise/cms/contentful/ContentfulCmsServiceIT.java +++ b/cms-contentful/it/com/commercetools/sunrise/cms/contentful/ContentfulCmsServiceIT.java @@ -2,10 +2,8 @@ import com.commercetools.sunrise.cms.CmsPage; import com.commercetools.sunrise.cms.CmsServiceException; -import org.junit.Before; import org.junit.Test; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -14,11 +12,13 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.ThrowableAssert.catchThrowable; public class ContentfulCmsServiceIT { - private static final List SUPPORTED_LOCALES = Collections.singletonList(Locale.GERMANY); + private static final List SUPPORTED_LOCALES = singletonList(Locale.GERMANY); // credentials for contentful demo account private static final String IT_PREFIX = "CONTENTFUL_"; @@ -26,7 +26,6 @@ public class ContentfulCmsServiceIT { private static final String IT_CF_TOKEN = IT_PREFIX + "TOKEN"; private static final String PAGE_TYPE_NAME = "page"; private static final String PAGE_TYPE_ID_FIELD_NAME = "slug"; - private ContentfulCmsService contentfulCmsService; private static String spaceId() { return getValueForEnvVar(IT_CF_SPACE_ID); @@ -36,11 +35,6 @@ private static String token() { return getValueForEnvVar(IT_CF_TOKEN); } - @Before - public void setUp() throws Exception { - contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); - } - @Test public void whenCouldNotFetchEntry_thenThrowException() throws Exception { final ContentfulCmsService cmsService = ContentfulCmsService.of("", "", PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); @@ -52,63 +46,214 @@ public void whenCouldNotFetchEntry_thenThrowException() throws Exception { } @Test - public void whenAskForExistingStringContentThenGet() throws Exception { + public void whenAskForExistingStringContent_thenGet() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); + Optional content = waitAndGet(contentfulCmsService.page("finn", SUPPORTED_LOCALES)); - assertThat(content).isPresent(); - assertThat(content.get().field("pageContent.description")).contains("Fearless Abenteurer! Verteidiger von Pfannkuchen."); + assertThat(content).isPresent(); + Optional field = content.get().field("pageContent.description"); + assertThat(field).hasValue("Fearless Abenteurer! Verteidiger von Pfannkuchen."); } @Test - public void whenAskForExistingStringContentAndLocalesAreEmptyThenGetDefaultLocaleContent() throws Exception { - Optional content = waitAndGet(contentfulCmsService.page("finn", Collections.emptyList())); - assertThat(content).isPresent(); + public void whenAskForExistingStringContentAndLocalesAreEmpty_thenGetDefaultLocaleContent() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); - assertThat(content.get().field("pageContent.description")).contains("Fearless Abenteurer! Verteidiger von Pfannkuchen."); + Optional content = waitAndGet(contentfulCmsService.page("finn", emptyList())); + + assertThat(content).isPresent(); + Optional field = content.get().field("pageContent.description"); + assertThat(field).hasValue("Fearless Abenteurer! Verteidiger von Pfannkuchen."); } @Test - public void whenAskForExistingStringContentAndLocalesAreNullThenGetDefaultLocaleContent() throws Exception { + public void whenAskForExistingStringContentAndLocalesAreNull_thenGetDefaultLocaleContent() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); + Optional content = waitAndGet(contentfulCmsService.page("finn", null)); - assertThat(content).isPresent(); - assertThat(content.get().field("pageContent.description")).contains("Fearless Abenteurer! Verteidiger von Pfannkuchen."); + assertThat(content).isPresent(); + Optional field = content.get().field("pageContent.description"); + assertThat(field).hasValue("Fearless Abenteurer! Verteidiger von Pfannkuchen."); } @Test - public void whenAskForExistingStringContentWithNotDefaultLocaleThenGet() throws Exception { - Optional content = waitAndGet(contentfulCmsService.page("finn", Collections.singletonList(Locale.ENGLISH))); - assertThat(content).isPresent(); + public void whenAskForExistingStringContentWithNotDefaultLocale_thenGetDefaultLocaleContent() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); + + Optional content = waitAndGet(contentfulCmsService.page("finn", singletonList(Locale.ENGLISH))); - assertThat(content.get().field("pageContent.description")).contains("Fearless adventurer! Defender of pancakes."); + assertThat(content).isPresent(); + Optional field = content.get().field("pageContent.description"); + assertThat(field).hasValue("Fearless adventurer! Defender of pancakes."); } @Test - public void whenAskForExistingStringContentWithNotDefinedLocaleThenNotPresent() throws Exception { - Optional content = waitAndGet(contentfulCmsService.page("finn", Collections.singletonList(Locale.ITALIAN))); - assertThat(content).isEmpty(); + public void whenAskForExistingStringContentWithNotDefinedLocale_thenReturnEmpty() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); + + Optional content = waitAndGet(contentfulCmsService.page("finn", singletonList(Locale.ITALIAN))); + + assertThat(content).isNotPresent(); } @Test - public void whenAskForNotExistingStringContentThenNotPresent() throws Exception { + public void whenAskForNotExistingStringContent_thenReturnEmpty() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); + Optional content = waitAndGet(contentfulCmsService.page("finn", SUPPORTED_LOCALES)); + assertThat(content).isPresent(); - assertThat(content.get().field("pageContent.notExistingField")).isEmpty(); + Optional field = content.get().field("pageContent.notExistingField"); + assertThat(field).isNotPresent(); } @Test - public void whenAskForExistingAssetContentThenGet() throws Exception { + public void whenAskForExistingAssetContent_thenGet() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); + Optional content = waitAndGet(contentfulCmsService.page("jacke", SUPPORTED_LOCALES)); + assertThat(content).isPresent(); - assertThat(content.get().fieldOrEmpty("pageContent.image")).isEqualToIgnoringCase("//images.contentful.com/l6chdlzlf8jn/2iVeCh1FGoy00Oq8WEI2aI/93c3f0841fcf59743f57e238f6ed67aa/jake.png"); + String actual = content.get().fieldOrEmpty("pageContent.image"); + assertThat(actual).isEqualToIgnoringCase("//images.contentful.com/l6chdlzlf8jn/2iVeCh1FGoy00Oq8WEI2aI/93c3f0841fcf59743f57e238f6ed67aa/jake.png"); } @Test - public void whenAskForNotExistingAssetContentThenNotPresent() throws Exception { + public void whenAskForNotExistingAssetContent_thenReturnEmpty() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), PAGE_TYPE_NAME, PAGE_TYPE_ID_FIELD_NAME); + Optional content = waitAndGet(contentfulCmsService.page("jacke", SUPPORTED_LOCALES)); + assertThat(content).isPresent(); - assertThat(content.get().fieldOrEmpty("pageContent.notExistingAsset")).isEmpty(); + String actual = content.get().fieldOrEmpty("pageContent.notExistingAsset"); + assertThat(actual).isEmpty(); + + } + + @Test + public void whenAskForContentInArray_thenGetElement() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + Optional content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + Optional field = content.get().field("array[0].name"); + assertThat(field).hasValue("author1"); + } + + @Test + public void whenAskForContentInArrayOutsideTheScope_thenReturnEmpty() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + // array element consists of only 4 items + Optional field = content.get().field("array[4].name"); + assertThat(field).isNotPresent(); + } + + @Test + public void whenAskForContentInNestedArray_thenGetElement() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + Optional field = content.get().field("array[1].images[1].photo"); + assertThat(field).hasValue("//images.contentful.com/l6chdlzlf8jn/3slBXXe6WcsiM46OuEuIKe/bab374a315d1825c111d9d89843cafc0/logo.gif"); + } + + @Test + public void whenAskForTextContentArray_thenGetElement() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + assertThat(content).isPresent(); + Optional field = content.get().field("array[2].simpleText"); + assertThat(field).hasValue("simpleText1"); + } + + @Test + public void whenAskForLocation_thenGetLocationField() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + Optional field = content.get().field("locationField"); + assertThat(field).hasValue("{lon=19.62158203125, lat=51.37199469960235}"); + } + + @Test + public void whenAskForContentInMediaField_thenGetUrlField() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + Optional field = content.get().field("mediaOneFile"); + assertThat(field).hasValue("//images.contentful.com/l6chdlzlf8jn/2m1NzbeXYUksOGIwceEy0U/4e3cc53a96313a4bd822777af78a3b4d/some-5.jpg"); + } + + @Test + public void whenAskForContentInMediaArray_thenGetUrlElements() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + Optional field = content.get().field("mediaManyFiles[0]"); + assertThat(field).hasValue("//images.contentful.com/l6chdlzlf8jn/6j9p38phC0oU0g42aqUSc4/2eb0c261bc13353ed867b13076af6b1f/logo.gif"); + + field = content.get().field("mediaManyFiles[1]"); + assertThat(field).hasValue("//images.contentful.com/l6chdlzlf8jn/27BPx56xMcuGKe8uk8Auss/335581cd9daf3e9d0de254313e36d43b/some-5.jpg"); + } + + @Test + public void whenAskForArray_thenReturnEmpty() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + // textArrayField is an array element so it can't be fetched as a whole + Optional field = content.get().field("array[3].textArrayField"); + assertThat(field).isNotPresent(); + } + + @Test + public void whenAskForContentWithGermanOrEmptyLocales_thenGetGermanTranslation() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", SUPPORTED_LOCALES).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + Optional field = content.get().field("array[3].textArrayField[1]"); + assertThat(field).hasValue("zwei"); + + content = contentfulCmsService.page("finn", emptyList()).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + field = content.get().field("array[3].textArrayField[1]"); + assertThat(field).hasValue("zwei"); + + content = waitAndGet(contentfulCmsService.page("finn", singletonList(Locale.ITALIAN))); + + assertThat(content).isNotPresent(); + } + + @Test + public void whenAskForContentWithEnglishTranslation_thenGetEnglishTranslation() throws Exception { + ContentfulCmsService contentfulCmsService = ContentfulCmsService.of(spaceId(), token(), "finn", PAGE_TYPE_ID_FIELD_NAME); + + Optional content = contentfulCmsService.page("finn", singletonList(Locale.ENGLISH)).toCompletableFuture().get(5, TimeUnit.SECONDS); + + assertThat(content).isPresent(); + Optional field = content.get().field("array[3].textArrayField[1]"); + assertThat(field).hasValue("two"); } private T waitAndGet(final CompletionStage stage) throws InterruptedException, ExecutionException, TimeoutException { diff --git a/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/ContentfulCmsPage.java b/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/ContentfulCmsPage.java index 55a2282..1829a24 100644 --- a/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/ContentfulCmsPage.java +++ b/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/ContentfulCmsPage.java @@ -1,92 +1,184 @@ package com.commercetools.sunrise.cms.contentful; import com.commercetools.sunrise.cms.CmsPage; -import com.commercetools.sunrise.cms.contentful.models.FieldTypes; -import com.contentful.java.cda.CDAAsset; import com.contentful.java.cda.CDAEntry; import com.contentful.java.cda.CDAField; import org.apache.commons.lang3.StringUtils; import javax.annotation.Nullable; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import static com.commercetools.sunrise.cms.contentful.models.FieldType.ARRAY; +import static com.commercetools.sunrise.cms.contentful.models.FieldType.toStringStrategy; +import static java.util.Arrays.copyOfRange; import static org.apache.commons.lang3.StringUtils.split; public class ContentfulCmsPage implements CmsPage { + private static final Pattern ARRAY_KEY_PATTERN = Pattern.compile("(.+)\\[(\\d+)\\]$"); + private CDAEntry cdaEntry; private List locales; - public ContentfulCmsPage(CDAEntry cdaEntry, List locales) { + public ContentfulCmsPage(final CDAEntry cdaEntry, final List locales) { this.cdaEntry = cdaEntry; this.locales = locales; } @Override - public Optional field(final String fieldName) { - if (StringUtils.isEmpty(fieldName)) { + public Optional field(final String path) { + if (StringUtils.isEmpty(path)) { return Optional.empty(); } - final List allNames = new ArrayList<>(Arrays.asList(split(fieldName, "."))); - return Optional.ofNullable(getEntryWithContentField(cdaEntry, getEntryNamesList(allNames))) - .flatMap(entryWithContentField -> - getContent(entryWithContentField, getContentFieldName(allNames))); - } - private String getContentFieldName(List allNames) { - return allNames.size() > 1 ? allNames.get(allNames.size() - 1) : allNames.get(0); + final String[] pathSegments = split(path, "."); + final String fieldKey = pathSegments[pathSegments.length - 1]; + final String[] entryPathSegments = createEntryPathSegments(pathSegments); + return findEntry(entryPathSegments).flatMap(lastEntry -> + findContent(lastEntry, fieldKey)); } - private List getEntryNamesList(List allNames) { - return allNames.size() > 1 ? - allNames.subList(0, allNames.size() - 1) : Collections.emptyList(); + /** + * Form an array with last segment skipped which is expected to be a field name. + * + * @param pathSegments entire path segments + * @return path segments of entries only + */ + private String[] createEntryPathSegments(final String[] pathSegments) { + return copyOfRange(pathSegments, 0, pathSegments.length - 1); } - private Optional getContent(final CDAEntry entry, final String contentFieldName) { - return getCdaField(entry, contentFieldName) - .flatMap(cdaField -> { - final Object entryField = entry.getField(contentFieldName); - return getContentAccordingToFieldDefinition(entryField, cdaField); - }); + /** + * Traverse contained CDAEntry to match all path segments. + * + * @param pathSegments array of all path segments that lead to CDAEntry of interest + * @return CDAEntry that matches path segments + */ + private Optional findEntry(final String[] pathSegments) { + CDAEntry entry = cdaEntry; + + for (String key: pathSegments) { + Object nextEntry = null; + + Matcher arrayMatcher = ARRAY_KEY_PATTERN.matcher(key); + + if (arrayMatcher.find()) { + nextEntry = getEntryFromArray(entry, arrayMatcher); + } else if (entry.rawFields().containsKey(key)) { + nextEntry = entry.getField(key); + } + + if (nextEntry != null && nextEntry instanceof CDAEntry) { + entry = (CDAEntry) nextEntry; + } else { + return Optional.empty(); + } + } + + return Optional.of(entry); } - private CDAEntry getEntryWithContentField(@Nullable final CDAEntry cdaEntry, final List entryNamesList) { - if (entryNamesList.isEmpty()) { - return cdaEntry; - } else { - final String key = entryNamesList.get(0); - if (cdaEntry != null && cdaEntry.rawFields().containsKey(key)) { - Object item = cdaEntry.getField(key); - if (item instanceof CDAEntry) { - entryNamesList.remove(0); - return getEntryWithContentField((CDAEntry) item, entryNamesList); + /** + * Try to get an item from input entry which is supposed to contain an array of fields. + * + * After pattern has been matched arrayMatcher contains a regexp group of key and index. + * E.g. after matching 'key[1]' two groups of 'key' and '1' are contained in matcher. + * + * They are later used to find 'key' field in parentEntry and retrieve '1' (second) + * item from that field which is expected to form an array list. + * + * If the process fails null is returned. + * + * @param parentEntry should contain expected array field + * @param arrayMatcher contains key and index groups after pattern has been matched + * @return matched entry or null + */ + @Nullable + private Object getEntryFromArray(final CDAEntry parentEntry, final Matcher arrayMatcher) { + String arrayEntryKey = arrayMatcher.group(1); + int index = Integer.parseInt(arrayMatcher.group(2)); + if (parentEntry.rawFields().containsKey(arrayEntryKey)) { + Object field = parentEntry.getField(arrayEntryKey); + if (field instanceof List) { + List list = (List) field; + if (index < list.size()) { + return list.get(index); } } - return null; } + return null; + } + + /** + * Extract field from an entry and convert it its string representation. + * + * @param entry should contain expected field + * @param fieldKey id of field to be search inside entry + * @return string representation of the field + */ + private Optional findContent(final CDAEntry entry, final String fieldKey) { + Matcher arrayMatcher = ARRAY_KEY_PATTERN.matcher(fieldKey); + if (arrayMatcher.find()) { + String arrayFieldKey = arrayMatcher.group(1); + return findContentTypeField(entry, arrayFieldKey, true).flatMap(contentTypeField -> + getFieldFromArray(entry, arrayMatcher).map(field -> + getContentBasedOnType(field, contentTypeField))); + } + return findContentTypeField(entry, fieldKey, false).flatMap(contentTypeField -> + Optional.ofNullable(entry.getField(fieldKey)).map(field -> + getContentBasedOnType(field, contentTypeField))); } - // CDAField contains information related to field, like it's type - private Optional getCdaField(final CDAEntry lastLevelEntry, final String fieldName) { - return lastLevelEntry.contentType().fields() - .stream() - .filter(cdaField -> cdaField.id().equals(fieldName) - && FieldTypes.ALL_SUPPORTED.contains(cdaField.type())) - .findFirst(); + /** + * Try to get a field from input entry which is supposed to contain an array of fields. + * + * After pattern has been matched arrayMatcher contains a regexp group of key and index. + * E.g. after matching 'key[1]' two groups of 'key' and '1' are contained in matcher. + * + * They are later used to find 'key' field in entry and retrieve '1' (second) + * item from that field which is expected to form an array list. + * + * If the process fails empty optional object is returned. + * + * @param entry should contain expected array field + * @param arrayMatcher contains key and index groups after pattern has been matched + * @return matched field or empty optional object + */ + private Optional getFieldFromArray(final CDAEntry entry, final Matcher arrayMatcher) { + String arrayFieldKey = arrayMatcher.group(1); + int index = Integer.parseInt(arrayMatcher.group(2)); + Object field = entry.getField(arrayFieldKey); + Object item = null; + if (field != null && field instanceof List) { + List list = (List) field; + if (index < list.size()) { + item = list.get(index); + } + } + return Optional.ofNullable(item); } - private Optional getContentAccordingToFieldDefinition(@Nullable final Object localizedEntryField, - final CDAField cdaField) { - return Optional.ofNullable(localizedEntryField) - .map(entryField -> { - String content = null; - if (FieldTypes.CONVERTABLE_TO_STRING.contains(cdaField.type())) { - content = String.valueOf(entryField); - } else if (cdaField.linkType().equals(FieldTypes.LINK_ASSET)) { - content = ((CDAAsset) entryField).url(); - } - return content; - }); + /** + * Find content type of an entry and validate if it's supported by this implementation. + */ + private Optional findContentTypeField(final CDAEntry entry, final String fieldKey, + boolean arrayExpected) { + return entry.contentType().fields().stream() + .filter(field -> field.id().equals(fieldKey)) + .filter(field -> arrayExpected == ARRAY.type().equals(field.type())) + .findAny(); } + /** + * Convert content of the field to String if possible. + * + * @param field object to convert to string + * @param contentType information about field's type + * @return content of the field in String representation if possible + */ + private String getContentBasedOnType(final Object field, final CDAField contentType) { + return toStringStrategy(contentType).apply(field); + } } diff --git a/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/models/FieldType.java b/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/models/FieldType.java new file mode 100644 index 0000000..87a8e3b --- /dev/null +++ b/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/models/FieldType.java @@ -0,0 +1,74 @@ +package com.commercetools.sunrise.cms.contentful.models; + +import com.contentful.java.cda.CDAAsset; +import com.contentful.java.cda.CDAField; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public enum FieldType { + BOOLEAN("Boolean", true), + DATE("Date", true), + INTEGER("Integer", true), + NUMBER("Number", true), + SYMBOL("Symbol", true), + TEXT("Text", true), + LOCATION("Location", true), + LINK("Link", false), + ASSET("Asset", false), + ARRAY("Array", false); + + private static final Set WITH_STRING_REPRESENTATION = Collections.unmodifiableSet( + Arrays.stream(values()) + .filter(contentType -> contentType.hasStringRepresentation) + .map(FieldType::type) + .collect(Collectors.toSet())); + + private final String type; + private final boolean hasStringRepresentation; + + FieldType(String type, boolean hasStringRepresentation) { + this.type = type; + this.hasStringRepresentation = hasStringRepresentation; + } + + public String type() { + return type; + } + + public static Function toStringStrategy(final CDAField contentType) { + if (hasStringRepresentation(getType(contentType))) { + return String::valueOf; + } else if (isAsset(getLinkType(contentType))) { + return field -> ((CDAAsset) field).url(); + } + return field -> null; + } + + private static boolean hasStringRepresentation(final String type) { + return WITH_STRING_REPRESENTATION.contains(type); + } + + private static String getType(final CDAField contentType) { + return isArray(contentType) ? castToString(contentType.items().get("type")) : contentType.type(); + } + + private static String getLinkType(final CDAField contentType) { + return isArray(contentType) ? castToString(contentType.items().get("linkType")) : contentType.linkType(); + } + + private static boolean isAsset(final String linkType) { + return ASSET.type().equals(linkType); + } + + private static boolean isArray(final CDAField contentType) { + return ARRAY.type().equals(contentType.type()); + } + + private static String castToString(final Object type) { + return type != null && type instanceof String ? (String) type : null; + } +} diff --git a/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/models/FieldTypes.java b/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/models/FieldTypes.java deleted file mode 100644 index 9ecfa02..0000000 --- a/cms-contentful/src/main/java/com/commercetools/sunrise/cms/contentful/models/FieldTypes.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.commercetools.sunrise.cms.contentful.models; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public final class FieldTypes { - public static final String BOOLEAN = "Boolean"; - public static final String DATE = "Date"; - public static final String INTEGER = "Integer"; - public static final String LINK = "Link"; - public static final String NUMBER = "Number"; - public static final String SYMBOL = "Symbol"; - public static final String TEXT = "Text"; - public static final String LINK_ASSET = "Asset"; - public static final List CONVERTABLE_TO_STRING = Collections.unmodifiableList( - Arrays.asList(BOOLEAN, DATE, INTEGER, NUMBER, SYMBOL, TEXT)); - public static final List ALL_SUPPORTED = Collections.unmodifiableList( - new ArrayList(CONVERTABLE_TO_STRING) {{ - add(LINK); - }}); - - private FieldTypes() { - throw new AssertionError(); - } -} \ No newline at end of file diff --git a/cms-contentful/src/test/java/com/commercetools/sunrise/cms/contentful/ContentfulCmsPageTest.java b/cms-contentful/src/test/java/com/commercetools/sunrise/cms/contentful/ContentfulCmsPageTest.java index 02fc129..d3ec633 100644 --- a/cms-contentful/src/test/java/com/commercetools/sunrise/cms/contentful/ContentfulCmsPageTest.java +++ b/cms-contentful/src/test/java/com/commercetools/sunrise/cms/contentful/ContentfulCmsPageTest.java @@ -4,13 +4,13 @@ import com.contentful.java.cda.CDAContentType; import com.contentful.java.cda.CDAEntry; import com.contentful.java.cda.CDAField; -import org.junit.Before; import org.junit.Test; import java.util.*; -import static com.commercetools.sunrise.cms.contentful.models.FieldTypes.*; +import static com.commercetools.sunrise.cms.contentful.models.FieldType.*; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -19,16 +19,11 @@ public class ContentfulCmsPageTest { private static final List SUPPORTED_LOCALES = asList(Locale.GERMANY, Locale.US); private static final String FIELD_NAME = "leftTop"; private static final String CONTENT_VALUE = "Content of left top"; - private final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, CONTENT_VALUE); - private ContentfulCmsPage contentfulCmsPage; - - @Before - public void setUp() throws Exception { - contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); - } @Test public void whenEntryDoesNotHaveRequiredField_thenReturnOptionalEmpty() throws Exception { + CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, CONTENT_VALUE); + ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final String notMatchingFieldName = "notMatchingFieldName"; final Optional content = contentfulCmsPage.field(notMatchingFieldName); @@ -37,6 +32,8 @@ public void whenEntryDoesNotHaveRequiredField_thenReturnOptionalEmpty() throws E @Test public void whenEntryExistsInSupportedLanguage_thenReturnIt() throws Exception { + CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, CONTENT_VALUE); + ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); assertThat(content).contains("Content of left top"); @@ -44,7 +41,7 @@ public void whenEntryExistsInSupportedLanguage_thenReturnIt() throws Exception { @Test public void whenEntryFieldTypeIsText_thenReturnOptionalString() throws Exception { - final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, CONTENT_VALUE, TEXT); + CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, CONTENT_VALUE); final ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); @@ -54,7 +51,7 @@ public void whenEntryFieldTypeIsText_thenReturnOptionalString() throws Exception @Test public void whenEntryFieldTypeIsDate_thenReturnOptionalString() throws Exception { final String fieldContent = "2015-11-06T09:45:27"; - final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, DATE); + final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, DATE.type()); final ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); @@ -64,7 +61,7 @@ public void whenEntryFieldTypeIsDate_thenReturnOptionalString() throws Exception @Test public void whenEntryFieldTypeIsInteger_thenReturnOptionalString() throws Exception { final int fieldContent = 13; - final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, INTEGER); + final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, INTEGER.type()); final ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); @@ -74,7 +71,7 @@ public void whenEntryFieldTypeIsInteger_thenReturnOptionalString() throws Except @Test public void whenEntryFieldTypeIsNumber_thenReturnOptionalString() throws Exception { final double fieldContent = 3.14; - final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, NUMBER); + final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, NUMBER.type()); final ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); @@ -84,7 +81,7 @@ public void whenEntryFieldTypeIsNumber_thenReturnOptionalString() throws Excepti @Test public void whenEntryFieldTypeIsBoolean_thenReturnOptionalString() throws Exception { final boolean fieldContent = true; - final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, BOOLEAN); + final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, BOOLEAN.type()); final ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); @@ -96,7 +93,7 @@ public void whenEntryFieldTypeIsLinkAsset_thenReturnOptionalString() throws Exce final String fieldContent = "//some.url"; final CDAAsset mockAsset = mock(CDAAsset.class); when(mockAsset.url()).thenReturn(fieldContent); - final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, mockAsset, LINK, true); + final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, mockAsset, LINK.type(), true); final ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); @@ -108,7 +105,7 @@ public void whenEntryFieldTypeLinkIsOtherThanAsset_thenReturnOptionalEmpty() thr final String fieldContent = "//some.url"; final CDAAsset mockAsset = mock(CDAAsset.class); when(mockAsset.url()).thenReturn(fieldContent); - final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, mockAsset, LINK, false); + final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, mockAsset, LINK.type(), false); final ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); @@ -120,39 +117,85 @@ public void whenAssetHasNoUrl_thenReturnOptionalEmpty() throws Exception { final String fieldContent = null; final CDAAsset mockAsset = mock(CDAAsset.class); when(mockAsset.url()).thenReturn(fieldContent); - final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, mockAsset, LINK, true); + final CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, mockAsset, LINK.type(), true); final ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); final Optional content = contentfulCmsPage.field(FIELD_NAME); assertThat(content).isEmpty(); } - private CDAEntry mockEntry(final String fieldName, - final String fieldContent) { - return mockEntry(fieldName, fieldContent, SYMBOL, false); + @Test + public void whenEntryFieldTypeIsLocation_thenReturnOptionalString() throws Exception { + Object fieldContent = "{lon=19.62158203125, lat=51.37199469960235}"; + CDAEntry mockCdaEntry = mockEntry(FIELD_NAME, fieldContent, LOCATION.type()); + ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, SUPPORTED_LOCALES); + + Optional content = contentfulCmsPage.field(FIELD_NAME); + + assertThat(content.isPresent()); + assertThat(content.get()).isEqualTo("{lon=19.62158203125, lat=51.37199469960235}"); + } + + @Test + public void whenEntryFieldTypeIsArrayOfText_thenReturnOptionalString() throws Exception { + CDAEntry mockCdaEntry = mockEntry("textArrayField", createArray("two"), TEXT.type(), false); + ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaEntry, singletonList(Locale.ENGLISH)); + + Optional content = contentfulCmsPage.field("textArrayField[1]"); + + assertThat(content.isPresent()); + assertThat(content.get()).isEqualTo("two"); + } + + @Test + public void whenEntryFieldTypeIsArrayOfLinkAssetsInsideArrayEntry_thenReturnOptionalString() throws Exception { + CDAAsset mockAsset = mock(CDAAsset.class); + when(mockAsset.url()).thenReturn("//url"); + CDAEntry mockCdaEntry = mockEntry("assetArrayField", createArray(mockAsset), ASSET.type(), false); + CDAEntry mockCdaArrayEntry = mockEntry("array", createArray(mockCdaEntry), ARRAY.type(), false); + ContentfulCmsPage contentfulCmsPage = new ContentfulCmsPage(mockCdaArrayEntry, singletonList(Locale.ENGLISH)); + + Optional content = contentfulCmsPage.field("array[1].assetArrayField[1]"); + + assertThat(content.isPresent()); + assertThat(content.get()).isEqualTo("//url"); + } + + private ArrayList createArray(Object object) { + ArrayList array = new ArrayList<>(); + array.add(new Object()); + array.add(object); + array.add(new Object()); + return array; + } + + private CDAEntry mockEntry(final String fieldName, final String fieldContent) { + return mockEntry(fieldName, fieldContent, SYMBOL.type(), false); } - private CDAEntry mockEntry(final String fieldName, - final Object fieldContent, final String fieldType) { + private CDAEntry mockEntry(final String fieldName, final Object fieldContent, final String fieldType) { return mockEntry(fieldName, fieldContent, fieldType, false); } - private CDAEntry mockEntry(final String fieldName, - final Object fieldContent, final String fieldType, - final Boolean isLinkedAsset) { + private CDAEntry mockEntry(final String fieldName, final Object fieldContent, final String fieldType, final Boolean isLinkedAsset) { CDAEntry mockCdaEntry = mock(CDAEntry.class); CDAField mockCdaField = mock(CDAField.class); // mock entry type CDAContentType mockContentType = mock(CDAContentType.class); when(mockCdaEntry.contentType()).thenReturn(mockContentType); - when(mockCdaField.type()).thenReturn(fieldType); - when(mockCdaField.linkType()).thenReturn(isLinkedAsset ? LINK_ASSET : "otherLinkedType"); - when(mockCdaField.id()).thenReturn(FIELD_NAME); + when(mockCdaField.type()).thenReturn(fieldContent instanceof ArrayList ? ARRAY.type() : fieldType); + when(mockCdaField.linkType()).thenReturn(isLinkedAsset ? ASSET.type() : "otherLinkedType"); + when(mockCdaField.id()).thenReturn(fieldName); + Map items = new HashMap<>(); + items.put("type", fieldType); + items.put("linkType", fieldType); + when(mockCdaField.items()).thenReturn(items); when(mockContentType.fields()).thenReturn(Collections.singletonList(mockCdaField)); // mock field content Map mockRawFields = new HashMap<>(); + mockRawFields.put(fieldName, new Object()); when(mockCdaEntry.getField(fieldName)).thenReturn(fieldContent); when(mockCdaEntry.rawFields()).thenReturn(mockRawFields);