/*
* 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.prism.path.ParentPathSegment;
import com.evolveum.midpoint.repo.sql.query.QueryException;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaDataNodeDefinition;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaLinkDefinition;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import org.apache.commons.lang.Validate;
/**
* Describes current state in ItemPath resolution.
*
* We know what remains to be resolved.
* We know the HQL item we are pointing to.
* We know last transition - how we got here.
*
* This object is unmodifiable.
*
* @author mederly
*/
public class ItemPathResolutionState implements DebugDumpable {
private static final Trace LOGGER = TraceManager.getTrace(ItemPathResolutionState.class);
final private ItemPath remainingItemPath;
final private HqlDataInstance hqlDataInstance;
final private JpaLinkDefinition lastTransition; // how we got here (optional)
final private ItemPathResolver itemPathResolver; // provides auxiliary functionality
public ItemPathResolutionState(ItemPath pathToResolve, HqlDataInstance hqlDataInstance, ItemPathResolver itemPathResolver) {
Validate.notNull(pathToResolve, "pathToResolve");
Validate.notNull(hqlDataInstance, "hqlDataInstance");
Validate.notNull(itemPathResolver, "itemPathResolver");
this.remainingItemPath = pathToResolve;
this.hqlDataInstance = hqlDataInstance;
this.lastTransition = null;
this.itemPathResolver = itemPathResolver;
}
public ItemPath getRemainingItemPath() {
return remainingItemPath;
}
public HqlDataInstance getHqlDataInstance() {
return hqlDataInstance;
}
public JpaLinkDefinition getLastTransition() {
return lastTransition;
}
public ItemPathResolver getItemPathResolver() {
return itemPathResolver;
}
public boolean isFinal() {
return ItemPath.isNullOrEmpty(remainingItemPath);
}
/**
* Executes transition to next state. Modifies query context by adding joins as necessary.
*
* Precondition: !isFinal()
* Precondition: adequate transition exists
*
* @param itemDefinition Target item definition (used/required only for "any" properties)
* @param singletonOnly Collections are forbidden
* @return destination state - always not null
*/
public ItemPathResolutionState nextState(ItemDefinition itemDefinition, boolean singletonOnly, PrismContext prismContext) throws QueryException {
// special case - ".." when having previous state means returning to that state
// used e.g. for Exists (some-path, some-conditions AND Equals(../xxx, yyy))
//
// This is brutal hack, to be thought again.
if (remainingItemPath.startsWith(ParentPathSegment.class) && hqlDataInstance.getParentItem() != null) {
return new ItemPathResolutionState(
remainingItemPath.tail(),
hqlDataInstance.getParentItem(),
itemPathResolver);
}
DataSearchResult<?> result = hqlDataInstance.getJpaDefinition().nextLinkDefinition(remainingItemPath, itemDefinition, prismContext);
LOGGER.trace("nextLinkDefinition on '{}' returned '{}'", remainingItemPath, result != null ? result.getLinkDefinition() : "(null)");
if (result == null) { // sorry we failed (however, this should be caught before -> so IllegalStateException)
throw new IllegalStateException("Couldn't find '" + remainingItemPath + "' in " + hqlDataInstance.getJpaDefinition());
}
JpaLinkDefinition linkDefinition = result.getLinkDefinition();
String newHqlPath = hqlDataInstance.getHqlPath();
if (linkDefinition.hasJpaRepresentation()) {
if (singletonOnly && linkDefinition.isMultivalued()) {
throw new QueryException("Collections are not allowable for right-side paths"); // TODO better message + context
}
if (!linkDefinition.isEmbedded() || linkDefinition.isMultivalued()) {
LOGGER.trace("Adding join for '{}' to context", linkDefinition);
newHqlPath = itemPathResolver.addJoin(linkDefinition, hqlDataInstance.getHqlPath());
} else {
newHqlPath += "." + linkDefinition.getJpaName();
}
}
HqlDataInstance<?> parentDataInstance;
if (!remainingItemPath.startsWith(ParentPathSegment.class)) {
// TODO what about other special cases? (@, ...)
parentDataInstance = hqlDataInstance;
} else {
parentDataInstance = null;
}
return new ItemPathResolutionState(
result.getRemainder(),
new HqlDataInstance<>(newHqlPath, result.getTargetDefinition(), parentDataInstance),
itemPathResolver);
}
@Override
public String debugDump() {
return debugDump(0);
}
public String debugDumpNoParent() {
return debugDump(0, false);
}
@Override
public String debugDump(int indent) {
return debugDump(indent, true);
}
public String debugDump(int indent, boolean showParent) {
StringBuilder sb = new StringBuilder();
DebugUtil.indentDebugDump(sb, indent);
sb.append("ItemPathResolutionState:\n");
DebugUtil.indentDebugDump(sb, indent + 1);
sb.append("Remaining path: ").append(remainingItemPath).append("\n");
DebugUtil.indentDebugDump(sb, indent + 1);
sb.append("Last transition: ").append(lastTransition).append("\n");
DebugUtil.indentDebugDump(sb, indent + 1);
sb.append("HQL data item:\n").append(hqlDataInstance.debugDump(indent + 2, showParent));
return sb.toString();
}
@Override
public String toString() {
return "ItemPathResolutionState{" +
"remainingItemPath=" + remainingItemPath +
", hqlDataInstance='" + hqlDataInstance + '\'' +
'}';
}
}