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

com.adobe.acs.commons.contentsync.ContentReader Maven / Gradle / Ivy

There is a newer version: 6.10.0
Show newest version
/*-
 * #%L
 * ACS AEM Commons Bundle
 * %%
 * Copyright (C) 2013 - 2022 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.contentsync;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.PropertyDefinition;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonString;
import javax.json.JsonValue;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;

public class ContentReader {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    static final String BINARY_DATA_PLACEHOLDER = "0";

    private final NodeTypeManager nodeTypeManager;
    private final Collection knownPropertyPrefixes;

    public ContentReader(Session session) throws RepositoryException {
        Workspace workspace = session.getWorkspace();

        knownPropertyPrefixes = new HashSet<>(Arrays.asList(workspace.getNamespaceRegistry().getPrefixes()));
        nodeTypeManager = workspace.getNodeTypeManager();
    }

    /**
     * Recursive sanitize the give JCR node and remove protected properties
     *
     * @param node json node representing a JCR node
     * @return  sanitized json
     * @see #getProtectedProperties(JsonObject)
     */
    public JsonObject sanitize(JsonObject node) throws RepositoryException {
        JsonObjectBuilder out = Json.createObjectBuilder();
        sanitize(node, out);
        return out.build();
    }

    private void sanitize(JsonObject node, JsonObjectBuilder out) throws RepositoryException {
        Collection sanitizedProperties = getProtectedProperties(node);

        for (Map.Entry field : node.entrySet()) {
            String name = field.getKey();
            int colonIdx = name.indexOf(':');
            if (colonIdx > 0) {
                // sanitize unknown namespaces. These can come, for example, from asset metadata
                String prefix = name.substring(0, colonIdx);
                if (!knownPropertyPrefixes.contains(prefix)) {
                    log.trace("skipping protected property: {}", name);
                    continue;
                }
            }
            // sanitize protected properties
            if (sanitizedProperties.contains(name)) {
                log.trace("skipping unknown namespace: {}", name);
                continue;
            }

            JsonValue value = field.getValue();
            switch (value.getValueType()) {
                case OBJECT:
                    JsonObjectBuilder obj = Json.createObjectBuilder();
                    sanitize((JsonObject) value, obj);
                    out.add(name, obj);
                    break;
                case ARRAY:
                    JsonArray array = (JsonArray) value;
                    out.add(name, array);
                    break;
                default:
                    if (colonIdx == 0) {
                        // Leading colon in Sling GET Servlet JSON designates binary data, e.g. :jcr:data
                        // Put the real property instead (without a leading colon) and set a  dummy value
                        out.add(name.substring(1), BINARY_DATA_PLACEHOLDER);
                    } else {
                        out.add(name, value);
                    }
                    break;
            }
        }

    }

    /**
     * Collect protected properties of a given JCR node (non-recursively).
     * The list of protected properties consists of:
     * - properties protected by node's primary type
     * - properties protected by node's mixins
     * 

* For example, if a cq:Page node does not have any mixins applied this method would return *

     *      ["jcr:created", "jcr:createdBy"]
     *  
*

* If cq:Page is versionable, i.e. has the "mix:versionable" mixin type, then this method would return * properties protected by the primary type (cq:Page ) and the mixin (mix:versionable) and the list would be *

     *      ["jcr:created", "jcr:createdBy", "jcr:versionHistory", "jcr:baseVersion", "jcr:predecessors",
     *      "jcr:mergeFailed", "jcr:activity", "jcr:configuration", "jcr:isCheckedOut", "jcr:uuid" ]
     *  
* * @param node json representing a JCR node * @return the list of protected properties */ public List getProtectedProperties(JsonObject node) throws RepositoryException { Collection ignored = new HashSet<>(Arrays.asList(JCR_PRIMARYTYPE, JCR_MIXINTYPES)); List props = new ArrayList<>(); props.add("rep:policy"); // ACLs are not importable List checkTypes = new ArrayList<>(); String primaryType = node.getString(JCR_PRIMARYTYPE); checkTypes.add(primaryType); JsonArray mixins = node.getJsonArray(JCR_MIXINTYPES); if (mixins != null) { for (JsonValue item : mixins) { checkTypes.add(((JsonString) item).getString()); } } for (String typeName : checkTypes) { NodeType nodeType = nodeTypeManager.getNodeType(typeName); for (PropertyDefinition definition : nodeType.getPropertyDefinitions()) { if (definition.isProtected() && !ignored.contains(definition.getName())) { props.add(definition.getName()); } } } return props; } private void collectBinaryProperties(JsonObject node, String parent, List binaryProperties) { for (Map.Entry field : node.entrySet()) { String name = field.getKey(); JsonValue value = field.getValue(); switch (value.getValueType()) { case OBJECT: collectBinaryProperties((JsonObject) value, parent + "/" + name, binaryProperties); break; case NUMBER: // leading colon in Sling GET Servlet JSON and a numeric value designate binary data if (name.startsWith(":")) { String propPath = parent + "/" + name.substring(1); binaryProperties.add(propPath); } break; default: break; } } } /** * Recursively collect binary properties from a given json node. *

* For example, if node represents a cq:Page object with inline images, * the output would look like * *

     *  [
     *    /jcr:content/image/file/jcr:content/jcr:data,
     *    /jcr:content/image/file/jcr:content/dam:thumbnails/dam:thumbnail_480.png/jcr:content/jcr:data,
     *    /jcr:content/image/file/jcr:content/dam:thumbnails/dam:thumbnail_60.png/jcr:content/jcr:data,
     *    /jcr:content/image/file/jcr:content/dam:thumbnails/dam:thumbnail_300.png/jcr:content/jcr:data,
     *    /jcr:content/image/file/jcr:content/dam:thumbnails/dam:thumbnail_48.png/jcr:content/jcr:data
     *  ]
     * 
* * @param node json representing a JCR node * @return list of property paths relative to the json root */ public List collectBinaryProperties(JsonObject node) { List binaryProperties = new ArrayList<>(); collectBinaryProperties(node, "", binaryProperties); return binaryProperties; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy