com.adobe.acs.commons.components.longformtext.impl.LongFormTextComponentImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.adobe.acs.commons.components.longformtext.impl.LongFormTextComponentImpl.java

Source

/*
 * #%L
 * ACS AEM Commons Bundle
 * %%
 * Copyright (C) 2015 Adobe
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package com.adobe.acs.commons.components.longformtext.impl;

import com.adobe.acs.commons.components.longformtext.LongFormTextComponent;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.commons.jcr.JcrUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.commons.html.HtmlParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * ACS AEM Commons - Components - Long-Form Text
 * Provides support for the ACS AEM Commons Long-form Text Component.
 */
@Component
@Service
public class LongFormTextComponentImpl implements LongFormTextComponent {
    private static final Logger log = LoggerFactory.getLogger(LongFormTextComponentImpl.class);

    @Reference
    private HtmlParser htmlParser;

    @Override
    public final String[] getTextParagraphs(final String text) {
        List<String> paragraphs = new ArrayList<String>();

        try {
            final Document doc = htmlParser.parse(null, IOUtils.toInputStream(text), "UTF-8");
            doc.getDocumentElement().normalize();

            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

            final NodeList bodies = doc.getElementsByTagName("body");

            if (bodies != null && bodies.getLength() == 1) {
                final org.w3c.dom.Node body = bodies.item(0);
                final NodeList children = body.getChildNodes();

                for (int i = 0; i < children.getLength(); i++) {
                    StringWriter writer = new StringWriter();
                    StreamResult result = new StreamResult(writer);

                    final org.w3c.dom.Node child = children.item(i);
                    if (child == null) {
                        log.warn("Found a null dom node.");
                        continue;
                    } else if (child.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE) {
                        log.warn("Found a dom node is not an element; skipping");
                        continue;
                    }

                    stripNamespaces(child);
                    transformer.transform(new DOMSource(child), result);
                    writer.flush();

                    final String outerHTML = writer.toString();

                    if (StringUtils.isNotBlank(outerHTML)) {
                        paragraphs.add(outerHTML);
                    }
                }
            } else {
                log.debug("HTML does not have a single body tag. Cannot parse as expected.");
            }
        } catch (Exception e) {
            log.warn("Long Form Text encountered a parser error: {}", e);
        }

        return paragraphs.toArray(new String[paragraphs.size()]);
    }

    @Override
    public final void mergeParagraphSystems(final Resource resource, final int textParagraphSize)
            throws RepositoryException {
        if (resource == null || ResourceUtil.isNonExistingResource(resource) || !this.isModifiable(resource)) {
            // Nothing to merge, or user does not have access to merge
            return;
        }

        final Node targetNode = this.getOrCreateLastParagraphSystemResource(resource, textParagraphSize);

        if (targetNode == null) {
            log.info("Could not find last target node to merge long-form-text text inline par resources: {}",
                    textParagraphSize);
            return;
        }

        for (final Resource child : resource.getChildren()) {
            int index = this.getResourceIndex(child);

            if (index > textParagraphSize) {
                this.moveChildrenToNode(child, targetNode);
            }
        }
    }

    @Override
    public boolean hasContents(final Resource resource, final int index) {
        final Resource parResource = resource.getChild(LONG_FORM_TEXT_PAR + index);

        return parResource != null && parResource.listChildren().hasNext();
    }

    private void moveChildrenToNode(Resource resource, Node targetNode) throws RepositoryException {
        for (Resource child : resource.getChildren()) {

            // Use this to create a unique node name; else existing components might get overwritten.
            final Node uniqueNode = JcrUtil.createUniqueNode(targetNode, child.getName(),
                    JcrConstants.NT_UNSTRUCTURED, targetNode.getSession());

            // Once we have a unique node we made as a place holder, we can copy over it w the real component content
            JcrUtil.copy(child.adaptTo(Node.class), targetNode, uniqueNode.getName(), true);
        }

        // Remove the old long-form-text-par- node
        resource.adaptTo(Node.class).remove();

        // Save all changes
        targetNode.getSession().save();
    }

    private Node getOrCreateLastParagraphSystemResource(final Resource resource, final int lastIndex)
            throws RepositoryException {
        final String resourceName = LONG_FORM_TEXT_PAR + lastIndex;
        final Resource lastResource = resource.getChild(resourceName);
        if (lastResource != null) {
            return lastResource.adaptTo(Node.class);
        }

        final Node parentNode = resource.adaptTo(Node.class);

        if (parentNode == null) {
            return null;
        }

        final Session session = parentNode.getSession();
        final Node node = JcrUtil.createPath(parentNode, resourceName, false, JcrConstants.NT_UNSTRUCTURED,
                JcrConstants.NT_UNSTRUCTURED, session, true);

        return node;
    }

    private int getResourceIndex(final Resource resource) {
        final String resourceName = resource.getName();
        if (!StringUtils.startsWith(resourceName, LONG_FORM_TEXT_PAR)) {
            return -1;
        }

        final String indexStr = StringUtils.removeStart(resourceName, LONG_FORM_TEXT_PAR);

        try {
            return Integer.parseInt(indexStr);
        } catch (NumberFormatException ex) {
            return -1;
        }
    }

    private boolean isModifiable(final Resource resource) throws RepositoryException {
        final String writePermissions = "add_node,set_property,remove";
        final Session userSession = resource.getResourceResolver().adaptTo(Session.class);
        final String path = resource.getPath();

        try {
            userSession.checkPermission(path, writePermissions);
        } catch (java.security.AccessControlException e) {
            log.debug("User does not have modify permissions [ {} ] on [ {} ]", writePermissions,
                    resource.getPath());
            return false;
        }
        return true;
    }

    /**
     * Method borrowed from: https://blog.avisi.nl/2013/07/24/java-stripping-namespaces-from-xml-using-dom/
     *
     * Recursively renames the namespace of a node.
     * @param node the starting node.
     */
    private void stripNamespaces(org.w3c.dom.Node node) {
        Document document = node.getOwnerDocument();
        if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
            document.renameNode(node, null, node.getNodeName());
        }
        NodeList list = node.getChildNodes();
        for (int i = 0; i < list.getLength(); ++i) {
            stripNamespaces(list.item(i));
        }
    }
}