Changeset 2387
- Timestamp:
- 05/20/08 14:33:21 (7 months ago)
- 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 437 437 bool isAllowLeadingWildcard() throws ServerError; 438 438 439 439 440 // Filters ~~~~~~~~~~~~~~~~~~~~~~ 440 441 … … 452 453 void onlyAnnotatedWith(StringSet classes) throws ServerError; 453 454 455 454 456 // Fetches, order, counts, etc ~~~~~~~~~~~~~~~~~~~~~~ 455 457 … … 460 462 void fetchAlso(StringSet fetches) throws ServerError; 461 463 464 462 465 // Reset ~~~~~~~~~~~~~~~~~~~~~~~~~ 463 466 464 467 void resetDefaults() throws ServerError; 465 468 469 466 470 // Query state ~~~~~~~~~~~~~~~~~~~~~~~~~ 471 467 472 void byGroupForTags(string group) throws ServerError; 468 473 void byTagForGroups(string tag) throws ServerError; 469 474 void byFullText(string query) throws ServerError; 475 void byHqlQuery(string query, omero::sys::Parameters params) throws ServerError; 470 476 void bySomeMustNone(StringSet some, StringSet must, StringSet none) throws ServerError; 471 477 void byAnnotatedWith(AnnotationList examples) throws ServerError; 472 478 void clearQueries() throws ServerError; 473 479 480 void union() throws ServerError; 481 void intersection() throws ServerError; 482 void complement() throws ServerError; 483 484 474 485 // Retrieval ~~~~~~~~~~~~~~~~~~~~~~~~~ 486 475 487 bool hasNext() throws ServerError; 476 488 omero::model::IObject next() throws ServerError; 477 489 IObjectList results() throws ServerError; 478 490 479 491 // Currently unused 480 492 SearchMetadata currentMetadata() throws ServerError; 481 493 SearchMetadataList currentMetadataList() throws ServerError; 482 494 483 495 // Unused; Part of Java Iterator interface 484 496 void remove() throws ServerError; -
trunk/components/common/src/ome/api/IQuery.java
r2035 r2387 214 214 * query has proper permissions. 215 215 * 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 * 216 236 * If a {@link Page} is desired, add it to the query parameters. 217 237 * -
trunk/components/common/src/ome/api/Search.java
r2327 r2387 457 457 458 458 /** 459 * Provides the main {@link IQuery} method here to take advantage of the460 * various filters which are provided. See461 * {@link IQuery#findAllByQuery(String, Parameters)} for thesemantics.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. 462 462 * 463 463 * @param query … … 467 467 * @see IQuery#findAllByQuery(String, Parameters) 468 468 */ 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 474 471 /** 475 472 * Finds entities annotated with an {@link Annotation} similar to the … … 481 478 * {@link OriginalFile#getId()}, etc.</li> 482 479 * </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> 483 485 * 484 486 * @param examples … … 486 488 */ 487 489 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("foo"); 501 * service.union(); 502 * service.onlyType(Dataset.class); 503 * service.byFullText("foo"); 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("foo"); 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("foo"); 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(); 488 563 489 564 /** -
trunk/components/server/src/ome/services/SearchBean.java
r2327 r2387 8 8 package ome.services; 9 9 10 import java.io.Serializable; 10 11 import java.sql.Timestamp; 11 12 import java.util.ArrayList; 12 13 import java.util.Arrays; 13 import java.util.Collections;14 14 import java.util.List; 15 15 import java.util.Map; … … 31 31 import ome.api.ServiceInterface; 32 32 import ome.conditions.ApiUsageException; 33 import ome.conditions.InternalException; 33 34 import ome.model.IObject; 34 35 import ome.model.annotations.Annotation; … … 36 37 import ome.parameters.Parameters; 37 38 import ome.services.search.AnnotatedWith; 39 import ome.services.search.Complement; 38 40 import ome.services.search.FullText; 39 41 import ome.services.search.HqlQuery; 42 import ome.services.search.Intersection; 40 43 import ome.services.search.SearchAction; 41 44 import ome.services.search.SearchValues; 42 45 import ome.services.search.SomeMustNone; 43 46 import ome.services.search.TagsAndGroups; 47 import ome.services.search.Union; 44 48 import ome.services.util.Executor; 45 49 import ome.services.util.OmeroAroundInvoke; … … 73 77 private final static Log log = LogFactory.getLog(SearchBean.class); 74 78 75 private final List<SearchAction> actions = Collections 76 .synchronizedList(new ArrayList<SearchAction>()); 79 private final ActionList actions = new ActionList(); 77 80 78 81 private final SearchValues values = new SearchValues(); … … 212 215 } 213 216 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 214 237 // 215 238 // FETCH METHODS … … 233 256 return false; 234 257 } 235 SearchAction action = actions. remove(0);258 SearchAction action = actions.popFirst(); 236 259 List<IObject> list = (List<IObject>) executor.execute(null, action); 237 260 results.add(list); … … 642 665 } 643 666 } 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 } 644 749 } -
trunk/components/server/src/ome/services/search/AnnotatedWith.java
r2259 r2387 18 18 import ome.model.annotations.FileAnnotation; 19 19 import ome.model.annotations.LongAnnotation; 20 import ome.model.annotations.QueryAnnotation; 21 import ome.model.annotations.TagAnnotation; 20 22 import ome.model.annotations.TextAnnotation; 21 23 import ome.model.annotations.ThumbnailAnnotation; 22 24 import ome.model.annotations.TimestampAnnotation; 25 import ome.model.annotations.UrlAnnotation; 26 import ome.model.annotations.XmlAnnotation; 23 27 import ome.model.core.OriginalFile; 24 28 import ome.model.display.Thumbnail; … … 107 111 for (int i = 0; i < annotation.length; i++) { 108 112 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 } 110 125 type[i] = String.class; 111 126 path[i] = "textValue"; … … 145 160 + annotation); 146 161 } 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. 148 166 for (Class ac : values.fetchAnnotations) { 149 167 if (annCls[i].isAssignableFrom(ac)) { … … 152 170 } 153 171 } 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 } 154 179 } 155 180 … … 165 190 qb.select("this"); 166 191 qb.from(cls.getName(), "this"); 192 193 // Joins 167 194 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 } 172 201 } 173 202 … … 191 220 // Main criteria 192 221 if (useNamespace) { 193 notNullOrLikeOrEqual(qb, ann + ".n ame", type[j], annotation[j]222 notNullOrLikeOrEqual(qb, ann + ".ns", type[j], annotation[j] 194 223 .getNs(), useLike, values.caseSensitive); 195 224 } 196 225 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 } 199 235 200 236 annotatedBetween(qb, ann[j] + "."); -
trunk/components/server/src/ome/services/search/FullText.java
r2385 r2387 8 8 package ome.services.search; 9 9 10 import java.util.List; 11 10 12 import ome.conditions.ApiUsageException; 13 import ome.conditions.SecurityViolation; 11 14 import ome.model.IAnnotated; 12 15 import ome.services.SearchBean; … … 19 22 import org.apache.lucene.search.SortField; 20 23 import org.hibernate.Criteria; 21 import org.hibernate.Query;22 import org.hibernate.ScrollableResults;23 24 import org.hibernate.Session; 24 25 import org.hibernate.criterion.Restrictions; … … 30 31 31 32 /** 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. 33 35 * 34 36 * @author Josh Moore, josh at glencoesoftware.com … … 37 39 public class FullText extends SearchAction { 38 40 39 private final static QueryParser parser = new QueryParser(40 "combined_fields", new StandardAnalyzer());41 42 41 private static final long serialVersionUID = 1L; 43 42 44 43 private final String queryStr; 45 44 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; 53 46 54 47 public FullText(SearchValues values, String query) { … … 79 72 this.queryStr = query; 80 73 try { 74 final QueryParser parser = new QueryParser("combined_fields", 75 new StandardAnalyzer()); 81 76 parser.setAllowLeadingWildcard(values.leadingWildcard); 82 77 q = parser.parse(queryStr); … … 88 83 } 89 84 90 public Object doWork(TransactionStatus status, Session session, 91 ServiceFactory sf) { 85 public Object doWork(TransactionStatus status, Session s, ServiceFactory sf) { 92 86 93 Classcls = values.onlyTypes.get(0);87 final Class<?> cls = values.onlyTypes.get(0); 94 88 95 this.session = Search.createFullTextSession(session);89 FullTextSession session = Search.createFullTextSession(s); 96 90 Criteria criteria = session.createCriteria(cls); 97 91 AnnotationCriteria ann = new AnnotationCriteria(criteria, … … 118 112 return null; // EARLY EXIT ! 119 113 } else { 120 for (Class annCls : values.onlyAnnotatedWith) {114 for (Class<?> annCls : values.onlyAnnotatedWith) { 121 115 SimpleExpression ofType = new TypeEqualityExpression( 122 116 "class", annCls); … … 130 124 131 125 // Main query 132 FullTextQuery ftQuery = this.session.createFullTextQuery(this.q);126 FullTextQuery ftQuery = session.createFullTextQuery(this.q, cls); 133 127 ftQuery.setCriteriaQuery(criteria); 134 128 … … 151 145 } 152 146 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; 155 164 } 156 157 165 } -
trunk/components/server/src/ome/services/search/HqlQuery.java
r2098 r2387 8 8 package ome.services.search; 9 9 10 import ome.api.IQuery; 10 11 import ome.parameters.Parameters; 11 import ome.services.SearchBean;12 12 import ome.system.ServiceFactory; 13 13 … … 16 16 17 17 /** 18 * Query template used by {@link SearchBean} to store user requests.18 * Delegate to {@link IQuery#findAllByQuery(String, Parameters)}. 19 19 * 20 20 * @author Josh Moore, josh at glencoesoftware.com -
trunk/components/server/src/ome/services/search/SearchAction.java
r2386 r2387 8 8 package ome.services.search; 9 9 10 import java.io.Serializable; 10 11 import java.util.List; 11 12 … … 36 37 * @since 3.0-Beta3 37 38 */ 38 public abstract class SearchAction implements ome.services.util.Executor.Work { 39 public abstract class SearchAction implements Serializable, 40 ome.services.util.Executor.Work { 39 41 40 42 protected final SearchValues values = new SearchValues(); … … 46 48 } 47 49 this.values.copy(values); 50 } 51 52
