Expand my Community achievements bar.

Applications for the 2024-2025 Adobe Experience Manager Champion Program are open!
SOLVED

AEM QueryBuilder API: Where do I put my custom created predicate?

Avatar

Level 5

I am using a custom predicate which is: https://github.com/arunpatidar02/aem63app-repo/blob/master/java/CaseInsensitiveLikePredicate.java

 

I want to test it in local AEM server and so want to know what's the process of integrating this predicate to my local AEM author instance? I have a folder crx-quickstart am I supposed to tap into that? Please help!

 

I tried following: https://experienceleague.adobe.com/docs/experience-manager-65/developing/platform/query-builder/impl...

 

However documentation only talks about creating the predicate and not about how to deploy it to your local AEM instance. Here are steps I followed:

 

1. Clone the Predicate Repository (https://github.com/Adobe-Marketing-Cloud/aem-search-custom-predicate-evaluator)

2. Change the Code in 

src/main/java/com/adobe/aem/docs/search/ReplicationPredicateEvaluator.java

to

package com.aem.community.core.utils;

import com.day.cq.search.Predicate;
import com.day.cq.search.eval.AbstractPredicateEvaluator;
import com.day.cq.search.eval.EvaluationContext;

import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.query.Row;

/**
 * Custom case insensitive predicate for like operation e.g.
 * caseinsensitive.property=jcr:content/@jcr:title 
 * caseinsensitive.value=queryString %
 *
 */
@Component(factory = "com.day.cq.search.eval.PredicateEvaluator/caseinsensitive")
public class CaseInsensitiveLikePredicate extends AbstractPredicateEvaluator {
	public static final String PROPERTY = "property";
	public static final String VALUE = "value";
	public static final String WILDCARD = "%";
	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	public boolean includes(Predicate predicate, Row row, EvaluationContext context) {
		if (predicate.hasNonEmptyValue(PROPERTY)) {
			return true;
		}
		return super.includes(predicate, row, context);
	}

	@Override
	public String getXPathExpression(Predicate predicate, EvaluationContext context) {
		if (!predicate.hasNonEmptyValue(PROPERTY)) {
			return null;
		}
		if (predicate.hasNonEmptyValue(PROPERTY) && null == predicate.get(VALUE)) {
			return super.getXPathExpression(predicate, context);
		}
		if (predicate.hasNonEmptyValue(PROPERTY)) {
			predicate.get(VALUE);
			if (WILDCARD.equals(predicate.get(VALUE))) {
				logger.info("Case Insensitive Query only has wildcard, ignoring predicate");
				return "";
			}
			logger.info("jcr:like(fn:lower-case(" + predicate.get(PROPERTY) + "), '"
					+ predicate.get(VALUE).toLowerCase() + "')");
			return "jcr:like(fn:lower-case(" + predicate.get(PROPERTY) + "),'" + predicate.get(VALUE).toLowerCase()
					+ "')";
		}
		return null;
	}
}

3. run the command: `mvn clean install -PautoInstallSinglePackage` from CLI. ( I am using AEM 6.5) which results in

 

[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] error: Source option 5 is no longer supported. Use 6 or later.
[ERROR] error: Target option 1.5 is no longer supported. Use 1.6 or later.

 

After performing the following steps, I have three questions:

 

1. In step 2 where I add my custom predicate, do I also need to change anything in pom.xml to make sure it gets correctly configured?

2. After having my custom predicate, what am I supposed to do to load or deploy in my AEM localhost:4502 for testing?

3. Looking at the code I shared which I took from one of the github repos (https://github.com/arunpatidar02/aem63app-repo/blob/master/java/CaseInsensitiveLikePredicate.java) would this also cover the case where I want to use this predicate in groups or would it work standalone only? Here's an example:

 

I know this will work:

 

 * caseinsensitive.property=jcr:content/@jcr:title 
 * caseinsensitive.value=queryString %

 

but would this work too?

 

1_group.1_caseinsensitive.property=jcr:content/@jcr:title 
1_group.1_caseinsensitive.value=queryString %

 

1 Accepted Solution

Avatar

Correct answer by
Level 5

After looking into code-base and testing out random things, here's how I was able to replicate the same to my AEM instance:

 

1. Step 1: As suggested by @Ganthimathi_R, create a raw project using the following command:

mvn -B archetype:generate \
 -D archetypeGroupId=com.adobe.granite.archetypes \
 -D archetypeArtifactId=aem-project-archetype \
 -D archetypeVersion=23 \
 -D aemVersion=6.5.0 \
 -D appTitle="My Site" \
 -D appId="mysite" \
 -D groupId="com.mysite" \
 -D frontendModule=general \
 -D includeExamples=n

Make sure to change the names as per your need. 

 

Step 2: Write the custom predicate as required: (and save it in src/main/java/com/mysite/core/<FOLDERNAME>/CustomCasePredicate.java)

```

package com.mysite.core.search;

import java.util.Comparator;
import javax.jcr.query.Row;
import org.apache.sling.api.resource.ValueMap;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.search.Predicate;
import com.day.cq.search.eval.AbstractPredicateEvaluator;
import com.day.cq.search.eval.EvaluationContext;
/**
 * Custom class to support case sensitive compare
 * path=
 * type=
 * customcase.property=
 * {optional} customcase.fulltext=
 * customcase.case=upper/lower/UPPER/LOWER/no_case/NO_CASE
 * orderby=customcase
 *
 */
@Component(factory="com.day.cq.search.eval.PredicateEvaluator/customcase")
public class CustomCasePredicate extends AbstractPredicateEvaluator {
    private static final Logger logger = LoggerFactory.getLogger(CustomCasePredicate.class);
    public static final String PROPERTY = "property";
    public static final String FULL_TEXT = "fulltext";
    public static final String CASE = "case";
    public static final String LOWER_CASE = "lower";
    public static final String UPPER_CASE = "upper";
    public static final String NO_CASE = "no_case";
    @Override
    public boolean includes(Predicate predicate, Row row,
                            EvaluationContext context) {
        if(predicate.hasNonEmptyValue(PROPERTY)){
            return true;
        }
        return super.includes(predicate, row, context);
    }

    @Override
    public String getXPathExpression(Predicate predicate,
                                     EvaluationContext context) {
        if(!predicate.hasNonEmptyValue(PROPERTY)){
            return null;
        }
        if(predicate.hasNonEmptyValue(PROPERTY) && null==predicate.get(FULL_TEXT)){
            return super.getXPathExpression(predicate, context);
        }
        if(predicate.get(CASE).equalsIgnoreCase(LOWER_CASE)){
            return "jcr:like(fn:lower-case(" + predicate.get(PROPERTY) + "),'" + predicate.get(FULL_TEXT) + "')";
        }
        if(predicate.get(CASE).equalsIgnoreCase(UPPER_CASE)){
            return "jcr:like(fn:upper-case(" + predicate.get(PROPERTY) + "),'" + predicate.get(FULL_TEXT) + "')";
        }
        if(predicate.get(CASE).equalsIgnoreCase(NO_CASE)){
            return "jcr:like(fn:lower-case(" + predicate.get(PROPERTY) + "),'" + predicate.get(FULL_TEXT).toLowerCase()+ "')";
        }
        return super.getXPathExpression(predicate, context);
    }

    @Override
    public Comparator<Row> getOrderByComparator(final Predicate predicate, final EvaluationContext context) {
        return new Comparator<Row>() {
            public int compare(Row r1, Row r2) {
                String[] property1;
                String[] property2;
                try {
                    if(null!=r1&&null!=r2&&null!=predicate.get(PROPERTY)){
                        ValueMap valueMap1 = context.getResource(r1).adaptTo(ValueMap.class);
                        ValueMap valueMap2 = context.getResource(r2).adaptTo(ValueMap.class);
                        property1 = valueMap1.get(predicate.get(PROPERTY),String[].class);
                        property2 =valueMap2.get(predicate.get(PROPERTY),String[].class);
                        boolean isCompare = null!=property1 && null!=property2;
                        if(isCompare) logger.debug("value    "+ property1[0] + "   "+ property2[0]);
                        if(isCompare && predicate.get(CASE).equalsIgnoreCase(LOWER_CASE)){
                            return property1[0].toLowerCase().compareTo(property2[0].toLowerCase());
                        }
                        if(isCompare && predicate.get(CASE).equalsIgnoreCase(UPPER_CASE)){
                            return property1[0].toUpperCase().compareTo(property2[0].toUpperCase());
                        }else{
                            return isCompare ? property1[0].compareToIgnoreCase(property2[0]):1;
                        }
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage());
                    e.printStackTrace();
                }
                return 1;
            }

        };
    }

}

The above is an example however you can use the same to implement case insensetivity fulltext search in a given property and you can use the same with groups as well.

 

Step 3: Run the command: mvn clean install -PautoInstallPackage to deploy it to the local instance. You can also view methods to deploy the same to cloud as well.

 

I had been looking here and there for the same and in case this is helpful to you do upvote so that it reaches the right people when required. Please note that this repo:https://github.com/Adobe-Marketing-Cloud/aem-search-custom-predicate-evaluator only supports upto AEM 6.4. If you're on 6.5+ mostly this approach would work like a charm.

 

 

View solution in original post

7 Replies

Avatar

Community Advisor

@spidey1405 

Use Query Builder Debugger tool for dry run your queries, optimize them and then implement it in the code.

AEM Query Builder debugger URL:-  http://localhost:4502/libs/cq/search/content/querydebug.html

Avatar

Level 5

@Ganthimathi_R Thank you for your response. I am already aware of AEM Query Builder debugger URL, my question is more about how do I deploy the same to localhost. I have my code but it would need some setup or deployment to take into effect and be recognised by the AEM right? How do I achieve that?

Avatar

Community Advisor

@spidey1405 Try the below configuration on your pom.xml which could resolve your compilation error,

<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

Avatar

Level 5

@Ganthimathi_R Right now it is:

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <!-- use version 2.3.2 to have java 1.5 as the default -->
                <version>2.3.2</version>
            </plugin>

I have updated it to what you said:

<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

Now I get a new error:

[ERROR] Failed to execute goal org.apache.felix:maven-scr-plugin:1.7.2:scr (generate-scr-descriptor) on project aem-custom-predicate-evaluator: Execution generate-scr-descriptor of goal org.apache.felix:maven-scr-plugin:1.7.2:scr failed: org.apache.felix.scrplugin.tags.annotation.AnnotationTagProvider is not an ImageIO SPI class -> [Help 1]

Avatar

Community Advisor

@spidey1405 Verify your maven version and update it under <pluginManagement>

 

 
<pluginManagement>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</pluginManagement>
 
The above one is for this section
<build>
<plugins>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

Avatar

Level 5

@Ganthimathi_R PluginManagement is supposed to be put under <plugins> right? or within <plugin> that I just wrote?

 

Here's how it looks now:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.6</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>

And gives me following error:

 

Plugin org.apache.maven.plugins:maven-compiler-plugin:3.8.6 or one of its dependencies could not be resolved: Failed to read artifact descriptor for org.apache.maven.plugins:maven-compiler-plugin:jar:3.8.6: Could not transfer artifact org.apache.maven.plugins:maven-compiler-plugin:pom:3.8.6 from/to maven-default-http-blocker (http://0.0.0.0/ Blocked mirror for repositories: [adobe (http://repo.adobe.com/nexus/content/groups/public/, default, releases+snapshots)]

Avatar

Correct answer by
Level 5

After looking into code-base and testing out random things, here's how I was able to replicate the same to my AEM instance:

 

1. Step 1: As suggested by @Ganthimathi_R, create a raw project using the following command:

mvn -B archetype:generate \
 -D archetypeGroupId=com.adobe.granite.archetypes \
 -D archetypeArtifactId=aem-project-archetype \
 -D archetypeVersion=23 \
 -D aemVersion=6.5.0 \
 -D appTitle="My Site" \
 -D appId="mysite" \
 -D groupId="com.mysite" \
 -D frontendModule=general \
 -D includeExamples=n

Make sure to change the names as per your need. 

 

Step 2: Write the custom predicate as required: (and save it in src/main/java/com/mysite/core/<FOLDERNAME>/CustomCasePredicate.java)

```

package com.mysite.core.search;

import java.util.Comparator;
import javax.jcr.query.Row;
import org.apache.sling.api.resource.ValueMap;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.search.Predicate;
import com.day.cq.search.eval.AbstractPredicateEvaluator;
import com.day.cq.search.eval.EvaluationContext;
/**
 * Custom class to support case sensitive compare
 * path=
 * type=
 * customcase.property=
 * {optional} customcase.fulltext=
 * customcase.case=upper/lower/UPPER/LOWER/no_case/NO_CASE
 * orderby=customcase
 *
 */
@Component(factory="com.day.cq.search.eval.PredicateEvaluator/customcase")
public class CustomCasePredicate extends AbstractPredicateEvaluator {
    private static final Logger logger = LoggerFactory.getLogger(CustomCasePredicate.class);
    public static final String PROPERTY = "property";
    public static final String FULL_TEXT = "fulltext";
    public static final String CASE = "case";
    public static final String LOWER_CASE = "lower";
    public static final String UPPER_CASE = "upper";
    public static final String NO_CASE = "no_case";
    @Override
    public boolean includes(Predicate predicate, Row row,
                            EvaluationContext context) {
        if(predicate.hasNonEmptyValue(PROPERTY)){
            return true;
        }
        return super.includes(predicate, row, context);
    }

    @Override
    public String getXPathExpression(Predicate predicate,
                                     EvaluationContext context) {
        if(!predicate.hasNonEmptyValue(PROPERTY)){
            return null;
        }
        if(predicate.hasNonEmptyValue(PROPERTY) && null==predicate.get(FULL_TEXT)){
            return super.getXPathExpression(predicate, context);
        }
        if(predicate.get(CASE).equalsIgnoreCase(LOWER_CASE)){
            return "jcr:like(fn:lower-case(" + predicate.get(PROPERTY) + "),'" + predicate.get(FULL_TEXT) + "')";
        }
        if(predicate.get(CASE).equalsIgnoreCase(UPPER_CASE)){
            return "jcr:like(fn:upper-case(" + predicate.get(PROPERTY) + "),'" + predicate.get(FULL_TEXT) + "')";
        }
        if(predicate.get(CASE).equalsIgnoreCase(NO_CASE)){
            return "jcr:like(fn:lower-case(" + predicate.get(PROPERTY) + "),'" + predicate.get(FULL_TEXT).toLowerCase()+ "')";
        }
        return super.getXPathExpression(predicate, context);
    }

    @Override
    public Comparator<Row> getOrderByComparator(final Predicate predicate, final EvaluationContext context) {
        return new Comparator<Row>() {
            public int compare(Row r1, Row r2) {
                String[] property1;
                String[] property2;
                try {
                    if(null!=r1&&null!=r2&&null!=predicate.get(PROPERTY)){
                        ValueMap valueMap1 = context.getResource(r1).adaptTo(ValueMap.class);
                        ValueMap valueMap2 = context.getResource(r2).adaptTo(ValueMap.class);
                        property1 = valueMap1.get(predicate.get(PROPERTY),String[].class);
                        property2 =valueMap2.get(predicate.get(PROPERTY),String[].class);
                        boolean isCompare = null!=property1 && null!=property2;
                        if(isCompare) logger.debug("value    "+ property1[0] + "   "+ property2[0]);
                        if(isCompare && predicate.get(CASE).equalsIgnoreCase(LOWER_CASE)){
                            return property1[0].toLowerCase().compareTo(property2[0].toLowerCase());
                        }
                        if(isCompare && predicate.get(CASE).equalsIgnoreCase(UPPER_CASE)){
                            return property1[0].toUpperCase().compareTo(property2[0].toUpperCase());
                        }else{
                            return isCompare ? property1[0].compareToIgnoreCase(property2[0]):1;
                        }
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage());
                    e.printStackTrace();
                }
                return 1;
            }

        };
    }

}

The above is an example however you can use the same to implement case insensetivity fulltext search in a given property and you can use the same with groups as well.

 

Step 3: Run the command: mvn clean install -PautoInstallPackage to deploy it to the local instance. You can also view methods to deploy the same to cloud as well.

 

I had been looking here and there for the same and in case this is helpful to you do upvote so that it reaches the right people when required. Please note that this repo:https://github.com/Adobe-Marketing-Cloud/aem-search-custom-predicate-evaluator only supports upto AEM 6.4. If you're on 6.5+ mostly this approach would work like a charm.