org.structr.web.entity.dom.Content.java Source code

Java tutorial

Introduction

Here is the source code for org.structr.web.entity.dom.Content.java

Source

/**
 * Copyright (C) 2010-2016 Structr GmbH
 *
 * This file is part of Structr <http://structr.org>.
 *
 * Structr is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Structr is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Structr.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.structr.web.entity.dom;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.java.textilej.parser.MarkupParser;
import net.java.textilej.parser.markup.confluence.ConfluenceDialect;
import net.java.textilej.parser.markup.mediawiki.MediaWikiDialect;
import net.java.textilej.parser.markup.textile.TextileDialect;
import net.java.textilej.parser.markup.trac.TracWikiDialect;
import org.apache.commons.lang3.StringUtils;
import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Asciidoctor.Factory;
import org.pegdown.Parser;
import org.pegdown.PegDownProcessor;
import org.structr.common.Permission;
import org.structr.common.PropertyView;
import org.structr.common.SecurityContext;
import org.structr.common.error.ErrorBuffer;
import org.structr.common.error.FrameworkException;
import org.structr.core.Adapter;
import org.structr.core.graph.search.SearchCommand;
import org.structr.core.property.BooleanProperty;
import org.structr.core.property.Property;
import org.structr.core.property.PropertyKey;
import org.structr.core.property.StringProperty;
import org.structr.web.common.AsyncBuffer;
import org.structr.web.common.RenderContext;
import org.structr.web.common.RenderContext.EditMode;
import static org.structr.web.entity.dom.DOMNode.hideOnDetail;
import static org.structr.web.entity.dom.DOMNode.hideOnIndex;
import org.structr.web.entity.html.Textarea;
import org.structr.web.entity.relation.Sync;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

//~--- classes ----------------------------------------------------------------

/**
 * Represents a content node. This class implements the org.w3c.dom.Text interface.
 * All methods in the W3C Text interface are based on the raw database content.
 *
 *
 */
public class Content extends DOMNode implements Text {

    private static final Logger logger = Logger.getLogger(Content.class.getName());
    public static final Property<String> contentType = new StringProperty("contentType").indexed();
    public static final Property<String> content = new StringProperty("content").indexed();
    public static final Property<Boolean> isContent = new BooleanProperty("isContent").defaultValue(true)
            .readOnly();

    private static final Map<String, Adapter<String, String>> contentConverters = new LinkedHashMap<>();

    private static final ThreadLocalAsciiDocProcessor asciiDocProcessor = new ThreadLocalAsciiDocProcessor();
    private static final ThreadLocalTracWikiProcessor tracWikiProcessor = new ThreadLocalTracWikiProcessor();
    private static final ThreadLocalTextileProcessor textileProcessor = new ThreadLocalTextileProcessor();
    private static final ThreadLocalPegDownProcessor pegDownProcessor = new ThreadLocalPegDownProcessor();
    private static final ThreadLocalMediaWikiProcessor mediaWikiProcessor = new ThreadLocalMediaWikiProcessor();
    private static final ThreadLocalConfluenceProcessor confluenceProcessor = new ThreadLocalConfluenceProcessor();

    public static final org.structr.common.View uiView = new org.structr.common.View(Content.class, PropertyView.Ui,
            content, contentType, parent, pageId, syncedNodes, sharedComponent, dataKey, restQuery, cypherQuery,
            xpathQuery, functionQuery, hideOnDetail, hideOnIndex, showForLocales, hideForLocales, showConditions,
            hideConditions, isContent, isDOMNode);

    public static final org.structr.common.View publicView = new org.structr.common.View(Content.class,
            PropertyView.Public, content, contentType, parent, pageId, syncedNodes, sharedComponent, dataKey,
            restQuery, cypherQuery, xpathQuery, functionQuery, hideOnDetail, hideOnIndex, showForLocales,
            hideForLocales, showConditions, hideConditions, isContent, isDOMNode);
    //~--- static initializers --------------------------------------------

    static {

        contentConverters.put("text/markdown", new Adapter<String, String>() {

            @Override
            public String adapt(String s) throws FrameworkException {

                if (s != null) {
                    return pegDownProcessor.get().markdownToHtml(s);
                }

                return "";
            }

        });
        contentConverters.put("text/textile", new Adapter<String, String>() {

            @Override
            public String adapt(String s) throws FrameworkException {

                if (s != null) {
                    return textileProcessor.get().parseToHtml(s);
                }

                return "";

            }

        });
        contentConverters.put("text/mediawiki", new Adapter<String, String>() {

            @Override
            public String adapt(String s) throws FrameworkException {

                if (s != null) {
                    return mediaWikiProcessor.get().parseToHtml(s);
                }

                return "";
            }

        });
        contentConverters.put("text/tracwiki", new Adapter<String, String>() {

            @Override
            public String adapt(String s) throws FrameworkException {

                if (s != null) {
                    return tracWikiProcessor.get().parseToHtml(s);
                }

                return "";

            }

        });
        contentConverters.put("text/confluence", new Adapter<String, String>() {

            @Override
            public String adapt(String s) throws FrameworkException {

                if (s != null) {
                    return confluenceProcessor.get().parseToHtml(s);
                }

                return "";

            }

        });
        contentConverters.put("text/asciidoc", new Adapter<String, String>() {

            @Override
            public String adapt(String s) throws FrameworkException {

                if (s != null) {
                    return asciiDocProcessor.get().render(s, new HashMap<String, Object>());
                }

                return "";

            }

        });
        //      contentConverters.put("text/plain", new Adapter<String, String>() {
        //
        //         @Override
        //         public String adapt(String s) throws FrameworkException {
        //
        //            return StringEscapeUtils.escapeHtml(s);
        //
        //         }
        //
        //      });

    }

    @Override
    public boolean onModification(SecurityContext securityContext, ErrorBuffer errorBuffer)
            throws FrameworkException {

        for (final Sync rel : getOutgoingRelationships(Sync.class)) {

            Content syncedNode = (Content) rel.getTargetNode();

            // sync content only
            syncedNode.setProperty(content, getProperty(content));
            syncedNode.setProperty(contentType, getProperty(contentType));
            syncedNode.setProperty(name, getProperty(name));
        }

        final Sync rel = getIncomingRelationship(Sync.class);

        if (rel != null) {

            final Content otherNode = (Content) rel.getSourceNode();

            if (otherNode != null) {

                // sync both ways
                otherNode.setProperty(content, getProperty(content));
                otherNode.setProperty(contentType, getProperty(contentType));
                otherNode.setProperty(name, getProperty(name));

            }

        }

        try {

            increasePageVersion();

        } catch (FrameworkException ex) {

            logger.log(Level.WARNING, "Updating page version failed", ex);

        }

        return true;

    }

    @Override
    public boolean contentEquals(DOMNode otherNode) {

        if (otherNode instanceof Content) {

            final String content1 = getTextContent();
            final String content2 = ((Content) otherNode).getTextContent();

            if (content1 == null && content2 == null) {
                return true;
            }

            if (content1 != null && content2 != null) {

                return content1.equals(content2);
            }
        }

        return false;
    }

    @Override
    public void updateFromNode(final DOMNode newNode) throws FrameworkException {

        if (newNode instanceof Content) {

            final Map<String, Object> properties = new HashMap<>();
            properties.put("content", newNode.getProperty(Content.content));

            updateFromPropertyMap(properties);
        }
    }

    //   @Override
    //   public void updateFromPropertyMap(final Map<String, Object> properties) throws FrameworkException {
    //
    //      this.setProperty(Content.content, properties.get(Content.content));
    //
    //      // update visibility as well
    //      this.setProperty(Content.visibleToPublicUsers, properties.get(Content.visibleToPublicUsers));
    //      this.setProperty(Content.visibleToAuthenticatedUsers, properties.get(Content.visibleToAuthenticatedUsers));
    //   }

    @Override
    public String getIdHash() {

        final DOMNode _parent = getProperty(DOMNode.parent);
        if (_parent != null) {

            String dataHash = _parent.getProperty(DOMNode.dataHashProperty);
            if (dataHash == null) {
                dataHash = _parent.getIdHash();
            }

            return dataHash + "Content" + treeGetChildPosition(this);
        }

        return super.getIdHash();
    }

    @Override
    public java.lang.Object getPropertyForIndexing(final PropertyKey key) {

        if (key.equals(Content.content)) {

            String value = getProperty(Content.content);
            if (value != null) {

                return SearchCommand.escapeForLucene(value);
            }

        }

        return super.getPropertyForIndexing(key);

    }

    @Override
    public void renderContent(final RenderContext renderContext, final int depth) throws FrameworkException {

        try {

            if (isDeleted() || isHidden() || !displayForLocale(renderContext)
                    || !displayForConditions(renderContext)) {
                return;
            }

            final String id = getUuid();
            final EditMode edit = renderContext.getEditMode(securityContext.getUser(false));
            final boolean inBody = renderContext.inBody();
            final AsyncBuffer out = renderContext.getBuffer();

            String _contentType = getProperty(contentType);

            // fetch content with variable replacement
            String _content = getPropertyWithVariableReplacement(renderContext, Content.content);

            if (!(EditMode.RAW.equals(edit) || EditMode.WIDGET.equals(edit))
                    && (_contentType == null || ("text/plain".equals(_contentType)))) {

                _content = escapeForHtml(_content);

            }

            if (EditMode.CONTENT.equals(edit) && inBody && this.isGranted(Permission.write, securityContext)) {

                if ("text/javascript".equals(_contentType)) {

                    // Javascript will only be given some local vars
                    // TODO: Is this neccessary?
                    out.append("// data-structr-type='").append(getType()).append("'\n// data-structr-id='")
                            .append(id).append("'\n");

                } else if ("text/css".equals(_contentType)) {

                    // CSS will only be given some local vars
                    // TODO: Is this neccessary?
                    out.append("/* data-structr-type='").append(getType()).append("'*/\n/* data-structr-id='")
                            .append(id).append("'*/\n");

                } else {

                    //            // In edit mode, add an artificial comment tag around content nodes within body to make them editable
                    final String cleanedContent = StringUtils
                            .remove(StringUtils.remove(org.apache.commons.lang3.StringUtils
                                    .replace(getProperty(Content.content), "\n", "\\\\n"), "<!--"), "-->");
                    out.append("<!--data-structr-id=\"".concat(id).concat("\" data-structr-raw-value=\"")
                            .concat(escapeForHtmlAttributes(cleanedContent)).concat("\"-->"));

                }

            }

            // No contentType-specific rendering in DATA edit mode
            //if (!edit.equals(EditMode.DATA)) {

            // examine content type and apply converter

            if (_contentType != null) {

                final Adapter<String, String> converter = contentConverters.get(_contentType);

                if (converter != null) {

                    try {

                        // apply adapter
                        _content = converter.adapt(_content);
                    } catch (FrameworkException fex) {

                        logger.log(Level.WARNING, "Unable to convert content: {0}", fex.getMessage());

                    }

                }

            }

            // replace newlines with <br /> for rendering
            if (((_contentType == null) || _contentType.equals("text/plain")) && (_content != null)
                    && !_content.isEmpty()) {

                final DOMNode _parent = getProperty(Content.parent);
                if (_parent == null || !(_parent instanceof Textarea)) {

                    _content = _content.replaceAll("[\\n]{1}", "<br>");
                }
            }
            //}

            if (_content != null) {

                //buffer.append(indent(depth, true)).append(_content);

                // insert whitespace to make element clickable
                if (EditMode.CONTENT.equals(edit) && _content.length() == 0) {
                    _content = "--- empty ---";
                }

                out.append(_content);
            }

            if (EditMode.CONTENT.equals(edit) && inBody && !("text/javascript".equals(getProperty(contentType)))
                    && !("text/css".equals(getProperty(contentType)))) {

                //         buffer.append("</span>");
                out.append("<!---->");
            }

        } catch (Throwable t) {

            // catch exception to prevent ugly status 500 error pages in frontend.
            t.printStackTrace();
        }

    }

    //   @Override
    //   protected Object getEditModeValue(final SecurityContext securityContext, final RenderContext renderContext, final GraphObject dataObject, final PropertyKey referenceKeyProperty, final Object defaultValue) {
    //
    //      Object value      = dataObject.getProperty(StructrApp.getConfiguration().getPropertyKeyForJSONName(dataObject.getClass(), referenceKeyProperty.jsonName()));
    //      boolean canWrite  = dataObject instanceof AbstractNode ? securityContext.isAllowed((AbstractNode) dataObject, Permission.write) : true;
    //
    //      if (getProperty(Content.editable) && EditMode.DATA.equals(renderContext.getEditMode(securityContext.getUser(false))) && renderContext.inBody() && canWrite && !referenceKeyProperty.isReadOnly()) {
    //
    //         String editModeValue = "<span data-structr-type=\"" + referenceKeyProperty.typeName()
    //            + "\" data-structr-id=\"" + dataObject.getUuid()
    ////            + "\" data-structr-content-type=\"" + StringUtils.defaultString(dataObject.getProperty(Content.contentType), "")
    ////            + "\" data-structr-visible-to-authenticated-users=\"" + dataObject.getProperty(AbstractNode.visibleToAuthenticatedUsers)
    ////            + "\" data-structr-visible-to-public-users=\"" + dataObject.getProperty(AbstractNode.visibleToPublicUsers)
    //            + "\" data-structr-key=\"" + referenceKeyProperty.jsonName() + "\">" + value + "</span>";
    //
    //         logger.log(Level.FINEST, "Edit mode value: {0}", editModeValue);
    //
    //         return editModeValue;
    //
    //      } else {
    //
    //         return value != null ? value : defaultValue;
    //
    //      }
    //
    //   }
    @Override
    public boolean isSynced() {
        return false;
    }

    // ----- interface org.w3c.dom.Text -----

    @Override
    public Text splitText(int offset) throws DOMException {

        checkWriteAccess();

        String text = getProperty(content);

        if (text != null) {

            int len = text.length();

            if (offset < 0 || offset > len) {

                throw new DOMException(DOMException.INDEX_SIZE_ERR, INDEX_SIZE_ERR_MESSAGE);

            } else {

                final String firstPart = text.substring(0, offset);
                final String secondPart = text.substring(offset);

                final Document document = getOwnerDocument();
                final Node parent = getParentNode();

                if (document != null && parent != null) {

                    try {

                        // first part goes into existing text element
                        setProperty(content, firstPart);

                        // second part goes into new text element
                        Text newNode = document.createTextNode(secondPart);

                        // make new node a child of old parent
                        parent.appendChild(newNode);

                        return newNode;

                    } catch (FrameworkException fex) {

                        throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());

                    }

                } else {

                    throw new DOMException(DOMException.INVALID_STATE_ERR, CANNOT_SPLIT_TEXT_WITHOUT_PARENT);
                }
            }
        }

        throw new DOMException(DOMException.INDEX_SIZE_ERR, INDEX_SIZE_ERR_MESSAGE);
    }

    @Override
    public boolean isElementContentWhitespace() {

        checkReadAccess();

        String text = getProperty(content);

        if (text != null) {

            return !text.matches("[\\S]*");
        }

        return false;
    }

    @Override
    public String getWholeText() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Text replaceWholeText(String string) throws DOMException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public String getData() throws DOMException {

        checkReadAccess();

        return getProperty(content);
    }

    @Override
    public void setData(final String data) throws DOMException {

        checkWriteAccess();
        try {
            setProperty(content, data);

        } catch (FrameworkException fex) {

            throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());

        }
    }

    @Override
    public int getLength() {

        String text = getProperty(content);

        if (text != null) {

            return text.length();
        }

        return 0;
    }

    @Override
    public String substringData(int offset, int count) throws DOMException {

        checkReadAccess();

        String text = getProperty(content);

        if (text != null) {

            try {

                return text.substring(offset, offset + count);

            } catch (IndexOutOfBoundsException iobex) {

                throw new DOMException(DOMException.INDEX_SIZE_ERR, INDEX_SIZE_ERR_MESSAGE);
            }
        }

        return "";
    }

    @Override
    public void appendData(final String data) throws DOMException {

        checkWriteAccess();

        try {
            String text = getProperty(content);
            setProperty(content, text.concat(data));

        } catch (FrameworkException fex) {

            throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());

        }
    }

    @Override
    public void insertData(final int offset, final String data) throws DOMException {

        checkWriteAccess();

        try {

            String text = getProperty(content);

            String leftPart = text.substring(0, offset);
            String rightPart = text.substring(offset);

            StringBuilder buf = new StringBuilder(text.length() + data.length() + 1);
            buf.append(leftPart);
            buf.append(data);
            buf.append(rightPart);

            // finally, set content to concatenated left, data and right parts
            setProperty(content, buf.toString());

        } catch (FrameworkException fex) {

            throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());

        }
    }

    @Override
    public void deleteData(final int offset, final int count) throws DOMException {

        checkWriteAccess();

        // finally, set content to concatenated left and right parts
        try {

            String text = getProperty(content);

            String leftPart = text.substring(0, offset);
            String rightPart = text.substring(offset + count);

            setProperty(content, leftPart.concat(rightPart));

        } catch (FrameworkException fex) {

            throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());

        }
    }

    @Override
    public void replaceData(final int offset, final int count, final String data) throws DOMException {

        checkWriteAccess();

        // finally, set content to concatenated left and right parts
        try {

            String text = getProperty(content);

            String leftPart = text.substring(0, offset);
            String rightPart = text.substring(offset + count);

            StringBuilder buf = new StringBuilder(leftPart.length() + data.length() + rightPart.length());
            buf.append(leftPart);
            buf.append(data);
            buf.append(rightPart);

            setProperty(content, buf.toString());

        } catch (FrameworkException fex) {

            throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());

        }
    }

    // ----- interface org.w3c.dom.Node -----
    @Override
    public String getTextContent() throws DOMException {
        return getData();
    }

    @Override
    public void setTextContent(String textContent) throws DOMException {
        setData(textContent);
    }

    @Override
    public String getLocalName() {
        return null;
    }

    @Override
    public short getNodeType() {
        return TEXT_NODE;
    }

    @Override
    public String getNodeName() {
        return "#text";
    }

    @Override
    public String getNodeValue() throws DOMException {
        return getData();
    }

    @Override
    public void setNodeValue(String data) throws DOMException {
        setData(data);
    }

    @Override
    public NamedNodeMap getAttributes() {
        return null;
    }

    @Override
    public boolean hasAttributes() {
        return false;
    }

    // ----- interface DOMImportable -----
    @Override
    public Node doImport(Page newPage) throws DOMException {

        // for #text elements, importing is basically a clone operation
        return newPage.createTextNode(getData());
    }

    //~--- inner classes --------------------------------------------------

    private static class ThreadLocalConfluenceProcessor extends ThreadLocal<MarkupParser> {

        @Override
        protected MarkupParser initialValue() {

            return new MarkupParser(new ConfluenceDialect());

        }

    }

    private static class ThreadLocalMediaWikiProcessor extends ThreadLocal<MarkupParser> {

        @Override
        protected MarkupParser initialValue() {

            return new MarkupParser(new MediaWikiDialect());

        }

    }

    private static class ThreadLocalPegDownProcessor extends ThreadLocal<PegDownProcessor> {

        @Override
        protected PegDownProcessor initialValue() {

            return new PegDownProcessor(Parser.ALL);

        }

    }

    private static class ThreadLocalTextileProcessor extends ThreadLocal<MarkupParser> {

        @Override
        protected MarkupParser initialValue() {

            return new MarkupParser(new TextileDialect());

        }

    }

    private static class ThreadLocalTracWikiProcessor extends ThreadLocal<MarkupParser> {

        @Override
        protected MarkupParser initialValue() {

            return new MarkupParser(new TracWikiDialect());

        }
    }

    private static class ThreadLocalAsciiDocProcessor extends ThreadLocal<Asciidoctor> {

        @Override
        protected Asciidoctor initialValue() {

            return Factory.create();
        }
    }
}