/* * Copyright 2007 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.services.search; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import ome.conditions.ApiUsageException; import ome.model.annotations.Annotation; import ome.model.annotations.BooleanAnnotation; import ome.model.annotations.CommentAnnotation; import ome.model.annotations.DoubleAnnotation; import ome.model.annotations.FileAnnotation; import ome.model.annotations.LongAnnotation; import ome.model.annotations.TagAnnotation; import ome.model.annotations.TermAnnotation; import ome.model.annotations.TextAnnotation; import ome.model.annotations.TimestampAnnotation; import ome.model.annotations.XmlAnnotation; import ome.model.core.OriginalFile; import ome.model.internal.Details; import ome.system.ServiceFactory; import ome.tools.hibernate.QueryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.hibernate.Session; import org.springframework.transaction.annotation.Transactional; /** * Query for {@link ome.api.Search} which uses an example {@link Annotation} * instance as the basis for comparison. Instances of the specified * {@link SearchValues#onlyTypes type} are found with a matching annotation. * * Currently only the class of the annotation and its main attribute -- * {@link TextAnnotation#textValue}, {@link FileAnnotation#file}, etc. -- are * considered. Use the other methods on {@link ome.api.Search} like * {@link ome.api.Search#onlyOwnedBy(Details)} to refine your search. * * Ignores {@link ome.api.Search#onlyAnnotatedWith(Class...)} * * @author Josh Moore, josh at glencoesoftware.com * @since 3.0-Beta3 */ public class AnnotatedWith extends SearchAction { private static final Logger log = LoggerFactory.getLogger(AnnotatedWith.class); private static final long serialVersionUID = 1L; private final Annotation[] annotation; private final String[] path; private final Class[] annCls; private final Class[] type; private final Object[] value; private final boolean[] fetch; /** * copy of fetchAnnotations list, so that items which are already fetched * via {@link #fetch} are not fetched again. */ private final List<Class> fetchAnnotationsCopy = new ArrayList<Class>(); private final boolean useNamespace; private final boolean useLike; private final Class cls; public AnnotatedWith(SearchValues values, Annotation[] annotation, boolean useNamespace, boolean useLike) { super(values); this.annotation = annotation; this.useNamespace = useNamespace; this.useLike = useLike; // Note: It should be possible to set cls = IAnnotated.class, but // there must be a way to then remove the conditionals for // IGlobals below. if (values.onlyTypes == null || values.onlyTypes.size() != 1) { throw new ApiUsageException( "Searches by annotated with are currently limited " + "to a single type\n" + "Plese use Search.onlyType()"); } else { cls = values.onlyTypes.get(0); } if (annotation == null || annotation.length == 0) { throw new ApiUsageException("Must specify at least one annotation."); } this.path = new String[annotation.length]; this.annCls = new Class[annotation.length]; this.type = new Class[annotation.length]; this.value = new Object[annotation.length]; this.fetch = new boolean[annotation.length]; this.fetchAnnotationsCopy.addAll(values.fetchAnnotations); for (int i = 0; i < annotation.length; i++) { if (annotation[i] instanceof TextAnnotation) { // FIXME This should be unneccessary. See ticket:976 if (annotation[i] instanceof CommentAnnotation) { annCls[i] = CommentAnnotation.class; } else if (annotation[i] instanceof TagAnnotation) { annCls[i] = TagAnnotation.class; } else if (annotation[i] instanceof TermAnnotation) { annCls[i] = TermAnnotation.class; } else if (annotation[i] instanceof XmlAnnotation) { annCls[i] = XmlAnnotation.class; } else { annCls[i] = TextAnnotation.class; } type[i] = String.class; path[i] = "textValue"; value[i] = ((TextAnnotation) annotation[i]).getTextValue(); } else if (annotation[i] instanceof BooleanAnnotation) { annCls[i] = BooleanAnnotation.class; type[i] = Boolean.class; path[i] = "boolValue"; value[i] = ((BooleanAnnotation) annotation[i]).getBoolValue(); } else if (annotation[i] instanceof TimestampAnnotation) { annCls[i] = TimestampAnnotation.class; type[i] = Timestamp.class; path[i] = "timeValue"; value[i] = ((TimestampAnnotation) annotation[i]).getTimeValue(); } else if (annotation[i] instanceof FileAnnotation) { annCls[i] = FileAnnotation.class; type[i] = OriginalFile.class; path[i] = "file"; value[i] = ((FileAnnotation) annotation[i]).getFile(); } else if (annotation[i] instanceof DoubleAnnotation) { annCls[i] = DoubleAnnotation.class; type[i] = Double.class; path[i] = "doubleValue"; value[i] = ((DoubleAnnotation) annotation[i]).getDoubleValue(); } else if (annotation[i] instanceof LongAnnotation) { annCls[i] = LongAnnotation.class; type[i] = Long.class; path[i] = "longValue"; value[i] = ((LongAnnotation) annotation[i]).getLongValue(); } else { throw new ApiUsageException("Unsupported annotation type:" + annotation); } // If we have an example of the given annoation, then we can // fetch it directly and don't need to use the fetchAnnotationsCopy // collection. for (Class ac : values.fetchAnnotations) { if (annCls[i].isAssignableFrom(ac)) { fetch[i] = true; fetchAnnotationsCopy.remove(ac); } } // On the other hand, if the value is null, then we might as well // treat this as a fetch request and search only on Ann.class =. // See the guards around the Joins section and the the call to // notNullOrLikeOrEqual below. if (value[i] == null) { fetchAnnotationsCopy.add(annCls[i]); } } } @Transactional(readOnly = true) public Object doWork(Session session, ServiceFactory sf) { String[] link = new String[annotation.length]; String[] ann = new String[annotation.length]; QueryBuilder qb = new QueryBuilder(); qb.select("this"); qb.from(cls.getName(), "this"); // Joins for (int i = 0; i < ann.length; i++) { if (value[i] != null) { link[i] = qb.unique_alias("link"); ann[i] = link[i] + "_child"; qb.join("this.annotationLinks", link[i], false, fetch[i]); qb.join(link[i] + ".child", ann[i], false, fetch[i]); } } // fetch annotations for (int i = 0; i < fetchAnnotationsCopy.size(); i++) { qb.join("this.annotationLinks", "fetchannlink" + i, false, true); qb.join("fetchannlink" + i + ".child", "fetchannchild" + i, false, true); } qb.where(); for (int i = 0; i < fetchAnnotationsCopy.size(); i++) { qb.and("fetchannchild" + i + ".class = " + fetchAnnotationsCopy.get(i).getSimpleName()); } ids(qb, "this."); ownerOrGroup(cls, qb, "this."); createdOrModified(cls, qb, "this."); for (int j = 0; j < annotation.length; j++) { // Main criteria if (useNamespace) { notNullOrLikeOrEqual(qb, ann + ".ns", type[j], annotation[j] .getNs(), useLike, values.caseSensitive); } // If the value of the annotation is null, we assume that we are not // actually searching for null annotations (whose nullability is // actually a by-product of polymorphism), instead we assume the // null acts like a wildcard, in which case this search has been // added to the fetchAnnotationsCopy collection above. if (value[j] != null) { notNullOrLikeOrEqual(qb, ann[j] + "." + path[j], type[j], value[j], useLike, values.caseSensitive); } annotatedBetween(qb, ann[j] + "."); annotatedBy(qb, ann[j] + "."); } // orderBy for (String orderBy : values.orderBy) { String orderByPath = orderByPath(orderBy); boolean ascending = orderByAscending(orderBy); qb.order("this." + orderByPath, ascending); } log.debug(qb.toString()); return qb.query(session).list(); } }