ResourceExtension.java 18.3 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  org.apache.commons.lang3.StringUtils
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Properties
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.api.SlingHttpServletRequest
 *  org.apache.sling.api.SlingHttpServletResponse
 *  org.apache.sling.api.request.RequestPathInfo
 *  org.apache.sling.api.resource.Resource
 *  org.apache.sling.api.resource.ResourceResolver
 *  org.apache.sling.api.resource.ResourceUtil
 *  org.apache.sling.api.resource.ValueMap
 *  org.apache.sling.scripting.sightly.Record
 *  org.apache.sling.scripting.sightly.SightlyException
 *  org.apache.sling.scripting.sightly.extension.RuntimeExtension
 *  org.apache.sling.scripting.sightly.render.RenderContext
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.adobe.cq.sightly.internal.extensions;

import com.adobe.cq.sightly.WCMResourceOptions;
import com.adobe.cq.sightly.WCMScriptHelper;
import com.adobe.cq.sightly.internal.PrintWriterResponseWrapper;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.script.Bindings;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.scripting.sightly.Record;
import org.apache.sling.scripting.sightly.SightlyException;
import org.apache.sling.scripting.sightly.extension.RuntimeExtension;
import org.apache.sling.scripting.sightly.render.RenderContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
@Service(value={RuntimeExtension.class})
@Properties(value={@Property(name="org.apache.sling.scripting.sightly.extension.name", value={"includeResource"}), @Property(name="service.ranking", intValue={1000})})
public class ResourceExtension
implements RuntimeExtension {
    private static final Logger LOG = LoggerFactory.getLogger(ResourceExtension.class);
    public static final String FUNCTION = "includeResource";
    private static final String OPTION_RESOURCE_TYPE = "resourceType";
    private static final String OPTION_PATH = "path";
    private static final String OPTION_PREPEND_PATH = "prependPath";
    private static final String OPTION_APPEND_PATH = "appendPath";
    private static final String OPTION_SELECTORS = "selectors";
    private static final String OPTION_REMOVE_SELECTORS = "removeSelectors";
    private static final String OPTION_ADD_SELECTORS = "addSelectors";
    private static final String OPTION_REPLACE_SELECTORS = "replaceSelectors";
    private static final String OPTION_WCMMODE = "wcmmode";
    private static final String OPTION_DECORATION_TAG_NAME = "decorationTagName";
    private static final String OPTION_CSS_CLASS_NAME = "cssClassName";
    private static final String CQ_HTML_TAG = "cq:htmlTag";
    private static final String CQ_TAG_NAME = "cq:tagName";
    private static final String CQ_CLASS = "class";
    private static final String RESOURCE_NAME = "resourceName";
    public static final int ARG_COUNT = 2;

    public /* varargs */ Object call(RenderContext renderContext, Object ... arguments) {
        String resourceType;
        PathInfo pathInfo;
        Bindings bindings = renderContext.getBindings();
        if (arguments.length != 2) {
            throw new SightlyException(String.format("Extension %s requires %d arguments.", "includeResource", 2));
        }
        HashMap<String, Object> opts = new HashMap<String, Object>((Map)arguments[1]);
        Object resourceObject = arguments[0];
        Map<String, Object> resourceProperties = Collections.EMPTY_MAP;
        if (resourceObject instanceof Record) {
            resourceProperties = this.convertRecord((Record)resourceObject);
            String resourceName = this.coerceString(this.getAndRemoveOption(resourceProperties, "resourceName"));
            if (StringUtils.isEmpty((CharSequence)resourceName)) {
                throw new SightlyException("The resource model is missing the mandatory resourceName property.");
            }
            pathInfo = new PathInfo(resourceName);
            resourceType = this.coerceString(this.getAndRemoveOption(resourceProperties, "sling:resourceType"));
            String templateOverriddenResourceType = this.coerceString(this.getAndRemoveOption(opts, "resourceType"));
            if (StringUtils.isNotEmpty((CharSequence)templateOverriddenResourceType)) {
                resourceType = templateOverriddenResourceType;
            }
            if (StringUtils.isEmpty((CharSequence)resourceType)) {
                throw new SightlyException("Cannot retrieve the resource type to use for SyntheticResource " + resourceName);
            }
        } else {
            pathInfo = new PathInfo(this.coerceString(resourceObject));
            resourceType = this.coerceString(this.getAndRemoveOption(opts, "resourceType"));
        }
        String path = pathInfo.path;
        String finalPath = this.buildPath(path, opts, (Resource)bindings.get("resource"));
        WCMResourceOptions wcmResourceOptions = new WCMResourceOptions();
        wcmResourceOptions.setOptionDecorationTagName(this.coerceString(this.getAndRemoveOption(opts, "decorationTagName")));
        wcmResourceOptions.setCssClassName(this.coerceString(this.getAndRemoveOption(opts, "cssClassName")));
        wcmResourceOptions.setWcmMode(this.coerceString(this.getAndRemoveOption(opts, "wcmmode")));
        this.handleHTMLTag(renderContext, wcmResourceOptions, resourceType, finalPath);
        Map<String, String> dispatcherOptionsMap = this.handleSelectors(bindings, pathInfo.selectors, opts);
        String dispatcherOptions = this.createDispatcherOptions(dispatcherOptionsMap);
        WCMScriptHelper wcmScriptHelper = (WCMScriptHelper)bindings.get("slyWcmHelper");
        this.escapeOptions(renderContext, wcmResourceOptions);
        StringWriter writer = new StringWriter();
        PrintWriterResponseWrapper response = new PrintWriterResponseWrapper(new PrintWriter(writer), (SlingHttpServletResponse)bindings.get("response"));
        wcmScriptHelper.includeResource((SlingHttpServletResponse)response, finalPath, dispatcherOptions, resourceType, wcmResourceOptions, resourceProperties);
        return writer.toString();
    }

    private void escapeOptions(RenderContext renderContext, WCMResourceOptions wcmResourceOptions) {
        String tagName;
        String cssClassName = wcmResourceOptions.getCssClassName();
        if (cssClassName != null) {
            wcmResourceOptions.setCssClassName(this.escape(renderContext, cssClassName, "attribute"));
        }
        if ((tagName = wcmResourceOptions.getDecorationTagName()) != null) {
            wcmResourceOptions.setOptionDecorationTagName(this.escape(renderContext, tagName, "elementName"));
        }
    }

    private String escape(RenderContext renderContext, String value, String context) {
        Object result = renderContext.call("xss", new Object[]{value, context});
        return result instanceof String ? (String)result : null;
    }

    private Map<String, String> handleSelectors(Bindings bindings, Set<String> selectors, Map<String, Object> options) {
        Object selectorsObject;
        if (selectors.isEmpty()) {
            SlingHttpServletRequest request = (SlingHttpServletRequest)bindings.get("request");
            selectors.addAll(Arrays.asList(request.getRequestPathInfo().getSelectors()));
        }
        HashMap<String, String> dispatcherOptionsMap = new HashMap<String, String>();
        if (options.containsKey("selectors")) {
            selectorsObject = this.getAndRemoveOption(options, "selectors");
            selectors.clear();
            this.handleAddSelectorsOption(selectors, selectorsObject);
            dispatcherOptionsMap.put("addSelectors", this.getSelectorString(selectors));
            dispatcherOptionsMap.put("replaceSelectors", " ");
        }
        if (options.containsKey("addSelectors")) {
            selectorsObject = this.getAndRemoveOption(options, "addSelectors");
            this.handleAddSelectorsOption(selectors, selectorsObject);
            dispatcherOptionsMap.put("addSelectors", this.getSelectorString(selectors));
            dispatcherOptionsMap.put("replaceSelectors", " ");
        }
        if (options.containsKey("removeSelectors")) {
            Object selectorString;
            selectorsObject = this.getAndRemoveOption(options, "removeSelectors");
            if (selectorsObject instanceof String) {
                String[] parts;
                selectorString = (Object[])selectorsObject;
                for (String s : parts = selectorString.split("\\.")) {
                    selectors.remove(s);
                }
            } else if (selectorsObject instanceof Object[]) {
                selectorString = (Object[])selectorsObject;
                int parts = selectorString.length;
                for (int i = 0; i < parts; ++i) {
                    Object s = selectorString[i];
                    String selector = this.coerceString(s);
                    if (!StringUtils.isNotEmpty((CharSequence)selector)) continue;
                    selectors.remove(selector);
                }
            } else if (selectorsObject == null) {
                selectors.clear();
            }
            if (StringUtils.isEmpty((CharSequence)(selectorString = this.getSelectorString(selectors)))) {
                dispatcherOptionsMap.put("replaceSelectors", " ");
            } else {
                dispatcherOptionsMap.put("addSelectors", this.getSelectorString(selectors));
                dispatcherOptionsMap.put("replaceSelectors", " ");
            }
        }
        return dispatcherOptionsMap;
    }

    private void handleAddSelectorsOption(Set<String> selectors, Object selectorsObject) {
        if (selectorsObject instanceof String) {
            String selectorString = (String)selectorsObject;
            String[] parts = selectorString.split("\\.");
            selectors.addAll(Arrays.asList(parts));
        } else if (selectorsObject instanceof Object[]) {
            this.addSelectorsFromArray(selectors, (Object[])selectorsObject);
        }
    }

    private void addSelectorsFromArray(Set<String> selectors, Object[] selectorsObject) {
        for (Object s : selectorsObject) {
            String selector = this.coerceString(s);
            if (!StringUtils.isNotEmpty((CharSequence)selector)) continue;
            selectors.add(selector);
        }
    }

    private String buildPath(Object pathObj, Map<String, Object> options, Resource currentResource) {
        String path;
        String finalPath;
        String prependPath = this.getOption("prependPath", options, "");
        if (prependPath == null) {
            prependPath = "";
        }
        if (StringUtils.isNotEmpty((CharSequence)prependPath)) {
            if (!prependPath.startsWith("/")) {
                prependPath = "/" + prependPath;
            }
            if (!prependPath.endsWith("/")) {
                prependPath = prependPath + "/";
            }
        }
        path = this.getOption("path", options, StringUtils.isNotEmpty((CharSequence)(path = this.coerceString(pathObj))) ? path : "");
        String appendPath = this.getOption("appendPath", options, "");
        if (appendPath == null) {
            appendPath = "";
        }
        if (StringUtils.isNotEmpty((CharSequence)appendPath) && !appendPath.startsWith("/")) {
            appendPath = "/" + appendPath;
        }
        if (!(finalPath = prependPath + path + appendPath).startsWith("/")) {
            finalPath = currentResource.getPath() + "/" + finalPath;
        }
        return ResourceUtil.normalize((String)finalPath);
    }

    private String createDispatcherOptions(Map<String, String> options) {
        if (options == null || options.isEmpty()) {
            return null;
        }
        StringBuilder buffer = new StringBuilder();
        boolean hasPreceding = false;
        for (Map.Entry<String, String> option : options.entrySet()) {
            if (hasPreceding) {
                buffer.append(", ");
            }
            String key = option.getKey();
            buffer.append(key).append("=");
            String strVal = option.getValue();
            if (strVal == null) {
                strVal = "";
            }
            buffer.append(strVal);
            hasPreceding = true;
        }
        return buffer.toString();
    }

    private String coerceString(Object obj) {
        if (obj instanceof String) {
            return (String)obj;
        }
        return null;
    }

    private String getOption(String option, Map<String, Object> options, String defaultValue) {
        if (options.containsKey(option)) {
            return (String)options.get(option);
        }
        return defaultValue;
    }

    private Object getAndRemoveOption(Map<String, Object> options, String property) {
        return options.remove(property);
    }

    private Set<String> getSelectorsFromPath(String path) {
        LinkedHashSet<String> selectors = new LinkedHashSet<String>();
        if (path != null) {
            int lastDotPos;
            int dotPos;
            String processingPath = path;
            int lastSlashPos = path.lastIndexOf(47);
            if (lastSlashPos > -1) {
                processingPath = path.substring(lastSlashPos + 1, path.length());
            }
            if ((dotPos = processingPath.indexOf(46)) > -1 && (lastDotPos = processingPath.lastIndexOf(46)) > dotPos) {
                String selectorString = processingPath.substring(dotPos + 1, lastDotPos);
                String[] selectorParts = selectorString.split("\\.");
                selectors.addAll(Arrays.asList(selectorParts));
            }
        }
        return selectors;
    }

    private String getSelectorString(Set<String> selectors) {
        StringBuilder sb = new StringBuilder();
        int i = 0;
        for (String s : selectors) {
            sb.append(s);
            if (i >= selectors.size() - 1) continue;
            sb.append(".");
            ++i;
        }
        return sb.toString();
    }

    private void handleHTMLTag(RenderContext renderContext, WCMResourceOptions resourceOptions, String resourceType, String path) {
        if (StringUtils.isEmpty((CharSequence)resourceOptions.getDecorationTagName())) {
            boolean cqHTMLTagFound;
            String superType;
            ResourceResolver adminResolver = renderContext.getScriptResourceResolver();
            Resource currentResource = (Resource)renderContext.getBindings().get("resource");
            if (resourceType == null) {
                Resource includedResource = path.startsWith("/") ? currentResource.getResourceResolver().getResource(path) : currentResource.getChild(path);
                if (includedResource != null) {
                    resourceType = includedResource.getResourceType();
                }
            }
            if (resourceType != null && !(cqHTMLTagFound = this.populateWCMResourceOptions(adminResolver, resourceType, resourceOptions)) && (superType = ResourceUtil.getResourceSuperType((ResourceResolver)adminResolver, (String)resourceType)) != null) {
                String resourceSuperType = ResourceUtil.resourceTypeToPath((String)superType);
                this.populateWCMResourceOptions(adminResolver, resourceSuperType, resourceOptions);
            }
        }
    }

    private boolean populateWCMResourceOptions(ResourceResolver resourceResolver, String resourceType, WCMResourceOptions resourceOptions) {
        if (resourceType.startsWith("/")) {
            Resource htmlTag;
            Resource component = resourceResolver.getResource(resourceType);
            if (component != null && (htmlTag = component.getChild("cq:htmlTag")) != null) {
                ValueMap htmlTagProperties = (ValueMap)htmlTag.adaptTo(ValueMap.class);
                resourceOptions.setOptionDecorationTagName((String)htmlTagProperties.get("cq:tagName", String.class));
                resourceOptions.setCssClassName((String)htmlTagProperties.get("class", String.class));
                return true;
            }
        } else {
            for (String sp : resourceResolver.getSearchPath()) {
                Resource htmlTag;
                Resource component = resourceResolver.getResource(sp + "/" + resourceType);
                if (component == null || (htmlTag = component.getChild("cq:htmlTag")) == null) continue;
                ValueMap htmlTagProperties = (ValueMap)htmlTag.adaptTo(ValueMap.class);
                resourceOptions.setOptionDecorationTagName((String)htmlTagProperties.get("cq:tagName", (Object)resourceOptions.getDecorationTagName()));
                resourceOptions.setCssClassName((String)htmlTagProperties.get("class", (Object)resourceOptions.getCssClassName()));
                return true;
            }
        }
        return false;
    }

    private Map<String, Object> convertRecord(Record record) {
        if (record != null) {
            Set properties = record.getPropertyNames();
            HashMap<String, Object> map = new HashMap<String, Object>(properties.size());
            for (String property : properties) {
                map.put(property, record.getProperty(property));
            }
            return map;
        }
        return Collections.emptyMap();
    }

    private class PathInfo {
        private String path;
        private Set<String> selectors;

        PathInfo(String path) {
            this.selectors = ResourceExtension.this.getSelectorsFromPath(path);
            if (this.selectors.isEmpty()) {
                this.path = path;
            } else {
                String selectorString = ResourceExtension.this.getSelectorString(this.selectors);
                this.path = path.replace("." + selectorString, "");
            }
        }
    }

}