/*
* $Id$
*
* Copyright 2008 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.api;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
import ome.annotations.NotNull;
import ome.conditions.ApiUsageException;
import ome.model.IObject;
import ome.model.annotations.Annotation;
import ome.model.core.OriginalFile;
import ome.model.internal.Details;
import ome.parameters.Parameters;
import ome.util.search.LuceneQueryBuilder;
import org.hibernate.search.ProjectionConstants;
/**
* Central search interface, allowing Web2.0 style queries. Each {@link Search}
* instance keeps up with several queries and lazily-loads the results as
* {@link #hasNext()}, {@link #next()} and {@link #results()} are called. These
* queries are created by the "by*" methods.
*
* Each instance also has a number of settings which can all be changed from
* their defaults via accessors, e.g.{@link #setBatchSize(int)} or
* {@link #setCaseSentivice(boolean)}.
*
* The only methods which are required for the proper functioning of a
* {@link Search} instance are:
* <ul>
* <li>{@link #onlyType(Class)}, {@link #onlyTypes(Class...)} OR
* {@link #allTypes()}</li>
* <li>Any by* method to create a query</li>
* </ul>
* Use of the {@link #allTypes()} method is discouraged, since it is possibly
* very resource intensive, which is why any attempt to receive results without
* specifically setting types or allowing all is prohibited.
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 3.0-Beta3
* @see ome.api.IQuery
*/
public interface Search extends ome.api.StatefulServiceInterface,
java.util.Iterator<IObject> {
// Constants ~
// =========================================================================
/**
* {@link String} constant used to look up the score value of Lucene queries
* returned by this interface. Not all queries will fill this value.
*/
public final static String SCORE = ProjectionConstants.SCORE;
/**
* Default {@link #getBatchSize() batch size}
*/
public final static int DEFAULT_BATCH_SIZE = 1000;
/**
* Default {@link #isMergedBatches() merged-batches}
*/
public final static boolean DEFAULT_MERGED_BATCHES = false;
/**
* Default {@link #isCaseSensitive() case sensitivity}
*/
public final static boolean DEFAULT_CASE_SENSITIVTY = false;
/**
* Default {@link #isUseProjections() use-projections}
*/
public final static boolean DEFAULT_USE_PROJECTIONS = false;
/**
* Default {@link #isReturnUnloaded() return-unloaded}
*/
public final static boolean DEFAULT_RETURN_UNLOADED = false;
/**
* Default {@link #isAllowLeadingWildcard() leading-wildcard setting}
*/
public final static boolean ALLOW_LEADING_WILDCARD = false;
/**
* Treat date range as import date
*/
public static final String DATE_TYPE_IMPORT = LuceneQueryBuilder.DATE_IMPORT;
/**
* Treat date range as acquisition date
*/
public static final String DATE_TYPE_ACQUISITION = LuceneQueryBuilder.DATE_ACQUISITION;
// Non-Query State ~
// =========================================================================
/**
* Returns the number of active queries. This means that
* <code>activeQueries</code> gives the minimum number of remaining calls
* to {@link #results()} when batches are not
* {@link #isMergedBatches() merged}.
*
* @return number of active queries
*/
int activeQueries();
/**
* Sets the maximum number of results that will be returned by one call to
* {@link #results()}. If batches are not {@link #isMergedBatches() merged},
* then results may often be less than the batch size. If batches are
* {@link #isMergedBatches() merged}, then only the last call to
* {@link #results()} can be less than batch size.
*
* Note: some query types may not support batching at the query level, and
* all instances must then be loaded into memory simultaneously.
*
* @param size
* maximum number of results per call to {@link #results()}
*/
void setBatchSize(int size);
/**
* Returns the current batch size. If {@link #setBatchSize(int)} has not
* been called, the {@link #DEFAULT_BATCH_SIZE default value} will be in
* effect.
*
* @return maximum number of results per call to {@link #results()}
* @see #DEFAULT_BATCH_SIZE
*/
int getBatchSize();
/**
* Set whether or not results from two separate queries can be returned in
* the same call to {@link #results()}.
*/
void setMergedBatches(boolean merge);
/**
* Returns the current merged-batches setting. If
* {@link #setMergedBatches(boolean)} has not been called, the
* {@link #DEFAULT_MERGED_BATCHES default value} will be in effect.
*/
boolean isMergedBatches();
/**
* Sets the case sensitivity on all queries where case-sensitivity is
* supported.
*/
void setCaseSentivice(boolean caseSensitive);
/**
* Returns the current case sensitivity setting. If
* {@link #setCaseSentivice(boolean)} has not been called, the
* {@link #DEFAULT_CASE_SENSITIVTY default value} will be in effect.
*/
boolean isCaseSensitive();
/**
* Determines if Lucene queries should not hit the database. Instead all
* values which are stored in the index will be loaded into the object,
* which includes the id. However, the entity will not be marked unloaded
* and therefore it is especially important to not allow a
* projection-instance to be saved back to the server. This can result in
* DATA LOSS.
*/
void setUseProjections(boolean useProjections);
/**
* Returns the current use-projection setting. If true, the client must be
* careful with all results that are returned. See
* {@link #setUseProjections(boolean) for more.} If
* {@link #setUseProjections(boolean)} has not been called, the
* {@link #DEFAULT_USE_PROJECTIONS} will be in effect.
*/
boolean isUseProjections();
/**
* Determines if all results should be returned as unloaded objects. This is
* particularly useful for creating lists for further querying via
* {@link IQuery}. This value overrides the
* {@link #setUseProjections(boolean)} setting.
*/
void setReturnUnloaded(boolean returnUnloaded);
/**
* Returns the current return-unloaded setting. If true, all returned
* entities will be unloaded. If {@link #setReturnUnloaded(boolean)} has not
* been called, the {@link #DEFAULT_RETURN_UNLOADED default value} will be
* in effect.
*/
boolean isReturnUnloaded();
/**
* Permits full-text queries with a leading query if true.
*
* @see #isAllowLeadingWildcard()
* @see #byFullText(String)
* @see #bySomeMustNone(String[], String[], String[])
*/
void setAllowLeadingWildcard(boolean allowLeadingWildcard);
/**
* Returns the current leading-wildcard setting. If false,
* {@link #byFullText(String)} and
* {@link #bySomeMustNone(String[], String[], String[])} will throw an
* {@link ApiUsageException}, since leading-wildcard searches are quite
* slow. Use {@link #setAllowLeadingWildcard(boolean)} in order to permit
* this usage.
*/
boolean isAllowLeadingWildcard();
// Filters ~~~~~~~~~~~~~~~~~~~~~
/**
* Restricts the search to a single type. All return values will match this
* type.
*/
<T extends IObject> void onlyType(Class<T> klass);
/**
* Restricts searches to a set of types. The entities returned are
* guaranteed to be one of these types.
*/
<T extends IObject> void onlyTypes(Class<T>... classes);
/**
* Permits all types to be returned. For some types of queries, this carries
* a performance penalty as every database table must be hit.
*/
void allTypes();
/**
* Restricts the set of {@link IObject#getId() ids} which will be checked.
* This is useful for testing one of the given restrictions on a reduced set
* of objects.
*
* @param ids
* Can be null, in which case the previous restriction is
* removed.
*/
void onlyIds(Long... ids);
/**
* Uses the {@link Details#getOwner()} and {@link Details#getGroup()}
* information to restrict the entities which will be returned. If both are
* non-null, the two restrictions are joined by an AND.
*
* @param d
* Can be null, in which case the previous restriction is
* removed.
*/
void onlyOwnedBy(Details d);
/**
* Uses the {@link Details#getOwner()} and {@link Details#getGroup()}
* information to restrict the entities which will be returned. If both are
* non-null, the two restrictions are joined by an AND.
*
* @param d
* Can be null, in which case the previous restriction is
* removed.
*/
void notOwnedBy(Details d);
/**
* Restricts the time between which an entity may have been created.
*
* @param start
* Can be null, i.e. interval open to negative infinity.
* @param stop
* Can be null, i.e. interval opens to positive infinity.
*/
void onlyCreatedBetween(java.sql.Timestamp start, java.sql.Timestamp stop);
/**
* Restricts the time between which an entity may have last been modified.
*
* @param start
* Can be null, i.e. interval open to negative infinity.
* @param stop
* Can be null, i.e. interval open to positive infinity.
*/
void onlyModifiedBetween(java.sql.Timestamp start, java.sql.Timestamp stop);
/**
* Restricts entities by the time in which any annotation (which matches the
* other filters) was added them. This matches the
* {@link Details#getCreationEvent() creation event} of the
* {@link Annotation}.
*
* @param start
* Can be null, i.e. interval open to negative infinity.
* @param stop
* Can be null, i.e. interval open to positive infinity.
*/
void onlyAnnotatedBetween(Timestamp start, Timestamp stop);
/**
* Restricts entities by who has annotated them with an {@link Annotation}
* matching the other filters. As {@link #onlyOwnedBy(Details)}, the
* {@link Details#getOwner()} and {@link Details#getGroup()} information is
* combined with an AND condition.
*
* @param d
* Can be null, in which case any previous restriction is
* removed.
*/
void onlyAnnotatedBy(Details d);
/**
* Restricts entities by who has not annotated them with an
* {@link Annotation} matching the other filters. As
* {@link #notOwnedBy(Details)}, the {@link Details#getOwner()} and
* {@link Details#getGroup()} information is combined with an AND condition.
*
* @param d
* Can be null, in which case any previous restriction is
* removed.
*/
void notAnnotatedBy(Details d);
/**
* Restricts entities to having an {@link Annotation} of all the given
* types. This is useful in combination with the other onlyAnnotated*
* methods to say, e.g., only annotated with a file by user X. By default,
* this value is <code>null</code> and imposes no restriction. Passing an
* empty array implies an object that is not annotated at all.
*
*
* Note: If the semantics were OR, then a client would have to query each
* class individually, and compare all the various values, checking which
* ids are where. However, since this method defaults to AND, multiple calls
* (optionally with {@link #isMergedBatches()} and
* {@link #isReturnUnloaded()}) and combine the results. Duplicate ids are
* still possible, so a set of some form should be used to collect the
* results.
*
* @param classes
* Can be empty, in which case restriction is removed.
*/
void onlyAnnotatedWith(Class... classes);
// Fetches, order, counts, etc ~~~~~~~~~~~~~~~~~~~~~~
/**
* A path from the target entity which will be added to the current stack of
* order statements applied to the query.
*
* @param path
* Non-null.
* @see #unordered()
*/
void addOrderByAsc(@NotNull
String path);
/**
* A path from the target entity which will be added to the current stack of
* order statements applied to the query.
*
* @param path
* Non-null.
* @see #unordered()
*/
void addOrderByDesc(@NotNull
String path);
/**
* Removes the current stack of order statements.
*
* @see #addOrderByAsc(String)
* @see #addOrderByDesc(String)
*/
void unordered();
/**
* Queries the database for all {@link Annotation annotations} of the given
* types for all returned instances.
*
* @param classes
* Can be empty, which removes previous fetch setting.
*/
void fetchAnnotations(Class... classes);
/**
* Adds a fetch clause for loading non-annotation fields of returned
* entities. Each fetch is a hibernate clause in dot notation.
*
* @param fetches
* Can be empty, which removes previous fetch setting.
*/
void fetchAlso(String... fetches);
// Reset ~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Resets all settings (non-query state) to the original default values, as
* if the instance had just be created.
*/
void resetDefaults();
// Query State ~
// =========================================================================
/**
* Returns transient (without ID)
* {@link ome.model.annotations.TextAnnotation} instances which represent
* terms which are similar to the given terms. For example, if the argument
* is "cell", one return value might have as its textValue: "cellular"
* while another has "cellularize".
*
* No filtering or fetching is performed.
*
* @param terms
* Cannot be empty.
*/
void bySimilarTerms(String...terms);
/**
* Returns transient (without ID)
* {@link ome.model.annotations.TagAnnotation} instances which
* represent all the {@link ome.model.annotations.TagAnnotation tags} in
* the given group. The entities are transient and without ownership since
* multiple users can own the same tag. This method will override settings
* for types.
*
* @param group
* Can be null or empty to return all tags.
*/
void byGroupForTags(String group);
/**
* Creates a query which will return transient (without ID)
* {@link ome.model.annotations.TagAnnotation} instances which represent
* all the {@link ome.model.annotations.TagAnnotation tag groups} which
* the given tag belongs to. The entities are transient and without
* ownership since multiple users can own the same tag group. This method
* will override settings for types.
*
* @param tag
* Can be null or empty to return all groups.
*/
void byTagForGroups(String tag);
/**
* Passes the query as is to the Lucene backend.
*
* @param query
* May not be null or of zero length.
*/
void byFullText(String query);
/**
* Builds a Lucene query and passes it to the Lucene backend.
*
* @param fields The fields (comma separated) to search in (name, description, ...)
* @param from The date range from, in the form YYYYMMDD (may be null)
* @param to The date range to (inclusive), in the form YYYYMMDD (may be null)
* @param dateType {@link #DATE_TYPE_ACQUISITION} or {@link #DATE_TYPE_IMPORT}
* @param query
* May not be null or of zero length.
*/
void byLuceneQueryBuilder(String fields, String from,
String to, String dateType, String query);
/*
* TODO: An idea: void byWildcardSql();
*/
/**
* Builds a Lucene query and passes it to {@link #byFullText(String)}.
*
* @param some
* Some (at least one) of these terms must be present in the
* document. May be null.
* @param must
* All of these terms must be present in the document. May be
* null.
* @param none
* None of these terms may be present in the document. May be
* null.
*/
void bySomeMustNone(String[] some, String[] must, String[] none);
/**
* Delegates to {@link IQuery#findAllByQuery(String, Parameters)} method to
* take advantage of the {@link #and()}, {@link #or()}, and
* {@link #not()} methods, or queue-semantics.
*
* @param query
* Not null.
* @param p
* May be null. Defaults are then in effect.
* @see IQuery#findAllByQuery(String, Parameters)
*/
void byHqlQuery(String query, Parameters p);
/**
* Finds entities annotated with an {@link Annotation} similar to the
* example. This does not use Hibernate's
* {@link IQuery#findByExample(IObject) Query-By-Example} mechanism, since
* that cannot handle joins. The fields which are used are:
* <ul>
* <li>the main content of the annotation : String,
* {@link OriginalFile#getId()}, etc.</li>
* </ul>
* <p>
* If the main content is <em>null</em> it is assumed to be a wildcard
* searched, and only the type of the annotation is searched. Currently,
* ListAnnotations are not supported.
* <p>
*
* @param examples
* Not empty.
*/
void byAnnotatedWith(Annotation... examples);
/**
* Applies the next by* method to the previous by* method, so that a call
* {@link #hasNext()}, {@link #next()}, or {@link #results()} sees only
* the union of the two calls.
*
* For example,
*
* <pre>
* service.onlyType(Image.class);
* service.byFullText("foo");
* service.or();
* service.onlyType(Dataset.class);
* service.byFullText("foo");
* </pre>
*
* will return both Images and Datasets together.
*
* Calling this method overrides a previous setting of {@link #and()} or
* {@link #not()}. If there is no active queries (i.e.
* {@link #activeQueries()} > 0), then an {@link ApiUsageException} will be
* thrown.
*/
void or();
/**
* Applies the next by* method to the previous by* method, so that a call
* {@link #hasNext()}, {@link #next()}, or {@link #results()} sees only
* the intersection of the two calls.
*
* For example,
*
* <pre>
* service.onlyType(Image.class);
* service.byFullText("foo");
* service.intersection();
* service.byAnnotatedWith(TagAnnotation.class);
* </pre>
*
* will return only the Images with TagAnnotations.
*
* <p>
* Calling this method overrides a previous setting of {@link #or()} or
* {@link #not()}. If there is no active queries (i.e.
* {@link #activeQueries()} > 0), then an {@link ApiUsageException} will be
* thrown.
* </p>
*/
void and();
/**
* Applies the next by* method to the previous by* method, so that a call
* {@link #hasNext()}, {@link #next()}, or {@link #results()} sees only
* the intersection of the two calls.
*
* For example,
*
* <pre>
* service.onlyType(Image.class);
* service.byFullText("foo");
* service.complement();
* service.byAnnotatedWith(TagAnnotation.class);
* </pre>
*
* will return all the Images <em>not</em> annotated with TagAnnotation.
* <p>
* Calling this method overrides a previous setting of {@link #or()} or
* {@link #and()}. If there is no active queries (i.e.
* {@link #activeQueries()} > 0), then an {@link ApiUsageException} will be
* thrown.
* </p>
*/
void not();
/**
* Returns entities with the given UUID strings
*
* @param uuids
*/
// These are currently not used often enough to be worth implementing
// the query.
// void byUUID(String[] uuids);
/**
* Removes all active queries (leaving {@link #resetDefaults() settings}
* alone), such that {@link #activeQueries()} will return 0.
*/
void clearQueries();
// Retrieval ~
// =========================================================================
/**
* Returns true if another call to {@link #next()} is valid. A call to
* {@link #next()} may throw an exception for another reason, however.
*/
boolean hasNext();
/**
* Returns the next entity from the current query. If the previous call
* returned the last entity from a given query, the first entity from the
* next query will be returned and {@link #activeQueries()} decremented.
* Since this method only returns the entity itself, a single call to
* {@link #currentMetadata()} may follow this call to gather the extra
* metadata which is returned in the map via {@link #results}.
*
* @throws ApiUsageException
* if {@link #hasNext()} returns false.
*/
IObject next() throws ApiUsageException;
/**
* Provides access to the extra query information (for example Lucene score
* and boost values) for a single call to {@link #next()}. This method may
* only be called once for any given call to {@link #next()}.
*/
Map<String, Annotation> currentMetadata();
/**
* Unsupported operation.
*/
void remove() throws UnsupportedOperationException;
/**
* Returns up to {@link #getBatchSize() batch size} number of results along
* with the related query metadata. If
* {@link #isMergedBatches() batches are merged} then the results from
* multiple queries may be returned together.
*
* @throws ApiUsageException
* if {@link #hasNext()} returns false.
*/
<T extends IObject> List<T> results() throws ApiUsageException;
/**
* Provides access to the extra query information (for example Lucene score
* and boost values) for a single call to {@link #results()}. This method
* may only be called once for any given call to {@link #results()}.
*/
List<Map<String, Annotation>> currentMetadataList();
}