• Views
  • Iteration Report
  • My Iteration Report
  •  
OMERO.server
  • Login
  • Help/Guide
  • About Trac
  • Preferences
  • Wiki
  • Timeline
  • Roadmap
  • Browse Source
  • View Tickets
  • Search

Context Navigation

  • ← Previous Changeset
  • Next Changeset →

Changeset 2387

Show
Ignore:
Timestamp:
05/20/08 14:33:21 (7 months ago)
Author:
jmoore
Message:

OmeroSearch improvements for ticket:975 and ticket:976

  • Added concept of union, intersection, complement
  • Re-enablded byHqlQuery() (More documentation on IQuery)
  • Further specified the logic of AnnotatedWith with a null value
  • Added specific TextAnnotation subclasses for now
  • Fixed serialization of FullText before hasNext() is called
  • SecurityViolation still being thrown in multiuser tests
Location:
trunk/components
Files:
3 added
9 modified

  • blitz/resources/omero/API.ice (modified) (3 diffs)
  • common/src/ome/api/IQuery.java (modified) (1 diff)
  • common/src/ome/api/Search.java (modified) (4 diffs)
  • server/src/ome/services/SearchBean.java (modified) (7 diffs)
  • server/src/ome/services/search/AnnotatedWith.java (modified) (6 diffs)
  • server/src/ome/services/search/Complement.java (added)
  • server/src/ome/services/search/FullText.java (modified) (9 diffs)
  • server/src/ome/services/search/HqlQuery.java (modified) (2 diffs)
  • server/src/ome/services/search/Intersection.java (added)
  • server/src/ome/services/search/SearchAction.java (modified) (3 diffs)
  • server/src/ome/services/search/Union.java (added)
  • server/test/ome/server/itests/search/SearchTest.java (modified) (11 diffs)

Legend:

Unmodified
Added
Removed
  • trunk/components/blitz/resources/omero/API.ice

    r2370 r2387  
    437437        bool isAllowLeadingWildcard() throws ServerError; 
    438438 
     439 
    439440        // Filters ~~~~~~~~~~~~~~~~~~~~~~ 
    440441 
    … …  
    452453        void onlyAnnotatedWith(StringSet classes) throws ServerError; 
    453454 
     455 
    454456        // Fetches, order, counts, etc ~~~~~~~~~~~~~~~~~~~~~~ 
    455457 
    … …  
    460462        void fetchAlso(StringSet fetches) throws ServerError; 
    461463 
     464 
    462465        // Reset ~~~~~~~~~~~~~~~~~~~~~~~~~ 
    463466 
    464467        void resetDefaults() throws ServerError; 
    465468 
     469 
    466470        // Query state  ~~~~~~~~~~~~~~~~~~~~~~~~~ 
     471 
    467472        void byGroupForTags(string group) throws ServerError; 
    468473        void byTagForGroups(string tag) throws ServerError; 
    469474        void byFullText(string query) throws ServerError; 
     475        void byHqlQuery(string query, omero::sys::Parameters params) throws ServerError; 
    470476        void bySomeMustNone(StringSet some, StringSet must, StringSet none) throws ServerError; 
    471477        void byAnnotatedWith(AnnotationList examples) throws ServerError; 
    472478        void clearQueries() throws ServerError; 
    473479 
     480        void union() throws ServerError; 
     481        void intersection() throws ServerError; 
     482        void complement() throws ServerError; 
     483 
     484 
    474485        // Retrieval  ~~~~~~~~~~~~~~~~~~~~~~~~~ 
     486 
    475487        bool hasNext() throws ServerError; 
    476488        omero::model::IObject next() throws ServerError; 
    477489        IObjectList results() throws ServerError; 
    478          
     490 
    479491        // Currently unused 
    480492        SearchMetadata currentMetadata() throws ServerError; 
    481493        SearchMetadataList currentMetadataList() throws ServerError; 
    482          
     494 
    483495        // Unused; Part of Java Iterator interface 
    484496        void remove() throws ServerError; 
  • trunk/components/common/src/ome/api/IQuery.java

    r2035 r2387  
    214214     * query has proper permissions. 
    215215     *  
     216     * Queries can only return lists of {@link IObject} instances. This means 
     217     * all must be of the form: 
     218     *  
     219     * <pre> 
     220     * select this from SomeModelClass this ... 
     221     * </pre> 
     222     *  
     223     * though the alias "this" is unimportant. Do not try to return multiple 
     224     * classes in one call like: 
     225     *  
     226     * <pre> 
     227     * select this, that from SomeClass this, SomeOtherClass that ... 
     228     * </pre> 
     229     *  
     230     * nor to project values out of an object: 
     231     *  
     232     * <pre> 
     233     * select this.name from SomeClass this ... 
     234     * </pre> 
     235     *  
    216236     * If a {@link Page} is desired, add it to the query parameters. 
    217237     *  
  • trunk/components/common/src/ome/api/Search.java

    r2327 r2387  
    457457 
    458458    /** 
    459      * Provides the main {@link IQuery} method here to take advantage of the 
    460      * various filters which are provided. See 
    461      * {@link IQuery#findAllByQuery(String, Parameters)} for the semantics. 
     459     * Delegates to {@link IQuery#findAllByQuery(String, Parameters)} method to 
     460     * take advantage of the {@link #intersection()}, {@link #union()}, and 
     461     * {@link #complement()} methods, or queue-semantics. 
    462462     *  
    463463     * @param query 
    … …  
    467467     * @see IQuery#findAllByQuery(String, Parameters) 
    468468     */ 
    469     // Disabling. This needs to be more intelligent before it can 
    470     // be provided here. Something of the form byHql(String[] join, String[] 
    471     // where) etc. 
    472     // Quite possibly note worth it. 
    473     // void byHqlQuery(String query, Parameters p); 
     469    void byHqlQuery(String query, Parameters p); 
     470 
    474471    /** 
    475472     * Finds entities annotated with an {@link Annotation} similar to the 
    … …  
    481478     * {@link OriginalFile#getId()}, etc.</li> 
    482479     * </ul> 
     480     * <p> 
     481     * If the main content is <em>null</em> it is assumed to be a wildcard 
     482     * searched, and only the type of the annotation is searched. Currently, 
     483     * ListAnnotations are not supported. 
     484     * <p> 
    483485     *  
    484486     * @param examples 
    … …  
    486488     */ 
    487489    void byAnnotatedWith(Annotation... examples); 
     490 
     491    /** 
     492     * Applies the next by* method to the previous by* method, so that a call 
     493     * {@link #hasNext()}, {@link #next()}, or {@link #results()} sees only 
     494     * the union of the two calls. 
     495     *  
     496     * For example, 
     497     *  
     498     * <pre> 
     499     * service.onlyType(Image.class); 
     500     * service.byFullText(&quot;foo&quot;); 
     501     * service.union(); 
     502     * service.onlyType(Dataset.class); 
     503     * service.byFullText(&quot;foo&quot;); 
     504     * </pre> 
     505     *  
     506     * will return both Images and Datasets together. 
     507     *  
     508     * Calling this method overrides a previous setting of 
     509     * {@link #intersection()} or {@link #complement()}. If there is no active 
     510     * queries (i.e. {@link #activeQueries()} > 0), then an 
     511     * {@link ApiUsageException} will be thrown. 
     512     */ 
     513    void union(); 
     514 
     515    /** 
     516     * Applies the next by* method to the previous by* method, so that a call 
     517     * {@link #hasNext()}, {@link #next()}, or {@link #results()} sees only 
     518     * the intersection of the two calls. 
     519     *  
     520     * For example, 
     521     *  
     522     * <pre> 
     523     * service.onlyType(Image.class); 
     524     * service.byFullText(&quot;foo&quot;); 
     525     * service.intersection(); 
     526     * service.byAnnotatedWith(TagAnnotation.class); 
     527     * </pre> 
     528     *  
     529     * will return only the Images with TagAnnotations. 
     530     *  
     531     * <p> 
     532     * Calling this method overrides a previous setting of {@link #union()} or 
     533     * {@link #complement()}. If there is no active queries (i.e. 
     534     * {@link #activeQueries()} > 0), then an {@link ApiUsageException} will be 
     535     * thrown. 
     536     * </p> 
     537     */ 
     538    void intersection(); 
     539 
     540    /** 
     541     * Applies the next by* method to the previous by* method, so that a call 
     542     * {@link #hasNext()}, {@link #next()}, or {@link #results()} sees only 
     543     * the intersection of the two calls. 
     544     *  
     545     * For example, 
     546     *  
     547     * <pre> 
     548     * service.onlyType(Image.class); 
     549     * service.byFullText(&quot;foo&quot;); 
     550     * service.complement(); 
     551     * service.byAnnotatedWith(TagAnnotation.class); 
     552     * </pre> 
     553     *  
     554     * will return all the Images <em>not</em> annotated with TagAnnotation. 
     555     * <p> 
     556     * Calling this method overrides a previous setting of {@link #union()} or 
     557     * {@link #intersection()}. If there is no active queries (i.e. 
     558     * {@link #activeQueries()} > 0), then an {@link ApiUsageException} will be 
     559     * thrown. 
     560     * </p> 
     561     */ 
     562    void complement(); 
    488563 
    489564    /** 
  • trunk/components/server/src/ome/services/SearchBean.java

    r2327 r2387  
    88package ome.services; 
    99 
     10import java.io.Serializable; 
    1011import java.sql.Timestamp; 
    1112import java.util.ArrayList; 
    1213import java.util.Arrays; 
    13 import java.util.Collections; 
    1414import java.util.List; 
    1515import java.util.Map; 
    … …  
    3131import ome.api.ServiceInterface; 
    3232import ome.conditions.ApiUsageException; 
     33import ome.conditions.InternalException; 
    3334import ome.model.IObject; 
    3435import ome.model.annotations.Annotation; 
    … …  
    3637import ome.parameters.Parameters; 
    3738import ome.services.search.AnnotatedWith; 
     39import ome.services.search.Complement; 
    3840import ome.services.search.FullText; 
    3941import ome.services.search.HqlQuery; 
     42import ome.services.search.Intersection; 
    4043import ome.services.search.SearchAction; 
    4144import ome.services.search.SearchValues; 
    4245import ome.services.search.SomeMustNone; 
    4346import ome.services.search.TagsAndGroups; 
     47import ome.services.search.Union; 
    4448import ome.services.util.Executor; 
    4549import ome.services.util.OmeroAroundInvoke; 
    … …  
    7377    private final static Log log = LogFactory.getLog(SearchBean.class); 
    7478 
    75     private final List<SearchAction> actions = Collections 
    76             .synchronizedList(new ArrayList<SearchAction>()); 
     79    private final ActionList actions = new ActionList(); 
    7780 
    7881    private final SearchValues values = new SearchValues(); 
    … …  
    212215    } 
    213216 
     217    // LOGICAL COMBINATIONS 
     218 
     219    @Transactional 
     220    @RolesAllowed("user") 
     221    public void union() { 
     222        actions.union(); 
     223    } 
     224 
     225    @Transactional 
     226    @RolesAllowed("user") 
     227    public void intersection() { 
     228        actions.intersection(); 
     229    } 
     230 
     231    @Transactional 
     232    @RolesAllowed("user") 
     233    public void complement() { 
     234        actions.complement(); 
     235    } 
     236 
    214237    // 
    215238    // FETCH METHODS 
    … …  
    233256            return false; 
    234257        } 
    235         SearchAction action = actions.remove(0); 
     258        SearchAction action = actions.popFirst(); 
    236259        List<IObject> list = (List<IObject>) executor.execute(null, action); 
    237260        results.add(list); 
    … …  
    642665        } 
    643666    } 
     667 
     668    /** 
     669     * Synchronized helper collection for maintaining {@link SearchAction} 
     670     * instances. Also knows how to do logical joins (union, etc.) 
     671     */ 
     672    private static class ActionList implements Serializable { 
     673 
     674        private static final long serialVersionUID = 1L; 
     675 
     676        enum State { 
     677            normal, union, intersection, complement; 
     678        } 
     679 
     680        private State state = State.normal; 
     681 
     682        final private List<SearchAction> actions = new ArrayList<SearchAction>(); 
     683 
     684        synchronized void union() { 
     685            state = State.union; 
     686        } 
     687 
     688        synchronized void intersection() { 
     689            state = State.intersection; 
     690        } 
     691 
     692        synchronized void complement() { 
     693            state = State.complement; 
     694        } 
     695 
     696        synchronized void add(SearchAction b) { 
     697 
     698            // Any call to "add" reset the state of the ActionList 
     699            State previousState = state; 
     700            this.state = State.normal; 
     701 
     702            SearchAction a; 
     703            switch (previousState) { 
     704            case normal: 
     705                actions.add(b); 
     706                break; 
     707            case union: 
     708                a = popLast(); 
     709                actions.add(new Union(b.copyOfValues(), a, b)); 
     710                break; 
     711            case intersection: 
     712                a = popLast(); 
     713                actions.add(new Intersection(b.copyOfValues(), a, b)); 
     714                break; 
     715            case complement: 
     716                a = popLast(); 
     717                actions.add(new Complement(b.copyOfValues(), a, b)); 
     718                break; 
     719            default: 
     720                throw new InternalException("Unknown state:" + state); 
     721            } 
     722        } 
     723 
     724        synchronized int size() { 
     725            return actions.size(); 
     726        } 
     727 
     728        synchronized void clear() { 
     729            actions.clear(); 
     730        } 
     731 
     732        synchronized SearchAction popFirst() { 
     733            assertNonZero(); 
     734            return actions.remove(0); 
     735        } 
     736 
     737        synchronized SearchAction popLast() { 
     738            assertNonZero(); 
     739            return actions.remove(actions.size() - 1); 
     740        } 
     741 
     742        synchronized void assertNonZero() { 
     743            if (actions.size() == 0) { 
     744                throw new ApiUsageException("There must be at least 1" 
     745                        + " active query for this operation."); 
     746            } 
     747        } 
     748    } 
    644749} 
  • trunk/components/server/src/ome/services/search/AnnotatedWith.java

    r2259 r2387  
    1818import ome.model.annotations.FileAnnotation; 
    1919import ome.model.annotations.LongAnnotation; 
     20import ome.model.annotations.QueryAnnotation; 
     21import ome.model.annotations.TagAnnotation; 
    2022import ome.model.annotations.TextAnnotation; 
    2123import ome.model.annotations.ThumbnailAnnotation; 
    2224import ome.model.annotations.TimestampAnnotation; 
     25import ome.model.annotations.UrlAnnotation; 
     26import ome.model.annotations.XmlAnnotation; 
    2327import ome.model.core.OriginalFile; 
    2428import ome.model.display.Thumbnail; 
    … …  
    107111        for (int i = 0; i < annotation.length; i++) { 
    108112            if (annotation[i] instanceof TextAnnotation) { 
    109                 annCls[i] = TextAnnotation.class; 
     113                // FIXME This should be unneccessary. See ticket:976 
     114                if (annotation[i] instanceof TagAnnotation) { 
     115                    annCls[i] = TagAnnotation.class; 
     116                } else if (annotation[i] instanceof QueryAnnotation) { 
     117                    annCls[i] = QueryAnnotation.class; 
     118                } else if (annotation[i] instanceof UrlAnnotation) { 
     119                    annCls[i] = UrlAnnotation.class; 
     120                } else if (annotation[i] instanceof XmlAnnotation) { 
     121                    annCls[i] = XmlAnnotation.class; 
     122                } else { 
     123                    annCls[i] = TextAnnotation.class; 
     124                } 
    110125                type[i] = String.class; 
    111126                path[i] = "textValue"; 
    … …  
    145160                        + annotation); 
    146161            } 
    147             // fetch annotations 
     162 
     163            // If we have an example of the given annoation, then we can 
     164            // fetch it directly and don't need to use the fetchAnnotationsCopy 
     165            // collection. 
    148166            for (Class ac : values.fetchAnnotations) { 
    149167                if (annCls[i].isAssignableFrom(ac)) { 
    … …  
    152170                } 
    153171            } 
     172            // On the other hand, if the value is null, then we might as well 
     173            // treat this as a fetch request and search only on Ann.class =. 
     174            // See the guards around the Joins section and the the call to 
     175            // notNullOrLikeOrEqual below. 
     176            if (value[i] == null) { 
     177                fetchAnnotationsCopy.add(annCls[i]); 
     178            } 
    154179        } 
    155180 
    … …  
    165190        qb.select("this"); 
    166191        qb.from(cls.getName(), "this"); 
     192 
     193        // Joins 
    167194        for (int i = 0; i < ann.length; i++) { 
    168             link[i] = qb.unique_alias("link"); 
    169             ann[i] = link[i] + "_child"; 
    170             qb.join("this.annotationLinks", link[i], false, fetch[i]); 
    171             qb.join(link[i] + ".child", ann[i], false, fetch[i]); 
     195            if (value[i] != null) { 
     196                link[i] = qb.unique_alias("link"); 
     197                ann[i] = link[i] + "_child"; 
     198                qb.join("this.annotationLinks", link[i], false, fetch[i]); 
     199                qb.join(link[i] + ".child", ann[i], false, fetch[i]); 
     200            } 
    172201        } 
    173202 
    … …  
    191220            // Main criteria 
    192221            if (useNamespace) { 
    193                 notNullOrLikeOrEqual(qb, ann + ".name", type[j], annotation[j] 
     222                notNullOrLikeOrEqual(qb, ann + ".ns", type[j], annotation[j] 
    194223                        .getNs(), useLike, values.caseSensitive); 
    195224            } 
    196225 
    197             notNullOrLikeOrEqual(qb, ann[j] + "." + path[j], type[j], value[j], 
    198                     useLike, values.caseSensitive); 
     226            // If the value of the annotation is null, we assume that we are not 
     227            // actually searching for null annotations (whose nullability is 
     228            // actually a by-product of polymorphism), instead we assume the 
     229            // null acts like a wildcard, in which case this search has been 
     230            // added to the fetchAnnotationsCopy collection above. 
     231            if (value[j] != null) { 
     232                notNullOrLikeOrEqual(qb, ann[j] + "." + path[j], type[j], 
     233                        value[j], useLike, values.caseSensitive); 
     234            } 
    199235 
    200236            annotatedBetween(qb, ann[j] + "."); 
  • trunk/components/server/src/ome/services/search/FullText.java

    r2385 r2387  
    88package ome.services.search; 
    99 
     10import java.util.List; 
     11 
    1012import ome.conditions.ApiUsageException; 
     13import ome.conditions.SecurityViolation; 
    1114import ome.model.IAnnotated; 
    1215import ome.services.SearchBean; 
    … …  
    1922import org.apache.lucene.search.SortField; 
    2023import org.hibernate.Criteria; 
    21 import org.hibernate.Query; 
    22 import org.hibernate.ScrollableResults; 
    2324import org.hibernate.Session; 
    2425import org.hibernate.criterion.Restrictions; 
    … …  
    3031 
    3132/** 
    32  * Query template used by {@link SearchBean} to store user requests. 
     33 * Search based on Lucene's {@link Query} class. Takes a Google-like search 
     34 * string and returns fully formed objects via Hibernate Search. 
    3335 *  
    3436 * @author Josh Moore, josh at glencoesoftware.com 
    … …  
    3739public class FullText extends SearchAction { 
    3840 
    39     private final static QueryParser parser = new QueryParser( 
    40             "combined_fields", new StandardAnalyzer()); 
    41  
    4241    private static final long serialVersionUID = 1L; 
    4342 
    4443    private final String queryStr; 
    4544 
    46     private FullTextSession session; 
    47  
    48     private org.apache.lucene.search.Query q; 
    49  
    50     private Query query; 
    51  
    52     private ScrollableResults scroll; 
     45    private final org.apache.lucene.search.Query q; 
    5346 
    5447    public FullText(SearchValues values, String query) { 
    … …  
    7972        this.queryStr = query; 
    8073        try { 
     74            final QueryParser parser = new QueryParser("combined_fields", 
     75                    new StandardAnalyzer()); 
    8176            parser.setAllowLeadingWildcard(values.leadingWildcard); 
    8277            q = parser.parse(queryStr); 
    … …  
    8883    } 
    8984 
    90     public Object doWork(TransactionStatus status, Session session, 
    91             ServiceFactory sf) { 
     85    public Object doWork(TransactionStatus status, Session s, ServiceFactory sf) { 
    9286 
    93         Class cls = values.onlyTypes.get(0); 
     87        final Class<?> cls = values.onlyTypes.get(0); 
    9488 
    95         this.session = Search.createFullTextSession(session); 
     89        FullTextSession session = Search.createFullTextSession(s); 
    9690        Criteria criteria = session.createCriteria(cls); 
    9791        AnnotationCriteria ann = new AnnotationCriteria(criteria, 
    … …  
    118112                    return null; // EARLY EXIT ! 
    119113                } else { 
    120                     for (Class annCls : values.onlyAnnotatedWith) { 
     114                    for (Class<?> annCls : values.onlyAnnotatedWith) { 
    121115                        SimpleExpression ofType = new TypeEqualityExpression( 
    122116                                "class", annCls); 
    … …  
    130124 
    131125        // Main query 
    132         FullTextQuery ftQuery = this.session.createFullTextQuery(this.q); 
     126        FullTextQuery ftQuery = session.createFullTextQuery(this.q, cls); 
    133127        ftQuery.setCriteriaQuery(criteria); 
    134128 
    … …  
    151145        } 
    152146 
    153         query = ftQuery; 
    154         return query.list(); 
     147        final String ticket975 = "ticket:975 - Wrong return type: %s instead of %s\n" 
     148                + "Under some circumstances, byFullText and related methods \n" 
     149                + "like bySomeMustNone can return instances of the wrong \n" 
     150                + "types. One known case is the use of onlyAnnotatedWith(). \n" 
     151                + "If you are recieving this error, please try using the \n" 
     152                + "intersection/union methods to achieve the same results."; 
     153 
     154        List<?> check975 = ftQuery.list(); 
     155 
     156        // WORKAROUND 
     157        for (Object object : check975) { 
     158            if (!cls.isAssignableFrom(object.getClass())) { 
     159                throw new ApiUsageException(String.format(ticket975, object 
     160                        .getClass(), cls)); 
     161            } 
     162        } 
     163        return check975; 
    155164    } 
    156  
    157165} 
  • trunk/components/server/src/ome/services/search/HqlQuery.java

    r2098 r2387  
    88package ome.services.search; 
    99 
     10import ome.api.IQuery; 
    1011import ome.parameters.Parameters; 
    11 import ome.services.SearchBean; 
    1212import ome.system.ServiceFactory; 
    1313 
    … …  
    1616 
    1717/** 
    18  * Query template used by {@link SearchBean} to store user requests. 
     18 * Delegate to {@link IQuery#findAllByQuery(String, Parameters)}. 
    1919 *  
    2020 * @author Josh Moore, josh at glencoesoftware.com 
  • trunk/components/server/src/ome/services/search/SearchAction.java

    r2386 r2387  
    88package ome.services.search; 
    99 
     10import java.io.Serializable; 
    1011import java.util.List; 
    1112 
    … …  
    3637 * @since 3.0-Beta3 
    3738 */ 
    38 public abstract class SearchAction implements ome.services.util.Executor.Work { 
     39public abstract class SearchAction implements Serializable, 
     40        ome.services.util.Executor.Work { 
    3941 
    4042    protected final SearchValues values = new SearchValues(); 
    … …  
    4648        } 
    4749        this.values.copy(values); 
     50    } 
     51 
     52