/*
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2013 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.contentfinder.querybuilder.impl.viewhandler;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.dam.api.DamConstants;
import com.day.cq.search.Predicate;
import com.day.cq.search.eval.FulltextPredicateEvaluator;
import com.day.cq.search.eval.JcrPropertyPredicateEvaluator;
import com.day.cq.wcm.api.NameConstants;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
public final class GQLToQueryBuilderConverter {
private GQLToQueryBuilderConverter() {
}
private static final Logger log = LoggerFactory.getLogger(GQLToQueryBuilderConverter.class);
public static final String DELIMITER = ContentFinderConstants.DELIMITER;
public static final String CF_TYPE = ContentFinderConstants.CF_TYPE;
public static final String CF_PATH = ContentFinderConstants.CF_PATH;
public static final String CF_FULLTEXT = ContentFinderConstants.CF_FULLTEXT;
public static final String CF_MIMETYPE = ContentFinderConstants.CF_MIMETYPE;
public static final String CF_ORDER = ContentFinderConstants.CF_ORDER;
public static final String CF_LIMIT = ContentFinderConstants.CF_LIMIT;
public static final String CF_OFFSET = ContentFinderConstants.CF_OFFSET;
public static final String CF_NAME = ContentFinderConstants.CF_NAME;
public static final String CF_TAGS = ContentFinderConstants.CF_TAGS;
public static final int GROUP_PATH = ContentFinderConstants.GROUP_PATH;
public static final int GROUP_TYPE = ContentFinderConstants.GROUP_TYPE;
public static final int GROUP_NAME = ContentFinderConstants.GROUP_NAME;
public static final int GROUP_MIMETYPE = ContentFinderConstants.GROUP_MIMETYPE;
public static final int GROUP_TAGS = ContentFinderConstants.GROUP_TAGS;
public static final int GROUP_FULLTEXT = ContentFinderConstants.GROUP_FULLTEXT;
public static final int GROUP_PROPERTY_USERDEFINED = ContentFinderConstants.GROUP_PROPERTY_USERDEFINED;
public static final int GROUP_ORDERBY_USERDEFINED = ContentFinderConstants.GROUP_ORDERBY_USERDEFINED;
public static final int GROUP_ORDERBY_SCORE = ContentFinderConstants.GROUP_ORDERBY_SCORE;
public static final int GROUP_ORDERBY_MODIFIED = ContentFinderConstants.GROUP_ORDERBY_MODIFIED;
public static final int DEFAULT_OFFSET = ContentFinderConstants.DEFAULT_OFFSET;
public static final int DEFAULT_LIMIT = ContentFinderConstants.DEFAULT_LIMIT;
/**
* Checks if request forces QueryBuilder mode
*
* @param request
* @return
*/
public static boolean convertToQueryBuilder(final SlingHttpServletRequest request) {
return (has(request, ContentFinderConstants.CONVERT_TO_QUERYBUILDER_KEY) && ContentFinderConstants.CONVERT_TO_QUERYBUILDER_VALUE
.equals(get(request, ContentFinderConstants.CONVERT_TO_QUERYBUILDER_KEY)));
}
public static Map<String, String> addPath(final SlingHttpServletRequest request, Map<String, String> map) {
if (has(request, CF_PATH)) {
map = put(request, map, CF_PATH, GROUP_PATH, true);
} else {
map.put(CF_PATH, request.getRequestPathInfo().getSuffix());
}
return map;
}
public static Map<String, String> addType(final SlingHttpServletRequest request, Map<String, String> map) {
if (has(request, CF_TYPE)) {
map = put(request, map, CF_TYPE, GROUP_TYPE, true);
}
return map;
}
public static Map<String, String> addName(final SlingHttpServletRequest request, Map<String, String> map) {
if (has(request, CF_NAME)) {
map = put(request, map, CF_NAME, "nodename", GROUP_NAME, true);
}
return map;
}
public static Map<String, String> addOrder(final SlingHttpServletRequest request, Map<String, String> map,
final String queryString) {
if (has(request, CF_ORDER)) {
int count = 1;
for (String value : getAll(request, CF_ORDER)) {
value = StringUtils.trim(value);
final String orderGroupId = String.valueOf(GROUP_ORDERBY_USERDEFINED + count) + "_orderby";
boolean sortAsc = false;
if (StringUtils.startsWith(value, "-")) {
sortAsc = false;
value = StringUtils.removeStart(value, "-");
} else if (StringUtils.startsWith(value, "+")) {
sortAsc = true;
value = StringUtils.removeStart(value, "+");
}
map.put(orderGroupId, StringUtils.trim(value));
map.put(orderGroupId + ".sort", sortAsc ? Predicate.SORT_ASCENDING : Predicate.SORT_DESCENDING);
count++;
}
} else {
final boolean isPage = isPage(request);
final boolean isAsset = isAsset(request);
final String prefix = getPropertyPrefix(request);
if (StringUtils.isNotBlank(queryString)) {
map.put(GROUP_ORDERBY_SCORE + "_orderby", "@" + JcrConstants.JCR_SCORE);
map.put(GROUP_ORDERBY_SCORE + "_orderby.sort", Predicate.SORT_DESCENDING);
}
String modifiedOrderProperty = "@" + JcrConstants.JCR_LASTMODIFIED;
if (isPage) {
modifiedOrderProperty = "@" + prefix + NameConstants.PN_PAGE_LAST_MOD;
} else if (isAsset) {
modifiedOrderProperty = "@" + prefix + JcrConstants.JCR_LASTMODIFIED;
}
map.put(GROUP_ORDERBY_MODIFIED + "_orderby", modifiedOrderProperty);
map.put(GROUP_ORDERBY_MODIFIED + "_orderby.sort", Predicate.SORT_DESCENDING);
}
return map;
}
public static Map<String, String> addMimeType(final SlingHttpServletRequest request, Map<String, String> map) {
final boolean isAsset = isAsset(request);
final String prefix = getPropertyPrefix(request);
if (isAsset && has(request, CF_MIMETYPE)) {
map.put(GROUP_MIMETYPE + "_group.1_property.operation", "like");
map.put(GROUP_MIMETYPE + "_group.1_property", prefix + DamConstants.DC_FORMAT);
map.put(GROUP_MIMETYPE + "_group.1_property.value", "%" + get(request, CF_MIMETYPE) + "%");
}
return map;
}
public static Map<String, String> addTags(final SlingHttpServletRequest request, Map<String, String> map) {
if (has(request, CF_TAGS)) {
final String prefix = getPropertyPrefix(request);
final String groupId = GROUP_TAGS + "_group";
final String tagProperty = prefix + NameConstants.PN_TAGS;
map.put(groupId + ".p.or", "true");
if (hasMany(request, CF_TAGS)) {
final String[] tags = getAll(request, CF_TAGS);
int i = 1;
for (final String tag : tags) {
map.put(groupId + "." + i + "_tagid.property", tagProperty);
map.put(groupId + "." + i + "_tagid", tag);
i++;
}
} else {
map.put(groupId + ".1_tagid.property", tagProperty);
map.put(groupId + ".1_tagid", get(request, CF_TAGS));
}
}
return map;
}
public static Map<String, String> addFulltext(final SlingHttpServletRequest request, Map<String, String> map,
final String queryString) {
if (StringUtils.isNotBlank(queryString)) {
final String groupId = GROUP_FULLTEXT + "_group";
map.put(groupId + "." + FulltextPredicateEvaluator.FULLTEXT, queryString);
map.put(groupId + ".p.or", "true");
}
return map;
}
public static Map<String, String> addLimitAndOffset(final SlingHttpServletRequest request,
Map<String, String> map) {
if (has(request, CF_LIMIT)) {
// Both limits and offsets are computed from CF's limit field X..Y
final String offset = String.valueOf(getOffset(request));
final String limit = String.valueOf(getLimit(request));
map.put("p.offset", String.valueOf(offset));
map.put("p.limit", limit);
} else {
map.put("p.limit", String.valueOf(DEFAULT_LIMIT));
}
return map;
}
public static Map<String, String> addProperty(final SlingHttpServletRequest request, Map<String, String> map,
final String requestKey, final int count) {
if (!ArrayUtils.contains(ContentFinderConstants.PROPERTY_BLACKLIST, requestKey)) {
map = putProperty(request, map, requestKey, JcrPropertyPredicateEvaluator.PROPERTY,
(GROUP_PROPERTY_USERDEFINED + count), true);
} else {
log.debug("Rejecting property [ {} ] due to blacklist match", requestKey);
}
return map;
}
public static boolean isValidProperty(final String key) {
return (!ArrayUtils.contains(ContentFinderConstants.PROPERTY_BLACKLIST, key));
}
/**
* Checks if the provided key has more than 1 values (comma delimited)
*
* @param request
* @param key
* @return
*/
public static boolean hasMany(SlingHttpServletRequest request, String key) {
final RequestParameter rp = request.getRequestParameter(key);
if (rp == null) {
return false;
}
return getAll(request, key).length > 1;
}
/**
* Checks if the provided key has ANY values (1 or more)
*
* @param request
* @param key
* @return
*/
public static boolean has(SlingHttpServletRequest request, String key) {
return request.getParameterValues(key) != null;
}
/**
* Returns a single value for a query parameter key
*
* @param request
* @param key
* @return
*/
public static String get(SlingHttpServletRequest request, String key) {
return StringUtils.trim(request.getRequestParameter(key).toString());
}
/**
* Returns a String array from a comma delimited list of values
*
* @param request
* @param key
* @return
*/
public static String[] getAll(SlingHttpServletRequest request, String key) {
final RequestParameter rp = request.getRequestParameter(key);
if (rp == null) {
return new String[0];
}
return StringUtils.split(rp.getString(), DELIMITER);
}
/**
* Convenience wrapper
*
* @param request
* @param map
* @param predicate
* @param group
* @param or
* @return
*/
public static Map<String, String> put(SlingHttpServletRequest request, Map<String, String> map,
String predicate, int group, boolean or) {
return putAll(map, predicate, getAll(request, predicate), group, or);
}
public static Map<String, String> put(SlingHttpServletRequest request, Map<String, String> map,
String requestKey, String predicate, int group, boolean or) {
return putAll(map, predicate, getAll(request, requestKey), group, or);
}
/**
* Used when the request key is different from the Predicate
*
* @param request
* @param map
* @param requestKey
* @param predicate
* @param group
* @param or
* @return
*/
public static Map<String, String> putProperty(SlingHttpServletRequest request, Map<String, String> map,
String requestKey, String predicate, int group, boolean or) {
// putAll(map, "property", "jcr:titke", "value", [x,y,z], 10, true)
return putAll(map, predicate, requestKey, JcrPropertyPredicateEvaluator.VALUE,
getAll(request, requestKey), group, or);
}
/**
* Helper method for adding comma delimited values into a Query Builder predicate
*
* @param map
* @param predicate
* @param values
* @param group
* @param or
* @return
*/
public static Map<String, String> putAll(Map<String, String> map, String predicate, String[] values,
int group, boolean or) {
final String groupId = String.valueOf(group) + "_group";
int count = 1;
for (final String value : values) {
final String predicateId = count + "_" + predicate;
map.put(groupId + "." + predicateId, StringUtils.trim(value));
count++;
}
map.put(groupId + ".p.or", String.valueOf(or));
return map;
}
/**
* @param map
* @param predicateValue => jcr:title
* @param predicate => property
* @param predicateSuffix => value
* @param values => [Square, Triangle]
* @param group => ID
* @param or => true/false
* @return
*/
public static Map<String, String> putAll(Map<String, String> map, String predicate, String predicateValue,
String predicateSuffix, String[] values, int group, boolean or) {
final String groupId = String.valueOf(group) + "_group";
map.put(groupId + "." + predicate, predicateValue);
int count = 1;
for (final String value : values) {
final String predicateId = predicate;
final String predicateSuffixId = count + "_" + predicateSuffix;
map.put(groupId + "." + predicateId + "." + predicateSuffixId, StringUtils.trim(value));
count++;
}
map.put(groupId + ".p.or", String.valueOf(or));
return map;
}
/**
* Checks of the query param node type is that of a CQ Page
*
* @param request
* @return
*/
public static boolean isPage(final SlingHttpServletRequest request) {
if (has(request, CF_TYPE)) {
String nodeType = get(request, CF_TYPE);
return StringUtils.equals(nodeType, NameConstants.NT_PAGE);
}
return false;
}
/**
* Checks of the query param node type is that of a DAM Asset
*
* @param request
* @return
*/
public static boolean isAsset(final SlingHttpServletRequest request) {
if (has(request, CF_TYPE)) {
String nodeType = get(request, CF_TYPE);
return StringUtils.equals(nodeType, DamConstants.NT_DAM_ASSET);
}
return false;
}
public static String getPropertyPrefix(final SlingHttpServletRequest request) {
final boolean isPage = isPage(request);
final boolean isAsset = isAsset(request);
String prefix = "";
if (isPage) {
prefix = JcrConstants.JCR_CONTENT + "/";
} else if (isAsset) {
prefix = JcrConstants.JCR_CONTENT + "/" + DamConstants.METADATA_FOLDER + "/";
}
return prefix;
}
/**
* Extract the query limit from the ContentFinder Query Parameter notation
*
* @param request
* @return
*/
public static int getLimit(final SlingHttpServletRequest request) {
if (has(request, CF_LIMIT)) {
final String value = get(request, CF_LIMIT);
final String[] limits = StringUtils.split(value, "..");
if (value.matches("^(\\d)+\\.\\.(\\d)+$")) {
// 10..20
return Integer.parseInt(limits[1]) - Integer.parseInt(limits[0]);
} else if (value.matches("^\\.\\.(\\d)+$")) {
// ..20
return Integer.parseInt(limits[0]);
} else if (value.matches("^(\\d)+\\.\\.$")) {
// 20..
// Not upper limit
return DEFAULT_LIMIT;
}
log.info("Could not find valid LIMIT for QueryBuilder-based ContentFinder: {}", value);
} else {
log.info("Could not find any LIMIT for QueryBuilder-based ContentFinder");
}
return DEFAULT_LIMIT;
}
/**
* Extract the query offset from the ContentFinder Query Parameter notation
*
* @param request
* @return
*/
public static int getOffset(final SlingHttpServletRequest request) {
if (has(request, CF_LIMIT)) {
final String value = get(request, CF_LIMIT);
final String[] offsets = StringUtils.split(value, "..");
if (value.matches("^(\\d)+\\.\\.(\\d)+$")) {
// 10..20
return Integer.parseInt(offsets[0]);
} else if (value.matches("^\\.\\.(\\d)+$")) {
// ..20
return Integer.parseInt(offsets[0]);
} else if (value.matches("^(\\d)+\\.\\.$")) {
// 20..
return Integer.parseInt(offsets[0]);
}
log.info("Could not find valid OFFSET for QueryBuilder-based ContentFinder: {}", value);
} else {
log.info("Could not find any OFFSET for QueryBuilder-based ContentFinder");
}
return DEFAULT_OFFSET;
}
}