/** * 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; } } }