/*
* 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.hqm;
import com.evolveum.midpoint.prism.query.OrderDirection;
import com.evolveum.midpoint.repo.sql.data.common.RObject;
import com.evolveum.midpoint.repo.sql.query.QueryException;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaEntityDefinition;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaLinkDefinition;
import com.evolveum.midpoint.repo.sql.query2.hqm.condition.Condition;
import com.evolveum.midpoint.repo.sql.util.ClassMapper;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Query in HQL that is being created.
*
* @author mederly
*/
public abstract class HibernateQuery {
private static final String INDENT_STRING = " ";
// projection elements - i.e. select projectionElement1, projectionElement2, ..., projectionElementN from ...
private List<ProjectionElement> projectionElements = new ArrayList<>();
/**
* Primary entity for this query, along with joined entities.
* For example,
* RUser u
* Or,
* RUser u
* left join u.assignments a with ...
*
* (originally, we thought about cross-joins with other entities, hence "primary entity")
*/
private EntityReference primaryEntity; // not null
/**
* List of conditions in the "where" clause. They are to be interpreted as a conjunction.
*/
private List<Condition> conditions = new ArrayList<>();
public class Ordering {
@NotNull private final String byProperty;
private final OrderDirection direction;
Ordering(@NotNull String byProperty, OrderDirection direction) {
this.byProperty = byProperty;
this.direction = direction;
}
@NotNull
public String getByProperty() {
return byProperty;
}
public OrderDirection getDirection() {
return direction;
}
}
private List<Ordering> orderingList = new ArrayList<>();
public HibernateQuery(@NotNull JpaEntityDefinition primaryEntityDef) {
primaryEntity = createItemSpecification(primaryEntityDef);
}
protected HibernateQuery(EntityReference primaryEntity) {
this.primaryEntity = primaryEntity;
}
public List<ProjectionElement> getProjectionElements() {
return projectionElements;
}
public void addProjectionElement(ProjectionElement element) {
projectionElements.add(element);
}
public void addProjectionElementsFor(List<String> items) {
for (String item : items) {
addProjectionElement(new GenericProjectionElement(item));
}
}
public EntityReference getPrimaryEntity() {
return primaryEntity;
}
public void setPrimaryEntity(EntityReference primaryEntity) {
this.primaryEntity = primaryEntity;
}
public List<Condition> getConditions() {
return conditions;
}
public void addCondition(Condition condition) {
conditions.add(condition);
}
public String getAsHqlText(int indent, boolean distinct) {
StringBuilder sb = new StringBuilder();
indent(sb, indent);
sb.append("select");
if (distinct) {
sb.append(" distinct");
}
sb.append("\n");
ProjectionElement.dumpToHql(sb, projectionElements, indent + 1); // we finish at the end of the last line (not at the new line)
sb.append("\n");
indent(sb, indent);
sb.append("from\n");
primaryEntity.dumpToHql(sb, indent + 1);
if (!conditions.isEmpty()) {
sb.append("\n");
indent(sb, indent);
sb.append("where\n");
Condition.dumpToHql(sb, conditions, indent+1);
}
if (!orderingList.isEmpty()) {
sb.append("\n");
indent(sb, indent);
sb.append("order by ");
boolean first = true;
for (Ordering ordering : orderingList) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(ordering.byProperty);
if (ordering.direction != null) {
switch (ordering.direction) {
case DESCENDING: sb.append(" desc"); break;
case ASCENDING: sb.append(" asc"); break;
default: throw new IllegalStateException("Unknown ordering: " + ordering.direction);
}
}
}
}
return sb.toString();
}
public static void indent(StringBuilder sb, int indent) {
while (indent-- > 0) {
sb.append(INDENT_STRING);
}
}
public EntityReference createItemSpecification(JpaEntityDefinition entityDef) {
String alias = createAlias(entityDef);
return new EntityReference(alias, entityDef.getJpaClassName());
}
public String createAlias(JpaEntityDefinition def) {
return createAlias(def.getJpaClassName(), true);
}
public String createAlias(JpaLinkDefinition linkDefinition) {
Validate.notNull(linkDefinition.getJpaName(), "Got unnamed transition");
return createAlias(linkDefinition.getJpaName(), false);
}
private static final int LIMIT = 100;
public String createAlias(String name, boolean entity) {
String prefix;
//we want to skip 'R' prefix for entity definition names (a bit of hack)
int prefixIndex = entity ? 1 : 0;
prefix = Character.toString(name.charAt(prefixIndex)).toLowerCase();
int index = 2;
String alias = prefix;
while (hasAlias(alias)) {
alias = prefix + Integer.toString(index);
index++;
if (index > LIMIT) {
throw new IllegalStateException("Alias index for '" + name
+ "' is more than " + LIMIT + "? This probably should not happen.");
}
}
return alias;
}
private boolean hasAlias(String alias) {
if (primaryEntity != null && primaryEntity.containsAlias(alias)) {
return true;
}
return false;
}
public String getPrimaryEntityAlias() {
return getPrimaryEntity().getAlias();
}
public void addOrdering(String propertyPath, OrderDirection direction) {
orderingList.add(new Ordering(propertyPath, direction));
}
public List<Ordering> getOrderingList() {
return orderingList;
}
public abstract RootHibernateQuery getRootQuery();
// used to narrow the primary entity e.g. from RObject to RUser (e.g. during ItemValueRestriction processing)
public void narrowPrimaryEntity(JpaEntityDefinition newDefinition) throws QueryException {
String oldEntityName = getPrimaryEntity().getName();
Class<? extends RObject> oldEntityClass = ClassMapper.getHqlClassForHqlName(oldEntityName);
Class<? extends RObject> newEntityClass = newDefinition.getJpaClass();
if (!(oldEntityClass.isAssignableFrom(newEntityClass))) {
throw new QueryException("Cannot narrow primary entity definition from " + oldEntityClass + " to " + newEntityClass);
}
getPrimaryEntity().setName(newDefinition.getJpaClassName()); // alias stays the same
}
}