Expand my Community achievements bar.

Don’t miss the AEM Skill Exchange in SF on Nov 14—hear from industry leaders, learn best practices, and enhance your AEM strategy with practical tips.

global content find and replace in touch UI

Avatar

Level 5

Hi Team,

        we are trying to implement global content find and replace in touch UI(AEMasCS). which was similar functionality in classic UI below. Could you please let us know any help on this.

 

http://localhost:4502/siteadmin#/content/wknd/us/en/

instead siteadmin interface ,we need this functionality on touch UI(specifically in AEMasCS environment)

rajat168_0-1721985352924.png

i have also gone through this link content find & replace global · Issue #15 · Adobe-Consulting-Services/acs-aem-tools · GitHub  

 - Kindly let us know,is there any update on this issue?

11 Replies

Avatar

Community Advisor

Hi @rajat168 
If you want this feature to be implemented in Touch UI, please raise it at Adobe Experience Manager Ideas.



Arun Patidar

Avatar

Level 5

Hi @arunpatidar , thanks for your valuable input. i have raised my idea on this Implementation of find and replace content on AEMasCS  portal. Kindly let us know,is there any alternate solution of this same?

Avatar

Community Advisor

Hi @rajat168 
We have used groovy script to find and replace certain link and content globally. I think you can use the same, otherwise you have to reply on AEM search and manual replacement.



Arun Patidar

Avatar

Level 5

@arunpatidar  thanks, im new to groovy, could you please help me how to use this groovy console for find the content using FindReplaceServlet.java in groovy console.

 

1) how to call the FindReplaceServlet.java 

2) how to pass the search content to this servlet?

3) how to perform replace.

rajat168_0-1722332967954.png

 

 

Avatar

Community Advisor

Hi @rajat168 
You can create a mock request in java to call servlet, more details

https://experienceleaguecommunities.adobe.com/t5/adobe-experience-manager/how-to-access-sling-reques... 

 

You may need to check the implementation of FindReplaceServlet.java using java decompiler to know the exact parameters



Arun Patidar

Avatar

Level 5

Hi @arunpatidar , i have re-used the FindReplaceServlet.java, the issue we face was since we are traversing more than 10000 node it getting impact the server environment.

package com.day.cq.wcm.core.impl.servlets;

import com.day.cq.commons.JSONWriterUtil;
import com.day.cq.commons.servlets.AbstractPredicateServlet;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.xss.XSSProtectionService;
import com.day.text.ISO9075;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections.Predicate;
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.Reference;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
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.ValueMap;
import org.apache.sling.api.servlets.HtmlResponse;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.io.JSONWriter;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Deprecated
@Component(metatype = true, label = "%findreplace.name", description = "%findreplace.description")
@Service
@Properties({@Property(name = "service.description", value = {"Find and Replace servlet"}, propertyPrivate = true), @Property(name = "sling.servlet.methods", value = {"GET", "POST"}, propertyPrivate = true), @Property(name = "sling.servlet.resourceTypes", value = {"cq:Page"}, propertyPrivate = true), @Property(name = "sling.servlet.extensions", value = {"json", "html"}, propertyPrivate = true), @Property(name = "sling.servlet.selectors", value = {"find", "replace"}, propertyPrivate = true)})
public class FindReplaceServlet extends AbstractPredicateServlet {
  private static final long serialVersionUID = -4122234496210000472L;
  
  private static final Logger log = LoggerFactory.getLogger(FindReplaceServlet.class);
  
  @Reference(policy = ReferencePolicy.STATIC)
  private XSSProtectionService xss;
  
  private static final String PARAM_FIND = "f";
  
  private static final String PARAM_REPLACE = "r";
  
  private static final String PARAM_CASE_SENSITIVE = "cs";
  
  private static final String PARAM_WHOLE_WORDS = "wwo";
  
  private static final String PARAM_PATH = "p";
  
  @Property({"jcr:title", "jcr:description", "jcr:text", "text"})
  protected static final String CONFIG_SEARCH_SCOPE = "scope";
  
  protected final String[] htmlSpecialCharacters = new String[] { " " };
  
  protected String[] scope;
  
  protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response, Predicate predicate) throws ServletException, IOException {
    response.setContentType("application/json");
    response.setCharacterEncoding("utf-8");
    Resource resource = request.getResource();
    PageManager pm = (PageManager)resource.getResourceResolver().adaptTo(PageManager.class);
    String find = request.getParameter("f");
    boolean caseSensitive = "true".equalsIgnoreCase(request.getParameter("cs"));
    boolean wholeWords = "true".equalsIgnoreCase(request.getParameter("wwo"));
    if (find == null || find.length() == 0)
      return; 
    try {
      Writer out = response.getWriter();
      JSONWriter writer = new JSONWriter(out);
      writer.object();
      writer.key("matches");
      writer.array();
      for (String path : findPages(resource, find, caseSensitive, wholeWords)) {
        Page p = pm.getPage(path);
        if (p != null) {
          writer.object();
          JSONWriterUtil.write(writer, "title", p.getTitle(), JSONWriterUtil.WriteMode.AVOID_XSS, this.xss);
          writer.key("path").value(p.getPath());
          writer.endObject();
        } 
      } 
      writer.endArray();
      writer.endObject();
    } catch (JSONException e) {
      throw new ServletException(e);
    } 
  }
  
  protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
    Iterable<String> paths;
    Resource resource = request.getResource();
    PageManager pm = (PageManager)resource.getResourceResolver().adaptTo(PageManager.class);
    String find = request.getParameter("f");
    String replace = request.getParameter("r");
    boolean caseSensitive = "true".equalsIgnoreCase(request.getParameter("cs"));
    boolean wholeWords = "true".equalsIgnoreCase(request.getParameter("wwo"));
    if (find == null || find.length() == 0 || replace == null)
      return; 
    if (request.getParameterMap().containsKey("p")) {
      paths = new HashSet<>(Arrays.asList(request.getParameterValues("p")));
    } else {
      paths = findPages(resource, find, caseSensitive, wholeWords);
    } 
    HtmlResponse htmlResp = new HtmlResponse();
    Pattern pattern = createPattern(find, caseSensitive);
    for (String path : paths) {
      Page p = pm.getPage(path);
      if (p != null && 
        deepReplace(p.getContentResource(), pattern, replace, wholeWords)) {
        ModifiableValueMap values = (ModifiableValueMap)p.getContentResource().adaptTo(ModifiableValueMap.class);
        values.put("cq:lastModified", Calendar.getInstance());
        values.put("cq:lastModifiedBy", ((Session)resource
            .getResourceResolver().adaptTo(Session.class)).getUserID());
        try {
          p.getContentResource().getResourceResolver().commit();
          htmlResp.onModified(path);
        } catch (PersistenceException e) {
          p.getContentResource().getResourceResolver().revert();
        } 
      } 
    } 
    htmlResp.send((HttpServletResponse)response, true);
  }
  
  protected Iterable<String> findPages(Resource resource, String find, boolean caseSensitive, boolean wholeWords) {
    String path = resource.getPath();
    String likeExpression = find.replaceAll("'", "''");
    likeExpression = likeExpression.replaceAll("\\\\", "\\\\\\\\");
    likeExpression = likeExpression.replaceAll("%", "\\\\%");
    likeExpression = likeExpression.replaceAll("_", "\\\\_");
    if (!caseSensitive)
      likeExpression = likeExpression.toLowerCase(); 
    StringBuffer stmt = new StringBuffer("/jcr:root");
    stmt.append(ISO9075.encodePath(path));
    stmt.append("//*[");
    if (wholeWords)
      stmt.append("jcr:contains(., '" + likeExpression + "') and "); 
    stmt.append("(");
    String or = "";
    for (String prop : this.scope) {
      stmt.append(or);
      or = " or ";
      stmt.append("jcr:like(");
      if (!caseSensitive)
        stmt.append("fn:lower-case("); 
      stmt.append("@").append(ISO9075.encode(prop));
      if (!caseSensitive)
        stmt.append(")"); 
      stmt.append(",'%").append(likeExpression).append("%')");
    } 
    stmt.append(")]");
    if (!wholeWords)
      stmt.append(" option(traversal ok)"); 
    Iterator<Resource> matches = resource.getResourceResolver().findResources(stmt
        .toString(), "xpath");
    PageManager pm = (PageManager)resource.getResourceResolver().adaptTo(PageManager.class);
    Pattern pattern = createPattern(find, caseSensitive);
    Set<String> paths = new HashSet<>();
    while (matches.hasNext()) {
      Resource r = matches.next();
      if (!wholeWords || hasMatchingValues(r, pattern, wholeWords))
        paths.add(pm.getContainingPage(r).getPath()); 
    } 
    return paths;
  }
  
  protected boolean hasMatchingValues(Resource resource, Pattern pattern, boolean wholeWords) {
    return ((getMatchingValueNames(resource, pattern, wholeWords)).length > 0);
  }
  
  protected String[] getMatchingValueNames(Resource resource, Pattern pattern, boolean wholeWords) {
    ValueMap values = (ValueMap)resource.adaptTo(ValueMap.class);
    List<String> names = new ArrayList<>();
    String repl = "";
    while (true) {
      repl = repl + "x";
      if (!repl.equals(pattern.pattern())) {
        for (String name : this.scope) {
          String v = (String)values.get(name, String.class);
          if (v != null && 
            !v.equals(replace(v, pattern, repl, wholeWords)))
            names.add(name); 
        } 
        return names.<String>toArray(new String[names.size()]);
      } 
    } 
  }
  
  protected boolean deepReplace(Resource resource, Pattern pattern, String repl, boolean wholeWords) {
    boolean mod = replaceMatchingValues(resource, pattern, repl, wholeWords);
    ResourceResolver resolver = resource.getResourceResolver();
    Iterator<Resource> children = resolver.listChildren(resource);
    while (children.hasNext()) {
      if (deepReplace(children.next(), pattern, repl, wholeWords))
        mod = true; 
    } 
    return mod;
  }
  
  protected boolean replaceMatchingValues(Resource resource, Pattern pattern, String repl, boolean wholeWords) {
    boolean mod = false;
    ModifiableValueMap values = (ModifiableValueMap)resource.adaptTo(ModifiableValueMap.class);
    for (String name : this.scope) {
      boolean multiple = false;
      Property prop = (Property)values.get(name, Property.class);
      try {
        if (prop != null && prop.isMultiple())
          multiple = true; 
      } catch (RepositoryException e) {
        log.warn("Unable to check if property '{}' of [{}] is multiple, assuming it's single-valued", name, resource.getPath());
      } 
      if (prop != null)
        if (multiple) {
          String[] v = (String[])values.get(name, String[].class);
          boolean multipleMod = false;
          for (int i = 0; i < v.length; i++) {
            String result = replace(v[i], pattern, repl, wholeWords);
            if (!result.equals(v[i])) {
              multipleMod = mod = true;
              v[i] = result;
            } 
          } 
          if (multipleMod)
            values.put(name, v); 
        } else {
          String v = (String)values.get(name, String.class);
          String result = replace(v, pattern, repl, wholeWords);
          if (!result.equals(v)) {
            mod = true;
            values.put(name, result);
          } 
        }  
    } 
    try {
      resource.getResourceResolver().commit();
    } catch (PersistenceException e) {
      resource.getResourceResolver().revert();
      mod = false;
      log.warn("Unable to save replacement changes wihtin [{}]", resource.getPath(), e);
    } 
    return mod;
  }
  
  protected Pattern createPattern(String find, boolean caseSensitive) {
    StringBuffer regex = new StringBuffer();
    for (int i = 0; i < find.length(); i++) {
      char c = find.charAt(i);
      if (Character.isLetterOrDigit(c)) {
        regex.append(c);
      } else {
        regex.append("\\").append(c);
      } 
    } 
    int flags = 0;
    if (!caseSensitive) {
      flags |= 0x2;
      flags |= 0x40;
    } 
    return Pattern.compile(regex.toString(), flags);
  }
  
  protected String replace(String input, Pattern pattern, String repl, boolean wholeWords) {
    StringBuffer sb = new StringBuffer();
    repl = repl.replaceAll("\\\\", "\\\\\\\\");
    repl = repl.replaceAll("\\$", "\\\\\\$");
    Matcher m = pattern.matcher(input);
    while (m.find()) {
      int s = m.start();
      int e = m.end();
      if (!wholeWords || (isWordDelim(input, s - 1) && isWordDelim(input, e))) {
        m.appendReplacement(sb, repl);
        continue;
      } 
      m.appendReplacement(sb, input.substring(s, e));
    } 
    m.appendTail(sb);
    return sb.toString();
  }
  
  protected final boolean isWordDelim(String input, int idx) {
    if (idx == -1 || idx == input.length())
      return true; 
    char c = input.charAt(idx);
    if (c == '&')
      return isHtmlSpecialCharacter(input, idx); 
    return (c == '\000' || c == '/' || c == ' ' || c == '\r' || c == '\n' || c == '\t' || c == '.' || c == ';' || c == '(' || c == ')' || c == ',' || c == '<' || c == '>' || c == '"' || c == '\\');
  }
  
  protected final boolean isHtmlSpecialCharacter(String input, int start_id) {
    int end_id = input.indexOf(';', start_id);
    if (end_id == -1)
      return false; 
    String htmlTestString = input.substring(start_id, end_id + 1);
    for (String htmlChar : this.htmlSpecialCharacters) {
      if (htmlTestString.equalsIgnoreCase(htmlChar))
        return true; 
    } 
    return false;
  }
  
  protected void activate(ComponentContext context) {
    this.scope = (String[])context.getProperties().get("scope");
  }
  
  protected void bindXss(XSSProtectionService paramXSSProtectionService) {
    this.xss = paramXSSProtectionService;
  }
  
  protected void unbindXss(XSSProtectionService paramXSSProtectionService) {
    if (this.xss == paramXSSProtectionService)
      this.xss = null; 
  }
}

Avatar

Level 5

this search query internally using below oak:index(pathReference),could you please let us know how to call this oak:index?, since when we execute the search query it was not able to traverse more than 20000 node(PFB log).

rajat168_0-1722339840782.png

 

oak.plugins.index.cursor.TraversingCursor Traversed 20000 nodes with filter Filter(
java.lang.Exception: call stack
	at org.apache.jackrabbit.oak.plugins.index.cursor.TraversingCursor.fetchNext(TraversingCursor.java:164) [org.apache.jackrabbit.oak-core:1.62.0]
	at org.apache.jackrabbit.oak.plugins.index.cursor.TraversingCursor.next(TraversingCursor.java:141) [org.apache.jackrabbit.oak-core:1.62.0]
	at org.apache.jackrabbit.oak.plugins.index.cursor.PrefetchCursor.next(PrefetchCursor.java:76) [org.apache.jackrabbit.oak-core:1.62.0]
	at org.apache.jackrabbit.oak.query.ast.SelectorImpl.nextInternal(SelectorImpl.java:537) [org.apache.jackrabbit.oak-core:1.62.0]
	at org.apache.jackrabbit.oak.query.ast.SelectorImpl.next(SelectorImpl.java:525) [org.apache.jackrabbit.oak-core:1.62.0]
	at org.apache.jackrabbit.oak.query.Qu

 

Avatar

Administrator

@rajat168 Did you find the suggestions from users helpful? Please let us know if you require more information. Otherwise, please mark the answer as correct for posterity. If you've discovered a solution yourself, we would appreciate it if you could share it with the community. Thank you!



Kautuk Sahni

Avatar

Level 5

Hi @kautuk_sahni , i didnt find any further help on this... kindly help on this...