/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.ambari.server.api.query;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.ambari.server.api.query.render.DefaultRenderer;
import org.apache.ambari.server.api.query.render.Renderer;
import org.apache.ambari.server.api.resources.ResourceDefinition;
import org.apache.ambari.server.api.resources.ResourceInstance;
import org.apache.ambari.server.api.resources.ResourceInstanceFactoryImpl;
import org.apache.ambari.server.api.resources.SubResourceDefinition;
import org.apache.ambari.server.api.services.BaseRequest;
import org.apache.ambari.server.api.services.Result;
import org.apache.ambari.server.api.services.ResultImpl;
import org.apache.ambari.server.api.util.TreeNode;
import org.apache.ambari.server.api.util.TreeNodeImpl;
import org.apache.ambari.server.controller.internal.QueryResponseImpl;
import org.apache.ambari.server.controller.predicate.AndPredicate;
import org.apache.ambari.server.controller.predicate.EqualsPredicate;
import org.apache.ambari.server.controller.spi.ClusterController;
import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
import org.apache.ambari.server.controller.spi.NoSuchResourceException;
import org.apache.ambari.server.controller.spi.PageRequest;
import org.apache.ambari.server.controller.spi.PageRequest.StartingPoint;
import org.apache.ambari.server.controller.spi.PageResponse;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.QueryResponse;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.ResourceProvider;
import org.apache.ambari.server.controller.spi.Schema;
import org.apache.ambari.server.controller.spi.SortRequest;
import org.apache.ambari.server.controller.spi.SystemException;
import org.apache.ambari.server.controller.spi.TemporalInfo;
import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
import org.apache.ambari.server.controller.utilities.PredicateHelper;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.apache.ambari.server.security.authorization.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default read query.
*/
public class QueryImpl implements Query, ResourceInstance {
/**
* Definition for the resource type. The definition contains all information specific to the
* resource type.
*/
private final ResourceDefinition resourceDefinition;
/**
* The cluster controller.
*/
private final ClusterController clusterController;
/**
* Properties of the query which make up the select portion of the query.
*/
private final Set<String> requestedProperties = new HashSet<>();
/**
* Map that associates categories with temporal data.
*/
private final Map<String, TemporalInfo> temporalInfoMap = new HashMap<>();
/**
* Map of primary and foreign key values.
*/
private final Map<Resource.Type, String> keyValueMap = new HashMap<>();
/**
* Map of properties from the request.
*/
private final Map<String, String> requestInfoProperties = new HashMap<>();
/**
* Set of query results.
*/
Map<Resource, QueryResult> queryResults = new LinkedHashMap<>();
/**
* Set of populated query results
*/
Map<Resource, QueryResult> populatedQueryResults = new LinkedHashMap<>();
/**
* Sub-resources of the resource which is being operated on.
* Should only be added via {@link #addSubResource(String, QueryImpl)}
*/
private final Map<String, QueryImpl> requestedSubResources = new HashMap<>();
/**
* Sub-resource instances of this resource.
* Map of resource name to resource instance.
*/
private Map<String, QueryImpl> availableSubResources;
/**
* Indicates that the query should include all available properties.
*/
private boolean allProperties = false;
/**
* The user supplied predicate.
*/
private Predicate userPredicate;
/**
* The user supplied page request information.
*/
private PageRequest pageRequest;
/**
* The user supplied sorting information.
*/
private SortRequest sortRequest;
/**
* The sub resource properties referenced in the user predicate.
*/
private final Set<String> subResourcePredicateProperties = new HashSet<>();
/**
* Associated renderer. The default renderer is used unless
* an alternate renderer is specified for the request. The renderer
* is responsible for determining which properties are selected
* as well as the overall structure of the result.
*/
private Renderer renderer;
/**
* Sub-resource predicate.
*/
private Predicate subResourcePredicate;
/**
* Processed predicate.
*/
private Predicate processedPredicate;
/**
* The logger.
*/
private final static Logger LOG =
LoggerFactory.getLogger(QueryImpl.class);
// ----- Constructor -------------------------------------------------------
/**
* Constructor
*
* @param keyValueMap the map of key values
* @param resourceDefinition the resource definition
* @param clusterController the cluster controller
*/
public QueryImpl(Map<Resource.Type, String> keyValueMap,
ResourceDefinition resourceDefinition,
ClusterController clusterController) {
this.resourceDefinition = resourceDefinition;
this.clusterController = clusterController;
setKeyValueMap(keyValueMap);
}
// ----- Query -------------------------------------------------------------
@Override
public void addProperty(String propertyId, TemporalInfo temporalInfo) {
if (propertyId.equals("*")) {
// wildcard
addAllProperties(temporalInfo);
} else{
if (! addPropertyToSubResource(propertyId, temporalInfo)) {
if (propertyId.endsWith("/*")) {
propertyId = propertyId.substring(0, propertyId.length() - 2);
}
addLocalProperty(propertyId);
if (temporalInfo != null) {
temporalInfoMap.put(propertyId, temporalInfo);
}
}
}
}
@Override
public void addLocalProperty(String property) {
requestedProperties.add(property);
}
@Override
public Result execute()
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
queryForResources();
return getResult(null);
}
@Override
public Predicate getPredicate() {
return createPredicate();
}
@Override
public Set<String> getProperties() {
return Collections.unmodifiableSet(requestedProperties);
}
@Override
public void setUserPredicate(Predicate predicate) {
userPredicate = predicate;
}
@Override
public void setPageRequest(PageRequest pageRequest) {
this.pageRequest = pageRequest;
}
@Override
public void setSortRequest(SortRequest sortRequest) {
this.sortRequest = sortRequest;
}
@Override
public void setRenderer(Renderer renderer) {
this.renderer = renderer;
renderer.init(clusterController);
}
@Override
public void setRequestInfoProps(Map<String, String> requestInfoProperties) {
if(requestInfoProperties != null) {
this.requestInfoProperties.putAll(requestInfoProperties);
}
}
@Override
public Map<String, String> getRequestInfoProps() {
return this.requestInfoProperties;
}
// ----- ResourceInstance --------------------------------------------------
@Override
public void setKeyValueMap(Map<Resource.Type, String> keyValueMap) {
this.keyValueMap.putAll(keyValueMap);
}
@Override
public Map<Resource.Type, String> getKeyValueMap() {
return new HashMap<>((keyValueMap));
}
@Override
public Query getQuery() {
return this;
}
@Override
public ResourceDefinition getResourceDefinition() {
return resourceDefinition;
}
@Override
public boolean isCollectionResource() {
return getKeyValueMap().get(getResourceDefinition().getType()) == null;
}
@Override
public Map<String, ResourceInstance> getSubResources() {
return new HashMap<String, ResourceInstance>(ensureSubResources());
}
// ----- Object overrides --------------------------------------------------
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
QueryImpl query = (QueryImpl) o;
return clusterController.equals(query.clusterController) && !(pageRequest != null ?
!pageRequest.equals(query.pageRequest) :
query.pageRequest != null) && requestedProperties.equals(query.requestedProperties) &&
resourceDefinition.equals(query.resourceDefinition) &&
keyValueMap.equals(query.keyValueMap) && !(userPredicate != null ?
!userPredicate.equals(query.userPredicate) :
query.userPredicate != null);
}
@Override
public int hashCode() {
int result = resourceDefinition.hashCode();
result = 31 * result + clusterController.hashCode();
result = 31 * result + requestedProperties.hashCode();
result = 31 * result + keyValueMap.hashCode();
result = 31 * result + (userPredicate != null ? userPredicate.hashCode() : 0);
result = 31 * result + (pageRequest != null ? pageRequest.hashCode() : 0);
return result;
}
// ----- helper methods ----------------------------------------------------
/**
* Get the map of sub-resources. Lazily create the map if required.
*/
protected Map<String, QueryImpl> ensureSubResources() {
if (availableSubResources == null) {
availableSubResources = new HashMap<>();
Set<SubResourceDefinition> setSubResourceDefs =
getResourceDefinition().getSubResourceDefinitions();
ClusterController controller = clusterController;
for (SubResourceDefinition subResDef : setSubResourceDefs) {
Resource.Type type = subResDef.getType();
Map<Resource.Type, String> valueMap = getKeyValueMap();
QueryImpl resource = new QueryImpl(valueMap,
ResourceInstanceFactoryImpl.getResourceDefinition(type, valueMap),
controller);
String subResourceName = getSubResourceName(resource.getResourceDefinition(), subResDef);
availableSubResources.put(subResourceName, resource);
}
}
return availableSubResources;
}
/**
* @param type the resource type
* @return whether sub-resources is in processed predicate
*/
private boolean populateResourceRequired(Resource.Type type) {
ResourceProvider resourceProvider = clusterController.ensureResourceProvider(type);
Set<String> unsupportedProperties =
resourceProvider.checkPropertyIds(PredicateHelper.getPropertyIds(processedPredicate));
return !unsupportedProperties.isEmpty() || hasSubResourcePredicate();
}
/**
* Query the cluster controller for the top level resources.
*/
private void queryForResources()
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
Resource.Type resourceType = getResourceDefinition().getType();
Predicate queryPredicate = createPredicate(getKeyValueMap(), processUserPredicate(userPredicate));
// must occur after processing user predicate and prior to creating request
finalizeProperties();
Request request = createRequest();
// use linked hash sets so that we maintain insertion and traversal order
// in the event that the resource provider already gave us a sorted set
// back
Set<Resource> resourceSet = new LinkedHashSet<>();
Set<Resource> providerResourceSet = new LinkedHashSet<>();
QueryResponse queryResponse = doQuery(resourceType, request, queryPredicate, true);
// If there is a page request and the predicate does not contain properties
// that need to be set
if ((pageRequest != null || sortRequest != null ) &&
!populateResourceRequired(resourceType)) {
PageResponse pageResponse = clusterController.getPage(resourceType,
queryResponse, request, queryPredicate, pageRequest, sortRequest);
// build a new set
for (Resource r : pageResponse.getIterable()) {
resourceSet.add(r);
providerResourceSet.add(r);
}
} else {
resourceSet.addAll(queryResponse.getResources());
providerResourceSet.addAll(queryResponse.getResources());
}
populatedQueryResults.put(null, new QueryResult(
request, queryPredicate, userPredicate, getKeyValueMap(), new QueryResponseImpl(resourceSet)));
queryResults.put(null, new QueryResult(
request, queryPredicate, userPredicate, getKeyValueMap(), queryResponse));
if (renderer.requiresPropertyProviderInput()) {
clusterController.populateResources(resourceType, providerResourceSet, request, queryPredicate);
}
// Optimization:
// Currently the steps executed when sub-resources are requested are:
// (1) Get *all* top-level resources
// (2) Populate all top-level resources
// (3) Query for and populate sub-resources of *all* top-level resources
// (4) Apply pagination and predicate on resources from above
//
// Though this works, it is very inefficient when either:
// (a) Predicate does not apply to sub-resources
// (b) Page request is present
// It is inefficient because we needlessly populate sub-resources that might not get
// used due to their top-level resources being filtered out by the predicate and paging
//
// The optimization is to apply the predicate and paging request on the top-level resources
// directly if there are no sub-resources predicates.
if ((pageRequest != null || userPredicate != null) && !hasSubResourcePredicate() && populateResourceRequired(resourceType)) {
QueryResponse newResponse = new QueryResponseImpl(resourceSet, queryResponse.isSortedResponse(), queryResponse.isPagedResponse(),
queryResponse.getTotalResourceCount());
PageResponse pageResponse = clusterController.getPage(resourceType, newResponse, request, queryPredicate, pageRequest, sortRequest);
// build a new set
Set<Resource> newResourceSet = new LinkedHashSet<>();
for (Resource r : pageResponse.getIterable()) {
newResourceSet.add(r);
}
populatedQueryResults.put(null, new QueryResult(request, queryPredicate, userPredicate, getKeyValueMap(), new QueryResponseImpl(newResourceSet)));
}
queryForSubResources();
}
/**
* Query the cluster controller for the sub-resources associated with
* this query object.
*/
private void queryForSubResources()
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
for (Map.Entry<String, QueryImpl> entry : requestedSubResources.entrySet()) {
QueryImpl subResource = entry.getValue();
Resource.Type resourceType = subResource.getResourceDefinition().getType();
Request request = subResource.createRequest();
Set<Resource> providerResourceSet = new HashSet<>();
for (QueryResult queryResult : populatedQueryResults.values()) {
for (Resource resource : queryResult.getQueryResponse().getResources()) {
Map<Resource.Type, String> map = getKeyValueMap(resource, queryResult.getKeyValueMap());
Predicate queryPredicate = subResource.createPredicate(map, subResource.processedPredicate);
Set<Resource> resourceSet = new LinkedHashSet<>();
try {
Set<Resource> queryResources =
subResource.doQuery(resourceType, request, queryPredicate, false).getResources();
providerResourceSet.addAll(queryResources);
resourceSet.addAll(queryResources);
} catch (NoSuchResourceException e) {
// do nothing ...
} catch (AuthorizationException e) {
// do nothing, since the user does not have access to the data ...
LOG.debug("User does not have authorization to get {} resources. The data will not be added to the response.", resourceType.name());
}
subResource.queryResults.put(resource,
new QueryResult(request, queryPredicate, subResourcePredicate, map, new QueryResponseImpl(resourceSet)));
subResource.populatedQueryResults.put(resource,
new QueryResult(request, queryPredicate, subResourcePredicate, map, new QueryResponseImpl(resourceSet)));
}
}
if (renderer.requiresPropertyProviderInput()) {
clusterController.populateResources(resourceType, providerResourceSet, request, subResourcePredicate);
}
subResource.queryForSubResources();
}
}
/**
* Query the cluster controller for the resources.
*
* @param type the resource type
* @param request the request information
* @param predicate the predicate
* @param checkEmptyResponse true if an empty query response can trigger a NoSuchResourceException
*
* @return the result of the cluster controller query
*
* @throws NoSuchResourceException if a specific resource was asked for and not found and checkEmptyResponse == true
*/
private QueryResponse doQuery(Resource.Type type, Request request, Predicate predicate, boolean checkEmptyResponse)
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
if (LOG.isDebugEnabled()) {
LOG.debug("Executing resource query: " + request + " where " + predicate);
}
QueryResponse queryResponse = clusterController.getResources(type, request, predicate);
if (checkEmptyResponse && queryResponse.getResources().isEmpty()) {
// If this is not a collection request then we must throw
// NoSuchResourceException (404 response) for an empty query result
if(!isCollectionResource()) {
throw new NoSuchResourceException(
"The requested resource doesn't exist: " + type.toString() + " not found where " + predicate + ".");
}
}
return queryResponse;
}
/**
* Get a map of property sets keyed by the resources associated with this
* query. The property sets should contain the joined sets of all of the
* requested properties from each resource's sub-resources.
*
* For example, if this query is associated with the resources AResource1,
* AResource1 and AResource3 as follows ...
*
* <pre>
* a_resources
* │
* └──AResource1 ─────────────AResource1 ─────────────AResource3
* │ │ │
* ├── b_resources ├── b_resources ├── BResources
* │ ├── BResource1 │ ├── BResource3 │ └── BResource5
* │ │ p1:1 │ │ p1:3 │ p1:5
* │ │ p2:5 │ │ p2:5 │ p2:5
* │ │ │ │ │
* │ └── BResource2 │ └── BResource4 └── c_resources
* │ p1:2 │ p1:4 └── CResource4
* │ p2:0 │ p2:0 p3:4
* │ │
* └── c_resources └── c_resources
* ├── CResource1 └── CResource3
* │ p3:1 p3:3
* │
* └── CResource2
* p3:2
*
* Given the following query ...
*
* api/v1/a_resources?b_resources/p1>3&b_resources/p2=5&c_resources/p3=1
*
* The caller should pass the following property ids ...
*
* b_resources/p1
* b_resources/p2
* c_resources/p3
*
* getJoinedResourceProperties should produce the following map of property sets
* by making recursive calls on the sub-resources of each of this query's resources,
* joining the resulting property sets, and adding them to the map keyed by the
* resource ...
*
* {
* AResource1=[{b_resources/p1=1, b_resources/p2=5, c_resources/p3=1},
* {b_resources/p1=2, b_resources/p2=0, c_resources/p3=1},
* {b_resources/p1=1, b_resources/p2=5, c_resources/p3=2},
* {b_resources/p1=2, b_resources/p2=0, c_resources/p3=2}],
* AResource2=[{b_resources/p1=3, b_resources/p2=5, c_resources/p3=3},
* {b_resources/p1=4, b_resources/p2=0, c_resources/p3=3}],
* AResource3=[{b_resources/p1=5, b_resources/p2=5, c_resources/p3=4}],
* }
* </pre>
*
* @param propertyIds
* the requested properties
* @param parentResource
* the parent resource; may be null
* @param category
* the sub-resource category; may be null
*
* @return a map of property sets keyed by the resources associated with this
* query
*/
protected Map<Resource, Set<Map<String, Object>>> getJoinedResourceProperties(Set<String> propertyIds,
Resource parentResource,
String category)
throws SystemException, UnsupportedPropertyException, NoSuchParentResourceException, NoSuchResourceException {
Map<Resource, Set<Map<String, Object>>> resourcePropertyMaps =
new HashMap<>();
Map<String, String> categoryPropertyIdMap =
getPropertyIdsForCategory(propertyIds, category);
for (Map.Entry<Resource, QueryResult> queryResultEntry : populatedQueryResults.entrySet()) {
QueryResult queryResult = queryResultEntry.getValue();
Resource queryParentResource = queryResultEntry.getKey();
// for each resource for the given parent ...
if (queryParentResource == parentResource) {
Iterable<Resource> iterResource = clusterController.getIterable(
resourceDefinition.getType(), queryResult.getQueryResponse(),
queryResult.getRequest(), queryResult.getPredicate(), null, null);
for (Resource resource : iterResource) {
// get the resource properties
Map<String, Object> resourcePropertyMap = new HashMap<>();
for (Map.Entry<String, String> categoryPropertyIdEntry : categoryPropertyIdMap.entrySet()) {
Object value = resource.getPropertyValue(categoryPropertyIdEntry.getValue());
if (value != null) {
resourcePropertyMap.put(categoryPropertyIdEntry.getKey(), value);
}
}
Set<Map<String, Object>> propertyMaps = new HashSet<>();
// For each sub category get the property maps for the sub resources
for (Map.Entry<String, QueryImpl> entry : requestedSubResources.entrySet()) {
String subResourceCategory = category == null ? entry.getKey() : category + "/" + entry.getKey();
QueryImpl subResource = entry.getValue();
Map<Resource, Set<Map<String, Object>>> subResourcePropertyMaps =
subResource.getJoinedResourceProperties(propertyIds, resource, subResourceCategory);
Set<Map<String, Object>> combinedSubResourcePropertyMaps = new HashSet<>();
for (Set<Map<String, Object>> maps : subResourcePropertyMaps.values()) {
combinedSubResourcePropertyMaps.addAll(maps);
}
propertyMaps = joinPropertyMaps(propertyMaps, combinedSubResourcePropertyMaps);
}
// add parent resource properties to joinedResources
if (!resourcePropertyMap.isEmpty()) {
if (propertyMaps.isEmpty()) {
propertyMaps.add(resourcePropertyMap);
} else {
for (Map<String, Object> propertyMap : propertyMaps) {
propertyMap.putAll(resourcePropertyMap);
}
}
}
resourcePropertyMaps.put(resource, propertyMaps);
}
}
}
return resourcePropertyMaps;
}
/**
* Finalize properties for entire query tree before executing query.
*/
private void finalizeProperties() {
ResourceDefinition rootDefinition = resourceDefinition;
QueryInfo rootQueryInfo = new QueryInfo(rootDefinition, requestedProperties);
TreeNode<QueryInfo> rootNode = new TreeNodeImpl<>(
null, rootQueryInfo, rootDefinition.getType().name());
TreeNode<QueryInfo> requestedPropertyTree = buildQueryPropertyTree(this, rootNode);
mergeFinalizedProperties(renderer.finalizeProperties(
requestedPropertyTree, isCollectionResource()), this);
}
/**
* Recursively build a tree of query information.
*
* @param query query to process
* @param node tree node associated with the query
*
* @return query info tree
*/
private TreeNode<QueryInfo> buildQueryPropertyTree(QueryImpl query, TreeNode<QueryInfo> node) {
for (QueryImpl subQuery : query.requestedSubResources.values()) {
ResourceDefinition childResource = subQuery.resourceDefinition;
QueryInfo queryInfo = new QueryInfo(childResource, subQuery.requestedProperties);
TreeNode<QueryInfo> childNode = node.addChild(queryInfo, childResource.getType().name());
buildQueryPropertyTree(subQuery, childNode);
}
return node;
}
/**
* Merge the tree of query properties returned by the renderer with properties in
* the query tree.
*
* @param node property tree node
* @param query query associated with the property tree node
*/
private void mergeFinalizedProperties(TreeNode<Set<String>> node, QueryImpl query) {
Set<String> finalizedProperties = node.getObject();
query.requestedProperties.clear();
// currently not exposing temporal information to renderer
query.requestedProperties.addAll(finalizedProperties);
for (TreeNode<Set<String>> child : node.getChildren()) {
Resource.Type childType = Resource.Type.valueOf(child.getName());
ResourceDefinition parentResource = query.resourceDefinition;
Set<SubResourceDefinition> subResources = parentResource.getSubResourceDefinitions();
String subResourceName = null;
for (SubResourceDefinition subResource : subResources) {
if (subResource.getType() == childType) {
ResourceDefinition resource = ResourceInstanceFactoryImpl.getResourceDefinition(
subResource.getType(), query.keyValueMap);
subResourceName = getSubResourceName(resource, subResource);
break;
}
}
QueryImpl subQuery = query.requestedSubResources.get(subResourceName);
if (subQuery == null) {
query.addProperty(subResourceName, null);
subQuery = query.requestedSubResources.get(subResourceName);
}
mergeFinalizedProperties(child, subQuery);
}
}
// Map the given set of property ids to corresponding property ids in the
// given sub-resource category.
private Map<String, String> getPropertyIdsForCategory(Set<String> propertyIds, String category) {
Map<String, String> map = new HashMap<>();
for (String propertyId : propertyIds) {
if (category == null || propertyId.startsWith(category)) {
map.put(propertyId, category==null ? propertyId : propertyId.substring(category.length() + 1));
}
}
return map;
}
// Join two sets of property maps into one.
private static Set<Map<String, Object>> joinPropertyMaps(Set<Map<String, Object>> propertyMaps1,
Set<Map<String, Object>> propertyMaps2) {
Set<Map<String, Object>> propertyMaps = new HashSet<>();
if (propertyMaps1.isEmpty()) {
return propertyMaps2;
}
if (propertyMaps2.isEmpty()) {
return propertyMaps1;
}
for (Map<String, Object> map1 : propertyMaps1) {
for (Map<String, Object> map2 : propertyMaps2) {
Map<String, Object> joinedMap = new HashMap<>(map1);
joinedMap.putAll(map2);
propertyMaps.add(joinedMap);
}
}
return propertyMaps;
}
// Get a result from this query.
private Result getResult(Resource parentResource)
throws UnsupportedPropertyException, SystemException, NoSuchResourceException, NoSuchParentResourceException {
Result result = new ResultImpl(true);
Resource.Type resourceType = getResourceDefinition().getType();
TreeNode<Resource> tree = result.getResultTree();
if (isCollectionResource()) {
tree.setProperty("isCollection", "true");
}
QueryResult queryResult = queryResults.get(parentResource);
if (queryResult != null) {
Predicate queryPredicate = queryResult.getPredicate();
Predicate queryUserPredicate = queryResult.getUserPredicate();
Request queryRequest = queryResult.getRequest();
QueryResponse queryResponse = queryResult.getQueryResponse();
if (hasSubResourcePredicate() && queryUserPredicate != null) {
queryPredicate = getExtendedPredicate(parentResource, queryUserPredicate);
}
Iterable<Resource> iterResource;
if (pageRequest == null) {
iterResource = clusterController.getIterable(
resourceType, queryResponse, queryRequest, queryPredicate,
null, sortRequest
);
} else {
PageResponse pageResponse = clusterController.getPage(
resourceType, queryResponse, queryRequest, queryPredicate,
pageRequest, sortRequest
);
iterResource = pageResponse.getIterable();
tree.setProperty("count", pageResponse.getTotalResourceCount().toString());
}
int count = 1;
for (Resource resource : iterResource) {
// add a child node for the resource and provide a unique name. The name is never used.
TreeNode<Resource> node = tree.addChild(
resource, resource.getType() + ":" + count++);
for (Map.Entry<String, QueryImpl> entry : requestedSubResources.entrySet()) {
String subResCategory = entry.getKey();
QueryImpl subResource = entry.getValue();
TreeNode<Resource> childResult = subResource.getResult(resource).getResultTree();
childResult.setName(subResCategory);
childResult.setProperty("isCollection", "false");
node.addChild(childResult);
}
}
}
return renderer.finalizeResult(result);
}
// Indicates whether or not this query has sub-resource elements
// in its predicate.
private boolean hasSubResourcePredicate() {
return !subResourcePredicateProperties.isEmpty();
}
// Alter the given predicate so that the resources referenced by
// the predicate will be extended to include the joined properties
// of their sub-resources.
private Predicate getExtendedPredicate(Resource parentResource,
Predicate predicate)
throws SystemException,
UnsupportedPropertyException,
NoSuchParentResourceException,
NoSuchResourceException {
Map<Resource, Set<Map<String, Object>>> joinedResources =
getJoinedResourceProperties(subResourcePredicateProperties, parentResource, null);
ExtendedResourcePredicateVisitor visitor =
new ExtendedResourcePredicateVisitor(joinedResources);
PredicateHelper.visit(predicate, visitor);
return visitor.getExtendedPredicate();
}
private void addAllProperties(TemporalInfo temporalInfo) {
allProperties = true;
if (temporalInfo != null) {
temporalInfoMap.put(null, temporalInfo);
}
for (Map.Entry<String, QueryImpl> entry : ensureSubResources().entrySet()) {
String name = entry.getKey();
if (! requestedSubResources.containsKey(name)) {
addSubResource(name, entry.getValue());
}
}
}
private boolean addPropertyToSubResource(String propertyId, TemporalInfo temporalInfo) {
int index = propertyId.indexOf("/");
String category = index == -1 ? propertyId : propertyId.substring(0, index);
Map<String, QueryImpl> subResources = ensureSubResources();
QueryImpl subResource = subResources.get(category);
if (subResource != null) {
addSubResource(category, subResource);
//only add if a sub property is set or if a sub category is specified
if (index != -1) {
subResource.addProperty(propertyId.substring(index + 1), temporalInfo);
}
return true;
}
return false;
}
private Predicate createInternalPredicate(Map<Resource.Type, String> mapResourceIds) {
Resource.Type resourceType = getResourceDefinition().getType();
Schema schema = clusterController.getSchema(resourceType);
Set<Predicate> setPredicates = new HashSet<>();
for (Map.Entry<Resource.Type, String> entry : mapResourceIds.entrySet()) {
if (entry.getValue() != null) {
String keyPropertyId = schema.getKeyPropertyId(entry.getKey());
if (keyPropertyId != null) {
setPredicates.add(new EqualsPredicate<>(keyPropertyId, entry.getValue()));
}
}
}
Predicate p = null;
if (setPredicates.size() == 1) {
p = setPredicates.iterator().next();
} else if (setPredicates.size() > 1) {
p = new AndPredicate(setPredicates.toArray(new Predicate[setPredicates.size()]));
} else {
return null;
}
Resource.Type type = getResourceDefinition().getType();
Predicate override = clusterController.getAmendedPredicate(type, p);
if (null != override) {
p = override;
}
return p;
}
private Predicate createPredicate() {
return createPredicate(getKeyValueMap(), userPredicate);
}
private Predicate createPredicate(Map<Resource.Type, String> keyValueMap, Predicate predicate) {
Predicate internalPredicate = createInternalPredicate(keyValueMap);
if (internalPredicate == null) {
return predicate;
}
return (predicate == null ? internalPredicate :
new AndPredicate(predicate, internalPredicate));
}
// Get a sub-resource predicate from the given predicate.
private Predicate getSubResourcePredicate(Predicate predicate, String category) {
if (predicate == null) {
return null;
}
SubResourcePredicateVisitor visitor = new SubResourcePredicateVisitor(category);
PredicateHelper.visit(predicate, visitor);
return visitor.getSubResourcePredicate();
}
// Process the given predicate to remove sub-resource elements.
private Predicate processUserPredicate(Predicate predicate) {
if (predicate == null) {
return null;
}
ProcessingPredicateVisitor visitor = new ProcessingPredicateVisitor(this);
PredicateHelper.visit(predicate, visitor);
// add the sub-resource to the request
Set<String> categories = visitor.getSubResourceCategories();
for (String category : categories) {
addPropertyToSubResource(category, null);
}
// record the sub-resource properties on this query
subResourcePredicateProperties.addAll(visitor.getSubResourceProperties());
if (hasSubResourcePredicate()) {
for (Map.Entry<String, QueryImpl> entry : requestedSubResources.entrySet()) {
subResourcePredicate = getSubResourcePredicate(predicate, entry.getKey());
entry.getValue().processUserPredicate(subResourcePredicate);
}
}
processedPredicate = visitor.getProcessedPredicate();
return processedPredicate;
}
private Request createRequest() {
// Initiate this request's requestInfoProperties with the ones set from the original request
Map<String, String> requestInfoProperties = new HashMap<>(this.requestInfoProperties);
if (pageRequest != null) {
requestInfoProperties.put(BaseRequest.PAGE_SIZE_PROPERTY_KEY,
Integer.toString(pageRequest.getPageSize() + pageRequest.getOffset()));
requestInfoProperties.put(
BaseRequest.ASC_ORDER_PROPERTY_KEY,
Boolean.toString(pageRequest.getStartingPoint() == StartingPoint.Beginning
|| pageRequest.getStartingPoint() == StartingPoint.OffsetStart));
}
if (allProperties) {
return PropertyHelper.getReadRequest(Collections.<String> emptySet(),
requestInfoProperties, null, pageRequest, sortRequest);
}
Map<String, TemporalInfo> mapTemporalInfo = new HashMap<>();
TemporalInfo globalTemporalInfo = temporalInfoMap.get(null);
Set<String> setProperties = new HashSet<>();
setProperties.addAll(requestedProperties);
for (String propertyId : setProperties) {
TemporalInfo temporalInfo = temporalInfoMap.get(propertyId);
if (temporalInfo != null) {
mapTemporalInfo.put(propertyId, temporalInfo);
} else if (globalTemporalInfo != null) {
mapTemporalInfo.put(propertyId, globalTemporalInfo);
}
}
return PropertyHelper.getReadRequest(setProperties, requestInfoProperties,
mapTemporalInfo, pageRequest, sortRequest);
}
// Get a key value map based on the given resource and an existing key value map
private Map<Resource.Type, String> getKeyValueMap(Resource resource,
Map<Resource.Type, String> keyValueMap) {
Map<Resource.Type, String> resourceKeyValueMap = new HashMap<>(keyValueMap.size());
for (Map.Entry<Resource.Type, String> resourceIdEntry : keyValueMap.entrySet()) {
Resource.Type type = resourceIdEntry.getKey();
String value = resourceIdEntry.getValue();
if (value == null) {
Object o = resource.getPropertyValue(clusterController.getSchema(type).getKeyPropertyId(type));
value = o == null ? null : o.toString();
}
if (value != null) {
resourceKeyValueMap.put(type, value);
}
}
Schema schema = clusterController.getSchema(resource.getType());
Set<Resource.Type> types = schema.getKeyTypes();
for (Resource.Type type : types) {
String resourceKeyProp = schema.getKeyPropertyId(type);
Object resourceValue = resource.getPropertyValue(resourceKeyProp);
if (null != resourceValue) {
resourceKeyValueMap.put(type, resourceValue.toString());
}
}
return resourceKeyValueMap;
}
/**
* Add a sub query with the renderer set.
*
* @param name name of sub resource
* @param query sub resource
*/
private void addSubResource(String name, QueryImpl query) {
// renderer specified for request only applies to top level query
query.setRenderer(new DefaultRenderer());
requestedSubResources.put(name, query);
}
/**
* Obtain the name of a sub-resource.
*
* @param resource parent resource
* @param subResource sub-resource
*
* @return either the plural or singular sub-resource name based on whether the sub-resource is
* included as a collection
*/
private String getSubResourceName(ResourceDefinition resource, SubResourceDefinition subResource) {
return subResource.isCollection() ?
resource.getPluralName() :
resource.getSingularName();
}
// ----- inner class : QueryResult -----------------------------------------
/**
* Maintain information about an individual query and its result.
*/
private static class QueryResult {
private final Request request;
private final Predicate predicate;
private final Predicate userPredicate;
private final Map<Resource.Type, String> keyValueMap;
private final QueryResponse queryResponse;
// ----- Constructor -----------------------------------------------------
private QueryResult(Request request, Predicate predicate, Predicate userPredicate,
Map<Resource.Type, String> keyValueMap,
QueryResponse queryResponse) {
this.request = request;
this.predicate = predicate;
this.userPredicate = userPredicate;
this.keyValueMap = keyValueMap;
this.queryResponse = queryResponse;
}
// ----- accessors -------------------------------------------------------
public Request getRequest() {
return request;
}
public Predicate getPredicate() {
return predicate;
}
public Predicate getUserPredicate() {
return userPredicate;
}
public Map<Resource.Type, String> getKeyValueMap() {
return keyValueMap;
}
public QueryResponse getQueryResponse() {
return queryResponse;
}
}
}