View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.elasticsearch.index.objects.event;
23  
24  import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ADMIN_ROLE;
25  
26  import org.opencastproject.elasticsearch.impl.AbstractSearchQuery;
27  import org.opencastproject.elasticsearch.impl.IndexSchema;
28  import org.opencastproject.security.api.Permissions;
29  import org.opencastproject.security.api.Permissions.Action;
30  import org.opencastproject.security.api.User;
31  import org.opencastproject.util.requests.SortCriterion.Order;
32  
33  import org.apache.commons.lang3.StringUtils;
34  
35  import java.util.ArrayList;
36  import java.util.Date;
37  import java.util.HashSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  
42  /**
43   * This interface defines a fluent api for a query object used to lookup recording events in the search index.
44   */
45  public class EventSearchQuery extends AbstractSearchQuery {
46  
47    private final List<String> identifiers = new ArrayList<String>();
48    private String organization = null;
49    private User user = null;
50    private String title = null;
51    private String description = null;
52    private final Set<String> actions = new HashSet<String>();
53    private final List<String> presenters = new ArrayList<String>();
54    private final List<String> contributors = new ArrayList<String>();
55    private String subject = null;
56    private String location = null;
57    private String seriesId = null;
58    private String seriesName = null;
59    private String language = null;
60    private String source = null;
61    private String created = null;
62    private Date startFrom = null;
63    private Date startTo = null;
64    private Date technicalStartFrom = null;
65    private Date technicalStartTo = null;
66    private String creator = null;
67    private String publisher = null;
68    private String license = null;
69    private String rights = null;
70    private String accessPolicy = null;
71    private String managedAcl = null;
72    private String workflowState = null;
73    private Long workflowId = null;
74    private String workflowDefinition = null;
75    private Long duration = null;
76    private String startDate = null;
77    private String eventStatus = null;
78    private Boolean hasComments = null;
79    private Boolean hasOpenComments = null;
80    private final List<String> comments = new ArrayList<>();
81    private Boolean needsCutting = null;
82    private final List<String> publications = new ArrayList<String>();
83    private Boolean isPublished = null;
84    private Long archiveVersion = null;
85    private String agentId = null;
86    private Date technicalStartTime = null;
87    private Date technicalEndTime = null;
88    private final List<String> technicalPresenters = new ArrayList<String>();
89  
90    private static final Map<String, String> SORT_FIELDS = Map.of(
91            EventIndexSchema.TITLE, EventIndexSchema.TITLE.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION),
92            EventIndexSchema.CONTRIBUTOR, EventIndexSchema.CONTRIBUTOR.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION),
93            EventIndexSchema.PRESENTER, EventIndexSchema.PRESENTER.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION),
94            EventIndexSchema.SUBJECT, EventIndexSchema.SUBJECT.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION),
95            EventIndexSchema.DESCRIPTION, EventIndexSchema.DESCRIPTION.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION),
96            EventIndexSchema.LOCATION, EventIndexSchema.LOCATION.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION),
97            EventIndexSchema.SERIES_NAME, EventIndexSchema.SERIES_NAME.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION),
98            EventIndexSchema.CREATOR, EventIndexSchema.CREATOR.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION),
99            EventIndexSchema.PUBLISHER, EventIndexSchema.PUBLISHER.concat(IndexSchema.SORT_FIELD_NAME_EXTENSION)
100   );
101 
102   @SuppressWarnings("unused")
103   private EventSearchQuery() {
104   }
105 
106   @Override
107   protected String sortOrderFieldName(String field) {
108     if (SORT_FIELDS.containsKey(field)) {
109       return SORT_FIELDS.get(field);
110     }
111     return field;
112   }
113 
114   /**
115    * Creates a query that will return event documents.
116    */
117   public EventSearchQuery(String organization, User user) {
118     super(Event.DOCUMENT_TYPE);
119     this.organization = organization;
120     this.user = user;
121     this.actions.add(Permissions.Action.READ.toString());
122     if (!user.hasRole(GLOBAL_ADMIN_ROLE)) {
123       if (!user.getOrganization().getId().equals(organization)) {
124         throw new IllegalStateException("User's organization must match search organization");
125       }
126     }
127   }
128 
129   /**
130    * Selects recording events with the given identifier.
131    * <p>
132    * Note that this method may be called multiple times to support selection of multiple recording events.
133    *
134    * @param id
135    *          the recording identifier
136    * @return the enhanced search query
137    */
138   public EventSearchQuery withIdentifier(String id) {
139     if (StringUtils.isBlank(id)) {
140       throw new IllegalArgumentException("Identifier cannot be null");
141     }
142     this.identifiers.add(id);
143     return this;
144   }
145 
146   /**
147    * Returns the list of recording identifiers or an empty array if no identifiers have been specified.
148    *
149    * @return the identifiers
150    */
151   public String[] getIdentifier() {
152     return identifiers.toArray(new String[identifiers.size()]);
153   }
154 
155   /**
156    * Returns the organization of the recording
157    *
158    * @return the organization
159    */
160   public String getOrganization() {
161     return organization;
162   }
163 
164   /**
165    * Returns the user of this search query
166    *
167    * @return the user of this search query
168    */
169   public User getUser() {
170     return user;
171   }
172 
173   /**
174    * Selects recordings with the given title.
175    *
176    * @param title
177    *          the title
178    * @return the enhanced search query
179    */
180   public EventSearchQuery withTitle(String title) {
181     this.title = title;
182     return this;
183   }
184 
185   /**
186    * Returns the title of the recording.
187    *
188    * @return the title
189    */
190   public String getTitle() {
191     return title;
192   }
193 
194   /**
195    * Filter the recording events without any action checked.
196    *
197    * @return the enhanced search query
198    */
199   public EventSearchQuery withoutActions() {
200     this.actions.clear();
201     return this;
202   }
203 
204   /**
205    * Filter the recording events with the given action.
206    * <p>
207    * Note that this method may be called multiple times to support filtering by multiple actions.
208    *
209    * @param action
210    *          the action
211    * @return the enhanced search query
212    */
213   public EventSearchQuery withAction(Action action) {
214     if (action == null) {
215       throw new IllegalArgumentException("Action cannot be null");
216     }
217     this.actions.add(action.toString());
218     return this;
219   }
220 
221   /**
222    * Returns the list of actions or an empty array if no actions have been specified.
223    *
224    * @return the actions
225    */
226   public String[] getActions() {
227     return actions.toArray(new String[actions.size()]);
228   }
229 
230   /**
231    * Selects recording events with the given presenter.
232    * <p>
233    * Note that this method may be called multiple times to support selection of multiple recording events.
234    *
235    * @param presenter
236    *          the presenter
237    * @return the enhanced search query
238    */
239   public EventSearchQuery withPresenter(String presenter) {
240     if (StringUtils.isBlank(presenter)) {
241       throw new IllegalArgumentException("Presenter cannot be null");
242     }
243     this.presenters.add(presenter);
244     return this;
245   }
246 
247   /**
248    * Returns the list of recording presenters or an empty array if no presenter have been specified.
249    *
250    * @return the presenters
251    */
252   public String[] getPresenters() {
253     return presenters.toArray(new String[presenters.size()]);
254   }
255 
256   /**
257    * Selects recording events with the given contributor.
258    * <p>
259    * Note that this method may be called multiple times to support selection of multiple recording events.
260    *
261    * @param contributor
262    *          the contributor
263    * @return the enhanced search query
264    */
265   public EventSearchQuery withContributor(String contributor) {
266     if (StringUtils.isBlank(contributor)) {
267       throw new IllegalArgumentException("Contributor cannot be null");
268     }
269     this.contributors.add(contributor);
270     return this;
271   }
272 
273   /**
274    * Returns the list of recording contributors or an empty array if no contributors have been specified.
275    *
276    * @return the contributors
277    */
278   public String[] getContributors() {
279     return contributors.toArray(new String[contributors.size()]);
280   }
281 
282   /**
283    * Selects recording events with the given subject.
284    *
285    * @param subject
286    *          the subject
287    * @return the enhanced search query
288    */
289   public EventSearchQuery withSubject(String subject) {
290     this.subject = subject;
291     return this;
292   }
293 
294   /**
295    * Returns the subject of the recording.
296    *
297    * @return the subject
298    */
299   public String getSubject() {
300     return subject;
301   }
302 
303   /**
304    * Selects recordings with the given description.
305    *
306    * @param description
307    *          the description
308    * @return the enhanced search query
309    */
310   public EventSearchQuery withDescription(String description) {
311     this.description = description;
312     return this;
313   }
314 
315   /**
316    * Returns the description of the recording.
317    *
318    * @return the description
319    */
320   public String getDescription() {
321     return description;
322   }
323 
324   /**
325    * Selects recordings with the given location.
326    *
327    * @param location
328    *          the location
329    * @return the enhanced search query
330    */
331   public EventSearchQuery withLocation(String location) {
332     this.location = location;
333     return this;
334   }
335 
336   /**
337    * Returns the location of the recording.
338    *
339    * @return the location
340    */
341   public String getLocation() {
342     return location;
343   }
344 
345   /**
346    * Selects recordings with the given series identifier.
347    *
348    * @param seriesId
349    *          the series identifier
350    * @return the enhanced search query
351    */
352   public EventSearchQuery withSeriesId(String seriesId) {
353     this.seriesId = seriesId;
354     return this;
355   }
356 
357   /**
358    * Returns the series identifier of the recording.
359    *
360    * @return the series identifier
361    */
362   public String getSeriesId() {
363     return seriesId;
364   }
365 
366   /**
367    * Selects recordings with the given series name.
368    *
369    * @param seriesName
370    *          the series name
371    * @return the enhanced search query
372    */
373   public EventSearchQuery withSeriesName(String seriesName) {
374     this.seriesName = seriesName;
375     return this;
376   }
377 
378   /**
379    * Returns the series name of the recording.
380    *
381    * @return the series name
382    */
383   public String getSeriesName() {
384     return seriesName;
385   }
386 
387   /**
388    * Selects recordings with the given language.
389    *
390    * @param language
391    *          the language
392    * @return the enhanced search query
393    */
394   public EventSearchQuery withLanguage(String language) {
395     this.language = language;
396     return this;
397   }
398 
399   /**
400    * Returns the language of the recording.
401    *
402    * @return the language
403    */
404   public String getLanguage() {
405     return language;
406   }
407 
408   /**
409    * Selects recordings with the given source type.
410    *
411    * @param source
412    *          the source
413    * @return the enhanced search query
414    */
415   public EventSearchQuery withSource(String source) {
416     this.source = source;
417     return this;
418   }
419 
420   /**
421    * Returns the source of the recording.
422    *
423    * @return the source
424    */
425   public String getSource() {
426     return source;
427   }
428 
429   /**
430    * Selects recordings with the given creation date.
431    *
432    * @param created
433    *          the creation date
434    * @return the enhanced search query
435    */
436   public EventSearchQuery withCreated(String created) {
437     this.created = created;
438     return this;
439   }
440 
441   /**
442    * Returns the creation date of the recording.
443    *
444    * @return the creation date
445    */
446   public String getCreated() {
447     return created;
448   }
449 
450   /**
451    * The start date to start looking for events.
452    *
453    * @param startFrom
454    *          The start date to start looking for events
455    * @return the enhanced search query
456    */
457   public EventSearchQuery withStartFrom(Date startFrom) {
458     this.startFrom = startFrom;
459     return this;
460   }
461 
462   /**
463    * @return The Date after which all events returned should have been started
464    */
465   public Date getStartFrom() {
466     return startFrom;
467   }
468 
469   /**
470    * The start date to stop looking for events.
471    *
472    * @param startTo
473    *          The start date to stop looking for events
474    * @return the enhanced search query
475    */
476   public EventSearchQuery withStartTo(Date startTo) {
477     this.startTo = startTo;
478     return this;
479   }
480 
481   /**
482    * @return The Date before which all events returned should have been started
483    */
484   public Date getStartTo() {
485     return startTo;
486   }
487 
488   /**
489    * The technical start date to start looking for events.
490    *
491    * @param startFrom
492    *          The technical start date to start looking for events
493    * @return the enhanced search query
494    */
495   public EventSearchQuery withTechnicalStartFrom(Date startFrom) {
496     this.technicalStartFrom = startFrom;
497     return this;
498   }
499 
500   /**
501    * @return The technical date after which all events returned should have been started
502    */
503   public Date getTechnicalStartFrom() {
504     return technicalStartFrom;
505   }
506 
507   /**
508    * The technical start date to stop looking for events.
509    *
510    * @param startTo
511    *          The technical start date to stop looking for events
512    * @return the enhanced search query
513    */
514   public EventSearchQuery withTechnicalStartTo(Date startTo) {
515     this.technicalStartTo = startTo;
516     return this;
517   }
518 
519   /**
520    * @return The technical date before which all events returned should have been started
521    */
522   public Date getTechnicalStartTo() {
523     return technicalStartTo;
524   }
525 
526   /**
527    * Selects recordings with the given creator.
528    *
529    * @param creator
530    *          the creator
531    * @return the enhanced search query
532    */
533   public EventSearchQuery withCreator(String creator) {
534     this.creator = creator;
535     return this;
536   }
537 
538   /**
539    * Returns the creator of the recording.
540    *
541    * @return the creator
542    */
543   public String getCreator() {
544     return creator;
545   }
546 
547   /**
548    * Selects recordings with the given publisher.
549    *
550    * @param publisher
551    *          the publisher
552    * @return the enhanced search query
553    */
554   public EventSearchQuery withPublisher(String publisher) {
555     this.publisher = publisher;
556     return this;
557   }
558 
559   /**
560    * Returns the publisher of the recording.
561    *
562    * @return the publisher
563    */
564   public String getPublisher() {
565     return publisher;
566   }
567 
568   /**
569    * Selects recordings with the given license.
570    *
571    * @param license
572    *          the license
573    * @return the enhanced search query
574    */
575   public EventSearchQuery withLicense(String license) {
576     this.license = license;
577     return this;
578   }
579 
580   /**
581    * Returns the license of the recording.
582    *
583    * @return the license
584    */
585   public String getLicense() {
586     return license;
587   }
588 
589   /**
590    * Selects recordings with the given rights.
591    *
592    * @param rights
593    *          the rights
594    * @return the enhanced search query
595    */
596   public EventSearchQuery withRights(String rights) {
597     this.rights = rights;
598     return this;
599   }
600 
601   /**
602    * Returns the rights of the recording.
603    *
604    * @return the rights
605    */
606   public String getRights() {
607     return rights;
608   }
609 
610   /**
611    * Selects recordings with the given access policy.
612    *
613    * @param accessPolicy
614    *          the access policy
615    * @return the enhanced search query
616    */
617   public EventSearchQuery withAccessPolicy(String accessPolicy) {
618     this.accessPolicy = accessPolicy;
619     return this;
620   }
621 
622   /**
623    * Returns the access policy of the recording.
624    *
625    * @return the access policy
626    */
627   public String getAccessPolicy() {
628     return accessPolicy;
629   }
630 
631   /**
632    * Selects recordings with the given managed ACL name.
633    *
634    * @param managedAcl
635    *          the name of the managed ACL
636    * @return the enhanced search query
637    */
638   public EventSearchQuery withManagedAcl(String managedAcl) {
639     this.managedAcl = managedAcl;
640     return this;
641   }
642 
643   /**
644    * Returns the name of the managed ACL set to the recording.
645    *
646    * @return the name of the managed ACL
647    */
648   public String getManagedAcl() {
649     return managedAcl;
650   }
651 
652   /**
653    * Selects recordings with the given workflow state.
654    *
655    * @param workflowState
656    *          the workflow state
657    * @return the enhanced search query
658    */
659   public EventSearchQuery withWorkflowState(String workflowState) {
660     this.workflowState = workflowState;
661     return this;
662   }
663 
664   /**
665    * Returns the workflow state of the recording.
666    *
667    * @return the workflow state
668    */
669   public String getWorkflowState() {
670     return workflowState;
671   }
672 
673   /**
674    * Selects recordings with the given workflow id.
675    *
676    * @param workflowId
677    *          the workflow id
678    * @return the enhanced search query
679    */
680   public EventSearchQuery withWorkflowId(long workflowId) {
681     this.workflowId = workflowId;
682     return this;
683   }
684 
685   /**
686    * Returns the workflow id of the recording.
687    *
688    * @return the workflow id
689    */
690   public Long getWorkflowId() {
691     return workflowId;
692   }
693 
694   /**
695    * Selects recordings with the given workflow definition.
696    *
697    * @param workflowDefinition
698    *          the workflow definition
699    * @return the enhanced search query
700    */
701   public EventSearchQuery withWorkflowDefinition(String workflowDefinition) {
702     this.workflowDefinition = workflowDefinition;
703     return this;
704   }
705 
706   /**
707    * Returns the workflow definition of the recording.
708    *
709    * @return the workflow definition
710    */
711   public String getWorkflowDefinition() {
712     return workflowDefinition;
713   }
714 
715   /**
716    * Selects recordings with the given start date.
717    *
718    * @param startDate
719    *          the start date
720    * @return the enhanced search query
721    */
722   public EventSearchQuery withStartDate(String startDate) {
723     this.startDate = startDate;
724     return this;
725   }
726 
727   /**
728    * Returns the start date of the recording.
729    *
730    * @return the start date
731    */
732   public String getStartDate() {
733     return startDate;
734   }
735 
736   /**
737    * Selects recordings with the given duration.
738    *
739    * @param duration
740    *          the duration
741    * @return the enhanced search query
742    */
743   public EventSearchQuery withDuration(long duration) {
744     this.duration = duration;
745     return this;
746   }
747 
748   /**
749    * Returns the duration of the recording.
750    *
751    * @return the duration
752    */
753   public Long getDuration() {
754     return duration;
755   }
756 
757   /**
758    * Selects recordings with the given event status.
759    *
760    * @param eventStatus
761    *          the event status
762    * @return the enhanced search query
763    */
764   public EventSearchQuery withEventStatus(String eventStatus) {
765     this.eventStatus = eventStatus;
766     return this;
767   }
768 
769   /**
770    * Returns the event status of the recording.
771    *
772    * @return the event status
773    */
774   public String getEventStatus() {
775     return eventStatus;
776   }
777 
778   /**
779    * Selects recordings with the given has comments status.
780    *
781    * @param hasComments
782    *          the has comments status
783    * @return the enhanced search query
784    */
785   public EventSearchQuery withComments(boolean hasComments) {
786     this.hasComments = hasComments;
787     return this;
788   }
789 
790   /**
791    * Selects recordings with the given has open comments status.
792    *
793    * @param hasOpenComments
794    *          the has open comments status
795    * @return the enhanced search query
796    */
797   public EventSearchQuery withOpenComments(boolean hasOpenComments) {
798     this.hasOpenComments = hasOpenComments;
799     return this;
800   }
801 
802   /**
803    * Selects recordings with the given has comment need cutting status.
804    *
805    * @param needsCutting
806    *          the event has the comments status that it needs cutting
807    * @return the enhanced search query
808    */
809   public EventSearchQuery withNeedsCutting(boolean needsCutting) {
810     this.needsCutting = needsCutting;
811     return this;
812   }
813 
814   /**
815    * Returns the has comments status of the recording.
816    *
817    * @return the recording has comments status
818    */
819   public Boolean getHasComments() {
820     return hasComments;
821   }
822 
823   /**
824    * Returns the has open comments status of the recording.
825    *
826    * @return the recording has open comments status
827    */
828   public Boolean getHasOpenComments() {
829     return hasOpenComments;
830   }
831 
832   public EventSearchQuery withComments(String comment) {
833     if (StringUtils.isBlank(comment)) {
834       throw new IllegalArgumentException("Comment cannot be null");
835     }
836     this.comments.add(comment);
837     return this;
838   }
839 
840   public String[] getComments() {
841     return comments.toArray(new String[comments.size()]);
842   }
843 
844   /**
845    * Returns the has open comments reason that it needs cutting of the recording.
846    *
847    * @return the event has the open comments status that it needs cutting
848    */
849   public Boolean needsCutting() {
850     return needsCutting;
851   }
852 
853   /**
854    * Selects recording events with the given publication.
855    * <p>
856    * Note that this method may be called multiple times to support selection of multiple recording events.
857    *
858    * @param publication
859    *          the publication
860    * @return the enhanced search query
861    */
862   public EventSearchQuery withPublications(String publication) {
863     if (StringUtils.isBlank(publication)) {
864       throw new IllegalArgumentException("Publication cannot be null");
865     }
866     this.publications.add(publication);
867     return this;
868   }
869 
870   /**
871    * Returns the list of event publications or an empty array if no publications have been specified.
872    *
873    * @return the publications
874    */
875   public String[] getPublications() {
876     return publications.toArray(new String[publications.size()]);
877   }
878 
879   /**
880    * Selects recordings with the given is published status.
881    *
882    * @param isPublished
883    *          the is published status
884    * @return the enhanced search query
885    */
886   public EventSearchQuery withIsPublished(boolean isPublished) {
887     this.isPublished = isPublished;
888     return this;
889   }
890 
891   /**
892    * Returns the is published status of the recording.
893    *
894    * @return the recording is published status
895    */
896   public Boolean getIsPublished() {
897     return isPublished;
898   }
899 
900   /**
901    * Selects events with the given archive version.
902    *
903    * @param archiveVersion
904    *          the archive version
905    * @return the enhanced search query
906    */
907   public EventSearchQuery withArchiveVersion(long archiveVersion) {
908     this.archiveVersion = archiveVersion;
909     return this;
910   }
911 
912   /**
913    * Returns the archive version of the event.
914    *
915    * @return the archive version
916    */
917   public Long getArchiveVersion() {
918     return archiveVersion;
919   }
920 
921   /**
922    * Selects recordings with the given agent id.
923    *
924    * @param agentId
925    *          the agent id
926    * @return the enhanced search query
927    */
928   public EventSearchQuery withAgentId(String agentId) {
929     this.agentId = agentId;
930     return this;
931   }
932 
933   /**
934    * Returns the agent id of the recording.
935    *
936    * @return the agent id
937    */
938   public String getAgentId() {
939     return agentId;
940   }
941 
942   /**
943    * Selects recordings with the given technical start date.
944    *
945    * @param technicalStartTime
946    *          the start date
947    * @return the enhanced search query
948    */
949   public EventSearchQuery withTechnicalStartTime(Date technicalStartTime) {
950     this.technicalStartTime = technicalStartTime;
951     return this;
952   }
953 
954   /**
955    * Returns the technical start date of the recording.
956    *
957    * @return the technical start date
958    */
959   public Date getTechnicalStartTime() {
960     return technicalStartTime;
961   }
962 
963   /**
964    * Selects recordings with the given technical end date.
965    *
966    * @param technicalEndTime
967    *          the end date
968    * @return
969    * @return the enhanced search query
970    */
971   public EventSearchQuery withTechnicalEndTime(Date technicalEndTime) {
972     this.technicalEndTime = technicalEndTime;
973     return this;
974   }
975 
976   /**
977    * Returns the technical end date of the recording.
978    *
979    * @return the technical end date
980    */
981   public Date getTechnicalEndTime() {
982     return technicalEndTime;
983   }
984 
985   /**
986    * Selects recording events with the given technical presenters.
987    * <p>
988    * Note that this method may be called multiple times to support selection of multiple recording events.
989    *
990    * @param presenter
991    *          the presenter
992    * @return the enhanced search query
993    */
994   public EventSearchQuery withTechnicalPresenters(String presenter) {
995     if (StringUtils.isBlank(presenter)) {
996       throw new IllegalArgumentException("Presenter cannot be null");
997     }
998     this.technicalPresenters.add(presenter);
999     return this;
1000   }
1001 
1002   /**
1003    * Returns the list of technical presenters or an empty array if no presenters have been specified.
1004    *
1005    * @return the technical presenters
1006    */
1007   public String[] getTechnicalPresenters() {
1008     return technicalPresenters.toArray(new String[technicalPresenters.size()]);
1009   }
1010 
1011   /**
1012    * Defines the sort order for the recording start date.
1013    *
1014    * @param order
1015    *          the order
1016    * @return the enhanced search query
1017    */
1018   public EventSearchQuery sortByStartDate(Order order) {
1019     withSortOrder(EventIndexSchema.START_DATE, order);
1020     return this;
1021   }
1022 
1023   /**
1024    * Returns the sort order for the recording start date.
1025    *
1026    * @return the sort order
1027    */
1028   public Order getStartDateSortOrder() {
1029     return getSortOrder(EventIndexSchema.START_DATE);
1030   }
1031 
1032   /**
1033    * Defines the sort order for the technical recording start date.
1034    *
1035    * @param order
1036    *          the order
1037    * @return the enhanced search query
1038    */
1039   public EventSearchQuery sortByTechnicalStartDate(Order order) {
1040     withSortOrder(EventIndexSchema.TECHNICAL_START, order);
1041     return this;
1042   }
1043 
1044   /**
1045    * Returns the sort order for the technical recording start date.
1046    *
1047    * @return the sort order
1048    */
1049   public Order getTechnicalStartDateSortOrder() {
1050     return getSortOrder(EventIndexSchema.TECHNICAL_START);
1051   }
1052 
1053   /**
1054    * Defines the sort order for the recording end date.
1055    *
1056    * @param order
1057    *          the order
1058    * @return the enhanced search query
1059    */
1060   public EventSearchQuery sortByEndDate(Order order) {
1061     withSortOrder(EventIndexSchema.END_DATE, order);
1062     return this;
1063   }
1064 
1065   /**
1066    * Returns the sort order for the recording end date.
1067    *
1068    * @return the sort order
1069    */
1070   public Order getEndDateSortOrder() {
1071     return getSortOrder(EventIndexSchema.END_DATE);
1072   }
1073 
1074   /**
1075    * Defines the sort order for the technical recording end date.
1076    *
1077    * @param order
1078    *          the order
1079    * @return the enhanced search query
1080    */
1081   public EventSearchQuery sortByTechnicalEndDate(Order order) {
1082     withSortOrder(EventIndexSchema.TECHNICAL_END, order);
1083     return this;
1084   }
1085 
1086   /**
1087    * Returns the sort order for the technical recording end date.
1088    *
1089    * @return the sort order
1090    */
1091   public Order getTechnicalEndDateSortOrder() {
1092     return getSortOrder(EventIndexSchema.TECHNICAL_END);
1093   }
1094 
1095   /**
1096    * Defines the sort order for the recording date.
1097    *
1098    * @param order
1099    *          the order
1100    * @return the enhanced search query
1101    */
1102   public EventSearchQuery sortByDate(Order order) {
1103     withSortOrder(EventIndexSchema.END_DATE, order);
1104     return this;
1105   }
1106 
1107   /**
1108    * Returns the sort order for the recording date.
1109    *
1110    * @return the sort order
1111    */
1112   public Order getDateSortOrder() {
1113     return getSortOrder(EventIndexSchema.END_DATE);
1114   }
1115 
1116   /**
1117    * Defines the sort order for the recording date.
1118    *
1119    * @param order
1120    *          the order
1121    * @return the enhanced search query
1122    */
1123   public EventSearchQuery sortByTitle(Order order) {
1124     withSortOrder(EventIndexSchema.TITLE, order);
1125     return this;
1126   }
1127 
1128   /**
1129    * Returns the sort order for the recording start date.
1130    *
1131    * @return the sort order
1132    */
1133   public Order getTitleSortOrder() {
1134     return getSortOrder(EventIndexSchema.TITLE);
1135   }
1136 
1137   public EventSearchQuery sortByUID(Order order) {
1138     withSortOrder(EventIndexSchema.UID, order);
1139     return this;
1140   }
1141 
1142   public Order getUIDSortOrder() {
1143     return getSortOrder(EventIndexSchema.UID);
1144   }
1145 
1146   /**
1147    * Defines the sort order for the recording date.
1148    *
1149    * @param order
1150    *          the order
1151    * @return the enhanced search query
1152    */
1153   public EventSearchQuery sortByPresenter(Order order) {
1154     withSortOrder(EventIndexSchema.PRESENTER, order);
1155     return this;
1156   }
1157 
1158   /**
1159    * Returns the sort order for the recording start date.
1160    *
1161    * @return the sort order
1162    */
1163   public Order getPresentersSortOrder() {
1164     return getSortOrder(EventIndexSchema.PRESENTER);
1165   }
1166 
1167   /**
1168    * Defines the sort order for the location.
1169    *
1170    * @param order
1171    *          the sort order
1172    * @return the updated query
1173    */
1174   public EventSearchQuery sortByLocation(Order order) {
1175     withSortOrder(EventIndexSchema.LOCATION, order);
1176     return this;
1177   }
1178 
1179   /**
1180    * Returns the sort order for the location.
1181    *
1182    * @return the sort order
1183    */
1184   public Order getLocationSortOrder() {
1185     return getSortOrder(EventIndexSchema.LOCATION);
1186   }
1187 
1188   /**
1189    * Defines the sort order for the series name.
1190    *
1191    * @param order
1192    *          the sort order
1193    * @return the updated query
1194    */
1195   public EventSearchQuery sortBySeriesName(Order order) {
1196     withSortOrder(EventIndexSchema.SERIES_NAME, order);
1197     return this;
1198   }
1199 
1200   /**
1201    * Returns the sort order for the series name.
1202    *
1203    * @return the sort order
1204    */
1205   public Order getSeriesNameSortOrder() {
1206     return getSortOrder(EventIndexSchema.SERIES_NAME);
1207   }
1208 
1209   /**
1210    * Defines the sort order for the managed ACL.
1211    *
1212    * @param order
1213    *          the order
1214    * @return the enhanced search query
1215    */
1216   public EventSearchQuery sortByManagedAcl(Order order) {
1217     withSortOrder(EventIndexSchema.MANAGED_ACL, order);
1218     return this;
1219   }
1220 
1221   /**
1222    * Returns the sort order for the series managed ACL.
1223    *
1224    * @return the sort order
1225    */
1226   public Order getManagedAclSortOrder() {
1227     return getSortOrder(EventIndexSchema.MANAGED_ACL);
1228   }
1229 
1230   /**
1231    * Defines the sort order for the workflow state.
1232    *
1233    * @param order
1234    *          the sort order
1235    * @return the updated query
1236    */
1237   public EventSearchQuery sortByWorkflowState(Order order) {
1238     withSortOrder(EventIndexSchema.WORKFLOW_STATE, order);
1239     return this;
1240   }
1241 
1242   /**
1243    * Returns the sort order for the workflow state.
1244    *
1245    * @return the sort order
1246    */
1247   public Order getWorkflowStateSortOrder() {
1248     return getSortOrder(EventIndexSchema.WORKFLOW_STATE);
1249   }
1250 
1251   /**
1252    * Defines the sort order for the event status.
1253    *
1254    * @param order
1255    *          the sort order
1256    * @return the updated query
1257    */
1258   public EventSearchQuery sortByEventStatus(Order order) {
1259     withSortOrder(EventIndexSchema.EVENT_STATUS, order);
1260     return this;
1261   }
1262 
1263   /**
1264    * Returns the sort order for the event status.
1265    *
1266    * @return the sort order
1267    */
1268   public Order getEventStatusSortOrder() {
1269     return getSortOrder(EventIndexSchema.EVENT_STATUS);
1270   }
1271 
1272   /**
1273    * Defines the sort order for publication.
1274    *
1275    * @param order
1276    *          the sort order
1277    * @return the updated query
1278    */
1279   public EventSearchQuery sortByPublicationIgnoringInternal(Order order) {
1280     // TODO implement sort By Publication Ignoring Internal
1281     withSortOrder(EventIndexSchema.PUBLICATION, order);
1282     return this;
1283   }
1284 
1285   /**
1286    * Returns the sort order for the publication.
1287    *
1288    * @return the sort order
1289    */
1290   public Order getPublicationSortOrder() {
1291     // TODO implement getPublicationSortOrder
1292     return getSortOrder(EventIndexSchema.PUBLICATION);
1293   }
1294 }