
com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties Maven / Gradle / Ivy
package com.avaje.ebeaninternal.server.querydefn;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.avaje.ebean.ExpressionFactory;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.Query;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionFactory;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.expression.FilterExprPath;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.util.FilterExpressionList;
/**
* Represents the Properties of an Object Relational query.
*/
public class OrmQueryProperties implements Serializable {
private static final long serialVersionUID = -8785582703966455658L;
private String parentPath;
private String path;
private String properties;
private String trimmedProperties;
/**
* NB: -1 means no +query, 0 means use the default batch size.
*/
private int queryFetchBatch = -1;
private boolean queryFetchAll;
/**
* NB: -1 means no +lazy, 0 means use the default batch size.
*/
private int lazyFetchBatch = -1;
private FetchConfig fetchConfig;
private boolean cache;
private boolean readOnly;
/**
* Note this SHOULD be a LinkedHashSet to preserve order of the properties. This is to make using
* SqlSelect easier with predictable property/column ordering.
*/
private Set included;
/**
* Included bean joins.
*/
private Set includedBeanJoin;
/**
* Add these properties to the select so that the foreign key columns are included in the query.
*/
private Set secondaryQueryJoins;
private List secondaryChildren;
/**
* OrderBy properties that where on the main query but moved here as they relate to this (query
* join).
*/
@SuppressWarnings("rawtypes")
private OrderBy orderBy;
/**
* A filter that can be applied to the fetch of this path in the object graph.
*/
@SuppressWarnings("rawtypes")
private SpiExpressionList filterMany;
/**
* Construct with a given path (null == root path).
*/
public OrmQueryProperties(String path) {
this.path = path;
this.parentPath = SplitName.parent(path);
}
public OrmQueryProperties() {
this(null);
}
/**
* Used by query language parser.
*/
public OrmQueryProperties(String path, String properties) {
this(path);
setProperties(properties);
}
/**
* Move a OrderBy.Property from the main query to this query join.
*/
@SuppressWarnings("rawtypes")
public void addSecJoinOrderProperty(OrderBy.Property orderProp) {
if (orderBy == null) {
orderBy = new OrderBy();
}
orderBy.add(orderProp);
}
/**
* Set the Fetch configuration options for this path.
*/
public void setFetchConfig(FetchConfig fetchConfig) {
if (fetchConfig != null) {
this.fetchConfig = fetchConfig;
lazyFetchBatch = fetchConfig.getLazyBatchSize();
queryFetchBatch = fetchConfig.getQueryBatchSize();
queryFetchAll = fetchConfig.isQueryAll();
}
}
public FetchConfig getFetchConfig() {
return fetchConfig;
}
/**
* Set the comma delimited properties to fetch for this path.
*
* This can include the +query and +lazy type hints.
*
*/
public void setProperties(String properties) {
this.properties = properties;
this.trimmedProperties = properties;
parseProperties();
if (!isAllProperties()) {
Set parsed = parseIncluded(trimmedProperties);
if (parsed.contains("*")) {
this.included = null;
} else {
this.included = parsed;
}
} else {
this.included = null;
}
}
private boolean isAllProperties() {
return (trimmedProperties == null) || (trimmedProperties.length() == 0) || "*".equals(trimmedProperties);
}
/**
* Return the expressions used to filter on this path. This should be a many path to use this
* method.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public SpiExpressionList filterMany(Query rootQuery) {
if (filterMany == null) {
FilterExprPath exprPath = new FilterExprPath(path);
SpiExpressionFactory queryEf = (SpiExpressionFactory) rootQuery.getExpressionFactory();
ExpressionFactory filterEf = queryEf.createExpressionFactory();// exprPath);
filterMany = new FilterExpressionList(exprPath, filterEf, rootQuery);
// by default we need to make this a 'query join' now
queryFetchAll = true;
queryFetchBatch = 100;
lazyFetchBatch = 100;
}
return filterMany;
}
/**
* Return the filterMany expression list (can be null).
*/
public SpiExpressionList> getFilterManyTrimPath(int trimPath) {
if (filterMany == null) {
return null;
}
return filterMany.trimPath(trimPath);
}
/**
* Return the filterMany expression list (can be null).
*/
public SpiExpressionList> getFilterMany() {
return filterMany;
}
/**
* Set the filterMany expression list.
*/
public void setFilterMany(SpiExpressionList> filterMany) {
this.filterMany = filterMany;
}
/**
* Set the properties from deployment default FetchTypes.
*/
public void setDefaultProperties(String properties, Set included) {
this.properties = properties;
this.trimmedProperties = properties;
this.included = included;
}
/**
* Set the properties from a matching AutoTune tuned properties.
*/
public void setTunedProperties(OrmQueryProperties tunedProperties) {
if (tunedProperties.hasProperties()) {
this.properties = tunedProperties.properties;
this.trimmedProperties = tunedProperties.trimmedProperties;
this.included = tunedProperties.included;
this.queryFetchBatch = Math.max(queryFetchBatch, tunedProperties.queryFetchBatch);
this.lazyFetchBatch = Math.max(lazyFetchBatch, tunedProperties.lazyFetchBatch);
}
}
/**
* Define the select and joins for this query.
*/
@SuppressWarnings("unchecked")
public void configureBeanQuery(SpiQuery> query) {
if (trimmedProperties != null && trimmedProperties.length() > 0) {
query.select(trimmedProperties);
}
if (filterMany != null) {
SpiExpressionList> trimPath = filterMany.trimPath(path.length() + 1);
List underlyingList = trimPath.getUnderlyingList();
for (SpiExpression spiExpression : underlyingList) {
query.where().add(spiExpression);
}
}
if (secondaryChildren != null) {
int trimPath = path.length() + 1;
for (int i = 0; i < secondaryChildren.size(); i++) {
OrmQueryProperties p = secondaryChildren.get(i);
String path = p.getPath();
path = path.substring(trimPath);
query.fetch(path, p.getProperties(), p.getFetchConfig());
query.setFilterMany(path, p.getFilterManyTrimPath(trimPath));
}
}
if (orderBy != null) {
query.setOrder(orderBy.copyWithTrim(path));
}
}
/**
* Creates a copy of the OrmQueryProperties.
*/
public OrmQueryProperties copy() {
OrmQueryProperties copy = new OrmQueryProperties();
copy.parentPath = parentPath;
copy.path = path;
copy.properties = properties;
copy.trimmedProperties = trimmedProperties;
copy.cache = cache;
copy.readOnly = readOnly;
copy.queryFetchAll = queryFetchAll;
copy.queryFetchBatch = queryFetchBatch;
copy.lazyFetchBatch = lazyFetchBatch;
copy.filterMany = filterMany;
if (included != null) {
copy.included = new HashSet(included);
}
if (includedBeanJoin != null) {
copy.includedBeanJoin = new HashSet(includedBeanJoin);
}
return copy;
}
public boolean hasSelectClause() {
if ("*".equals(trimmedProperties)) {
// explicitly selected all properties
return true;
}
// explicitly selected some properties
return included != null;
}
public String toString() {
String s = "";
if (path != null) {
s += path + " ";
}
if (properties != null) {
s += "(" + properties + ") ";
} else if (included != null) {
s += "(" + included + ") ";
}
return s;
}
public boolean isChild(OrmQueryProperties possibleChild) {
return possibleChild.getPath().startsWith(path + ".");
}
/**
* For secondary queries add a child element.
*/
public void add(OrmQueryProperties child) {
if (secondaryChildren == null) {
secondaryChildren = new ArrayList();
}
secondaryChildren.add(child);
}
public int autoTunePlanHash() {
int hc = (path != null ? path.hashCode() : 1);
if (properties != null) {
hc = hc * 31 + properties.hashCode();
} else {
hc = hc * 31 + (included != null ? included.hashCode() : 1);
}
return hc;
}
/**
* Calculate the query plan hash.
*/
@SuppressWarnings("unchecked")
public void queryPlanHash(BeanQueryRequest> request, HashQueryPlanBuilder builder) {
builder.add(path);
if (properties != null) {
builder.add(properties);
} else {
builder.add(included);
}
builder.add(filterMany != null);
if (filterMany != null) {
filterMany.queryPlanHash(request, builder);
}
builder.add(lazyFetchBatch);
builder.add(queryFetchBatch);
builder.add(queryFetchAll);
}
public String getProperties() {
return properties;
}
/**
* Return true if this has properties.
*/
public boolean hasProperties() {
return properties != null || included != null;
}
/**
* Return true if this property is included as a bean join.
*
* If a property is included as a bean join then it should not be included as a reference/proxy to
* avoid duplication.
*
*/
public boolean isIncludedBeanJoin(String propertyName) {
return includedBeanJoin != null && includedBeanJoin.contains(propertyName);
}
/**
* Add a bean join property.
*/
public void includeBeanJoin(String propertyName) {
if (includedBeanJoin == null) {
includedBeanJoin = new HashSet();
}
includedBeanJoin.add(propertyName);
}
public boolean allProperties() {
return included == null;
}
/**
* This excludes the bean joined properties.
*
* This is because bean joins will have there own node in the SqlTree.
*
*/
public Set getSelectProperties() {
if (secondaryQueryJoins == null) {
return included;
}
LinkedHashSet temp = new LinkedHashSet(secondaryQueryJoins.size() + included.size());
temp.addAll(included);
temp.addAll(secondaryQueryJoins);
return temp;
}
public void addSecondaryQueryJoin(String property) {
if (secondaryQueryJoins == null) {
secondaryQueryJoins = new HashSet(4);
}
secondaryQueryJoins.add(property);
}
/**
* Return all the properties including the bean joins. This is the set that will be used by
* EntityBeanIntercept to determine if a property needs to be lazy loaded.
*/
public Set getAllIncludedProperties() {
if (included == null) {
return null;
}
if (includedBeanJoin == null && secondaryQueryJoins == null) {
return new LinkedHashSet(included);
}
LinkedHashSet s = new LinkedHashSet(2 * (included.size() + 5));
if (included != null) {
s.addAll(included);
}
if (includedBeanJoin != null) {
s.addAll(includedBeanJoin);
}
if (secondaryQueryJoins != null) {
s.addAll(secondaryQueryJoins);
}
return s;
}
public boolean isIncluded(String propName) {
if (includedBeanJoin != null && includedBeanJoin.contains(propName)) {
return false;
}
// all properties included
return included == null || included.contains(propName);
}
public void setQueryFetch(int batch, boolean queryFetchAll) {
this.queryFetchBatch = batch;
this.queryFetchAll = queryFetchAll;
}
public boolean isFetchJoin() {
return !isQueryFetch() && !isLazyFetch();
}
public boolean isQueryFetch() {
return queryFetchBatch > -1;
}
public int getQueryFetchBatch() {
return queryFetchBatch;
}
public boolean isQueryFetchAll() {
return queryFetchAll;
}
public boolean isLazyFetch() {
return lazyFetchBatch > -1;
}
public int getLazyFetchBatch() {
return lazyFetchBatch;
}
public boolean isReadOnly() {
return readOnly;
}
public boolean isCache() {
return cache;
}
public String getParentPath() {
return parentPath;
}
public String getPath() {
return path;
}
private void parseProperties() {
if (trimmedProperties == null) {
return;
}
int pos = trimmedProperties.indexOf("+readonly");
if (pos > -1) {
trimmedProperties = StringHelper.replaceString(trimmedProperties, "+readonly", "");
this.readOnly = true;
}
pos = trimmedProperties.indexOf("+cache");
if (pos > -1) {
trimmedProperties = StringHelper.replaceString(trimmedProperties, "+cache", "");
this.cache = true;
}
pos = trimmedProperties.indexOf("+query");
if (pos > -1) {
queryFetchBatch = parseBatchHint(pos, "+query");
}
pos = trimmedProperties.indexOf("+lazy");
if (pos > -1) {
lazyFetchBatch = parseBatchHint(pos, "+lazy");
}
trimmedProperties = trimmedProperties.trim();
while (trimmedProperties.startsWith(",")) {
trimmedProperties = trimmedProperties.substring(1).trim();
}
}
private int parseBatchHint(int pos, String option) {
int startPos = pos + option.length();
int endPos = findEndPos(startPos, trimmedProperties);
if (endPos == -1) {
trimmedProperties = StringHelper.replaceString(trimmedProperties, option, "");
return 0;
} else {
String batchParam = trimmedProperties.substring(startPos + 1, endPos);
if (endPos + 1 >= trimmedProperties.length()) {
trimmedProperties = trimmedProperties.substring(0, pos);
} else {
trimmedProperties = trimmedProperties.substring(0, pos) + trimmedProperties.substring(endPos + 1);
}
return Integer.parseInt(batchParam);
}
}
private int findEndPos(int pos, String props) {
if (pos < props.length()) {
if (props.charAt(pos) == '(') {
int endPara = props.indexOf(')', pos + 1);
if (endPara == -1) {
String m = "Error could not find ')' in " + props + " after position " + pos;
throw new RuntimeException(m);
}
return endPara;
}
}
return -1;
}
/**
* Parse the include separating by comma or semicolon.
*/
private static Set parseIncluded(String rawList) {
String[] res = rawList.split(",");
LinkedHashSet set = new LinkedHashSet(res.length + 3);
String temp;
for (int i = 0; i < res.length; i++) {
temp = res[i].trim();
if (temp.length() > 0) {
set.add(temp);
}
}
return Collections.unmodifiableSet(set);
}
public boolean isSame(OrmQueryProperties p2) {
if (included == null) {
return p2.included == null;
}
return included.equals(p2.included);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy