/* * Copyright (c) 2010-2015 Evolveum * * 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. */ package com.evolveum.midpoint.repo.sql.query2.resolution; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.repo.sql.data.common.RObject; import com.evolveum.midpoint.repo.sql.data.common.any.RAnyValue; import com.evolveum.midpoint.repo.sql.query.QueryException; import com.evolveum.midpoint.repo.sql.query.definition.VirtualQueryParam; import com.evolveum.midpoint.repo.sql.query2.InterpretationContext; import com.evolveum.midpoint.repo.sql.query2.QueryDefinitionRegistry2; import com.evolveum.midpoint.repo.sql.query2.definition.JpaAnyItemLinkDefinition; import com.evolveum.midpoint.repo.sql.query2.definition.JpaEntityDefinition; import com.evolveum.midpoint.repo.sql.query2.definition.JpaDataNodeDefinition; import com.evolveum.midpoint.repo.sql.query2.definition.JpaLinkDefinition; import com.evolveum.midpoint.repo.sql.query2.definition.VirtualCollectionSpecification; import com.evolveum.midpoint.repo.sql.query2.hqm.JoinSpecification; import com.evolveum.midpoint.repo.sql.query2.hqm.RootHibernateQuery; import com.evolveum.midpoint.repo.sql.query2.hqm.condition.AndCondition; import com.evolveum.midpoint.repo.sql.query2.hqm.condition.Condition; import com.evolveum.midpoint.repo.sql.util.RUtil; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import org.apache.commons.lang.ObjectUtils; import javax.xml.namespace.QName; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; /** * Responsible for resolving item paths - i.e. translating them into JPA paths along with creation of necessary joins. * Contains also methods that try to find proper specific entity definition when only general one (e.g. RObject) is provided. * * @author mederly */ public class ItemPathResolver { private static final Trace LOGGER = TraceManager.getTrace(ItemPathResolver.class); private InterpretationContext context; public ItemPathResolver(InterpretationContext interpretationContext) { this.context = interpretationContext; } /** * Resolves item path by creating a sequence of resolution states and preparing joins that are used to access JPA properties. * @param itemDefinition Definition for the (final) item pointed to. Optional - necessary only for extension items. * @param singletonOnly Means no collections are allowed (used for right-side path resolution and order criteria). */ public HqlDataInstance resolveItemPath(ItemPath relativePath, ItemDefinition itemDefinition, String currentHqlPath, JpaEntityDefinition baseEntityDefinition, boolean singletonOnly) throws QueryException { HqlDataInstance baseDataInstance = new HqlDataInstance(currentHqlPath, baseEntityDefinition, null); return resolveItemPath(relativePath, itemDefinition, baseDataInstance, singletonOnly); } public HqlDataInstance resolveItemPath(ItemPath relativePath, ItemDefinition itemDefinition, HqlDataInstance baseDataInstance, boolean singletonOnly) throws QueryException { ItemPathResolutionState currentState = new ItemPathResolutionState(relativePath, baseDataInstance, this); LOGGER.trace("Starting resolution and context update for item path '{}', singletonOnly='{}'", relativePath, singletonOnly); while (!currentState.isFinal()) { LOGGER.trace("Current resolution state:\n{}", currentState.debugDumpNoParent()); currentState = currentState.nextState(itemDefinition, singletonOnly, context.getPrismContext()); } LOGGER.trace("resolveItemPath({}) ending in resolution state of:\n{}", relativePath, currentState.debugDump()); return currentState.getHqlDataInstance(); } String addJoin(JpaLinkDefinition joinedItemDefinition, String currentHqlPath) throws QueryException { RootHibernateQuery hibernateQuery = context.getHibernateQuery(); String joinedItemJpaName = joinedItemDefinition.getJpaName(); String joinedItemFullPath = currentHqlPath + "." + joinedItemJpaName; String joinedItemAlias; if (!joinedItemDefinition.isMultivalued()) { /* * Let's check if we were already here i.e. if we had already created this join. * This is to avoid useless creation of redundant joins for singleton items. * * But how can we be sure that item is singleton if we look only at the last segment (which is single-valued)? * Imagine we are creating join for single-valued entity of u.abc.(...).xyz.ent where * ent is single-valued and not embedded (so we are to create something like "left join u.abc.(...).xyz.ent e"). * Then "u" is certainly a single value: either the root object, or some value pointed to by Exists restriction. * Also, abc, ..., xyz are surely single-valued: otherwise they would be connected by a join. So, * u.abc.(...).xyz.ent is a singleton. * * Look at it in other way: if e.g. xyz was multivalued, then we would have something like: * left join u.abc.(...).uvw.xyz x * left join x.ent e * And, therefore we would not be looking for u.abc.(...).xyz.ent. */ JoinSpecification existingJoin = hibernateQuery.getPrimaryEntity().findJoinFor(joinedItemFullPath); if (existingJoin != null) { // but let's check the condition as well String existingAlias = existingJoin.getAlias(); // we have to create condition for existing alias, to be matched to existing condition Condition conditionForExistingAlias = createJoinCondition(existingAlias, joinedItemDefinition, hibernateQuery); if (ObjectUtils.equals(conditionForExistingAlias, existingJoin.getCondition())) { LOGGER.trace("Reusing alias '{}' for joined path '{}'", existingAlias, joinedItemFullPath); return existingAlias; } } } joinedItemAlias = hibernateQuery.createAlias(joinedItemDefinition); Condition condition = createJoinCondition(joinedItemAlias, joinedItemDefinition, hibernateQuery); hibernateQuery.getPrimaryEntity().addJoin(new JoinSpecification(joinedItemAlias, joinedItemFullPath, condition)); return joinedItemAlias; } public String addTextInfoJoin(String currentHqlPath) throws QueryException { RootHibernateQuery hibernateQuery = context.getHibernateQuery(); String joinedItemJpaName = RObject.F_TEXT_INFO_ITEMS; String joinedItemFullPath = currentHqlPath + "." + joinedItemJpaName; String joinedItemAlias = hibernateQuery.createAlias(joinedItemJpaName, false); hibernateQuery.getPrimaryEntity().addJoin(new JoinSpecification(joinedItemAlias, joinedItemFullPath, null)); return joinedItemAlias; } private Condition createJoinCondition(String joinedItemAlias, JpaLinkDefinition joinedItemDefinition, RootHibernateQuery hibernateQuery) throws QueryException { Condition condition = null; if (joinedItemDefinition instanceof JpaAnyItemLinkDefinition) { JpaAnyItemLinkDefinition anyLinkDef = (JpaAnyItemLinkDefinition) joinedItemDefinition; AndCondition conjunction = hibernateQuery.createAnd(); if (anyLinkDef.getOwnerType() != null) { // null for assignment extensions conjunction.add(hibernateQuery.createEq(joinedItemAlias + ".ownerType", anyLinkDef.getOwnerType())); } conjunction.add(hibernateQuery.createEq(joinedItemAlias + "." + RAnyValue.F_NAME, RUtil.qnameToString(anyLinkDef.getItemName()))); condition = conjunction; } else if (joinedItemDefinition.getCollectionSpecification() instanceof VirtualCollectionSpecification) { VirtualCollectionSpecification vcd = (VirtualCollectionSpecification) joinedItemDefinition.getCollectionSpecification(); List<Condition> conditions = new ArrayList<>(vcd.getAdditionalParams().length); for (VirtualQueryParam vqp : vcd.getAdditionalParams()) { // e.g. name = "assignmentOwner", type = RAssignmentOwner.class, value = "ABSTRACT_ROLE" Object value = createQueryParamValue(vqp); Condition c = hibernateQuery.createEq(joinedItemAlias + "." + vqp.name(), value); conditions.add(c); } if (conditions.size() > 1) { condition = hibernateQuery.createAnd(conditions); } else if (conditions.size() == 1) { condition = conditions.iterator().next(); } } return condition; } /** * This method provides transformation from {@link String} value defined in * {@link com.evolveum.midpoint.repo.sql.query.definition.VirtualQueryParam#value()} to real object. Currently only * to simple types and enum values. */ private Object createQueryParamValue(VirtualQueryParam param) throws QueryException { Class type = param.type(); String value = param.value(); try { if (type.isPrimitive()) { return type.getMethod("valueOf", new Class[]{String.class}).invoke(null, new Object[]{value}); } if (type.isEnum()) { return Enum.valueOf(type, value); } } catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException |RuntimeException ex) { throw new QueryException("Couldn't transform virtual query parameter '" + param.name() + "' from String to '" + type + "', reason: " + ex.getMessage(), ex); } throw new QueryException("Couldn't transform virtual query parameter '" + param.name() + "' from String to '" + type + "', it's not yet implemented."); } /** * Finds the proper definition for (possibly abstract) entity. * Returns the most abstract entity that can be used. * Checks for conflicts, such as user.locality vs org.locality. * * @param path Path to be found (non-empty!) * @param itemDefinition Definition of target property, required/used only for "any" properties * @param clazz Kind of definition to be looked for * @param prismContext * @return Entity type definition + item definition, or null if nothing was found */ public <T extends JpaDataNodeDefinition> ProperDataSearchResult<T> findProperDataDefinition(JpaEntityDefinition baseEntityDefinition, ItemPath path, ItemDefinition itemDefinition, Class<T> clazz, PrismContext prismContext) throws QueryException { QueryDefinitionRegistry2 registry = QueryDefinitionRegistry2.getInstance(); ProperDataSearchResult<T> candidateResult = null; for (JpaEntityDefinition entityDefinition : findPossibleBaseEntities(baseEntityDefinition, registry)) { DataSearchResult<T> result = entityDefinition.findDataNodeDefinition(path, itemDefinition, clazz, prismContext); if (result != null) { if (candidateResult == null) { candidateResult = new ProperDataSearchResult<>(entityDefinition, result); } else { // Check for compatibility. As entities are presented from the more abstract to less abstract, // there is no possibility of false alarm. if (!candidateResult.getEntityDefinition().isAssignableFrom(entityDefinition)) { throw new QueryException("Unable to determine root entity for " + path + ": found incompatible candidates: " + candidateResult.getEntityDefinition() + " and " + entityDefinition); } } } } LOGGER.trace("findProperDataDefinition: base='{}', path='{}', def='{}', class={} -- returning '{}'", baseEntityDefinition, path, itemDefinition, clazz.getSimpleName(), candidateResult); return candidateResult; } private List<JpaEntityDefinition> findPossibleBaseEntities(JpaEntityDefinition entityDefinition, QueryDefinitionRegistry2 registry) { List<JpaEntityDefinition> retval = new ArrayList<>(); retval.add(entityDefinition); // (possibly) abstract one has to go first if (entityDefinition.isAbstract()) { // just for efficiency retval.addAll(registry.getChildrenOf(entityDefinition)); } return retval; } /** * Given existing entity definition and a request for narrowing it, tries to find refined definition. */ public JpaEntityDefinition findRestrictedEntityDefinition(JpaEntityDefinition baseEntityDefinition, QName specificTypeName) throws QueryException { QueryDefinitionRegistry2 registry = QueryDefinitionRegistry2.getInstance(); JpaEntityDefinition specificEntityDefinition = registry.findEntityDefinition(specificTypeName); if (!baseEntityDefinition.isAssignableFrom(specificEntityDefinition)) { throw new QueryException("Entity " + baseEntityDefinition + " cannot be restricted to " + specificEntityDefinition); } return specificEntityDefinition; } }