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

com.day.cq.wcm.commons.ReferenceSearch Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * Copyright 1997-2008 Day Management AG
 * Barfuesserplatz 6, 4001 Basel, Switzerland
 * All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Day Management AG, ("Confidential Information"). You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Day.
 */
package com.day.cq.wcm.commons;

import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.commons.predicate.AbstractResourcePredicate;
import com.day.cq.commons.predicates.ResourcePredicate;
import com.day.cq.search.PredicateGroup;
import com.day.cq.search.Query;
import com.day.cq.search.QueryBuilder;
import com.day.cq.search.result.SearchResult;
import com.day.cq.wcm.api.NameConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.WCMException;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
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.jcr.api.SlingRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.sling.api.resource.Resource.RESOURCE_TYPE_NON_EXISTING;

/**
 * ReferenceSearch provides methods that search references to
 * resources (e.g. a path in a property to an image)
 */
public class ReferenceSearch {
    
    private static final String HREF_REGEX = "(href\\s*=\\s*)([\"']/*[^\"']*[\"'])";
    private SlingRepository repository;
    
    private boolean isValidatePage = false;

    /**
     * default logger
     */
    private static final Logger log = LoggerFactory.getLogger(ReferenceSearch.class);

    /**
     * search root
     */
    private String searchRoot = "/";

    /**
     * exact
     */
    private boolean exact = false;

    /**
     * hollow
     */
    private boolean hollow = false;

    /**
     * reference limit per page
     */
    private int maxReferencesPerPage = -1;

    /**
     * Resource predicate, can be set to filter results based on a resource predicate {@link ResourcePredicate}
     */
    private ResourcePredicate resourcePredicate = null;

    /**
     * Resource predicate, can be set to filter results based on a resource predicate {@link AbstractResourcePredicate}
     */
    private AbstractResourcePredicate oldResourcePredicate = null;

    /**
     * Default limit for pagination of results is 100.
     */
    private static final int DEFAULT_LIMIT = 100;

    /**
     * Default offset for pagination of results is 0.
     */
    private static final int DEFAULT_START_OFFSET = 0;

    /**
     * Default Index Tag used for Reference Search in Sites
     */
    static final String DEFAULT_SITES_INDEX_TAG = "wcmReferenceSearch";

    /**
     * @deprecated The repository was used by the implementation to create an administrative session in
     * {@link #adjustReferences(ResourceResolver, String, String, String[])}. Instead, specify a resource resolver
     * which is sufficiently authorized to adjust references at the desired locations.
     * @param repository The repository
     * @return this
     */
    public ReferenceSearch setRepository(SlingRepository repository) {
        this.repository = repository;
        return this;
    }

    /**
     * Returns the search root. default is '/'
     * @return the search root.
     */
    public String getSearchRoot() {
        return searchRoot;
    }

    /**
     * Sets the search root
     * @param searchRoot the search root
     * @return this
     */
    public ReferenceSearch setSearchRoot(String searchRoot) {
        if (searchRoot == null || searchRoot.equals("")) {
            this.searchRoot = "/";
        } else {
            this.searchRoot = searchRoot;
        }
        return this;
    }

    /**
     * Returns the shallow flag.
     * @return the shallow flag.
     * @see #setExact(boolean)
     */
    public boolean isExact() {
        return exact;
    }

    /**
     * Sets the exact flag. If true only exact
     * references are searched (and replaced). otherwise also references to
     * child resources are included.
     *
     * @param exact true if perform an exact search
     * @return this;
     */
    public ReferenceSearch setExact(boolean exact) {
        this.exact = exact;
        return this;
    }

    /**
     * Returns the hollow flag.
     * @return the hollow flag.
     * @see #setHollow(boolean)
     */
    public boolean isHollow() {
        return hollow;
    }

    /**
     * Sets the hollow flag. If true
     * the returned info will contain only properties of the page
     * and not the page object itself.
     * @param hollow true if perform a hollow search
     * @return this;
     */
    public ReferenceSearch setHollow(boolean hollow) {
        this.hollow = hollow;
        return this;
    }

    /**
     * Returns the maximum number of references that can be added to a page info.
     * @return the reference limit per page.
     * @see #setMaxReferencesPerPage(int)
     */
    public int getMaxReferencesPerPage() {
        return maxReferencesPerPage;
    }

    /**
     * Sets the value of maxReferencesPerPage.
     * The maximum number of references that can be added to a page info.
     * @param maxReferencesPerPage The maximum number of references that can be added to a page info.
     * @return this;
     */
    public ReferenceSearch setMaxReferencesPerPage(int maxReferencesPerPage) {
        this.maxReferencesPerPage = maxReferencesPerPage;
        return this;
    }

    /**
     * Set the resourcePredicate, this predicate is evaluated for
     * each search result and can be used to filter results.
     *
     * @param resourcePredicate an instance of {@link ResourcePredicate}
     * @return this;
     */
    public ReferenceSearch setPredicate(ResourcePredicate resourcePredicate) {
        this.resourcePredicate = resourcePredicate;
        return this;
    }

    /**
     * Set the resourcePredicate, this predicate is evaluated for
     * each search result and can be used to filter results.
     *
     * @param resourcePredicate an instance of {@link AbstractResourcePredicate}
     * @return this;
     * @deprecated Use {@link #setPredicate(ResourcePredicate)} instead.
     */
    @Deprecated
    public ReferenceSearch setPredicate(AbstractResourcePredicate resourcePredicate) {
        this.oldResourcePredicate = resourcePredicate;
        return this;
    }


    /**
     * Construct a query based on the input perm and fetch the result by using cq queryBuilder.
     *
     * @param resolver ResourceResolver
     * @param root Path restriction for the query
     * @param pathList List of path strings to be matched in the query
     * @param offset offset
     * @param limit limit on number of results per request
     * @param nodeType Node type restriction to be used in the query.
     * @return
     */
    private Iterator buildQueryAndFetchResults(ResourceResolver resolver, String root, List pathList,
            int offset, int limit, String nodeType) {
        QueryBuilder queryBuilder = resolver.adaptTo(QueryBuilder.class);
        Map map = new HashMap<>();

        if (offset < 0) {
            offset = DEFAULT_START_OFFSET;
        }

        if (limit <= 0) {
            limit = DEFAULT_LIMIT;
        }

        if (!root.trim().isEmpty()) {
            map.put("path", root);
        }

        if (nodeType != null && !nodeType.trim().isEmpty()) {
            map.put("type", "cq:Page");
        }

        map.put("p.guessTotal", "true");
        map.put("p.offset", String.valueOf(offset));
        map.put("p.limit", String.valueOf(limit));
        map.put("p.indexTag", DEFAULT_SITES_INDEX_TAG);

        int index = 1;
        for (String resourcePath : pathList) {
            if (!resourcePath.trim().isEmpty()) {
                String predicateKey = String.format("group.%s_fulltext", index);
                map.put(predicateKey, resourcePath);
                index++;
            }
        }

        map.put("group.p.or", "true");

        Query query = queryBuilder.createQuery(PredicateGroup.create(map), resolver.adaptTo(Session.class));
        SearchResult result = query.getResult();
        return result.getResources();
    }

    /**
     * This Method is to get page references of the given resource path.
     * This API is recommended to use where reference count is required or where only page references are need to prepare reports.
     * This API should't be used to adjust the references as it doesn't
     * provide exact path to the descendant node of a page which actually hold the given resource path.
     * @param resolver ResourceResolver.
     * @param path It's given path for which page references need to be fetch.
     * @param limit upper limit on size of result in current request.
     * @param offset offset.
     * @return List of Pages having reference to the given resource path.
     */
	public List findPageReferencesForResource(ResourceResolver resolver, String path, int limit,
            int offset) {

        if (path == null) {
            return Collections.emptyList();
        }

        List pageReferences = new ArrayList();

        String root = searchRoot.equals("/") ? "" : searchRoot;
        PageManager manager = resolver.adaptTo(PageManager.class);

        List patternList = new ArrayList();
        List pathList = new ArrayList();

        prepareQueryInputs(patternList, pathList, path);
        Iterator resources = buildQueryAndFetchResults(resolver, root, pathList, offset, limit,
                NameConstants.NT_PAGE);

        resources.forEachRemaining(res -> {
            Page page = manager.getContainingPage(res);
            pageReferences.add(page);

            if (resourcePredicate != null && !resourcePredicate.test(res)) {
                pageReferences.remove(page);
            } else
            if (oldResourcePredicate != null && !oldResourcePredicate.evaluate(res)) {
                pageReferences.remove(page);
            }
        });

        return pageReferences;
    }

	/**
	 * This method is to get page references of the given resource path.
     * This API provides exact location of the child node of a page which actually holds the given resource path.
     * This API is recommended to use where reference are need to be adjusted.
	 * @param resolver ResourceResolver.
	 * @param path It's given path for which page references need to be fetch.
	 * @param limit upper limit on size of result in current request.
	 * @param offset offset.
	 * @return Map having resorce path a key and Info object as key. Info Holds information about the search results.
	 */
    public Map search(ResourceResolver resolver, String path, int limit, int offset) {
        if (path == null) {
            return Collections.emptyMap();
        }

        String root = searchRoot.equals("/") ? "" : searchRoot;
        PageManager manager = resolver.adaptTo(PageManager.class);

        Map infos = new HashMap();
        List patternList = new ArrayList();
        List pathList = new ArrayList();

        prepareQueryInputs(patternList, pathList, path);

        // nodetype is intentionally set to null in the input parameters of
        // buildQueryAndFetchResults, as Consumers
        // expect a complete path to the referring property of the page.
        Iterator resources = buildQueryAndFetchResults(resolver, root, pathList, offset, limit, null);
        processResultset(resources, manager, infos, patternList);
        return infos;
    }

	/**
	 * Prepare input string to be used as fultext search perm
	 * @param patternList pattern to be matched
	 * @param pathList input string to be used as fultext search perm
	 * @param path resource path for wich references to be fetched
	 */
    private void prepareQueryInputs(List patternList, List pathList, String path) {
        String qHtmlEncodeescPath;
        Pattern htmlEncodedescPattern;
        Pattern escUppercasePattern;
        String qEscUppercasePath;

        Pattern pattern = getSearchPattern(path);
        String qPath = escapeIllegalXpathSearchChars(path);
        patternList.add(pattern);
        pathList.add(qPath);

        // also search for escaped path, if contains special characters.
        String escPath = Text.escapePath(path);
        if (!escPath.equals(path)) {
            Pattern escPattern = getSearchPattern(escPath);
            String qEscPath = escapeIllegalXpathSearchChars(escPath);
            patternList.add(escPattern);
            pathList.add(qEscPath);

            // RTE in Classic UI and in Touch UI post CQ-4241224: all hex upper case encoded
            escPath = escapePathUsingUpperCaseHex(path);
            escUppercasePattern = getSearchPattern(escPath);
            qEscUppercasePath = escapeIllegalXpathSearchChars(escPath);
            patternList.add(escUppercasePattern);
            pathList.add(qEscUppercasePath);

            // RTE in Touch UI pre CQ-4241224: & encoded as HTML entity; others hex upper
            // case encoded
            if (escPath.contains("%26")) {
                escPath = escPath.replaceAll("%26", "&");
                htmlEncodedescPattern = getSearchPattern(escPath);
                qHtmlEncodeescPath = escapeIllegalXpathSearchChars(escPath);
                patternList.add(htmlEncodedescPattern);
                pathList.add(qHtmlEncodeescPath);
            }
        }
    }

    /**
     * Searches for references to the given path.
     * @param resolver the resource resolver
     * @param path the path to search for
     * @return reference infos
     * @deprecated use Search(ResourceResolver resolver, String path, String limit, String offset)
     */
    public Map search(ResourceResolver resolver, String path) {
        if (path == null) {
            return Collections.emptyMap();
        }
        String root = searchRoot.equals("/") ? "" : searchRoot;
        PageManager manager = resolver.adaptTo(PageManager.class);
        Map infos = new HashMap();

        Pattern pattern = getSearchPattern(path);
        String qPath = escapeIllegalXpathSearchChars(path);
        String query = String.format("%s//*[jcr:contains(., '\"%s\"')]", root, qPath);
        search(resolver, manager, infos, pattern, query);

        // also search for escaped path, if contains special characters
        String escPath = Text.escapePath(path);
        if (!escPath.equals(path)) {

            // already rewritten values: all hex lower case encoded
            Pattern escPattern = getSearchPattern(escPath);
            String qEscPath = escapeIllegalXpathSearchChars(escPath);
            query = String.format("%s//*[jcr:contains(., '%s')]", root, qEscPath);
            search(resolver, manager, infos, escPattern, query);

            // RTE in Classic UI and in Touch UI post CQ-4241224: all hex upper case encoded
            escPath = escapePathUsingUpperCaseHex(path);
            escPattern = getSearchPattern(escPath);
            qEscPath = escapeIllegalXpathSearchChars(escPath);
            query = String.format("%s//*[jcr:contains(., '%s')]", root, qEscPath);
            search(resolver, manager, infos, escPattern, query);

            // RTE in Touch UI pre CQ-4241224: & encoded as HTML entity; others hex upper
            // case encoded
            if (escPath.contains("%26")) {
                escPath = escPath.replaceAll("%26", "&");
                escPattern = getSearchPattern(escPath);
                qEscPath = escapeIllegalXpathSearchChars(escPath);
                query = String.format("%s//*[jcr:contains(., '%s')]", root, qEscPath);
                search(resolver, manager, infos, escPattern, query);
            }
        }
        return infos;
    }

    private void filterResultset(Map infos) {
        for (Iterator> entries = infos.entrySet().iterator(); entries.hasNext();) {
            Map.Entry entry = entries.next();
            if (entry.getValue().getProperties().isEmpty()) {
                entries.remove();
            } else {
                // filter based on predicate
                if (resourcePredicate != null || oldResourcePredicate != null) {
                    if (entry.getValue().page != null) {
                        Resource pageResource = entry.getValue().page.adaptTo(Resource.class);
                        if (pageResource != null) {
                            if (resourcePredicate != null && !resourcePredicate.test(pageResource)) {
                                entries.remove();
                            } else
                            if (oldResourcePredicate != null && !oldResourcePredicate.evaluate(pageResource)) {
                                entries.remove();
                            }
                        }
                    }
                }
            }
        }
    }

    private String escapePathUsingUpperCaseHex(String string) {
        try {
            BitSet validChars = Text.URISaveEx;
            char escape = '%';
            final char[] hexTable = "0123456789ABCDEF".toCharArray();
            byte[] bytes = string.getBytes("utf-8");
            StringBuilder out = new StringBuilder(bytes.length);
            for (byte aByte : bytes) {
                int c = aByte & 0xff;
                if (validChars.get(c) && c != escape) {
                    out.append((char) c);
                } else {
                    out.append(escape);
                    out.append(hexTable[(c >> 4) & 0x0f]);
                    out.append(hexTable[(c) & 0x0f]);
                }
            }
            return out.toString();
        } catch (UnsupportedEncodingException e) {
            throw new InternalError(e.toString());
        }
    }

    private void search(ResourceResolver resolver,
                        PageManager manager, Map infos,
            Pattern pattern, String query) {
        // adding index tag in the query
        query = query.concat(" option(index tag " + DEFAULT_SITES_INDEX_TAG + ")");

        log.debug("Searching for references using: {}", query);
        Iterator iter = null;
        try {
            iter = resolver.findResources(query, javax.jcr.query.Query.XPATH);
        } catch (SlingException e) {
            log.warn("error finding resources", e);
            return;
        }
        List patternList = new ArrayList();
        patternList.add(pattern);
        processResultset(iter, manager, infos, patternList);
    }

    /**
     * process the search results to check if the entries are not empty and match with the pattern and resourcePredicate.
     * @param iter iterator for result set
     * @param manager PageManager instance
     * @param infos Info
     * @param patternList pattern to be matched
     */
    private void processResultset(Iterator iter, PageManager manager, Map infos,
            List patternList) {
        log.debug("processing the search results and building the result set");
        // process the search results and build the result set
        while (iter.hasNext()) {
            Resource res = iter.next();
            Page page = manager.getContainingPage(res);
            if (page != null) {
                Info info = infos.get(page.getPath());
                if (info == null) {
                    info = new Info(page, hollow);
                    infos.put(page.getPath(), info);
                }
                try {
                    // analyze the properties of the resource
                    Node node = res.adaptTo(Node.class);
                    for (PropertyIterator pIter = node.getProperties(); pIter.hasNext();) {
                        // don't add properties any further if limit is exceeded
                        if (getMaxReferencesPerPage() >= 0
                                && info.getProperties().size() >= getMaxReferencesPerPage()) {
                            break;
                        }
                        Property p = pIter.nextProperty();
                        // only check string and name properties
                        if (p.getType() == PropertyType.STRING || p.getType() == PropertyType.NAME) {
                            if (p.isMultiple()) {
                                for (Value v : p.getValues()) {
                                    String value = v.getString();
                                    if (isPatternMatched(patternList, value)) {
                                        info.addProperty(p.getPath());
                                        break;
                                    }
                                }
                            } else {
                                String value = p.getString();
                                if (isPatternMatched(patternList, value)) {
                                    info.addProperty(p.getPath());
                                }
                            }
                        }
                    }
                } catch (RepositoryException e) {
                    log.error("Error while accessing " + res.getPath(), e);
                }
            }
        }

        // filter out those infos that are empty
        filterResultset(infos);
    }

    private boolean isPatternMatched(List patternList, String value) {
    	boolean isPatternMatched = false;
    	for(Pattern pattern : patternList) {
    		isPatternMatched = pattern.matcher(value).find();
    		if(isPatternMatched) {
    			return isPatternMatched;
    		}
    	}
    	return isPatternMatched;

    }

    /**
     * 

Adjusts all references to path to destination * in the pages specified by refPaths. If {@link #isExact()} * is true only exact references to path are * adjusted, otherwise all references to child resources are adjusted, too.

* *

The resource resolver needs to have sufficient permissions (i.e. jcr:read and * rep:alterProperties) on the nodes containing references.

* * @param resolver resolver to operate on. * @param path source path * @param destination destination path * @param refPaths paths of pages to be adjusted * @return collection of path to properties that were adjusted */ public Collection adjustReferences(ResourceResolver resolver, String path, String destination, String[] refPaths) { if (refPaths == null) { return Collections.emptyList(); } Set adjusted = new HashSet(); for (String p: refPaths) { Resource r = resolver.getResource(p); if (r == null) { log.warn("Given path does not address a resource: {}", p); continue; } Page page = r.adaptTo(Page.class); if (page == null) { log.warn("Given path does not address a page: {}", p); } Resource content = page != null ? page.getContentResource() : null; if (content == null) { log.warn("Given page does not have content: {}", p); } try { //Can be a case of complex asset. Node node = content != null ? content.adaptTo(Node.class) : r.adaptTo(Node.class); adjusted.addAll(adjustReferences(node, path, destination)); // CQ5-32249 - touch the pages outside of this loop to avoid an inefficient O(n^2) algorithm } catch (RepositoryException e) { log.error("Error while adjusting references on " + r.getPath(), e); } // #22466 - moving pages does not take into account usergenerated content try { String adjustedUGCPath = adjustUserGeneratedContentReference(r, path, destination); if (adjustedUGCPath != null) { adjusted.add(adjustedUGCPath); log.info("Adjusted user generated content path {}.", adjustedUGCPath); } } catch (Exception e) { log.error("Error while adjusting user generated references on " + r.getPath(), e); } } // CQ5-32249 - do the touch calls after the above loop to avoid an inefficient O(n^2) algorithm PageManager pm = resolver.adaptTo(PageManager.class); // #38440 - touch the pages that were adjusted for (final String pathOfAdjusted : adjusted) { final Resource adjustedResource = resolver.getResource(pathOfAdjusted); if (null != adjustedResource) { final Page adjustedPage = pm.getContainingPage(adjustedResource); if (null != adjustedPage) { try { pm.touch(adjustedPage.adaptTo(Node.class), true, Calendar.getInstance(), false); } catch (WCMException e) { log.error("could not update last modified on adjusted page [{}]: ", adjustedPage.getPath(), e); } } //CQ-4316424 - touch the contentFragments that were adjusted touchIfContentFragmentReference(pathOfAdjusted, resolver); } } // save changes try { resolver.commit(); } catch (PersistenceException e) { log.error("Error while adjusting references.", e); } return adjusted; } /** * @param adjustedPath path for which reference is adjusted * @param resolver resolver to operate on. */ private void touchIfContentFragmentReference(String adjustedPath, ResourceResolver resolver) { int lastIndexOfDelimiter = adjustedPath.lastIndexOf(searchRoot); String possibleCFRefPath = adjustedPath.substring(0, lastIndexOfDelimiter); Resource adjustedResource = resolver.getResource(possibleCFRefPath); if (adjustedResource != null) { if (adjustedResource.getParent() != null && adjustedResource.getParent().getName().equals("data")) { Resource contentRes = adjustedResource.getParent().getParent(); if (contentRes != null && contentRes.getName().equals(JcrConstants.JCR_CONTENT)) { Boolean isContentFragment = contentRes.getValueMap().get("contentFragment", Boolean.class); touchCFLastModified(resolver, contentRes, isContentFragment); } } } } /** * * @param resolver resolver to operate on. * @param contentRes Content Resource of adjusted resource * @param isContentFragment is Resource is ContentFragment Resource */ private void touchCFLastModified(ResourceResolver resolver, Resource contentRes, Boolean isContentFragment) { if (isContentFragment != null && isContentFragment) { ModifiableValueMap properties = contentRes.adaptTo(ModifiableValueMap.class); if (properties != null) { properties.put(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance()); properties.put(JcrConstants.JCR_LAST_MODIFIED_BY, resolver.getUserID()); // modify lastFragmentSave property (can be used for triggering a // workflow on save) properties.put("lastFragmentSave", Calendar.getInstance()); } else { log.warn("Cannot adapt " + contentRes.getPath() + " to ModifiableValueMap."); throw new IllegalStateException("properties should not be null, check logs for further investigation"); } } } /** * Adjusts all references to path to destination * in the properties below the specified node. If {@link #isExact()} * is true only exact references to path are * adjusted, otherwise all references to child resources are adjusted, too. * * @param node (content) node to traverse * @param path source path * @param destination destination path * @throws RepositoryException if an error during repository access occurs * @return collection of paths to properties that were adjusted */ public Collection adjustReferences(Node node, String path, String destination) throws RepositoryException { return adjustReferences(node, path, destination, false, Collections.emptySet(), null, false); } public Collection adjustReferences(Node node, String path, String destination, boolean shallow, Set excludedProperties) throws RepositoryException { return adjustReferences(node, path, destination, shallow, excludedProperties, null, false); } public Collection adjustReferences(Node node, String path, String destination, boolean withoutChildNodes, ResourceResolver resolver) throws RepositoryException { return adjustReferences(node, path, destination, false, Collections.emptySet(), resolver, withoutChildNodes); } /** * Adjusts all references to path to destination * in the properties below the specified node. If {@link #isExact()} * is true only exact references to path are * adjusted, otherwise all references to child resources are adjusted, too. * * @param node (content) node to adjust * @param path source path * @param destination destination path * @param shallow if true child nodes are not traversed * @param excludedProperties a set of excluded property names * @param resolver resolver to operate on. * @param withoutChildNodes with child nodes or not * @throws RepositoryException if an error during repository access occurs * @return collection of paths to properties that were adjusted */ public Collection adjustReferences(Node node, String path, String destination, boolean shallow, Set excludedProperties, ResourceResolver resolver, boolean withoutChildNodes) throws RepositoryException { Set adjusted = new HashSet(); Pattern pattern = getReplacementPattern(path); String escDest = Text.escapePath(destination); for (PropertyIterator iter = node.getProperties(); iter.hasNext();) { Property p = iter.nextProperty(); // only check string, path and name properties if (!excludedProperties.contains(p.getName()) && p.getType() == PropertyType.STRING || p.getType() == PropertyType.NAME || p.getType() == PropertyType.PATH) { if (p.isMultiple()) { Value[] values = p.getValues(); boolean modified = false; for (int i=0; inull
if not matches */ protected String rewrite(String value, String from, Pattern p, String to, String escTo) { return rewrite(value, from, p, to, escTo, null, false); } /** * Internal rewrite method. * @param value the property value * @param from original path * @param p the replacement pattern * @param to to path * @param escTo escaped to path * @param resolver ResourceResolver for checking valid href * @param withoutChildNodes if true no child nodes are copied * @return rewritten path or null if not matches */ protected String rewrite(String value, String from, Pattern p, String to, String escTo, ResourceResolver resolver, boolean withoutChildNodes) { // first check unescaped direct property value if (value.equals(from)) { return to; } else if (value.startsWith(from + "#") || value.startsWith(from + ".html")) { // #34356 - handle cases where the path is followed by // an anchor or the .html suffix // TODO: There should be a less brittle way of doing this! return to + value.substring(from.length()); } else if (!exact) { if (value.startsWith(from + "/")) { return to + value.substring(from.length()); } } // ... then replace escaped references in rich text properties if (resolver != null && isValidatePage) { return replaceEscapedReferencesRTEWithValidHref(value, p, escTo, resolver, withoutChildNodes); } else { return replaceEscapedReferencesRTE(value, p, escTo); } } private static String replaceEscapedReferencesRTE(String value, Pattern p, String escTo) { Matcher m = p.matcher(value); StringBuffer ret = null; String repl = "$1" + escTo + "$3"; while (m.find()) { if (ret == null) { ret = new StringBuffer(); } m.appendReplacement(ret, repl); } if (ret == null) { return null; } else { m.appendTail(ret); return ret.toString(); } } private static String replaceEscapedReferencesRTEWithValidHref(String value, Pattern p, String escTo, ResourceResolver resolver, boolean withoutChildNodes) { Map adjusted = new HashMap<>(); StringBuffer ret = null; Pattern pattern = Pattern.compile(HREF_REGEX); Matcher matcher = pattern.matcher(value); while (matcher.find()) { if (ret == null) { ret = new StringBuffer(); } String href = matcher.group(2); if (!adjusted.containsKey(href)) { updateAdjusted(p, escTo, resolver, withoutChildNodes, adjusted, href); } matcher.appendReplacement(ret, "$1" + adjusted.get(href)); } if (ret == null) { return null; } matcher.appendTail(ret); return ret.toString(); } private static void updateAdjusted(Pattern p, String escTo, ResourceResolver resolver, boolean withoutChildNodes, Map adjusted, String href) { String newHref = replaceEscapedReferencesRTE(href, p, escTo); if (newHref == null) { newHref = href; } if (href.equals(newHref)) { adjusted.put(href, href); return; } if (!withoutChildNodes) { adjusted.put(href, newHref); return; } Resource resource = resolver.resolve(newHref.replaceAll("^\"|\"$", "")); if (resource.getResourceType().equals(RESOURCE_TYPE_NON_EXISTING)) { adjusted.put(href, href); } else { adjusted.put(href, newHref); } } /** * Escapes illegal XPath search characters. * * @param s the string to encode * @return the escaped string */ public static String escapeIllegalXpathSearchChars(String s) { StringBuffer sb = new StringBuffer(); for (char c: s.toCharArray()) { if (c == '!' || c == '(' || c == ')' || c == ':' || c == '^' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?' || c == '"' || c == '\\' || c == ' ' || c == '~') { sb.append('\\'); } else if (c == '\'') { sb.append(c); } sb.append(c); } return sb.toString(); } public ReferenceSearch setValidatePage(boolean validatePage) { isValidatePage = validatePage; return this; } /** * Holds information about the search results */ public static final class Info { private final Page page; private final String pageTitle; private final String pagePath; private final Set properties = new HashSet(); public Info(Page page) { this.page = page; pageTitle = page.getTitle(); pagePath = page.getPath(); } public Info(Page page, boolean hollow) { if (!hollow) { this.page = page; } else { this.page = null; } pageTitle = page.getTitle(); pagePath = page.getPath(); } private void addProperty(String path) { properties.add(path); } public Page getPage() { return page; } public Set getProperties() { return properties; } public String getPageTitle() { return pageTitle; } public String getPagePath() { return pagePath; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy