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