/*
* Copyright (c) WSO2 Inc. (http://wso2.com) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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.
*
*/
package org.wso2.carbon.registry.search.services;
import org.wso2.carbon.registry.common.ResourceData;
import org.wso2.carbon.registry.core.*;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.dataaccess.QueryProcessor;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.session.CurrentSession;
import org.wso2.carbon.registry.core.utils.MediaTypesUtils;
import java.util.*;
public class XPathQueryProcessor implements QueryProcessor {
public static final String XPATH_QUERY_MEDIA_TYPE = "application/vnd.wso2.xpath.query";
private static final String NO_CONTAINMENT_SUFFIX = "(.)";
private MetadataSearchService service;
public XPathQueryProcessor(MetadataSearchService service) {
this.service = service;
}
public Collection executeQuery(Registry registry, Resource resource, Map map)
throws RegistryException {
if (map != null &&
map.containsKey("query") && XPATH_QUERY_MEDIA_TYPE.equals(map.get("mediaType"))) {
String query = ((String) map.get("query")).substring(1);
String[] parts;
Set<String> paths;
int partsRead = 0;
if (query.indexOf('/') == 0) {
// this is a search anywhere query.
query = query.substring(1);
parts = query.split("/");
paths = executeQueryForPart(parts[0]);
if (paths.size() == 0) {
// if there we no hits, then no point reading more.
partsRead = Integer.MAX_VALUE;
} else {
// if there were some hits, then move to the next part.
partsRead = 1;
}
} else {
// this is a search from root query.
paths = Collections.singleton("/");
parts = query.split("/");
}
if (parts.length > partsRead) {
if (partsRead == 1) {
parts = query.substring(query.indexOf("/") + 1).split("/");
}
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
String relation;
Set<String> matches;
if (part.contains("[")) {
matches = executeQueryForPart("%" + part.substring(
part.indexOf("[")));
relation = part.substring(0, part.indexOf("["));
} else {
relation = part;
matches = null;
}
Set<String> associations = new HashSet<String>();
Set<String> temp = new HashSet<String>();
for (String path : paths) {
Association[] list;
if (relation.endsWith(NO_CONTAINMENT_SUFFIX)) {
list = registry.getAssociations(path, relation.substring(0,
relation.length() - NO_CONTAINMENT_SUFFIX.length()));
} else {
list = registry.getAssociations(path, relation);
String resourcePath = path + RegistryConstants.PATH_SEPARATOR +
relation;
if (registry.resourceExists(resourcePath)) {
if (i == parts.length - 1) {
// if this is the last part, then add children.
Resource containedResource = registry.get(resourcePath);
if (containedResource instanceof Collection) {
String[] children =
((Collection) containedResource).getChildren();
if (children != null) {
temp.addAll(Arrays.asList(children));
}
} else {
temp.add(resourcePath);
}
} else {
temp.add(resourcePath);
}
}
}
for (Association association : list) {
if (association.getSourcePath().equals(path) &&
association.getDestinationPath().startsWith(
RegistryConstants.ROOT_PATH)) {
associations.add(association.getDestinationPath());
}
}
}
temp.addAll(associations);
if (matches != null) {
// if matching did not happen, the matches would be null. Matches would be
// empty if matching happened and no matches were found. This is a different
// scenario.
temp.retainAll(matches);
}
paths = temp;
}
}
return new CollectionImpl(paths.toArray(new String[paths.size()]));
} else if (resource != null && XPATH_QUERY_MEDIA_TYPE.equals(resource.getMediaType())) {
throw new UnsupportedOperationException("Query-resource model is not supported");
} else {
throw new RegistryException("Unable to process XPath query. Pre-conditions have not " +
"been set due to some unknown reason.");
}
}
protected Set<String> executeQueryForPart(String part) throws RegistryException {
Set<String> paths = new HashSet<String>();
String[] subParts = part.split("\\[");
String mediaType = MediaTypesUtils.getMimeTypeFromHumanReadableMediaType(subParts[0]);
Map<String, String> base;
if (mediaType == null || mediaType.equals(subParts[0])) {
base = Collections.singletonMap("resourcePath", subParts[0]);
} else {
base = Collections.singletonMap("mediaType", mediaType);
}
if (subParts.length > 1) {
String predicates = subParts[1].substring(0, subParts[1].length() - 1).trim();
String[] predicateParts = predicates.split(" or ");
for (String predicatePart : predicateParts) {
Map<String, String> input = parsePredicatePart(base, predicatePart);
ResourceData[] results = service.search(CurrentSession.getTenantId(), input);
for (ResourceData result : results) {
paths.add(result.getResourcePath());
}
}
} else {
ResourceData[] results = service.search(CurrentSession.getTenantId(), base);
for (ResourceData result : results) {
paths.add(result.getResourcePath());
}
}
return paths;
}
protected Map<String, String> parsePredicatePart(Map<String, String> base, String predicatePart) {
Map<String, String> input = new HashMap<String, String>(base);
String[] expressions = predicatePart.trim().split(" and ");
for (String expression : expressions) {
String expr = expression.trim();
if (expr.contains("!=")) {
String[] temp = fixParams(expr.split("!="));
addNegatedItemsToInput(input, temp);
} else if (expr.contains(">=")) {
String[] temp = fixParams(expr.split(">="));
input.put("propertyName", temp[0]);
input.put("rightPropertyValue", temp[1]);
input.put("rightOp", "ge");
} else if (expr.contains(">")) {
String[] temp = fixParams(expr.split(">"));
input.put("propertyName", temp[0]);
input.put("rightPropertyValue", temp[1]);
input.put("rightOp", "gt");
} else if (expr.contains("<=")) {
String[] temp = fixParams(expr.split("<="));
input.put("propertyName", temp[0]);
input.put("leftPropertyValue", temp[1]);
input.put("leftOp", "le");
} else if (expr.contains("=")) {
String[] temp = fixParams(expr.split("="));
input.put(temp[0], temp[1]);
}
}
return input;
}
protected void addNegatedItemsToInput(Map<String, String> input, String[] temp) {
if (temp[0].equals("author")) {
input.put("authorNameNegate", Boolean.toString(true));
} else if (temp[0].equals("updater")) {
input.put("updaterNameNegate", Boolean.toString(true));
} else if (temp[0].equals("mediaType")) {
input.put("mediaTypeNegate", Boolean.toString(true));
} else if (temp[0].startsWith("created")) {
input.put("createdRangeNegate", Boolean.toString(true));
} else if (temp[0].startsWith("updated")) {
input.put("updatedRangeNegate", Boolean.toString(true));
} else {
// we cannot do a '!=' comparison for other situations.
return;
}
input.put(temp[0], temp[1]);
}
protected String[] fixParams(String[] temp) {
String[] strings = Arrays.copyOf(temp, temp.length);
strings[0] = strings[0].trim();
if (strings[0].startsWith("@")) {
strings[0] = strings[0].substring(1);
}
strings[1] = strings[1].trim();
if (strings[1].indexOf('\'') == 0) {
strings[1] = strings[1].substring(1, strings[1].length() - 1);
}
return strings;
}
}