blob: 7c0a0c0d80ca0e3efec68c9ffaaa9b0088415724 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.ofbiz.entity.finder;
import static org.ofbiz.base.util.UtilGenerics.cast;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.ObjectType;
import org.ofbiz.base.util.StringUtil;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.UtilXml;
import org.ofbiz.base.util.collections.FlexibleMapAccessor;
import org.ofbiz.base.util.string.FlexibleStringExpander;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.condition.EntityComparisonOperator;
import org.ofbiz.entity.condition.EntityCondition;
import org.ofbiz.entity.condition.EntityFunction;
import org.ofbiz.entity.condition.EntityJoinOperator;
import org.ofbiz.entity.condition.EntityOperator;
import org.ofbiz.entity.model.ModelEntity;
import org.ofbiz.entity.model.ModelFieldTypeReader;
import org.ofbiz.entity.util.EntityListIterator;
import org.w3c.dom.Element;
/**
* Uses the delegator to find entity values by a condition
*
*/
public final class EntityFinderUtil {
public static final String module = EntityFinderUtil.class.getName();
private EntityFinderUtil () {}
public static Map<FlexibleMapAccessor<Object>, Object> makeFieldMap(Element element) {
Map<FlexibleMapAccessor<Object>, Object> fieldMap = null;
List<? extends Element> fieldMapElementList = UtilXml.childElementList(element, "field-map");
if (fieldMapElementList.size() > 0) {
fieldMap = new HashMap<FlexibleMapAccessor<Object>, Object>(fieldMapElementList.size());
for (Element fieldMapElement: fieldMapElementList) {
// set the env-name for each field-name, noting that if no field-name is specified it defaults to the env-name
String fieldName = fieldMapElement.getAttribute("field-name");
String envName = fieldMapElement.getAttribute("from-field");
if (envName.isEmpty()) {
envName = fieldMapElement.getAttribute("env-name");
}
String value = fieldMapElement.getAttribute("value");
if (fieldName.isEmpty()) {
// no fieldName, use envName for both
fieldMap.put(FlexibleMapAccessor.getInstance(envName), FlexibleMapAccessor.getInstance(envName));
} else {
if (!value.isEmpty()) {
fieldMap.put(FlexibleMapAccessor.getInstance(fieldName), FlexibleStringExpander.getInstance(value));
} else {
// at this point we have a fieldName and no value, do we have a envName?
if (!envName.isEmpty()) {
fieldMap.put(FlexibleMapAccessor.getInstance(fieldName), FlexibleMapAccessor.getInstance(envName));
} else {
// no envName, use fieldName for both
fieldMap.put(FlexibleMapAccessor.getInstance(fieldName), FlexibleMapAccessor.getInstance(fieldName));
}
}
}
}
}
return fieldMap;
}
public static void expandFieldMapToContext(Map<FlexibleMapAccessor<Object>, Object> fieldMap, Map<String, Object> context, Map<String, Object> outContext) {
//Debug.logInfo("fieldMap: " + fieldMap, module);
if (fieldMap != null) {
for (Map.Entry<FlexibleMapAccessor<Object>, Object> entry: fieldMap.entrySet()) {
FlexibleMapAccessor<Object> serviceContextFieldAcsr = entry.getKey();
Object valueSrc = entry.getValue();
if (valueSrc instanceof FlexibleMapAccessor<?>) {
FlexibleMapAccessor<Object> contextEnvAcsr = cast(valueSrc);
serviceContextFieldAcsr.put(outContext, contextEnvAcsr.get(context));
} else if (valueSrc instanceof FlexibleStringExpander) {
FlexibleStringExpander valueExdr = (FlexibleStringExpander) valueSrc;
serviceContextFieldAcsr.put(outContext, valueExdr.expandString(context));
} else {
// hmmmm...
}
}
}
}
public static List<FlexibleStringExpander> makeSelectFieldExpanderList(Element element) {
List<FlexibleStringExpander> selectFieldExpanderList = null;
List<? extends Element> selectFieldElementList = UtilXml.childElementList(element, "select-field");
if (selectFieldElementList.size() > 0) {
selectFieldExpanderList = new ArrayList<FlexibleStringExpander>(selectFieldElementList.size());
for (Element selectFieldElement: selectFieldElementList) {
selectFieldExpanderList.add(FlexibleStringExpander.getInstance(selectFieldElement.getAttribute("field-name")));
}
}
return selectFieldExpanderList;
}
public static Set<String> makeFieldsToSelect(List<FlexibleStringExpander> selectFieldExpanderList, Map<String, Object> context) {
Set<String> fieldsToSelect = null;
if (UtilValidate.isNotEmpty(selectFieldExpanderList)) {
fieldsToSelect = new HashSet<String>();
for (FlexibleStringExpander selectFieldExpander: selectFieldExpanderList) {
fieldsToSelect.add(selectFieldExpander.expandString(context));
}
}
return fieldsToSelect;
}
public static List<String> makeOrderByFieldList(List<FlexibleStringExpander> orderByExpanderList, Map<String, Object> context) {
List<String> orderByFields = null;
if (UtilValidate.isNotEmpty(orderByExpanderList)) {
orderByFields = new ArrayList<String>(orderByExpanderList.size());
for (FlexibleStringExpander orderByExpander: orderByExpanderList) {
orderByFields.add(orderByExpander.expandString(context));
}
}
return orderByFields;
}
public static interface Condition extends Serializable {
public EntityCondition createCondition(Map<String, ? extends Object> context, ModelEntity modelEntity, ModelFieldTypeReader modelFieldTypeReader);
}
@SuppressWarnings("serial")
public static final class ConditionExpr implements Condition {
private final String fieldName;
private final EntityOperator<?,?,?> operator;
private final FlexibleMapAccessor<Object> envNameAcsr;
private final FlexibleStringExpander valueExdr;
private final FlexibleStringExpander ignoreExdr;
private final boolean ignoreIfNull;
private final boolean ignoreIfEmpty;
private final boolean ignoreCase;
public ConditionExpr(Element conditionExprElement) {
String fieldNameAttribute = conditionExprElement.getAttribute("field-name");
if (fieldNameAttribute.isEmpty()) {
fieldNameAttribute = conditionExprElement.getAttribute("name");
}
this.fieldName = fieldNameAttribute;
String operatorAttribute = conditionExprElement.getAttribute("operator");
if (operatorAttribute.isEmpty()) {
operatorAttribute = "equals";
}
this.operator = EntityOperator.lookup(operatorAttribute);
if (this.operator == null) {
throw new IllegalArgumentException("Could not find an entity operator for the name: " + operatorAttribute);
}
String fromFieldAttribute = conditionExprElement.getAttribute("from-field");
if (fromFieldAttribute.isEmpty()) {
fromFieldAttribute = conditionExprElement.getAttribute("env-name");
}
this.envNameAcsr = FlexibleMapAccessor.getInstance(fromFieldAttribute);
this.valueExdr = FlexibleStringExpander.getInstance(conditionExprElement.getAttribute("value"));
this.ignoreIfNull = "true".equals(conditionExprElement.getAttribute("ignore-if-null"));
this.ignoreIfEmpty = "true".equals(conditionExprElement.getAttribute("ignore-if-empty"));
this.ignoreCase = "true".equals(conditionExprElement.getAttribute("ignore-case"));
this.ignoreExdr = FlexibleStringExpander.getInstance(conditionExprElement.getAttribute("ignore"));
}
public EntityCondition createCondition(Map<String, ? extends Object> context, ModelEntity modelEntity, ModelFieldTypeReader modelFieldTypeReader) {
if ("true".equals(this.ignoreExdr.expandString(context))) {
return null;
}
if (modelEntity.getField(fieldName) == null) {
throw new IllegalArgumentException("Error in Entity Find: could not find field [" + fieldName + "] in entity with name [" + modelEntity.getEntityName() + "]");
}
Object value = envNameAcsr.get(context);
if (value == null && !valueExdr.isEmpty()) {
value = valueExdr.expandString(context);
}
if (this.ignoreIfNull && value == null) {
return null;
}
// If IN or BETWEEN operator, see if value is a literal list and split it
if ((operator.equals(EntityOperator.IN) || operator.equals(EntityOperator.BETWEEN) || operator.equals(EntityOperator.NOT_IN))
&& value instanceof String) {
String delim = null;
if (((String)value).indexOf("|") >= 0) {
delim = "|";
} else if (((String)value).indexOf(",") >= 0) {
delim = ",";
}
if (delim != null) {
value = StringUtil.split((String)value, delim);
}
}
// don't convert the field to the desired type if this is an IN or BETWEEN operator and we have a Collection
if (!((operator.equals(EntityOperator.IN) || operator.equals(EntityOperator.BETWEEN) || operator.equals(EntityOperator.NOT_IN))
&& value instanceof Collection<?>)) {
// now to a type conversion for the target fieldName
value = modelEntity.convertFieldValue(modelEntity.getField(fieldName), value, modelFieldTypeReader, context);
}
if (Debug.verboseOn()) Debug.logVerbose("Got value for fieldName [" + fieldName + "]: " + value, module);
if (this.ignoreIfEmpty && ObjectType.isEmpty(value)) {
return null;
}
if (operator == EntityOperator.NOT_EQUAL && value != null) {
// since some databases don't consider nulls in != comparisons, explicitly include them
// this makes more sense logically, but if anyone ever needs it to not behave this way we should add an "or-null" attribute that is true by default
if (ignoreCase) {
return EntityCondition.makeCondition(
EntityCondition.makeCondition(EntityFunction.UPPER_FIELD(fieldName), UtilGenerics.<EntityComparisonOperator<?,?>>cast(operator), EntityFunction.UPPER(value)),
EntityOperator.OR,
EntityCondition.makeCondition(fieldName, EntityOperator.EQUALS, null));
} else {
return EntityCondition.makeCondition(
EntityCondition.makeCondition(fieldName, UtilGenerics.<EntityComparisonOperator<?,?>>cast(operator), value),
EntityOperator.OR,
EntityCondition.makeCondition(fieldName, EntityOperator.EQUALS, null));
}
} else {
if (ignoreCase) {
// use the stuff to upper case both sides
return EntityCondition.makeCondition(EntityFunction.UPPER_FIELD(fieldName), UtilGenerics.<EntityComparisonOperator<?,?>>cast(operator), EntityFunction.UPPER(value));
} else {
return EntityCondition.makeCondition(fieldName, UtilGenerics.<EntityComparisonOperator<?,?>>cast(operator), value);
}
}
}
}
@SuppressWarnings("serial")
public static final class ConditionList implements Condition {
private final List<Condition> conditionList;
private final EntityOperator<?,?,?> operator;
public ConditionList(Element conditionListElement) {
String operatorAttribute = conditionListElement.getAttribute("combine");
if (operatorAttribute.isEmpty()) {
operatorAttribute = "and";
}
this.operator = EntityOperator.lookup(operatorAttribute);
if (this.operator == null) {
throw new IllegalArgumentException("Could not find an entity operator for the name: " + operatorAttribute);
}
List<? extends Element> subElements = UtilXml.childElementList(conditionListElement);
if (subElements.isEmpty()) {
this.conditionList = null;
} else {
List<Condition> conditionList = new ArrayList<Condition>(subElements.size());
for (Element subElement : subElements) {
if ("condition-expr".equals(subElement.getNodeName())) {
conditionList.add(new ConditionExpr(subElement));
} else if ("condition-list".equals(subElement.getNodeName())) {
conditionList.add(new ConditionList(subElement));
} else if ("condition-object".equals(subElement.getNodeName())) {
conditionList.add(new ConditionObject(subElement));
} else {
throw new IllegalArgumentException("Invalid element with name [" + subElement.getNodeName() + "] found under a condition-list element.");
}
}
this.conditionList = Collections.unmodifiableList(conditionList);
}
}
public EntityCondition createCondition(Map<String, ? extends Object> context, ModelEntity modelEntity, ModelFieldTypeReader modelFieldTypeReader) {
if (this.conditionList == null) {
return null;
}
if (this.conditionList.size() == 1) {
Condition condition = this.conditionList.get(0);
return condition.createCondition(context, modelEntity, modelFieldTypeReader);
}
List<EntityCondition> entityConditionList = new ArrayList<EntityCondition>(this.conditionList.size());
for (Condition curCondition: this.conditionList) {
EntityCondition econd = curCondition.createCondition(context, modelEntity, modelFieldTypeReader);
if (econd != null) {
entityConditionList.add(econd);
}
}
return EntityCondition.makeCondition(entityConditionList, UtilGenerics.<EntityJoinOperator>cast(operator));
}
}
@SuppressWarnings("serial")
public static final class ConditionObject implements Condition {
private final FlexibleMapAccessor<Object> fieldNameAcsr;
public ConditionObject(Element conditionExprElement) {
String fieldNameAttribute = conditionExprElement.getAttribute("field");
if (fieldNameAttribute.isEmpty()) {
fieldNameAttribute = conditionExprElement.getAttribute("field-name");
}
this.fieldNameAcsr = FlexibleMapAccessor.getInstance(fieldNameAttribute);
}
public EntityCondition createCondition(Map<String, ? extends Object> context, ModelEntity modelEntity, ModelFieldTypeReader modelFieldTypeReader) {
return (EntityCondition) fieldNameAcsr.get(context);
}
}
public static interface OutputHandler extends Serializable {
public void handleOutput(EntityListIterator eli, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr);
public void handleOutput(List<GenericValue> results, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr);
}
@SuppressWarnings("serial")
public static class LimitRange implements OutputHandler {
FlexibleStringExpander startExdr;
FlexibleStringExpander sizeExdr;
public LimitRange(Element limitRangeElement) {
this.startExdr = FlexibleStringExpander.getInstance(limitRangeElement.getAttribute("start"));
this.sizeExdr = FlexibleStringExpander.getInstance(limitRangeElement.getAttribute("size"));
}
int getStart(Map<String, Object> context) {
String startStr = this.startExdr.expandString(context);
try {
return Integer.parseInt(startStr);
} catch (NumberFormatException e) {
String errMsg = "The limit-range start number \"" + startStr + "\" was not valid: " + e.toString();
Debug.logError(e, errMsg, module);
throw new IllegalArgumentException(errMsg);
}
}
int getSize(Map<String, Object> context) {
String sizeStr = this.sizeExdr.expandString(context);
try {
return Integer.parseInt(sizeStr);
} catch (NumberFormatException e) {
String errMsg = "The limit-range size number \"" + sizeStr + "\" was not valid: " + e.toString();
Debug.logError(e, errMsg, module);
throw new IllegalArgumentException(errMsg);
}
}
public void handleOutput(EntityListIterator eli, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr) {
int start = getStart(context) + 1; // ELI index is one-based.
int size = getSize(context);
try {
listAcsr.put(context, eli.getPartialList(start, size));
eli.close();
} catch (GenericEntityException e) {
String errMsg = "Error getting partial list in limit-range with start=" + start + " and size=" + size + ": " + e.toString();
Debug.logError(e, errMsg, module);
throw new IllegalArgumentException(errMsg);
}
}
public void handleOutput(List<GenericValue> results, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr) {
List<GenericValue> result = null;
int start = getStart(context);
if (start < results.size()) {
int size = getSize(context);
int end = start + size;
if (end > results.size()) {
end = results.size();
}
result = results.subList(start, end);
} else {
result = new LinkedList<GenericValue>();
}
listAcsr.put(context, result);
}
}
@SuppressWarnings("serial")
public static class LimitView implements OutputHandler {
FlexibleStringExpander viewIndexExdr;
FlexibleStringExpander viewSizeExdr;
public LimitView(Element limitViewElement) {
this.viewIndexExdr = FlexibleStringExpander.getInstance(limitViewElement.getAttribute("view-index"));
this.viewSizeExdr = FlexibleStringExpander.getInstance(limitViewElement.getAttribute("view-size"));
}
int getIndex(Map<String, Object> context) {
String viewIndexStr = this.viewIndexExdr.expandString(context);
try {
return Integer.parseInt(viewIndexStr);
} catch (NumberFormatException e) {
String errMsg = "The limit-view view-index number \"" + viewIndexStr + "\" was not valid: " + e.toString();
Debug.logError(e, errMsg, module);
throw new IllegalArgumentException(errMsg);
}
}
int getSize(Map<String, Object> context) {
String viewSizeStr = this.viewSizeExdr.expandString(context);
try {
return Integer.parseInt(viewSizeStr);
} catch (NumberFormatException e) {
String errMsg = "The limit-view view-size number \"" + viewSizeStr + "\" was not valid: " + e.toString();
Debug.logError(e, errMsg, module);
throw new IllegalArgumentException(errMsg);
}
}
public void handleOutput(EntityListIterator eli, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr) {
int index = this.getIndex(context);
int size = this.getSize(context);
try {
listAcsr.put(context, eli.getPartialList(((index - 1) * size) + 1, size)); // ELI index is one-based.
eli.close();
} catch (GenericEntityException e) {
String errMsg = "Error getting partial list in limit-view with index=" + index + " and size=" + size + ": " + e.toString();
Debug.logError(e, errMsg, module);
throw new IllegalArgumentException(errMsg);
}
}
public void handleOutput(List<GenericValue> results, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr) {
List<GenericValue> result = null;
int index = this.getIndex(context);
int size = this.getSize(context);
int begin = (index - 1) * size;
if (begin < results.size()) {
int end = begin + size;
if (end > results.size()) {
end = results.size();
}
result = results.subList(begin, end);
} else {
result = new LinkedList<GenericValue>();
}
listAcsr.put(context, result);
}
}
@SuppressWarnings("serial")
public static class UseIterator implements OutputHandler {
public UseIterator(Element useIteratorElement) {
// no parameters, nothing to do
}
public void handleOutput(EntityListIterator eli, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr) {
listAcsr.put(context, eli);
}
public void handleOutput(List<GenericValue> results, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr) {
throw new IllegalArgumentException("Cannot handle output with use-iterator when the query is cached, or the result in general is not an EntityListIterator");
}
}
@SuppressWarnings("serial")
public static class GetAll implements OutputHandler {
public GetAll() {
// no parameters, nothing to do
}
public void handleOutput(EntityListIterator eli, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr) {
try {
listAcsr.put(context, eli.getCompleteList());
eli.close();
} catch (GenericEntityException e) {
String errorMsg = "Error getting list from EntityListIterator: " + e.toString();
Debug.logError(e, errorMsg, module);
throw new IllegalArgumentException(errorMsg);
}
}
public void handleOutput(List<GenericValue> results, Map<String, Object> context, FlexibleMapAccessor<Object> listAcsr) {
listAcsr.put(context, results);
}
}
}