/*
* 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.polystring.PolyString;
import com.evolveum.midpoint.prism.query.ComparativeFilter;
import com.evolveum.midpoint.prism.query.EqualFilter;
import com.evolveum.midpoint.prism.query.PropertyValueFilter;
import com.evolveum.midpoint.prism.query.ValueFilter;
import com.evolveum.midpoint.repo.sql.data.common.enums.SchemaEnum;
import com.evolveum.midpoint.repo.sql.query.QueryException;
import com.evolveum.midpoint.repo.sql.query2.resolution.HqlDataInstance;
import com.evolveum.midpoint.repo.sql.query2.InterpretationContext;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaEntityDefinition;
import com.evolveum.midpoint.repo.sql.query2.definition.JpaPropertyDefinition;
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.Condition;
import com.evolveum.midpoint.repo.sql.query2.hqm.condition.OrCondition;
import com.evolveum.midpoint.repo.sql.util.RUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import com.evolveum.prism.xml.ns._public.types_3.RawType;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.Validate;
import javax.xml.namespace.QName;
/**
* @author lazyman
* @author mederly
*/
public class PropertyRestriction extends ItemValueRestriction<PropertyValueFilter> {
private static final Trace LOGGER = TraceManager.getTrace(PropertyRestriction.class);
private JpaLinkDefinition<JpaPropertyDefinition> linkDefinition;
public PropertyRestriction(InterpretationContext context, PropertyValueFilter filter, JpaEntityDefinition baseEntityDefinition,
Restriction parent, JpaLinkDefinition<JpaPropertyDefinition> linkDefinition) {
super(context, filter, baseEntityDefinition, parent);
Validate.notNull(linkDefinition, "linkDefinition");
this.linkDefinition = linkDefinition;
}
@Override
public Condition interpretInternal() throws QueryException {
if (linkDefinition.getTargetDefinition().isLob()) {
throw new QueryException("Can't query based on clob property value '" + linkDefinition + "'.");
}
String propertyValuePath = getHqlDataInstance().getHqlPath();
if (filter.getRightHandSidePath() != null) {
return createPropertyVsPropertyCondition(propertyValuePath);
} else {
Object value = getValueFromFilter(filter);
Condition condition = createPropertyVsConstantCondition(propertyValuePath, value, filter);
return addIsNotNullIfNecessary(condition, propertyValuePath);
}
}
protected Condition createPropertyVsPropertyCondition(String leftPropertyValuePath) throws QueryException {
HqlDataInstance rightItem = getItemPathResolver().resolveItemPath(filter.getRightHandSidePath(),
filter.getRightHandSideDefinition(), getBaseHqlEntityForChildren(), true);
String rightHqlPath = rightItem.getHqlPath();
RootHibernateQuery hibernateQuery = context.getHibernateQuery();
// TODO take data types into account, e.g.
// PolyString vs PolyString
// PolyString vs String (and vice versa)
// non-string extension items vs non-string static items
//
// And also take matching rules into account as well.
if (filter instanceof EqualFilter) {
// left = right OR (left IS NULL AND right IS NULL)
Condition condition = hibernateQuery.createCompareXY(leftPropertyValuePath, rightHqlPath, "=", false);
OrCondition orCondition = hibernateQuery.createOr(
condition,
hibernateQuery.createAnd(
hibernateQuery.createIsNull(leftPropertyValuePath),
hibernateQuery.createIsNull(rightHqlPath)
)
);
return orCondition;
} else if (filter instanceof ComparativeFilter) {
ItemRestrictionOperation operation = findOperationForFilter(filter);
Condition condition = hibernateQuery.createCompareXY(leftPropertyValuePath, rightHqlPath, operation.symbol(), false);
return condition;
} else {
throw new QueryException("Right-side ItemPath is supported currently only for EqualFilter or ComparativeFilter, not for " + filter);
}
}
protected Object getValueFromFilter(ValueFilter filter) throws QueryException {
JpaPropertyDefinition def = linkDefinition.getTargetDefinition();
Object value;
if (filter instanceof PropertyValueFilter) {
value = getValue((PropertyValueFilter) filter);
} else {
throw new QueryException("Unknown filter '" + filter + "', can't get value from it.");
}
value = checkValueType(value, filter);
if (def.isEnumerated()) {
value = getRepoEnumValue((Enum) value, def.getJpaClass());
}
return value;
}
private Object checkValueType(Object value, ValueFilter filter) throws QueryException {
Class expectedType = linkDefinition.getTargetDefinition().getJaxbClass();
if (expectedType == null || value == null) {
return value; // nothing to check here
}
if (expectedType.isPrimitive()) {
expectedType = ClassUtils.primitiveToWrapper(expectedType);
}
//todo remove after some time [lazyman]
//attempt to fix value type for polystring (if it was string in filter we create polystring from it)
if (PolyString.class.equals(expectedType) && (value instanceof String)) {
LOGGER.debug("Trying to query PolyString value but filter contains String '{}'.", filter);
String orig = (String) value;
value = new PolyString(orig, context.getPrismContext().getDefaultPolyStringNormalizer().normalize(orig));
}
//attempt to fix value type for polystring (if it was polystringtype in filter we create polystring from it)
if (PolyString.class.equals(expectedType) && (value instanceof PolyStringType)) {
LOGGER.debug("Trying to query PolyString value but filter contains PolyStringType '{}'.", filter);
PolyStringType type = (PolyStringType) value;
value = new PolyString(type.getOrig(), type.getNorm());
}
if (String.class.equals(expectedType) && (value instanceof QName)) {
//eg. shadow/objectClass
value = RUtil.qnameToString((QName) value);
}
if (value instanceof RawType) { // MID-3850: but it's quite a workaround. Maybe we should treat RawType's earlier than this.
try {
return ((RawType) value).getParsedRealValue(expectedType);
} catch (SchemaException e) {
throw new QueryException("Couldn't parse value " + value + " as " + expectedType + ": " + e.getMessage(), e);
}
}
if (!expectedType.isAssignableFrom(value.getClass())) {
throw new QueryException("Value should be type of '" + expectedType + "' but it's '"
+ value.getClass() + "', filter '" + filter + "'.");
}
return value;
}
private Enum getRepoEnumValue(Enum schemaValue, Class repoType) throws QueryException {
if (schemaValue == null) {
return null;
}
if (SchemaEnum.class.isAssignableFrom(repoType)) {
return (Enum) RUtil.getRepoEnumValue(schemaValue, repoType);
}
Object[] constants = repoType.getEnumConstants();
for (Object constant : constants) {
Enum e = (Enum) constant;
if (e.name().equals(schemaValue.name())) {
return e;
}
}
throw new QueryException("Unknown enum value '" + schemaValue + "', which is type of '"
+ schemaValue.getClass() + "'.");
}
}