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

com.contentful.java.cda.rich.RichTextFactory Maven / Gradle / Ivy

There is a newer version: 9.1.0
Show newest version
package com.contentful.java.cda.rich;

import com.contentful.java.cda.ArrayResource;
import com.contentful.java.cda.CDAClient;
import com.contentful.java.cda.CDAEntry;
import com.contentful.java.cda.CDAField;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.contentful.java.cda.ResourceUtils.ensureContentType;

/**
 * This factory will be used in order to create the {@link CDARichNode}-graph representation of
 * the Contentful data returned by a rich text - field.
 */
@SuppressWarnings("unchecked")
public class RichTextFactory {
    private static final int HEADING_LEVEL_1 = 1;
    private static final int HEADING_LEVEL_2 = 2;
    private static final int HEADING_LEVEL_3 = 3;
    private static final int HEADING_LEVEL_4 = 4;
    private static final int HEADING_LEVEL_5 = 5;
    private static final int HEADING_LEVEL_6 = 6;
    public static final Map RESOLVER_MAP = new HashMap<>();

    static {
        // add leafs
        RESOLVER_MAP.put("text", raw -> new CDARichText(
                (CharSequence) raw.get("value"),
                resolveMarks((List>) raw.get("marks"))
        ));
        RESOLVER_MAP.put("hr", raw -> new CDARichHorizontalRule());

        // add blocks
        RESOLVER_MAP.put("blockquote", new BlockResolver<>(CDARichQuote::new));
        RESOLVER_MAP.put("paragraph", new BlockResolver<>(CDARichParagraph::new));
        RESOLVER_MAP.put("document", new BlockResolver<>(CDARichDocument::new));
        RESOLVER_MAP.put("list-item", new BlockResolver<>(CDARichListItem::new));
        RESOLVER_MAP.put("ordered-list", new BlockResolver<>(CDARichOrderedList::new));
        RESOLVER_MAP.put("unordered-list", new BlockResolver<>(CDARichUnorderedList::new));
        RESOLVER_MAP.put("table", new BlockResolver<>(CDARichTable::new));
        RESOLVER_MAP.put("table-row", new BlockResolver<>(CDARichTableRow::new));
        RESOLVER_MAP.put("table-header-cell", new BlockResolver<>(CDARichTableHeaderCell::new));
        RESOLVER_MAP.put("table-cell", new BlockResolver<>(CDARichTableCell::new));
        RESOLVER_MAP.put("hyperlink",
                new BlockAndDataResolver<>(CDARichHyperLink::new, "data"));
        RESOLVER_MAP.put("entry-hyperlink",
                new BlockAndDataResolver<>(CDARichHyperLink::new, "data"));
        RESOLVER_MAP.put("asset-hyperlink",
                new BlockAndDataResolver<>(CDARichHyperLink::new, "data"));
        RESOLVER_MAP.put("embedded-entry-block",
                new BlockAndDataResolver<>(CDARichEmbeddedBlock::new, "data"));
        RESOLVER_MAP.put("embedded-entry-inline",
                new BlockAndDataResolver<>(CDARichEmbeddedInline::new, "data"));
        RESOLVER_MAP.put("embedded-asset-block",
                new BlockAndDataResolver<>(CDARichEmbeddedBlock::new, "data"));
        RESOLVER_MAP.put("heading-1", new HeadingResolver(HEADING_LEVEL_1));
        RESOLVER_MAP.put("heading-2", new HeadingResolver(HEADING_LEVEL_2));
        RESOLVER_MAP.put("heading-3", new HeadingResolver(HEADING_LEVEL_3));
        RESOLVER_MAP.put("heading-4", new HeadingResolver(HEADING_LEVEL_4));
        RESOLVER_MAP.put("heading-5", new HeadingResolver(HEADING_LEVEL_5));
        RESOLVER_MAP.put("heading-6", new HeadingResolver(HEADING_LEVEL_6));
    }

    /**
     * Walk through the given array and resolve all rich text fields.
     *
     * @param array  the array to be walked.
     * @param client the client to be used if updating of types is needed.
     */
    public static void resolveRichTextField(ArrayResource array, CDAClient client) {
        for (CDAEntry entry : array.entries().values()) {
            ensureContentType(entry, client);
            for (CDAField field : entry.contentType().fields()) {
                if ("RichText".equals(field.type())) {
                    resolveRichDocument(entry, field);
                    resolveRichLink(array, entry, field);
                }
            }
        }
    }

    /**
     * Resolve all children of the top most document block.
     *
     * @param entry the entry to contain the field to be walked
     * @param field the id of the field to be walked.
     */
    private static void resolveRichDocument(CDAEntry entry, CDAField field) {
        final Map rawValue = (Map)
                entry.rawFields().get(field.id());
        if (rawValue == null) {
            return;
        }

        for (final String locale : rawValue.keySet()) {
            final Object raw = rawValue.get(locale);
            if (raw == null || raw instanceof CDARichNode) {
                // ignore null and already parsed values
                continue;
            }

            final Map map = (Map) rawValue.get(locale);
            entry.setField(locale, field.id(), RESOLVER_MAP.get("document").resolve(map));
        }
    }

    /**
     * Specific method for resolving rich text marks.
     *
     * @param rawMarks the json responded map from Contentful
     * @return objectified and parsed objects.
     */
    static List resolveMarks(List> rawMarks) {
        final List marks = new ArrayList<>(rawMarks.size());
        for (final Map rawMark : rawMarks) {
            final String type = (String) rawMark.get("type");
            if ("bold".equals(type)) {
                marks.add(new CDARichMark.CDARichMarkBold());
            } else if ("italic".equals(type)) {
                marks.add(new CDARichMark.CDARichMarkItalic());
            } else if ("underline".equals(type)) {
                marks.add(new CDARichMark.CDARichMarkUnderline());
            } else if ("code".equals(type)) {
                marks.add(new CDARichMark.CDARichMarkCode());
            } else if ("subscript".equals(type)) {
                marks.add(new CDARichMark.CDARichMarkSubscript());
            } else if ("superscript".equals(type)) {
                marks.add(new CDARichMark.CDARichMarkSuperscript());
            } else {
                marks.add(new CDARichMark.CDARichMarkCustom(type));
            }
        }
        return marks;
    }

    /**
     * Resolve one node.
     *
     * @param rawNode the map response from Contentful
     * @return a CDARichNode from this SDK.
     */
    public static CDARichNode resolveRichNode(Map rawNode) {
        final String type = (String) rawNode.get("nodeType");
        if (RESOLVER_MAP.containsKey(type)) {
            return RESOLVER_MAP.get(type).resolve(rawNode);
        } else {
            return null;
        }
    }

    /**
     * Resolve all links if possible. If linked to entry is not found, null it's field.
     *
     * @param array the array containing the complete response
     * @param entry the entry to be completed.
     * @param field the field pointing to a link.
     */
    private static void resolveRichLink(ArrayResource array, CDAEntry entry, CDAField field) {
        final Map rawValue = (Map)
                entry.rawFields().get(field.id());
        if (rawValue == null) {
            return;
        }

        for (final String locale : rawValue.keySet()) {
            final CDARichDocument document = entry.getField(locale, field.id());
            for (final CDARichNode node : document.getContent()) {
                resolveOneLink(array, field, locale, node);
            }
        }
    }

    /**
     * Link found, resolve it.
     *
     * @param array  the complete response from Contentful.
     * @param field  the field containing a link.
     * @param locale the locale of the link to be updated.
     * @param node   the node build from the response.
     */
    private static void resolveOneLink(ArrayResource array, CDAField field, String locale,
                                       CDARichNode node) {
        if (node instanceof CDARichHyperLink
                && ((CDARichHyperLink) node).data instanceof Map) {
            final CDARichHyperLink link = (CDARichHyperLink) node;
            final Map data = (Map) link.data;
            final Object target = data.get("target");

            if (target instanceof Map) {
                if (isLink(target)) {
                    final Map map = (Map) target;
                    final Map sys = (Map) map.get("sys");
                    final String linkType = (String) sys.get("linkType");
                    final String id = (String) sys.get("id");

                    if ("Asset".equals(linkType)) {
                        link.data = array.assets().get(id);
                    } else if ("Entry".equals(linkType)) {
                        link.data = array.entries().get(id);
                    }

                } else {
                    throw new IllegalStateException("Could not parse content of data field '"
                            + field.id() + "' for locale '" + locale + "' at node '" + node
                            + "'. Please check your content type model.");
                }
            } else if (target == null && data.containsKey("uri")) {
                link.data = data.get("uri");
            }
        } else if (node instanceof CDARichParagraph) {
            for (final CDARichNode child : ((CDARichParagraph) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichTableHeaderCell) {
            for (final CDARichNode child : ((CDARichTableHeaderCell) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichTableCell) {
            for (final CDARichNode child : ((CDARichTableCell) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichTableRow) {
            for (final CDARichNode child : ((CDARichTableRow) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichTable) {
            for (final CDARichNode child : ((CDARichTable) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichUnorderedList) {
            for (final CDARichNode child : ((CDARichUnorderedList) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichQuote) {
            for (final CDARichNode child : ((CDARichQuote) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichOrderedList) {
            for (final CDARichNode child : ((CDARichOrderedList) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichListItem) {
            for (final CDARichNode child : ((CDARichListItem) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        } else if (node instanceof CDARichHeading) {
            for (final CDARichNode child : ((CDARichHeading) node).getContent()) {
                resolveOneLink(array, field, locale, child);
            }
        }
    }

    /**
     * Is the give object a link of any kind?
     */
    private static boolean isLink(Object data) {
        try {
            final Map map = (Map) data;
            final Map sys = (Map) map.get("sys");
            final String type = (String) sys.get("type");
            final String linkType = (String) sys.get("linkType");
            final String id = (String) sys.get("id");

            if ("Link".equals(type)
                    && ("Entry".equals(linkType) || "Asset".equals(linkType)
                    && id != null)) {
                return true;
            }
        } catch (ClassCastException cast) {
            return false;
        }
        return false;
    }

    /**
     * Interface for resolving the type of a node by its raw representation.
     */
    private interface Resolver {
        CDARichNode resolve(Map raw);
    }

    public interface Supplier {
        T get();
    }

    /**
     * Simple interface for providing an instance based on a type.
     *
     * @param  the type an instance should be created for.
     */
    private interface SupplierWithData {
        /**
         * Create an object of type T.
         *
         * @param data the initialization data needed.
         * @return An instance of type T.
         */
        T get(Object data);
    }

    /**
     * Resolves a block of rich text
     *
     * @param  a block to be resolved.
     */
    public static class BlockResolver implements Resolver {
        final Supplier supplier;

        /**
         * Create a block resolver based on its given supplier.
         *
         * @param supplier an object to create more objects of type T.
         */
        public BlockResolver(Supplier supplier) {
            this.supplier = supplier;
        }

        /**
         * This method is called in order to try to create  rich text block node from a raw map
         * representation.
         *
         * @param raw representation of the block node coming from Contentful.
         * @return the rich node if resolving was successful.
         */
        @Override
        public CDARichNode resolve(Map raw) {
            final T resolved = getCDAType(raw);

            final List> contents = (List>)
                    raw.get("content");
            for (final Map rawNode : contents) {
                final CDARichNode resolvedNode = resolveRichNode(rawNode);
                if (resolvedNode != null) {
                    resolved.content.add(resolvedNode);
                }
            }
            return resolved;
        }

        /**
         * Convenience method to try and find out the type of the given raw map representation.
         *
         * @param raw a map coming from Contentful, parsed from the json response.
         * @return a new node based on the type of T.
         */
        T getCDAType(Map raw) {
            return supplier.get();
        }
    }

    /**
     * Resolve only headings from Contentful.
     */
    private static class HeadingResolver extends BlockResolver {
        final int level;

        /**
         * Create resolver using the given heading.
         *
         * @param level the level of the headings nesting. Should be positive and less then 7.
         */
        HeadingResolver(int level) {
            super(new Supplier() {
                @Override
                public CDARichHeading get() {
                    return new CDARichHeading(level);
                }
            });
            this.level = level;
        }
    }

    /**
     * Resolves a block containing more data.
     *
     * @param  Which type should the block be?
     */
    private static class BlockAndDataResolver
            extends BlockResolver {
        final SupplierWithData supplier;
        final String dataFieldKey;

        /**
         * Create the resolver.
         *
         * @param supplier     how to generate an object of type T?
         * @param dataFieldKey what other keys to be filtered?
         */
        BlockAndDataResolver(SupplierWithData supplier, String dataFieldKey) {
            super(null);
            this.supplier = supplier;
            this.dataFieldKey = dataFieldKey;
        }

        /**
         * Create an object of T
         *
         * @param raw a map coming from Contentful, parsed from the json response.
         * @return an object of Type T.
         */
        @Override
        T getCDAType(Map raw) {
            return supplier.get(raw.get(dataFieldKey));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy