/*
* 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.restriction;
import com.evolveum.midpoint.prism.PrismConstants;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.query.RefFilter;
import com.evolveum.midpoint.repo.sql.data.common.RObjectReference;
import com.evolveum.midpoint.repo.sql.data.common.any.ROExtReference;
import com.evolveum.midpoint.repo.sql.query.QueryException;
import com.evolveum.midpoint.repo.sql.query2.InterpretationContext;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaAnyReferenceDefinition;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaEntityDefinition;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaReferenceDefinition;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaLinkDefinition;
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.query2.hqm.condition.OrCondition;
import com.evolveum.midpoint.repo.sql.util.ClassMapper;
import com.evolveum.midpoint.repo.sql.util.RUtil;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import org.apache.commons.collections4.CollectionUtils;
import org.jetbrains.annotations.NotNull;
import javax.xml.namespace.QName;
import java.util.*;
import static com.evolveum.midpoint.repo.sql.util.RUtil.qnameToString;
/**
* @author lazyman
*/
public class ReferenceRestriction extends ItemValueRestriction<RefFilter> {
private static final Trace LOGGER = TraceManager.getTrace(ReferenceRestriction.class);
// Definition of the item being queried.
@NotNull private final JpaLinkDefinition<JpaReferenceDefinition> linkDefinition;
public ReferenceRestriction(InterpretationContext context, RefFilter filter, JpaEntityDefinition baseEntityDefinition,
Restriction parent, @NotNull JpaLinkDefinition<JpaReferenceDefinition> linkDefinition) {
super(context, filter, baseEntityDefinition, parent);
this.linkDefinition = linkDefinition;
}
@Override
public Condition interpretInternal() throws QueryException {
String hqlPath = hqlDataInstance.getHqlPath();
LOGGER.trace("interpretInternal starting with hqlPath = {}", hqlPath);
RootHibernateQuery hibernateQuery = context.getHibernateQuery();
List<PrismReferenceValue> values = filter.getValues();
if (CollectionUtils.isEmpty(values)) {
return hibernateQuery.createIsNull(hqlDataInstance.getHqlPath());
}
Set<String> oids = new HashSet<>();
Set<QName> relations = new HashSet<>();
Set<QName> targetTypes = new HashSet<>();
for (PrismReferenceValue value : values) {
if (value.getOid() == null) {
throw new QueryException("Null OID is not allowed in the reference query. Use empty reference list if needed.");
}
oids.add(value.getOid());
if (value.getRelation() == null) {
relations.add(SchemaConstants.ORG_DEFAULT);
} else {
// we intentionally don't normalize relations namespaces, to be able to do namespace-insensitive searches
// so the caller is responsible to unify namespaces if he needs to optimize queries (use IN instead of OR)
relations.add(value.getRelation());
}
targetTypes.add(qualifyTypeName(value.getTargetType()));
}
if (relations.size() > 1 || targetTypes.size() > 1) {
// we must use 'OR' clause
OrCondition rootOr = hibernateQuery.createOr();
values.forEach(prv -> rootOr
.add(createRefCondition(hibernateQuery, Collections.singleton(prv.getOid()), prv.getRelation(), prv.getTargetType())));
return rootOr;
} else {
return createRefCondition(hibernateQuery, oids, MiscUtil.extractSingleton(relations), MiscUtil.extractSingleton(targetTypes));
}
}
private QName qualifyTypeName(QName typeName) throws QueryException {
if (typeName != null) {
try {
return context.getPrismContext().getSchemaRegistry().qualifyTypeName(typeName);
} catch (SchemaException e) {
throw new QueryException("Cannot qualify name of the target type: " + typeName + ": " + e.getMessage(), e);
}
} else {
return null;
}
}
private Condition createRefCondition(RootHibernateQuery hibernateQuery,
Collection<String> oids, QName relation, QName targetType) {
String hqlPath = hqlDataInstance.getHqlPath();
final String TARGET_OID_HQL_PROPERTY, RELATION_HQL_PROPERTY, TARGET_TYPE_HQL_PROPERTY;
if (linkDefinition.getTargetDefinition() instanceof JpaAnyReferenceDefinition) {
TARGET_OID_HQL_PROPERTY = ROExtReference.F_TARGET_OID;
RELATION_HQL_PROPERTY = ROExtReference.F_RELATION;
TARGET_TYPE_HQL_PROPERTY = ROExtReference.F_TARGET_TYPE;
} else {
TARGET_OID_HQL_PROPERTY = RObjectReference.F_TARGET_OID;
RELATION_HQL_PROPERTY = RObjectReference.F_RELATION;
TARGET_TYPE_HQL_PROPERTY = RObjectReference.F_TARGET_TYPE;
}
AndCondition conjunction = hibernateQuery.createAnd();
conjunction.add(hibernateQuery.createEqOrInOrNull(hqlDataInstance.getHqlPath() + "." + TARGET_OID_HQL_PROPERTY, oids));
if (ObjectTypeUtil.isDefaultRelation(relation)) {
// Return references without relation or with "member" relation
conjunction.add(hibernateQuery.createIn(hqlPath + "." + RELATION_HQL_PROPERTY,
Arrays.asList(RUtil.QNAME_DELIMITER, qnameToString(SchemaConstants.ORG_DEFAULT))));
} else if (QNameUtil.match(relation, PrismConstants.Q_ANY)) {
// Return all relations => no restriction
} else {
// return references with specific relation
List<String> relationsToTest = new ArrayList<>();
relationsToTest.add(qnameToString(relation));
if (QNameUtil.noNamespace(relation)) {
relationsToTest.add(qnameToString(QNameUtil.setNamespaceIfMissing(relation, SchemaConstants.NS_ORG, null)));
} else if (SchemaConstants.NS_ORG.equals(relation.getNamespaceURI())) {
relationsToTest.add(qnameToString(new QName(relation.getLocalPart())));
} else {
// non-empty non-standard NS => nothing to add
}
conjunction.add(hibernateQuery.createEqOrInOrNull(hqlPath + "." + RELATION_HQL_PROPERTY, relationsToTest));
}
if (targetType != null) {
conjunction.add(handleEqInOrNull(hibernateQuery, hqlPath + "." + TARGET_TYPE_HQL_PROPERTY,
ClassMapper.getHQLTypeForQName(targetType)));
}
return conjunction;
}
private Condition handleEqInOrNull(RootHibernateQuery hibernateQuery, String propertyName, Object value) {
if (value == null) {
return hibernateQuery.createIsNull(propertyName);
} else {
return hibernateQuery.createEq(propertyName, value);
}
}
}