/* * $Id$ * * Copyright 2008 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.server.itests.search; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import ome.api.IUpdate; import ome.api.Search; import ome.conditions.ApiUsageException; import ome.model.IObject; 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.ImageAnnotationLink; import ome.model.annotations.LongAnnotation; import ome.model.annotations.TagAnnotation; import ome.model.annotations.TermAnnotation; import ome.model.core.Image; import ome.model.core.OriginalFile; import ome.model.internal.Details; import ome.model.internal.Permissions; import ome.model.meta.Experimenter; import ome.model.meta.ExperimenterGroup; import ome.parameters.Parameters; import ome.services.util.Executor; import ome.system.Principal; import ome.system.ServiceFactory; import ome.server.itests.FileUploader; import ome.testing.ObjectFactory; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Restrictions; import org.springframework.aop.framework.Advised; import org.springframework.transaction.annotation.Transactional; import org.testng.annotations.Test; /** * The following test provides examples of using the {@link Search} interface, * tests its completeness, has regression tests for various tickets, as well as * implementation specific tests which by and large can be ignored. * * The tests generally demonstrate "best practices" for using the {@link Search} * API, <em>except</em> for the many calls to * {@link IUpdate#indexObject(IObject)}. These are necessary for real-time * testing, but this solution would swamp the server if each client tried to * specify when indexing should take place. * * Instead, the server decides when objects are indexed, and there may be a * short delay. * * @see <a * href="http://lucene.apache.org/java/docs/queryparsersyntax.html">Query * Parser Syntax</a> */ @Test(groups = { "query", "fulltext", "search" }) public class SearchTest extends AbstractTest { // User Examples // ========================================================================= // This section tests provides various small example tests, and doesn't // try to comprehensively test the API public void testComplicatedLuceneQueries() { Image i = new Image("Image with A - 1 reagent"); i = this.iUpdate.saveAndReturnObject(i); this.iUpdate.indexObject(i); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText("\"A \\- 1\""); assertContainsObject(search, i); search.byFullText("A \\- 1"); assertContainsObject(search, i); search.bySomeMustNone(new String[] { "name:\"A \\- 1\"" }, null, null); assertContainsObject(search, i); search.bySomeMustNone(new String[] { "name:reagent" }, null, null); assertContainsObject(search, i); // The following doesn't work for some reason. // search.bySomeMustNone(new String[] { "name:A" }, null, null); // And this causes a parse exception // search.bySomeMustNone(new String[] { "name:*A*" }, null, null); // The following won't work since "-" is a Lucene special character. // search.byFullText("A - 1"); } public void testFullTextUsingIQuery() { String uuid = uuid(); String part = uuid.substring(0, uuid.indexOf("DASH")); Image i = new Image("myIQueryImageTest"); TagAnnotation tag = new TagAnnotation(); tag.setName("theTagNameInMyIQueryTest"); tag.setNs("theNamespaceInMyIQueryTest"); tag.setTextValue("some test and an " + uuid + " to search"); i.linkAnnotation(tag); i = this.iUpdate.saveAndReturnObject(i); this.iUpdate.indexObject(i); List<? extends IObject> list; list = this.iQuery.findAllByFullText(Image.class, "myIQueryImageTest", null); assertTrue(list.toString(), list.size() >= 1); list = this.iQuery.findAllByFullText(Image.class, "name:myIQueryImageTest", null); assertTrue(list.toString(), list.size() >= 1); list = this.iQuery.findAllByFullText(Image.class, "name:myIQuery*", null); assertTrue(list.toString(), list.size() >= 1); list = this.iQuery.findAllByFullText(Image.class, part + "*", null); assertTrue(list.toString(), list.size() == 1); list = this.iQuery.findAllByFullText(Image.class, uuid, null); assertTrue(list.toString(), list.size() == 1); list = this.iQuery.findAllByFullText(Image.class, "\"some*" + uuid + "*\"", null); assertTrue(list.toString(), list.size() == 1); list = this.iQuery.findAllByFullText(Image.class, "tag:" + uuid, null); assertTrue(list.toString(), list.size() == 1); list = this.iQuery.findAllByFullText(Image.class, "annotation:" + uuid, null); assertTrue(list.toString(), list.size() == 1); list = this.iQuery.findAllByFullText(Image.class, "annotation.name:theTagName*", null); assertTrue(list.toString(), list.size() >= 1); list = this.iQuery.findAllByFullText(Image.class, "annotation.ns:theNamespace*", null); assertTrue(list.toString(), list.size() >= 1); list = this.iQuery.findAllByFullText(Image.class, "annotation.type:TagAnnotation", null); assertTrue(list.toString(), list.size() >= 1); } // by<Query> // ========================================================================= // This section tests each query method with various combinations of // restrictions @Test public void testBySearchTerms() { String base = uuid(); String base1 = base + "1"; String base2 = base + "2"; String base3 = base + "3"; Image i1 = new Image(base1); Image i2 = new Image(base2); Image i3 = new Image(base3); i1 = iUpdate.saveAndReturnObject(i1); i2 = iUpdate.saveAndReturnObject(i2); i3 = iUpdate.saveAndReturnObject(i3); iUpdate.indexObject(i1); iUpdate.indexObject(i2); iUpdate.indexObject(i3); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.bySimilarTerms(base1); List annotations = search.results(); List<String> terms = new ArrayList<String>(); for (Object obj : annotations) { terms.add(((CommentAnnotation) obj).getTextValue()); } // Lower-casing is necessary since that's what's stored in the index. assertTrue(terms.contains(base2.toLowerCase())); assertTrue(terms.contains(base2.toLowerCase())); assertFalse(terms.contains(base.toLowerCase())); assertFalse(terms.contains(base1.toLowerCase())); } @Test public void testByGroupForTags() { String groupStr = uuid(); String tagStr = uuid(); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(tagStr); TagAnnotation grp = new TagAnnotation(); grp.setTextValue(groupStr); tag.linkAnnotation(grp); tag = iUpdate.saveAndReturnObject(tag); Search search = this.factory.createSearchService(); search.byGroupForTags(groupStr); assertEquals(1, search.results().size()); // Make another one groupStr = uuid(); grp = new TagAnnotation(); tag.linkAnnotation(grp); tag = iUpdate.saveAndReturnObject(tag); // Now we are sure that there are two taggroups in the db; // this should return all two then search.byGroupForTags(null); search.setBatchSize(2); assertEquals(2, search.results().size()); while (search.hasNext()) { search.results(); // Clear search } // Let's now add the tag to another tag group as another user // and try to filter out those results long oldUser = iAdmin.getEventContext().getCurrentUserId(); Experimenter e1 = new Experimenter(oldUser, false); Details d = Details.create(); d.setOwner(e1); Experimenter e = loginNewUserInOtherUsersGroup(e1); grp = new TagAnnotation(); groupStr = uuid(); grp.setTextValue(groupStr); tag.linkAnnotation(grp); tag = iUpdate.saveAndReturnObject(tag); // All queries finished? assertEquals(0, search.activeQueries()); assertFalse(search.hasNext()); search.onlyOwnedBy(d); search.byGroupForTags(groupStr); assertFalse(search.hasNext()); d.setOwner(e); search.onlyOwnedBy(d); search.byGroupForTags(groupStr); assertEquals(1, search.results().size()); search.onlyOwnedBy(null); search.byGroupForTags(groupStr); assertEquals(1, search.results().size()); } @Test public void testByTagForGroup() { String groupStr = uuid(); String tagStr = uuid(); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(tagStr); TagAnnotation grp = new TagAnnotation(); grp.setTextValue(groupStr); tag.linkAnnotation(grp); tag = iUpdate.saveAndReturnObject(tag); Search search = this.factory.createSearchService(); search.byTagForGroups(tagStr); assertEquals(1, search.results().size()); // Make another one tagStr = uuid(); tag = new TagAnnotation(); tag.linkAnnotation(grp); tag = iUpdate.saveAndReturnObject(tag); // Now we are sure that there are two tags for the one group; // this should return all two then search.byTagForGroups(null); search.setBatchSize(2); assertEquals(2, search.results().size()); while (search.hasNext()) { search.results(); // Clear search } // Let's now add another tag to the tag group as another user // and try to filter out those results long oldUser = iAdmin.getEventContext().getCurrentUserId(); Experimenter e1 = iAdmin.getExperimenter(oldUser); Details d = Details.create(); d.setOwner(new Experimenter(oldUser, false)); Experimenter e = loginNewUserInOtherUsersGroup(e1); tag = new TagAnnotation(); tagStr = uuid(); tag.setTextValue(tagStr); tag.linkAnnotation(grp.proxy()); tag = iUpdate.saveAndReturnObject(tag); // All queries finished? assertEquals(0, search.activeQueries()); assertFalse(search.hasNext()); search.onlyOwnedBy(d); search.byTagForGroups(tagStr); assertFalse(search.hasNext()); d.setOwner(e); search.onlyOwnedBy(d); search.byTagForGroups(tagStr); assertEquals(1, search.results().size()); search.onlyOwnedBy(null); search.byTagForGroups(tagStr); assertEquals(1, search.results().size()); } @Test public void testSimpleFullTextSearch() { Image i = new Image(); i.setName(uuid()); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText(i.getName()); int count = 0; while (search.hasNext()) { IObject obj = search.next(); count++; assertNotNull(obj); } assertTrue(count == 1); search.close(); search.onlyType(Image.class); search.byFullText(i.getName()); assertResults(search, 1); search.close(); } @Test public void testWildcardFullTextSearch() { Image i = new Image(); i.setName(uuid()); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText(i.getName()); assertResults(search, 1); String leadingWildcard = "*" + i.getName().substring(1, i.getName().length()); try { search.byFullText(leadingWildcard); fail("Should throw AUE"); } catch (ApiUsageException e) { // ok, and clear while (search.hasNext()) { search.results(); } } search.setAllowLeadingWildcard(true); search.byFullText(leadingWildcard); assertResults(search, 1); search.close(); } String[] sa(String... arr) { return arr; } @Test public void testSomeMustNone() { String abc = uuid(); String def = uuid(); String ghi = uuid(); String _123 = uuid(); String jkl = uuid(); String mno = uuid(); String pqr = uuid(); String _456 = uuid(); final String[] contained = new String[] { abc, def, ghi, _123 }; final String[] missing = new String[] { jkl, mno, pqr, _456 }; Image i = new Image(); i.setName(abc + " " + def + " " + ghi); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); loginRootKeepGroup(); final Search search = this.factory.createSearchService(); search.onlyType(Image.class); // Make sure we can find it simply search.bySomeMustNone(sa(abc), sa(), sa()); assertTrue(search.results().size() >= 1); // // Now we'll try more complicated queries // // This should return nothing since none is contained search.bySomeMustNone(sa(abc), sa(), sa(def)); assertResults(search, 0); // but if the none is not contained should be ok. search.bySomeMustNone(sa(abc), sa(abc), sa(jkl)); assertAtLeastResults(search, 1); // Simple must query search.bySomeMustNone(sa(), sa(abc), sa()); assertAtLeastResults(search, 1); // same, but with a matching none search.bySomeMustNone(sa(), sa(abc), sa(def)); assertResults(search, 0); // same again, but with non-matching none search.bySomeMustNone(sa(), sa(abc), sa(jkl)); assertAtLeastResults(search, 1); // // Mixing some and must // // Present must search.bySomeMustNone(sa(abc), sa(def), sa()); assertAtLeastResults(search, 1); // Missing must search.bySomeMustNone(sa(abc), sa(jkl), sa()); assertResults(search, 0); // Present must, missing some search.bySomeMustNone(sa(jkl), sa(def), sa()); assertAtLeastResults(search, 1); // // Using wildcards // // some with wildcard String part = abc.substring(0, abc.indexOf("DASH")) + "*"; search.bySomeMustNone(sa(part), sa(), sa()); assertAtLeastResults(search, 1); // must with wildcard search.bySomeMustNone(sa(), sa(part), sa()); assertAtLeastResults(search, 1); // none with wildcard search.bySomeMustNone(sa(), sa(), sa(part)); assertResults(search, 0); // // Multiterms // search.bySomeMustNone(sa(abc, def), null, null); assertAtLeastResults(search, 1); search.bySomeMustNone(null, sa(abc, def), null); assertAtLeastResults(search, 1); search.bySomeMustNone(null, null, sa(abc, def)); assertResults(search, 0); search.bySomeMustNone(sa(ghi, _123), sa(abc, def), null); assertAtLeastResults(search, 1); search.bySomeMustNone(sa(ghi, _123), sa(abc, def), sa(_456)); assertAtLeastResults(search, 1); search.bySomeMustNone(sa(ghi, _123), sa(abc, _456), sa(_456)); assertResults(search, 0); // // Completely empty // try { search.bySomeMustNone(null, null, null); fail("Should throw"); } catch (ApiUsageException aue) { // ok } try { search.bySomeMustNone(sa(), null, null); fail("Should throw"); } catch (ApiUsageException aue) { // ok } try { search.bySomeMustNone(sa(""), null, null); fail("Should throw"); } catch (ApiUsageException aue) { // ok } // // Queries with spaces // For the moment these return as expected since the parser splits into // keywords. // search.bySomeMustNone(sa("\"abc def\""), null, null); assertAtLeastResults(search, 1); } @Test public void testAnnotatedWith() { String uuid = uuid(); Image i = new Image(uuid); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(uuid); i.linkAnnotation(tag); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); TagAnnotation example = new TagAnnotation(); example.setTextValue(uuid); search.byAnnotatedWith(example); assertResults(search, 1); OriginalFile file1 = ObjectFactory.createFile(); file1 = iUpdate.saveAndReturnObject(file1); OriginalFile file2 = ObjectFactory.createFile(); file2 = iUpdate.saveAndReturnObject(file2); FileAnnotation fa1 = new FileAnnotation(); fa1.setFile(file1); i.linkAnnotation(fa1); FileAnnotation fa2 = new FileAnnotation(); fa2.setFile(file2); i.linkAnnotation(fa2); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); loginRoot(); // Properly uses the id FileAnnotation ex2 = new FileAnnotation(); ex2.setFile(new OriginalFile(file2.getId(), false)); search.byAnnotatedWith(ex2); assertResults(search, 1); // Finding by superclass // As of 4.0, text annotation is abstract, and so it's not possible // to do this. /* * CommentAnnotation txtAnn = new CommentAnnotation(); * txtAnn.setTextValue(uuid); search.byAnnotatedWith(txtAnn); * assertResults(search, 1); */ } @Test public void testAnnotatedWithNoValue() { String uuid = uuid(); Image i = new Image(uuid); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(uuid); i.linkAnnotation(tag); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // Now check if an empty example return results tag = new TagAnnotation(); search.byAnnotatedWith(tag); assertContainsObject(search, i); } @Test public void testAnnotatedWithNamespace() { fail("via namespace"); } @Test public void testAnnotatedWithMultiple() { Image i1 = new Image("i1"); Image i2 = new Image("i2"); String uuid = uuid(); TagAnnotation ta = new TagAnnotation(); ta.setTextValue(uuid); BooleanAnnotation ba = new BooleanAnnotation(); ba.setBoolValue(false); i1.linkAnnotation(ta); i2.linkAnnotation(ta); i2.linkAnnotation(ba); i1 = iUpdate.saveAndReturnObject(i1); i2 = iUpdate.saveAndReturnObject(i2); ta = new TagAnnotation(); ta.setTextValue(uuid); ba = new BooleanAnnotation(); ba.setBoolValue(false); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byAnnotatedWith(ta); assertResults(search, 2); search.byAnnotatedWith(ba); assertAtLeastResults(search, 1); search.byAnnotatedWith(ta, ba); assertResults(search, 1); } // boolean combinations // ======================================================================== // tests combinations of union, intersection, and complement. @Test public void testSimpleCombinations() { String uuid1 = uuid(); String uuid2 = uuid(); Image i1 = new Image(uuid1); Image i2 = new Image(uuid2); i1 = iUpdate.saveAndReturnObject(i1); i2 = iUpdate.saveAndReturnObject(i2); iUpdate.indexObject(i1); iUpdate.indexObject(i2); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // A + B search.byFullText(uuid1); search.or(); search.byFullText(uuid2); assertResults(search, 2); // A & B search.byFullText(uuid1); search.and(); search.byFullText(uuid2); assertResults(search, 0); // A - B search.byFullText(uuid1); search.not(); search.byFullText(uuid2); assertResults(search, 1); // A + B - B = A search.byFullText(uuid1); search.or(); search.byFullText(uuid2); search.not(); search.byFullText(uuid2); assertResults(search, 1); // With HQL search.onlyType(Image.class); search.byFullText(uuid1); search.and(); search.byHqlQuery("select i from Image i where i.id = " + i1.getId(), null); assertResults(search, 1); } @Test public void testCombinationsWithChainedList() { String uuid1 = uuid(); String uuid2 = uuid(); Image i1 = new Image(uuid1); Image i2 = new Image(uuid2); TagAnnotation t2 = new TagAnnotation(); i2.linkAnnotation(t2); i1 = iUpdate.saveAndReturnObject(i1); i2 = iUpdate.saveAndReturnObject(i2); iUpdate.indexObject(i1); iUpdate.indexObject(i2); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // No IDLIST-based join // here we are purposefully loading image2 in the first call (since // it has an annotation) and image1 in the second call (which calls // join). search.byFullText(uuid2); List<IObject> output = assertResults(search, 1); Image i = (Image) output.get(0); assertTrue(i.sizeOfAnnotationLinks() < 0); search.byFullText(uuid2); search.and(); search .byHqlQuery( "select i from Image i join fetch i.annotationLinks where i.id in (:IDLIST)", null); output = assertResults(search, 1); i = (Image) output.get(0); assertTrue(i.sizeOfAnnotationLinks() > 0); } @Test public void testCombinationsWithEmptyChainedList() { String uuid1 = uuid(); String uuid2 = uuid(); Image i1 = new Image(uuid1); Image i2 = new Image(uuid2); TagAnnotation t2 = new TagAnnotation(); i2.linkAnnotation(t2); i1 = iUpdate.saveAndReturnObject(i1); i2 = iUpdate.saveAndReturnObject(i2); iUpdate.indexObject(i1); iUpdate.indexObject(i2); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText(uuid1); search.and(); search.byFullText(uuid2); search.or(); search .byHqlQuery( "select i from Image i join fetch i.annotationLinks where i.id in (:IDLIST)", null); List<IObject> output = assertResults(search, 0); } @Test public void testCombinationsWithEmptyButOverwrittenChainedList() { String uuid1 = uuid(); String uuid2 = uuid(); Image i1 = new Image(uuid1); Image i2 = new Image(uuid2); TagAnnotation t2 = new TagAnnotation(); i2.linkAnnotation(t2); i1 = iUpdate.saveAndReturnObject(i1); i2 = iUpdate.saveAndReturnObject(i2); iUpdate.indexObject(i1); iUpdate.indexObject(i2); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText(uuid1); search.and(); search.byFullText(uuid2); search.or(); search .byHqlQuery( "select i from Image i join fetch i.annotationLinks where i.id in (:IDLIST)", new Parameters().addList("IDLIST", Arrays .asList(new Long[] { i1.getId(), i2.getId() }))); List<IObject> output = assertResults(search, 1); Image i = (Image) output.get(0); assertTrue(i.sizeOfAnnotationLinks() > 0); } // restrictions methods // ======================================================================== // The tests in the following sections should include all the by* methods // each testing a specific restriction @Test public void testOnlyIds() { // ignored by // byTagForGroups, byGroupForTags String uuid = uuid(); Image i1 = new Image(uuid); Image i2 = new Image(uuid); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(uuid); i1.linkAnnotation(tag); i2.linkAnnotation(tag); i1 = iUpdate.saveAndReturnObject(i1); i2 = iUpdate.saveAndReturnObject(i2); tag = new TagAnnotation(); tag.setTextValue(uuid); iUpdate.indexObject(i1); iUpdate.indexObject(i2); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // Regular search // full text search.byFullText(uuid); assertResults(search, 2); // annotated with search.byAnnotatedWith(tag); assertResults(search, 2); // Restrict to one id search.onlyIds(i1.getId()); // full text search.byFullText(uuid); assertResults(search, 1); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // Restrict to both ids search.onlyIds(i1.getId(), i2.getId()); // full text search.byFullText(uuid); assertResults(search, 2); // annotated with search.byAnnotatedWith(tag); assertResults(search, 2); // Restrict to unknown ids search.onlyIds(-1L, -2L, -3L); // full text search.byFullText(uuid); assertResults(search, 0); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // unrestrict search.onlyIds((java.lang.Long[]) null); // full text search.byFullText(uuid); assertResults(search, 2); // annotated with search.byAnnotatedWith(tag); assertResults(search, 2); } @Test public void testOnlyOwnedByOwner() { Experimenter e = loginNewUser(Permissions.COLLAB_READONLY); Details user = Details.create(); user.setOwner(e); String name = uuid(); Image i = new Image(name); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(name); i.linkAnnotation(tag); TagAnnotation grp = new TagAnnotation(); grp.setTextValue(name); tag.linkAnnotation(grp); i = iUpdate.saveAndReturnObject(i); // Recreating instance as example tag = new TagAnnotation(); tag.setTextValue(name); iUpdate.indexObject(i); loginRootKeepGroup(); long id = iAdmin.getEventContext().getCurrentUserId(); Experimenter self = new Experimenter(id, false); Details root = Details.create(); root.setOwner(self); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // With no restriction it should be found. // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tag search.byGroupForTags(name); assertResults(search, 1); // Restrict only to root, and then shouldn't be found search.notOwnedBy(null); search.onlyOwnedBy(root); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); // Restrict to not root, and then should be found again. search.onlyOwnedBy(null); search.notOwnedBy(root); // full text search.byFullText(name); assertResults(search, 1); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Now restrict to the user, and again one search.notOwnedBy(null); search.onlyOwnedBy(user); // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // But not-user should return nothing search.notOwnedBy(user); search.onlyOwnedBy(null); // full text search.byFullText(name); assertResults(search, 0); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); } @Test public void testOnlyOwnedByGroup() { Experimenter e = loginNewUser(Permissions.COLLAB_READONLY); ExperimenterGroup g = new ExperimenterGroup(iAdmin.getEventContext() .getCurrentGroupId(), false); Details user = Details.create(); user.setGroup(g); String name = uuid(); Image i = new Image(name); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(name); i.linkAnnotation(tag); TagAnnotation grp = new TagAnnotation(); grp.setTextValue(name); tag.linkAnnotation(grp); i = iUpdate.saveAndReturnObject(i); // Recreating instance as example tag = new TagAnnotation(); tag.setTextValue(name); iUpdate.indexObject(i); loginRootKeepGroup(); long id = iAdmin.getEventContext().getCurrentGroupId(); ExperimenterGroup self = new ExperimenterGroup(id, false); Details root = Details.create(); root.setGroup(self); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // With no restriction it should be found. // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Restrict only to root, and then shouldn't be found search.onlyOwnedBy(root); search.notOwnedBy(null); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); // Restrict to not root, and then should be found again. search.onlyOwnedBy(null); search.notOwnedBy(root); // full text search.byFullText(name); assertResults(search, 1); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Now restrict to the user, and again one search.onlyOwnedBy(user); search.notOwnedBy(null); // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // But not-user should return nothing search.notOwnedBy(user); search.onlyOwnedBy(null); // full text search.byFullText(name); assertResults(search, 0); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); } static Timestamp oneHourAgo, inOneHour, now; static { Calendar today = Calendar.getInstance(); today.set(Calendar.HOUR, today.get(Calendar.HOUR) - 1); oneHourAgo = new Timestamp(today.getTimeInMillis()); today = Calendar.getInstance(); today.set(Calendar.HOUR, today.get(Calendar.HOUR) + 1); inOneHour = new Timestamp(today.getTimeInMillis()); now = new Timestamp(System.currentTimeMillis()); } @Test public void testOnlyCreateBetween() { String name = uuid(); Image i = new Image(); i.setName(name); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(name); i.linkAnnotation(tag); TagAnnotation grp = new TagAnnotation(); grp.setTextValue(name); tag.linkAnnotation(grp); i = iUpdate.saveAndReturnObject(i); tag = new TagAnnotation(); tag.setTextValue(name); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // Find the Image // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Now restrict the search to past search.onlyCreatedBetween(null, oneHourAgo); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); // Future search.onlyCreatedBetween(inOneHour, null); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); // 2 hour period around now search.onlyCreatedBetween(oneHourAgo, inOneHour); // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Starting at now old 'now' search.onlyCreatedBetween(null, now); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); // Open them up again and should be found search.onlyCreatedBetween(null, null); // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); } @Test public void testOnlyModifiedBetween() { // Ignored by // byTagForGroups, byGroupForTags (tags are immutable) results always 1 String name = uuid(); Image i = new Image(); i.setName(name); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(name); i.linkAnnotation(tag); TagAnnotation grp = new TagAnnotation(); grp.setTextValue(name); tag.linkAnnotation(grp); i = iUpdate.saveAndReturnObject(i); tag = new TagAnnotation(); tag.setTextValue(name); iUpdate.indexObject(i); loginRootKeepGroup(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // Find the Image // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Now restrict the search to past search.onlyModifiedBetween(null, oneHourAgo); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // Future search.onlyModifiedBetween(inOneHour, null); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // 2 hour period around now search.onlyModifiedBetween(oneHourAgo, inOneHour); // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Starting at now old 'now' search.onlyModifiedBetween(null, now); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Open them up again and should be found search.onlyModifiedBetween(null, null); // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); } @Test public void testOnlyAnnotatedBetween() { String name = uuid(); Image i = new Image(); i.setName(name); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(name); i.linkAnnotation(tag); TagAnnotation grp = new TagAnnotation(); grp.setTextValue(name); tag.linkAnnotation(grp); i = iUpdate.saveAndReturnObject(i); tag = new TagAnnotation(); tag.setTextValue(name); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // Find the Image // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Now restrict the search to past search.onlyAnnotatedBetween(null, oneHourAgo); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); // Future search.onlyAnnotatedBetween(inOneHour, null); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); // 2 hour period around now search.onlyAnnotatedBetween(oneHourAgo, inOneHour); // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); // Starting at now old 'now' search.onlyAnnotatedBetween(null, now); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // tag for group search.byTagForGroups(name); assertResults(search, 0); // group for tags search.byGroupForTags(name); assertResults(search, 0); // Open them up again and should be found search.onlyAnnotatedBetween(null, null); // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(tag); assertResults(search, 1); // tag for group search.byTagForGroups(name); assertResults(search, 1); // group for tags search.byGroupForTags(name); assertResults(search, 1); } @Test public void testOnlyAnnotatedBy() { String name = uuid(); String tag = uuid(); Image i = new Image(); i.setName(name); TagAnnotation t = new TagAnnotation(); t.setTextValue(tag); i.linkAnnotation(t); TagAnnotation grp = new TagAnnotation(); grp.setTextValue(tag); t.linkAnnotation(grp); i = iUpdate.saveAndReturnObject(i); t = new TagAnnotation(); t.setTextValue(tag); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // Find the annotation // full text search.byFullText(name); assertEquals(1, search.results().size()); // annotated with search.byAnnotatedWith(t); assertResults(search, 1); // tag for group search.byTagForGroups(tag); assertResults(search, 1); // group for tags search.byGroupForTags(tag); assertResults(search, 1); // But if we restrict it to another user, there should be none Experimenter e1 = new Experimenter(iAdmin.getEventContext() .getCurrentUserId(), false); Experimenter e2 = loginNewUserInOtherUsersGroup(e1); Details d = Details.create(); d.setOwner(e2); search.onlyAnnotatedBy(d); search.notAnnotatedBy(null); // full text search.byFullText(name); assertFalse(search.hasNext()); // annotated with search.byAnnotatedWith(t); assertResults(search, 0); // tag for group search.byTagForGroups(tag); assertResults(search, 0); // group for tags search.byGroupForTags(tag); assertResults(search, 0); // Reversing the ownership should give results search.onlyAnnotatedBy(null); search.notAnnotatedBy(d); // full text search.byFullText(name); assertResults(search, 1); // annotated with search.byAnnotatedWith(t); assertResults(search, 1); // tag for group search.byTagForGroups(tag); assertResults(search, 1); // group for tags search.byGroupForTags(tag); assertResults(search, 1); } @Test public void testOnlyAnnotatedWith() { // ignored by byAnnotatedWith // ignored by byTagForGroups, byGroupForTags String name = uuid(); Image i = new Image(); i.setName(name); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); // Search for tagged image, which shouldn't be there search.onlyAnnotatedWith(TagAnnotation.class); search.onlyType(Image.class); search.byFullText(name); assertFalse(search.hasNext()); // But if we ask for Images which aren't annotated it should appear search.onlyAnnotatedWith(new Class[] {}); search.onlyType(Image.class); search.byFullText(name); assertEquals(1, search.results().size()); // Now let's tag it and see if it shows up TagAnnotation t = new TagAnnotation(); t.setTextValue(uuid()); t = iUpdate.saveAndReturnObject(t); ImageAnnotationLink link = new ImageAnnotationLink(i, t); iUpdate.saveObject(link); iUpdate.indexObject(i); loginRoot(); // Since we're looking for "no annotations" there should be no results search.byFullText(name); assertFalse(search.hasNext()); // And if we turn the annotations back on? search.onlyAnnotatedWith(TagAnnotation.class); search.byFullText(name); assertEquals(1, search.results().size()); } @Test(groups = { "HHH-879", "broken" }) public void testOnlyAnnotatedWithMultiple() { String name = uuid(); Image onlyTag = new Image(name); Image onlyBool = new Image(name); Image both = new Image(name); TagAnnotation tag = new TagAnnotation(); tag.setTextValue("tag"); BooleanAnnotation bool = new BooleanAnnotation(); bool.setBoolValue(false); onlyTag.linkAnnotation(tag); both.linkAnnotation(tag); both.linkAnnotation(bool); onlyBool.linkAnnotation(bool); IObject[] arr = iUpdate.saveAndReturnArray(new IObject[] { onlyTag, onlyBool, both }); for (IObject object : arr) { iUpdate.indexObject(object); } loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.onlyAnnotatedWith(TagAnnotation.class); search.byFullText(name); assertEquals(2, search.results().size()); search.onlyAnnotatedWith(BooleanAnnotation.class); search.byFullText(name); assertEquals(2, search.results().size()); search.onlyAnnotatedWith(BooleanAnnotation.class, TagAnnotation.class); search.byFullText(name); assertEquals(1, search.results().size()); } // other // ========================================================================= @Test public void testMergedBatches() { String uuid1 = uuid(), uuid2 = uuid(); Image i1 = new Image(uuid1); Image i2 = new Image(uuid2); i1 = iUpdate.saveAndReturnObject(i1); i2 = iUpdate.saveAndReturnObject(i2); iUpdate.indexObject(i1); iUpdate.indexObject(i2); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText(uuid1); assertResults(search, 1); search.byFullText(uuid2); assertResults(search, 1); search.bySomeMustNone(sa(uuid1, uuid2), null, null); assertResults(search, 2); // Everything looks ok, now try with batch search.setMergedBatches(true); search.byFullText(uuid1); search.byFullText(uuid2); assertResults(search, 2); } @Test public void testOrderBy() throws Exception { String uuid = uuid(); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(uuid); Image i1 = new Image(uuid); i1.setDescription("a"); i1.linkAnnotation(tag); Image i2 = new Image(uuid); i2.setDescription("b"); i2.linkAnnotation(tag); i1 = iUpdate.saveAndReturnObject(i1); Thread.sleep(2000L); // Waiting to test creation time ordering better i2 = iUpdate.saveAndReturnObject(i2); iUpdate.indexObject(i1); iUpdate.indexObject(i2); loginRoot(); tag = new TagAnnotation(); tag.setTextValue(uuid); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // Order by description desc search.unordered(); search.addOrderByDesc("description"); // full text search.byFullText(uuid); List<String> desc = new ArrayList<String>(); desc.add(i2.getDescription()); desc.add(i1.getDescription()); while (search.hasNext()) { assertEquals(desc.remove(0), ((Image) search.next()) .getDescription()); } // annotated with search.byAnnotatedWith(tag); desc = new ArrayList<String>(); desc.add(i2.getDescription()); desc.add(i1.getDescription()); while (search.hasNext()) { assertEquals(desc.remove(0), ((Image) search.next()) .getDescription()); } // Order by descript asc search.unordered(); search.addOrderByAsc("description"); // full text search.byFullText(uuid); List<String> asc = new ArrayList<String>(); asc.add(i1.getDescription()); asc.add(i2.getDescription()); while (search.hasNext()) { assertEquals(asc.remove(0), ((Image) search.next()) .getDescription()); } // annotated with search.byAnnotatedWith(tag); asc = new ArrayList<String>(); asc.add(i1.getDescription()); asc.add(i2.getDescription()); while (search.hasNext()) { assertEquals(asc.remove(0), ((Image) search.next()) .getDescription()); } // Ordered by id search.unordered(); search.addOrderByDesc("id"); // full text search.byFullText(uuid); List<Long> ids = new ArrayList<Long>(); ids.add(i2.getId()); ids.add(i1.getId()); while (search.hasNext()) { assertEquals(ids.remove(0), search.next().getId()); } // annotated with search.byAnnotatedWith(tag); ids = new ArrayList<Long>(); ids.add(i2.getId()); ids.add(i1.getId()); while (search.hasNext()) { assertEquals(ids.remove(0), search.next().getId()); } // Ordered by creation event id search.unordered(); search.addOrderByDesc("details.creationEvent.id"); // full text search.byFullText(uuid); ids = new ArrayList<Long>(); ids.add(i2.getId()); ids.add(i1.getId()); while (search.hasNext()) { assertEquals(ids.remove(0), search.next().getId()); } // annotated with search.byAnnotatedWith(tag); ids = new ArrayList<Long>(); ids.add(i2.getId()); ids.add(i1.getId()); while (search.hasNext()) { assertEquals(ids.remove(0), search.next().getId()); } // ordered by creation event time search.unordered(); search.addOrderByDesc("details.creationEvent.time"); // full text search.byFullText(uuid); ids = new ArrayList<Long>(); ids.add(i2.getId()); ids.add(i1.getId()); while (search.hasNext()) { assertEquals(ids.remove(0), search.next().getId()); } // annotated with search.byAnnotatedWith(tag); ids = new ArrayList<Long>(); ids.add(i2.getId()); ids.add(i1.getId()); while (search.hasNext()) { assertEquals(ids.remove(0), search.next().getId()); } // To test multiple sort fields, we add another image with an "a" // description, which should could before the other image with the "a" // description if we reverse the id order Image i3 = new Image(uuid); i3.setDescription("a"); i3.linkAnnotation(tag); i3 = iUpdate.saveAndReturnObject(i3); iUpdate.indexObject(i3); loginRoot(); tag = new TagAnnotation(); tag.setTextValue(uuid); // multi-ordering search.unordered(); search.addOrderByAsc("description"); search.addOrderByDesc("id"); // annotated with search.byAnnotatedWith(tag); List<Long> multi = new ArrayList<Long>(); multi.add(i3.getId()); multi.add(i1.getId()); multi.add(i2.getId()); while (search.hasNext()) { assertEquals(multi.remove(0), search.next().getId()); } // full text search.byFullText(uuid); multi = new ArrayList<Long>(); multi.add(i3.getId()); multi.add(i1.getId()); multi.add(i2.getId()); while (search.hasNext()) { assertEquals(multi.remove(0), search.next().getId()); } } @Test public void testFetchAnnotations() { String uuid = uuid(); Image i = new Image(uuid); TagAnnotation tag = new TagAnnotation(); tag.setTextValue(uuid); LongAnnotation la = new LongAnnotation(); la.setLongValue(1L); DoubleAnnotation da = new DoubleAnnotation(); da.setDoubleValue(0.0); i.linkAnnotation(tag); i.linkAnnotation(la); i.linkAnnotation(da); i = iUpdate.saveAndReturnObject(i); tag = new TagAnnotation(); tag.setTextValue(uuid); iUpdate.indexObject(i); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // No fetch returns empty annotations // full text search.byFullText(uuid); Image t = (Image) search.results().get(0); assertEquals(-1, t.sizeOfAnnotationLinks()); // annotated with search.byAnnotatedWith(tag); t = (Image) search.results().get(0); assertEquals(-1, t.sizeOfAnnotationLinks()); // Fetch only a given type search.fetchAnnotations(TagAnnotation.class); // annotated with search.byAnnotatedWith(tag); t = (Image) search.results().get(0); assertEquals(1, t.sizeOfAnnotationLinks()); // full text search.byFullText(uuid); t = (Image) search.results().get(0); assertEquals(3, t.sizeOfAnnotationLinks()); // fetch only a given type different from annotated-with type search.fetchAnnotations(DoubleAnnotation.class); // annotated with search.byAnnotatedWith(tag); t = (Image) search.results().get(0); assertEquals(1, t.sizeOfAnnotationLinks()); // full text search.byFullText(uuid); t = (Image) search.results().get(0); assertEquals(3, t.sizeOfAnnotationLinks()); // fetch two types search.fetchAnnotations(TagAnnotation.class, DoubleAnnotation.class); // annotated with search.byAnnotatedWith(tag); t = (Image) search.results().get(0); assertEquals(2, t.sizeOfAnnotationLinks()); // full text search.byFullText(uuid); t = (Image) search.results().get(0); assertEquals(3, t.sizeOfAnnotationLinks()); // Fetch all search.fetchAnnotations(Annotation.class); // annotated with search.byAnnotatedWith(tag); assertResults(search, 0); // TODO t = (Image) search.results().get(0); // TODO assertEquals(3, t.sizeOfAnnotationLinks()); // full text search.byFullText(uuid); t = (Image) search.results().get(0); assertEquals(3, t.sizeOfAnnotationLinks()); // resave and see if there is data loss search.fetchAnnotations(TagAnnotation.class); search.byAnnotatedWith(tag); t = (Image) search.next(); FileAnnotation f = new FileAnnotation(); t.linkAnnotation(f); iUpdate.saveObject(t); t = iQuery .findByQuery( "select t from Image t join fetch t.annotationLinks where t.id = :id", new Parameters().addId(t.getId())); assertEquals(4, t.sizeOfAnnotationLinks()); } @Test public void testFetchAlso() { fail("NYI"); } // bugs and particular issues // ========================================================================= // The general reader may want to stop reading at this point. @Test public void testCommentAnnotationDoesntTryToLoadUpdateEvent() { String uuid = uuid(); CommentAnnotation ta = new CommentAnnotation(); ta.setTextValue(uuid); ta = iUpdate.saveAndReturnObject(ta); iUpdate.indexObject(ta); loginRoot(); Search search = this.factory.createSearchService(); search.onlyType(CommentAnnotation.class); search.byFullText(uuid); assertResults(search, 1); } @Test public void testExperimenterDoesntTryToLoadOwner() { Search search = this.factory.createSearchService(); search.onlyType(Experimenter.class); search.byFullText("root"); assertAtLeastResults(search, 1); } @Test public void testLookingForExperimenterWithOwner() { Search search = this.factory.createSearchService(); search.onlyType(Experimenter.class); // Just root should work search.byFullText("root"); search.next(); // And filtered on "owner" (experimenter has none) should work, too. Details d = Details.create(); d.setOwner(new Experimenter(0L, false)); search.onlyOwnedBy(d); search.byFullText("root"); search.next(); } @Test(groups = "ticket:897", expectedExceptions = ApiUsageException.class) public void testLeadingQuestionMarkAlsoNotAllowed() { final String query = "?oo"; Search search = this.factory.createSearchService(); search.onlyType(Image.class); // search.setAllowLeadingWildcard(true); search.byFullText(query); fail("This should not be reached"); } @Test(groups = "ticket:897", expectedExceptions = ApiUsageException.class) public void testOnlyWildcardThrowsException() { // This seems only to be caused by a leading "*" and not a // leading "?" final String query = "*"; Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.setAllowLeadingWildcard(true); search.byFullText(query); fail("This should not be reached"); } /** * Attempting to make a lone "*" expand in other situations. */ @Test(groups = "ticket:897") public void testWildcardWithTerm() { final String query = "* term"; Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.setAllowLeadingWildcard(true); search.byFullText(query); // Seems to be ok. } /** * This was a first test for #975 which always passed. */ @Test(groups = "ticket:975") public void testImagesAndTagsReturnedSimple() { Image i = new Image("annotation"); TagAnnotation tag = new TagAnnotation(); tag.setTextValue("annotation"); i.linkAnnotation(tag); IUpdate update = this.factory.getUpdateService(); i = update.saveAndReturnObject(i); update.indexObject(i); Search search = this.factory.createSearchService(); search = this.factory.createSearchService(); search.onlyType(Image.class); search.bySomeMustNone(new String[] { "an*" }, null, null); boolean found = false; if (search.hasNext()) { List<IObject> l = search.results(); for (IObject object : l) { assertTrue("Must be an image", object instanceof Image); if (object.getId().equals(i.getId())) { found = true; } } } else { fail("Should have had results."); } assertTrue("Must be found", found); } /** * This displays the error that Jean-Marie was actually seeing. */ @Test(groups = "ticket:975") public void testImagesAndTagsReturnedAccurate() { Image i = new Image("annotation"); IUpdate update = this.factory.getUpdateService(); i = update.saveAndReturnObject(i); update.indexObject(i); i = new Image("foo"); TagAnnotation tag = new TagAnnotation(); tag.setTextValue("annotation"); i.linkAnnotation(tag); i = update.saveAndReturnObject(i); update.indexObject(i); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.bySomeMustNone(new String[] { "an*" }, null, null); for (IObject test : search.results()) { assertTrue(test.toString(), test instanceof Image); } Class[] klass = new Class[1]; klass[0] = TagAnnotation.class; search.onlyAnnotatedWith(klass); search.onlyType(Image.class); search.bySomeMustNone(new String[] { "an*" }, null, null); List<IObject> results = assertContainsObject(search, i); for (IObject test : results) { assertTrue(test.toString(), test instanceof Image); } } /** * Checking for a security leak due to this issue. */ @Test(groups = "ticket:975") public void testImagesAndTagsReturnedMultiuser() { final List<Long> ids = new ArrayList<Long>(); final IUpdate update = this.factory.getUpdateService(); // Save a public image Experimenter user1 = loginNewUser(Permissions.COLLAB_READONLY); Image i = new Image("foo"); i = update.saveAndReturnObject(i); update.indexObject(i); ids.add(i.getId()); // Create a private tag on the image Experimenter user2 = loginNewUserInOtherUsersGroup(user1); i = reloadImageWithAnnotationLinks(i); TagAnnotation tag = new TagAnnotation(); tag.setTextValue("annotation"); tag.getDetails().setPermissions(Permissions.USER_PRIVATE); i.linkAnnotation(tag); i = update.saveAndReturnObject(i); update.indexObject(i); // Return to first user and see if it sees the TagAnnotation loginUser(user1.getOmeName()); Search search = this.factory.createSearchService(); Class[] klass = new Class[1]; klass[0] = TagAnnotation.class; search.onlyAnnotatedWith(klass); search.onlyType(Image.class); search.bySomeMustNone(new String[] { "an*" }, null, null); for (IObject test : search.results()) { assertTrue(test.toString(), test instanceof Image); ids.remove(test.getId()); } assertTrue(ids + " should be empty", ids.size() == 0); } /** * Attempts to solve #975 by using the {@link Search#or()} method. */ @Test(groups = "ticket:975") public void testImagesAndTagsReturnedIntersection() { IUpdate update = this.factory.getUpdateService(); Image i = new Image("foo"); TagAnnotation tag = new TagAnnotation(); tag.setTextValue("annotation"); i.linkAnnotation(tag); i = update.saveAndReturnObject(i); update.indexObject(i); Search search = this.factory.createSearchService(); search.onlyType(Image.class); String[] q = new String[] { "an*" }; // checking manually search.bySomeMustNone(q, null, null); assertContainsObject(search, i); search.byAnnotatedWith(new TagAnnotation()); assertContainsObject(search, i); // checking via intersection search.bySomeMustNone(q, null, null); search.and(); search.byAnnotatedWith(new TagAnnotation()); assertContainsObject(search, i); } /** * Attempts to solve #975 by using the {@link Search#or()} method. */ @Test(groups = "ticket:975") public void testImagesAndTagsReturnedMultipleIntersection() { IUpdate update = this.factory.getUpdateService(); Image i = new Image("foo"); TagAnnotation tag = new TagAnnotation(); tag.setTextValue("annotation"); FileAnnotation file = new FileAnnotation(); i.linkAnnotation(tag); i.linkAnnotation(file); i = update.saveAndReturnObject(i); update.indexObject(i); Search search = this.factory.createSearchService(); search.onlyType(Image.class); String[] q = new String[] { "an*" }; // checking manually search.bySomeMustNone(q, null, null); assertContainsObject(search, i); search.byAnnotatedWith(new TagAnnotation(), new FileAnnotation()); assertContainsObject(search, i); // checking via intersection search.bySomeMustNone(q, null, null); search.and(); search.byAnnotatedWith(new TagAnnotation(), new FileAnnotation()); assertContainsObject(search, i); } /** * Checking for a security leak due to this issue when using union. */ @Test(groups = "ticket:975") public void testImagesAndTagsReturnedMultiuserIntersection() { final List<Long> ids = new ArrayList<Long>(); final IUpdate update = this.factory.getUpdateService(); // Save a public image Experimenter user1 = loginNewUser(); Image i = new Image("foo"); i = update.saveAndReturnObject(i); update.indexObject(i); ids.add(i.getId()); // Create a private tag on the image Experimenter user2 = loginNewUserInOtherUsersGroup(user1); i = reloadImageWithAnnotationLinks(i); TagAnnotation tag = new TagAnnotation(); tag.setTextValue("annotation"); tag.getDetails().setPermissions(Permissions.USER_PRIVATE); i.linkAnnotation(tag); i = update.saveAndReturnObject(i); update.indexObject(i); // Return to first user and see if it sees the TagAnnotation loginUser(user1.getOmeName()); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.bySomeMustNone(new String[] { "an*" }, null, null); search.and(); search.byAnnotatedWith(new TagAnnotation()); for (IObject test : search.results()) { assertTrue(test.toString(), test instanceof Image); ids.remove(test.getId()); } assertTrue(ids + " should be empty", ids.size() == 0); } /** * Checking for a security leak due to this issue when using union. */ @Test(groups = "ticket:975") public void testCriteriaSearchToReproduceSecurityViolation() { Executor ex = (Executor) this.applicationContext.getBean("executor"); Experimenter user1 = loginNewUser(); Image i = new Image("user1"); i = this.iUpdate.saveAndReturnObject(i); Experimenter user2 = loginNewUser(); TagAnnotation tag = new TagAnnotation(); tag.setTextValue("tag"); i = reloadImageWithAnnotationLinks(i); tag.getDetails().setPermissions(Permissions.USER_PRIVATE); tag = this.iUpdate.saveAndReturnObject(tag); loginUser(user1.getOmeName()); String uuid = this.iAdmin.getEventContext().getCurrentSessionUuid(); Principal p = new Principal(uuid, "user", "Test"); ex.execute(p, new Executor.SimpleWork(this, "reproduce sec vio") { @Transactional(readOnly=true) public Object doWork(Session session, ServiceFactory sf) { Criteria c = session.createCriteria(Image.class); Criteria links = c.createCriteria("annotationLinks"); Criteria ann = links.createCriteria("child"); ann.add(Restrictions.eq("textValue", "tag")); c.list(); return null; } }); fail("Surprising to reach here"); } @Test(groups = "ticket:995") public void testOnlyOwnedByReturnsWrongContent() { // Create user which will own the image. String uuid = uuid(); Experimenter owner = loginNewUser(Permissions.COLLAB_READONLY); Details d_owner = Details.create(); d_owner.setOwner(owner); // Add an image as the owner Image i = new Image(); i.setName("Some text " + uuid + " blah blah"); i = this.iUpdate.saveAndReturnObject(i); loginRoot(); this.iUpdate.indexObject(i); // Now login as another user who doesn't own that image Experimenter searcher = loginNewUserInOtherUsersGroup(owner); Details d_searcher = Details.create(); d_searcher.setOwner(searcher); Search search = this.factory.createSearchService(); search.setAllowLeadingWildcard(true); search.onlyType(Image.class); // We shouldn't find any results assertTicket955(search, uuid, d_searcher, 0); // Now let's change to the owner and see the results assertTicket955(search, uuid, d_owner, 1); // Now let's add annotations and similar and see the results loginUser(owner.getOmeName()); ImageAnnotationLink link = new ImageAnnotationLink(); link.setParent(new Image(i.getId(), false)); link.setChild(new TagAnnotation()); this.iUpdate.saveObject(link); loginRoot(); this.iUpdate.indexObject(i); loginUser(searcher.getOmeName()); // We stil shouldn't find any results assertTicket955(search, uuid, d_searcher, 0); // Now let's change to the owner and see the results assertTicket955(search, uuid, d_owner, 1); // Even searching as the owner should produce the same results loginUser(owner.getOmeName()); assertTicket955(search, uuid, d_searcher, 0); assertTicket955(search, uuid, d_owner, 1); } private void assertTicket955(Search search, String uuid, Details d, int n) { search.onlyOwnedBy(d); search.bySomeMustNone(new String[] { "*" + uuid + "*" }, null, null); assertResults(search, n); search.bySomeMustNone(new String[] { "*blah blah*" }, null, null); assertResults(search, n); } @Test public void testAddingTermAnnotation() throws Exception { String uuid = uuid(); Image i = new Image("name"); TermAnnotation term = new TermAnnotation(); term.setTermValue("go:" + uuid); i.linkAnnotation(term); loginRoot(); i = this.iUpdate.saveAndReturnObject(i); term = (TermAnnotation) i.linkedAnnotationList().get(0); this.iUpdate.indexObject(i); this.iUpdate.indexObject(term); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText("term:" + uuid); assertResults(search, 1); search.onlyType(Image.class); search.byFullText("go:" + uuid); assertResults(search, 1); } @Test public void testFileAnnotationIsFindableByFileName() throws Exception { String uuid = uuid(); Image i = new Image("name"); FileAnnotation fa = new FileAnnotation(); FileUploader uploader = new FileUploader(this.factory, "my-text", uuid, "/dev/null"); uploader.run(); fa.setFile(new OriginalFile(uploader.getId(), false)); i.linkAnnotation(fa); loginRoot(); i = this.iUpdate.saveAndReturnObject(i); fa = (FileAnnotation) i.linkedAnnotationList().get(0); this.iUpdate.indexObject(i); this.iUpdate.indexObject(fa); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText("file.name:" + uuid); assertResults(search, 1); search.onlyType(FileAnnotation.class); search.byFullText("file.name:" + uuid); assertResults(search, 1); } @Test public void testAttachingAnnotationAfterTheFact() throws Exception { String name = uuid(); String tag = uuid(); Image i = new Image(name); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); Search search = this.factory.createSearchService(); search.onlyType(Image.class); search.byFullText(name); assertResults(search, 1); TagAnnotation ta = new TagAnnotation(); ta.setTextValue(tag); ImageAnnotationLink link = new ImageAnnotationLink(new Image(i.getId(), false), ta); link = iUpdate.saveAndReturnObject(link); iUpdate.indexObject(link); // This indexing should cause another object be added to // PersistentEventLogLoader // For that to work we need to run FullTextThread once. ((Runnable) this.applicationContext.getBean("fullTextThread")).run(); search.byFullText(tag); assertResults(search, 1); } @Test public void testNewTokenizerTokenizesQueryAsExpected() throws Exception { // Setup String uuid = uuid().replaceAll("DASH", "-"); String[] parts = uuid.split("-"); String rejoined = parts[1] + "-" + parts[3] + "-" + parts[2]; Image image1 = new Image(uuid); Image image2 = new Image(rejoined); image1 = iUpdate.saveAndReturnObject(image1); image2 = iUpdate.saveAndReturnObject(image2); iUpdate.indexObject(image1); iUpdate.indexObject(image2); Search search = factory.createSearchService(); search.onlyType(Image.class); // Searching by one part should return both search.byFullText(parts[1]); assertResults(search, 2); // Searching by parts separated with a space should also return both // They are implicitly joined with "OR" search.byFullText(parts[1] + " " + parts[3]); assertResults(search, 2); // If the terms are joined by a hyphen, it will get stripped, but the // term remains one. Equivalent to "PART1 PART2" search.byFullText(parts[1] + "-" + parts[3]); assertResults(search, 1); search.byFullText("\"" + parts[1] + " " + parts[3] + "\""); assertResults(search, 1); // Search by uuid and should only find one. search.byFullText(uuid); assertResults(search, 1); } @Test(groups = "shoola:ticket:663") public void testParseException() throws Exception { String query = "(tag:100x, OR tag:aurora) +tag:eg5,+tag:jj99 -tag:ph3"; String name = uuid(); String tag = "aurora"; Image i = new Image(name); TagAnnotation aurora = new TagAnnotation(); aurora.setTextValue("aurora"); TagAnnotation jj99 = new TagAnnotation(); jj99.setTextValue("jj99"); TagAnnotation eg5 = new TagAnnotation(); eg5.setTextValue("eg5"); i.linkAnnotation(aurora); i.linkAnnotation(jj99); i.linkAnnotation(eg5); i = iUpdate.saveAndReturnObject(i); iUpdate.indexObject(i); Search search = this.factory.createSearchService(); search.onlyType(Image.class); // This string is corrupt, but it came from someMustNone try { search.byFullText(query); } catch (Exception e) { // Known problem. } search.bySomeMustNone(new String[] { "tag:100x", "tag:aurora" }, new String[] { "tag:eg5", "tag:jj99" }, new String[] { "tag:ph3" }); assertAtLeastResults(search, 1); } // Implementation specifics // ========================================================================= @Test public void testSerialization() throws Exception { Search search = this.factory.createSearchService(); search.onlyType(Experimenter.class); search.byFullText("root"); search.hasNext(); Search internal = search; int count = 0; while (internal instanceof Advised) { count++; if (count > 100) { throw new RuntimeException("Something's funky"); } internal = (Search) ((Advised) search).getTargetSource() .getTarget(); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(internal); byte[] array = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(array); ObjectInputStream ois = new ObjectInputStream(bais); internal = (Search) ois.readObject(); assertAtLeastResults(search, 1); } // Helpers // ========================================================================= List<IObject> assertResults(Search search, int k) { if (k == 0) { assertFalse(search.hasNext()); return null; } else { List<IObject> output = new ArrayList<IObject>(); while (search.hasNext()) { output.addAll(search.results()); } assertEquals(k, output.size()); return output; } } void assertAtLeastResults(Search search, int k) { assertTrue(search.results().size() >= k); // Clearing possible overflowing values. while (search.hasNext()) { search.results(); } } List<IObject> assertContainsObject(Search search, IObject test) { // Because of the weird subclassing issues, we use reflection here // to obtain a "true" proxy of this object, and then compare // against that. test = proxy(test); boolean found = false; List<IObject> results = new ArrayList<IObject>(); while (search.hasNext()) { for (IObject obj : search.results()) { if (test.getClass().isAssignableFrom(obj.getClass())) { if (obj.getId().equals(test.getId())) { found = true; } } else { results.add(obj); } } } assertTrue(test + " not found in results:" + results, found); return results; } private IObject proxy(IObject test) { try { return (IObject) test.getClass().getMethod("proxy").invoke(test); } catch (Exception e) { fail("Could not obtain a proxy for:" + test); } return null; } private Image reloadImageWithAnnotationLinks(Image i) { i = this.factory.getQueryService() .findByQuery( "select i from Image i " + "left outer join fetch i.annotationLinks " + "where i.id = :id", new Parameters().addId(i.getId())); return i; } }