/*
* Copyright 2007 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.search;
import java.io.Serializable;
import java.util.List;
import ome.conditions.ApiUsageException;
import ome.conditions.InternalException;
import ome.model.IGlobal;
import ome.model.IMutable;
import ome.model.IObject;
import ome.model.internal.Details;
import ome.model.meta.Experimenter;
import ome.model.meta.ExperimenterGroup;
import ome.services.SearchBean;
import ome.tools.hibernate.QueryBuilder;
import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.FetchMode;
import org.hibernate.HibernateException;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;
import org.hibernate.engine.TypedValue;
/**
* Serializable action used by {@link SearchBean} to generate results lazily.
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 3.0-Beta3
*/
public abstract class SearchAction implements Serializable,
ome.services.util.Executor.Work {
protected final SearchValues values = new SearchValues();
/**
* List of {@link IObject} instances which have currently been found. This
* {@link SearchAction} may want to take these values into account if
* present.
*/
protected List<IObject> chainedList;
public SearchAction(SearchValues values) {
if (values == null) {
throw new IllegalArgumentException(
"SearchValues argument must not be null");
}
this.values.copy(values);
}
/**
* Returns the {@link SearchAction} subclass as the description.
*/
public String description() {
return this.getClass().getName();
}
public SearchValues copyOfValues() {
SearchValues copy = new SearchValues();
copy.copy(this.values);
return copy;
}
/**
* Set the current list of found ids from previous searches, which should be
* chained in this search. See the documentation on each by* method in
* {@link ome.api.Search} to know how chained ids will be used, if at all.
*
* @param chainedList
* Can be null to disabled chaining.
*/
public void chainedSearch(List<IObject> chainedList) {
this.chainedList = chainedList;
}
protected void ids(Criteria criteria) {
ids(criteria, null, null);
}
protected void ids(QueryBuilder qb, String path) {
ids(null, qb, path);
}
private void ids(Criteria criteria, QueryBuilder qb, String path) {
if (values.onlyIds != null) {
if (criteria != null) {
criteria.add(Restrictions.in("id", values.onlyIds));
}
if (qb != null) {
String unique = qb.unique_alias("ids");
qb.and(String.format("%sid in (:%s) ", path, unique));
qb.paramList(unique, values.onlyIds);
}
}
}
protected void ownerOrGroup(Class cls, Criteria criteria) {
ownerOrGroup(cls, criteria, null, null);
}
protected void ownerOrGroup(Class cls, QueryBuilder qb, String path) {
ownerOrGroup(cls, null, qb, path);
}
private void ownerOrGroup(Class cls, Criteria criteria, QueryBuilder qb,
String path) {
if (!IGlobal.class.isAssignableFrom(cls)) {
OwnerOrGroup oog = new OwnerOrGroup(values.ownedBy, path);
if (oog.needed()) {
if (criteria != null) {
oog.on(criteria);
}
if (qb != null) {
oog.on(qb);
}
}
OwnerOrGroup noog = new OwnerOrGroup(values.notOwnedBy, path);
if (noog.needed()) {
if (criteria != null) {
noog.on(criteria, false);
}
if (qb != null) {
noog.on(qb, false);
}
}
}
}
protected void createdOrModified(Class cls, Criteria criteria) {
createdOrModified(cls, criteria, null, null);
}
protected void createdOrModified(Class cls, QueryBuilder qb, String path) {
createdOrModified(cls, null, qb, path);
}
private void createdOrModified(Class cls, Criteria criteria,
QueryBuilder qb, String path) {
if (!IGlobal.class.isAssignableFrom(cls)) {
if (criteria != null) {
criteria.createAlias("details.creationEvent", "create");
}
if (values.createdStart != null) {
if (criteria != null) {
criteria.add(Restrictions.gt("create.time",
values.createdStart));
}
if (qb != null) {
String ctime = qb.unique_alias("ctimestart");
qb.and(path + "details.creationEvent.time > :" + ctime);
qb.param(ctime, values.createdStart);
}
}
if (values.createdStop != null) {
if (criteria != null) {
criteria.add(Restrictions.lt("create.time",
values.createdStop));
}
if (qb != null) {
String ctime = qb.unique_alias("ctimestop");
qb.and(path + "details.creationEvent.time < :" + ctime);
qb.param(ctime, values.createdStop);
}
}
if (IMutable.class.isAssignableFrom(cls)) {
if (criteria != null) {
criteria.createAlias("details.updateEvent", "update");
}
if (values.modifiedStart != null) {
if (criteria != null) {
criteria.add(Restrictions.gt("update.time",
values.modifiedStart));
}
if (qb != null) {
String mtime = qb.unique_alias("mtimestart");
qb.and(path + "details.updateEvent.time > :" + mtime);
qb.param(mtime, values.modifiedStart);
}
}
if (values.modifiedStop != null) {
if (criteria != null) {
criteria.add(Restrictions.lt("update.time",
values.modifiedStop));
}
if (qb != null) {
String mtime = qb.unique_alias("mtimestart");
qb.and(path + "details.updateEvent.time < :" + mtime);
qb.param(mtime, values.modifiedStop);
}
}
}
}
}
protected void annotatedBy(AnnotationCriteria ann) {
annotatedBy(ann, null, null);
}
protected void annotatedBy(QueryBuilder qb, String path) {
annotatedBy(null, qb, path);
}
private void annotatedBy(AnnotationCriteria ann, QueryBuilder qb,
String path) {
OwnerOrGroup aoog = new OwnerOrGroup(values.annotatedBy, path);
if (aoog.needed()) {
if (ann != null) {
aoog.on(ann.getChild());
}
if (qb != null) {
aoog.on(qb);
}
}
OwnerOrGroup naoog = new OwnerOrGroup(values.notAnnotatedBy, path);
if (naoog.needed()) {
if (ann != null) {
naoog.on(ann.getChild(), false);
}
if (qb != null) {
naoog.on(qb, false);
}
}
}
protected void annotatedBetween(AnnotationCriteria ann) {
annotatedBetween(ann, null, null);
}
protected void annotatedBetween(QueryBuilder qb, String path) {
annotatedBetween(null, qb, path);
}
private void annotatedBetween(AnnotationCriteria ann, QueryBuilder qb,
String path) {
if (values.annotatedStart != null) {
if (ann != null) {
ann.getCreate().add(
Restrictions
.gt("anncreate.time", values.annotatedStart));
}
if (qb != null) {
String astart = qb.unique_alias("astart");
qb.and(path + "details.creationEvent.time > :" + astart);
qb.param(astart, values.annotatedStart);
}
}
if (values.annotatedStop != null) {
if (ann != null) {
ann.getCreate()
.add(
Restrictions.lt("anncreate.time",
values.annotatedStop));
}
if (qb != null) {
String astop = qb.unique_alias("astop");
qb.and(path + "details.creationEvent.time < :" + astop);
qb.param(astop, values.annotatedStop);
}
}
}
public static void notNullOrLikeOrEqual(QueryBuilder qb, String path,
Class type, Object value, boolean useLike, boolean caseSensitive) {
if (null == value) {
qb.and(path + " is null ");
} else {
String operator;
if (useLike && String.class.isAssignableFrom(type)) {
if (caseSensitive) {
operator = "like";
} else {
operator = "ilike";
}
} else {
operator = "=";
}
String alias = qb.unique_alias("main");
qb.and(path);
qb.append(operator);
qb.append(":");
qb.append(alias);
qb.appendSpace();
qb.param(alias, value);
}
}
public static Criterion notNullOrLikeOrEqual(String path, Class type,
Object value, boolean useLike, boolean caseSensitive) {
if (null == value) {
return Restrictions.isNull(path);
} else if (useLike && String.class.isAssignableFrom(type)) {
if (caseSensitive) {
return Restrictions.like(path, value);
} else {
return Restrictions.ilike(path, value);
}
} else {
return Restrictions.eq(path, value);
}
}
public static String orderByPath(String orderBy) {
String orderWithoutMode = orderBy.substring(1, orderBy.length());
return orderWithoutMode;
}
public static boolean orderByAscending(String orderBy) {
if (orderBy.startsWith("A")) {
return true;
} else if (orderBy.startsWith("D")) {
return false;
} else {
throw new InternalException(
"Unsupported orderBy mode added to values.orderBy");
}
}
}
/**
* Function-like class to assert either first the {@link Experimenter owner} of
* an object, or lacking that, the {@link ExperimenterGroup group}.
*/
class OwnerOrGroup {
String path;
long id;
OwnerOrGroup(Details d) {
this(d, "");
}
OwnerOrGroup(Details d, String prefix) {
if (prefix == null) {
prefix = "";
}
if (d != null) {
if (d.getOwner() != null) {
Long _id = d.getOwner().getId();
if (_id == null) {
throw new ApiUsageException("Id for owner cannot be null.");
}
id = _id.longValue();
path = prefix + "details.owner.id";
} else if (d.getGroup() != null) {
Long _id = d.getGroup().getId();
if (_id == null) {
throw new ApiUsageException("Id for group cannot be null.");
}
id = _id.longValue();
path = prefix + "details.group.id";
}
}
}
boolean needed() {
return path != null;
}
private void check() {
if (path == null) {
throw new ApiUsageException("Please call \"needs()\" first.");
}
}
/**
* @param criteria
* Should be not not null and should have a path of the form
* "details.owner.id" an "details.group.id".
*/
void on(Criteria criteria) {
on(criteria, true);
}
void on(Criteria criteria, boolean equals) {
check();
if (equals) {
criteria.add(Restrictions.eq(path, id));
} else {
criteria.add(Restrictions.ne(path, id));
}
}
void on(QueryBuilder qb) {
on(qb, true);
}
void on(QueryBuilder qb, boolean equals) {
check();
String op = equals ? "=" : "!=";
String unique = qb.unique_alias("owner");
qb.and(path);
qb.append(" ");
qb.append(op);
qb.append(" :");
qb.append(unique);
qb.appendSpace();
qb.param(unique, id);
}
}
/**
* Lazy loading class for {@link Criteria} instances related to annotations.
* Otherwise the null checks get absurd.
*/
class AnnotationCriteria {
final Criteria base;
final List<Class> fetchAnnotations;
Criteria annotationLinks;
Criteria annotationChild;
Criteria annCreateAlias;
final int joinType;
AnnotationCriteria(Criteria base, List<Class> fetchAnnotations) {
this.base = base;
this.fetchAnnotations = fetchAnnotations;
if (fetchAnnotations.size() > 0) {
joinType = Criteria.LEFT_JOIN;
base.setFetchMode("annotationLinks", FetchMode.JOIN);
getLinks().setFetchMode("child", FetchMode.JOIN);
} else {
joinType = Criteria.INNER_JOIN;
}
}
Criteria getLinks() {
if (annotationLinks == null) {
annotationLinks = base.createCriteria("annotationLinks", joinType);
}
return annotationLinks;
}
Criteria getChild() {
if (annotationChild == null) {
annotationChild = getLinks().createCriteria("child", joinType);
}
return annotationChild;
}
Criteria getCreate() {
if (annCreateAlias == null) {
annCreateAlias = getChild().createAlias("details.creationEvent",
"anncreate");
}
return annCreateAlias;
}
}
// Copied from http://opensource.atlassian.com/projects/hibernate/browse/HHH-746
class TypeEqualityExpression extends SimpleExpression {
private final Class classValue;
private final String classPropertyName;
public TypeEqualityExpression(String propertyName, Class value) {
super(propertyName, value, "=");
this.classPropertyName = propertyName;
this.classValue = value;
}
@Override
public TypedValue[] getTypedValues(Criteria criteria,
CriteriaQuery criteriaQuery) throws HibernateException {
return new TypedValue[] { fixDiscriminatorTypeValue(criteriaQuery
.getTypedValue(criteria, classPropertyName, classValue)) };
}
private TypedValue fixDiscriminatorTypeValue(TypedValue typedValue) {
Object value = typedValue.getValue();
// check to make sure we can reconstruct an equivalent TypedValue
if (!String.class.isInstance(value)
|| !typedValue.equals(new TypedValue(typedValue.getType(),
typedValue.getValue(), EntityMode.POJO))) {
return typedValue;
}
//
// If begins and ends with a quote,
// then replace leading and trailing apostrophes,
// otherwise return
//
String svalue = value.toString();
if (svalue.charAt(0) == '\''
&& svalue.charAt(svalue.length() - 1) == '\'') {
value = svalue.substring(1, svalue.length() - 1);
return new TypedValue(typedValue.getType(), value, EntityMode.POJO);
} else {
return typedValue;
}
}
}