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.scheduler.remote;
23  
24  import static java.nio.charset.StandardCharsets.UTF_8;
25  import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
26  import static org.apache.http.HttpStatus.SC_CONFLICT;
27  import static org.apache.http.HttpStatus.SC_CREATED;
28  import static org.apache.http.HttpStatus.SC_FORBIDDEN;
29  import static org.apache.http.HttpStatus.SC_NOT_FOUND;
30  import static org.apache.http.HttpStatus.SC_NO_CONTENT;
31  import static org.apache.http.HttpStatus.SC_OK;
32  import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
33  
34  import org.opencastproject.mediapackage.MediaPackage;
35  import org.opencastproject.mediapackage.MediaPackageParser;
36  import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
37  import org.opencastproject.metadata.dublincore.DublinCores;
38  import org.opencastproject.scheduler.api.Recording;
39  import org.opencastproject.scheduler.api.RecordingImpl;
40  import org.opencastproject.scheduler.api.SchedulerConflictException;
41  import org.opencastproject.scheduler.api.SchedulerException;
42  import org.opencastproject.scheduler.api.SchedulerService;
43  import org.opencastproject.scheduler.api.TechnicalMetadata;
44  import org.opencastproject.scheduler.api.TechnicalMetadataImpl;
45  import org.opencastproject.security.api.TrustedHttpClient;
46  import org.opencastproject.security.api.UnauthorizedException;
47  import org.opencastproject.serviceregistry.api.RemoteBase;
48  import org.opencastproject.serviceregistry.api.ServiceRegistry;
49  import org.opencastproject.util.DateTimeSupport;
50  import org.opencastproject.util.NotFoundException;
51  import org.opencastproject.util.UrlSupport;
52  
53  import net.fortuna.ical4j.model.Period;
54  import net.fortuna.ical4j.model.property.RRule;
55  
56  import org.apache.commons.lang3.BooleanUtils;
57  import org.apache.commons.lang3.StringUtils;
58  import org.apache.http.HttpResponse;
59  import org.apache.http.NameValuePair;
60  import org.apache.http.client.entity.UrlEncodedFormEntity;
61  import org.apache.http.client.methods.HttpDelete;
62  import org.apache.http.client.methods.HttpGet;
63  import org.apache.http.client.methods.HttpPost;
64  import org.apache.http.client.methods.HttpPut;
65  import org.apache.http.client.utils.URLEncodedUtils;
66  import org.apache.http.message.BasicNameValuePair;
67  import org.apache.http.util.EntityUtils;
68  import org.json.simple.JSONArray;
69  import org.json.simple.JSONObject;
70  import org.json.simple.parser.JSONParser;
71  import org.osgi.service.component.annotations.Component;
72  import org.osgi.service.component.annotations.Reference;
73  import org.slf4j.Logger;
74  import org.slf4j.LoggerFactory;
75  
76  import java.util.ArrayList;
77  import java.util.Collections;
78  import java.util.Date;
79  import java.util.HashMap;
80  import java.util.HashSet;
81  import java.util.List;
82  import java.util.Map;
83  import java.util.Map.Entry;
84  import java.util.Optional;
85  import java.util.Properties;
86  import java.util.Set;
87  import java.util.TimeZone;
88  
89  /**
90   * A proxy to a remote series service.
91   */
92  @Component(
93      immediate = true,
94      service = SchedulerService.class,
95      property = {
96          "service.description=Scheduler Remote Service Proxy"
97      }
98  )
99  public class SchedulerServiceRemoteImpl extends RemoteBase implements SchedulerService {
100 
101   private static final Logger logger = LoggerFactory.getLogger(SchedulerServiceRemoteImpl.class);
102 
103   /** A parser for handling JSON documents inside the body of a request. **/
104   private final JSONParser parser = new JSONParser();
105 
106   public SchedulerServiceRemoteImpl() {
107     super(JOB_TYPE);
108   }
109 
110   @Override
111   public void addEvent(Date startDateTime, Date endDateTime, String captureAgentId, Set<String> userIds,
112           MediaPackage mediaPackage, Map<String, String> wfProperties, Map<String, String> caMetadata,
113           Optional<String> schedulingSource) throws UnauthorizedException, SchedulerConflictException,
114           SchedulerException {
115     HttpPost post = new HttpPost("/");
116     String eventId = mediaPackage.getIdentifier().toString();
117     logger.debug("Start adding a new event {} through remote Schedule Service", eventId);
118 
119     List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
120     params.add(new BasicNameValuePair("start", Long.toString(startDateTime.getTime())));
121     params.add(new BasicNameValuePair("end", Long.toString(endDateTime.getTime())));
122     params.add(new BasicNameValuePair("agent", captureAgentId));
123     params.add(new BasicNameValuePair("users", StringUtils.join(userIds, ",")));
124     params.add(new BasicNameValuePair("mediaPackage", MediaPackageParser.getAsXml(mediaPackage)));
125     params.add(new BasicNameValuePair("wfproperties", toPropertyString(wfProperties)));
126     params.add(new BasicNameValuePair("agentparameters", toPropertyString(caMetadata)));
127     if (schedulingSource.isPresent()) {
128       params.add(new BasicNameValuePair("source", schedulingSource.get()));
129     }
130     post.setEntity(new UrlEncodedFormEntity(params, UTF_8));
131 
132     HttpResponse response = getResponse(post, SC_CREATED, SC_UNAUTHORIZED, SC_CONFLICT);
133     try {
134       if (response != null && SC_CREATED == response.getStatusLine().getStatusCode()) {
135         logger.info("Successfully added event {} to the scheduler service", eventId);
136         return;
137       } else if (response != null && SC_CONFLICT == response.getStatusLine().getStatusCode()) {
138         String errorJson = EntityUtils.toString(response.getEntity(), UTF_8);
139         JSONObject json = (JSONObject) parser.parse(errorJson);
140         JSONObject error = (JSONObject) json.get("error");
141         String errorCode = (String) error.get("code");
142         if (SchedulerConflictException.ERROR_CODE.equals(errorCode)) {
143           logger.info("Conflicting events found when adding event {}", eventId);
144           throw new SchedulerConflictException("Conflicting events found when adding event " + eventId);
145         } else {
146           throw new SchedulerException("Unexpected error code " + errorCode);
147         }
148       } else if (response != null && SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
149         logger.info("Unauthorized to create the event");
150         throw new UnauthorizedException("Unauthorized to create the event");
151       } else {
152         throw new SchedulerException("Unable to add event " + eventId + " to the scheduler service");
153       }
154     } catch (UnauthorizedException | SchedulerConflictException e) {
155       throw e;
156     } catch (Exception e) {
157       throw new SchedulerException("Unable to add event " + eventId + " to the scheduler service", e);
158     } finally {
159       closeConnection(response);
160     }
161   }
162 
163   @Override
164   public Map<String, Period> addMultipleEvents(RRule rRule, Date start, Date end, Long duration, TimeZone tz,
165           String captureAgentId, Set<String> userIds, MediaPackage templateMp, Map<String, String> wfProperties,
166           Map<String, String> caMetadata, Optional<String> schedulingSource)
167           throws UnauthorizedException, SchedulerConflictException, SchedulerException {
168     HttpPost post = new HttpPost("/");
169     logger.debug("Start adding a new events through remote Schedule Service");
170 
171     List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
172     params.add(new BasicNameValuePair("rrule", rRule.getValue()));
173     params.add(new BasicNameValuePair("start", Long.toString(start.getTime())));
174     params.add(new BasicNameValuePair("end", Long.toString(end.getTime())));
175     params.add(new BasicNameValuePair("duration", Long.toString(duration)));
176     params.add(new BasicNameValuePair("tz", tz.toZoneId().getId()));
177     params.add(new BasicNameValuePair("agent", captureAgentId));
178     params.add(new BasicNameValuePair("users", StringUtils.join(userIds, ",")));
179     params.add(new BasicNameValuePair("templateMp", MediaPackageParser.getAsXml(templateMp)));
180     params.add(new BasicNameValuePair("wfproperties", toPropertyString(wfProperties)));
181     params.add(new BasicNameValuePair("agentparameters", toPropertyString(caMetadata)));
182     if (schedulingSource.isPresent()) {
183       params.add(new BasicNameValuePair("source", schedulingSource.get()));
184     }
185     post.setEntity(new UrlEncodedFormEntity(params, UTF_8));
186 
187     String eventId = templateMp.getIdentifier().toString();
188 
189     HttpResponse response = getResponse(post, SC_CREATED, SC_UNAUTHORIZED, SC_CONFLICT);
190     try {
191       if (response != null && SC_CREATED == response.getStatusLine().getStatusCode()) {
192         logger.info("Successfully added events to the scheduler service");
193         return null;
194       } else if (response != null && SC_CONFLICT == response.getStatusLine().getStatusCode()) {
195         String errorJson = EntityUtils.toString(response.getEntity(), UTF_8);
196         JSONObject json = (JSONObject) parser.parse(errorJson);
197         JSONObject error = (JSONObject) json.get("error");
198         String errorCode = (String) error.get("code");
199         if (SchedulerConflictException.ERROR_CODE.equals(errorCode)) {
200           logger.info("Conflicting events found when adding event based on {}", eventId);
201           throw new SchedulerConflictException("Conflicting events found when adding event based on" + eventId);
202         } else {
203           throw new SchedulerException("Unexpected error code " + errorCode);
204         }
205       } else if (response != null && SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
206         logger.info("Unauthorized to create the event");
207         throw new UnauthorizedException("Unauthorized to create the event");
208       } else {
209         throw new SchedulerException("Unable to add event " + eventId + " to the scheduler service");
210       }
211     } catch (UnauthorizedException | SchedulerConflictException e) {
212       throw e;
213     } catch (Exception e) {
214       throw new SchedulerException("Unable to add event " + eventId + " to the scheduler service", e);
215     } finally {
216       closeConnection(response);
217     }
218   }
219 
220   @Override
221   public void updateEvent(String eventId, Optional<Date> startDateTime, Optional<Date> endDateTime,
222           Optional<String> captureAgentId, Optional<Set<String>> userIds, Optional<MediaPackage> mediaPackage,
223           Optional<Map<String, String>> wfProperties, Optional<Map<String, String>> caMetadata)
224                   throws NotFoundException, UnauthorizedException, SchedulerConflictException, SchedulerException {
225 
226     updateEvent(eventId, startDateTime, endDateTime, captureAgentId, userIds,
227                 mediaPackage, wfProperties, caMetadata, false);
228   }
229 
230   @Override
231   public void updateEvent(String eventId, Optional<Date> startDateTime, Optional<Date> endDateTime,
232           Optional<String> captureAgentId, Optional<Set<String>> userIds, Optional<MediaPackage> mediaPackage,
233           Optional<Map<String, String>> wfProperties, Optional<Map<String, String>> caMetadata, boolean allowConflict)
234                   throws NotFoundException, UnauthorizedException, SchedulerConflictException, SchedulerException {
235 
236     logger.debug("Start updating event {}.", eventId);
237     HttpPut put = new HttpPut("/" + eventId);
238 
239     List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
240     if (startDateTime.isPresent()) {
241       params.add(new BasicNameValuePair("start", Long.toString(startDateTime.get().getTime())));
242     }
243     if (endDateTime.isPresent()) {
244       params.add(new BasicNameValuePair("end", Long.toString(endDateTime.get().getTime())));
245     }
246     if (captureAgentId.isPresent()) {
247       params.add(new BasicNameValuePair("agent", captureAgentId.get()));
248     }
249     if (userIds.isPresent()) {
250       params.add(new BasicNameValuePair("users", StringUtils.join(userIds.get(), ",")));
251     }
252     if (mediaPackage.isPresent()) {
253       params.add(new BasicNameValuePair("mediaPackage", MediaPackageParser.getAsXml(mediaPackage.get())));
254     }
255     if (wfProperties.isPresent()) {
256       params.add(new BasicNameValuePair("wfproperties", toPropertyString(wfProperties.get())));
257     }
258     if (caMetadata.isPresent()) {
259       params.add(new BasicNameValuePair("agentparameters", toPropertyString(caMetadata.get())));
260     }
261     params.add(new BasicNameValuePair("allowConflict", BooleanUtils.toString(allowConflict, "true", "false", "false")));
262     put.setEntity(new UrlEncodedFormEntity(params, UTF_8));
263 
264     HttpResponse response = getResponse(put, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED, SC_FORBIDDEN, SC_CONFLICT);
265     try {
266       if (response != null) {
267         if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
268           logger.info("Event {} was not found by the scheduler service", eventId);
269           throw new NotFoundException("Event '" + eventId + "' not found on remote scheduler service!");
270         } else if (SC_OK == response.getStatusLine().getStatusCode()) {
271           logger.info("Event {} successfully updated with capture agent metadata.", eventId);
272           return;
273         } else if (response != null && SC_CONFLICT == response.getStatusLine().getStatusCode()) {
274           String errorJson = EntityUtils.toString(response.getEntity(), UTF_8);
275           JSONObject json = (JSONObject) parser.parse(errorJson);
276           JSONObject error = (JSONObject) json.get("error");
277           String errorCode = (String) error.get("code");
278           if (SchedulerConflictException.ERROR_CODE.equals(errorCode)) {
279             logger.info("Conflicting events found when updating event {}", eventId);
280             throw new SchedulerConflictException("Conflicting events found when updating event " + eventId);
281           } else {
282             throw new SchedulerException("Unexpected error code " + errorCode);
283           }
284         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
285           logger.info("Unauthorized to update the event {}.", eventId);
286           throw new UnauthorizedException("Unauthorized to update the event " + eventId);
287         } else if (SC_FORBIDDEN == response.getStatusLine().getStatusCode()) {
288           logger.info("Forbidden to update the event {}.", eventId);
289           throw new SchedulerException("Event with specified ID cannot be updated");
290         } else {
291           throw new SchedulerException("Unexpected status code " + response.getStatusLine());
292         }
293       }
294     } catch (NotFoundException | SchedulerConflictException | UnauthorizedException e) {
295       throw e;
296     } catch (Exception e) {
297       throw new SchedulerException("Unable to update event " + eventId + " to the scheduler service", e);
298     } finally {
299       closeConnection(response);
300     }
301     throw new SchedulerException("Unable to update  event " + eventId);
302   }
303 
304   @Override
305   public void removeEvent(String eventId) throws NotFoundException, UnauthorizedException, SchedulerException {
306     logger.debug("Start removing event {} from scheduling service.", eventId);
307     HttpDelete delete = new HttpDelete("/" + eventId);
308 
309     HttpResponse response = getResponse(delete, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED, SC_CONFLICT);
310     try {
311       if (response != null && SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
312         logger.info("Event {} was not found by the scheduler service", eventId);
313         throw new NotFoundException("Event '" + eventId + "' not found on remote scheduler service!");
314       } else if (response != null && SC_OK == response.getStatusLine().getStatusCode()) {
315         logger.info("Event {} removed from scheduling service.", eventId);
316         return;
317       } else if (response != null && SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
318         logger.info("Unauthorized to remove the event {}.", eventId);
319         throw new UnauthorizedException("Unauthorized to remove the event " + eventId);
320       }
321     } catch (UnauthorizedException | NotFoundException e) {
322       throw e;
323     } catch (Exception e) {
324       throw new SchedulerException("Unable to remove event " + eventId + " from the scheduler service", e);
325     } finally {
326       closeConnection(response);
327     }
328     throw new SchedulerException("Unable to remove  event " + eventId);
329   }
330 
331   @Override
332   public MediaPackage getMediaPackage(String eventId)
333           throws NotFoundException, UnauthorizedException, SchedulerException {
334     HttpGet get = new HttpGet(eventId.concat("/mediapackage.xml"));
335     HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
336     try {
337       if (response != null) {
338         if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
339           throw new NotFoundException("Event mediapackage '" + eventId + "' not found on remote scheduler service!");
340         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
341           logger.info("Unauthorized to get mediapacakge of the event {}.", eventId);
342           throw new UnauthorizedException("Unauthorized to get mediapackage of the event " + eventId);
343         } else {
344           MediaPackage mp = MediaPackageParser.getFromXml(EntityUtils.toString(response.getEntity(), UTF_8));
345           logger.info("Successfully get event mediapackage {} from the remote scheduler service", eventId);
346           return mp;
347         }
348       }
349     } catch (NotFoundException e) {
350       throw e;
351     } catch (UnauthorizedException e) {
352       throw e;
353     } catch (Exception e) {
354       throw new SchedulerException("Unable to parse event media package from remote scheduler service", e);
355     } finally {
356       closeConnection(response);
357     }
358     throw new SchedulerException("Unable to get event media package from remote scheduler service");
359   }
360 
361   @Override
362   public DublinCoreCatalog getDublinCore(String eventId)
363           throws NotFoundException, UnauthorizedException, SchedulerException {
364     HttpGet get = new HttpGet(eventId.concat("/dublincore.xml"));
365     HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
366     try {
367       if (response != null) {
368         if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
369           throw new NotFoundException("Event catalog '" + eventId + "' not found on remote scheduler service!");
370         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
371           logger.info("Unauthorized to get dublincore of the event {}.", eventId);
372           throw new UnauthorizedException("Unauthorized to get dublincore of the event " + eventId);
373         } else {
374           DublinCoreCatalog dublinCoreCatalog = DublinCores.read(response.getEntity().getContent());
375           logger.info("Successfully get event dublincore {} from the remote scheduler service", eventId);
376           return dublinCoreCatalog;
377         }
378       }
379     } catch (NotFoundException | UnauthorizedException e) {
380       throw e;
381     } catch (Exception e) {
382       throw new SchedulerException("Unable to parse event dublincore from remote scheduler service", e);
383     } finally {
384       closeConnection(response);
385     }
386     throw new SchedulerException("Unable to get event dublincore from remote scheduler service");
387   }
388 
389   @Override
390   public TechnicalMetadata getTechnicalMetadata(String eventId)
391           throws NotFoundException, UnauthorizedException, SchedulerException {
392     HttpGet get = new HttpGet(eventId.concat("/technical.json"));
393     HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
394     try {
395       if (response != null) {
396         if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
397           throw new NotFoundException("Event with id '" + eventId + "' not found on remote scheduler service!");
398         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
399           logger.info("Unauthorized to get the technical metadata of the event {}.", eventId);
400           throw new UnauthorizedException("Unauthorized to get the technical metadata of the event " + eventId);
401         } else {
402           String technicalMetadataJson = EntityUtils.toString(response.getEntity(), UTF_8);
403           JSONObject json = (JSONObject) parser.parse(technicalMetadataJson);
404           final String recordingId = (String) json.get("id");
405           final Date start = new Date(DateTimeSupport.fromUTC((String) json.get("start")));
406           final Date end = new Date(DateTimeSupport.fromUTC((String) json.get("end")));
407           final String location = (String) json.get("location");
408 
409           final Set<String> presenters = new HashSet<>();
410           JSONArray presentersArr = (JSONArray) json.get("presenters");
411           for (int i = 0; i < presentersArr.size(); i++) {
412             presenters.add((String) presentersArr.get(i));
413           }
414 
415           final Map<String, String> wfProperties = new HashMap<>();
416           JSONObject wfPropertiesObj = (JSONObject) json.get("wfProperties");
417           Set<Entry<String, String>> entrySet = wfPropertiesObj.entrySet();
418           for (Entry<String, String> entry : entrySet) {
419             wfProperties.put(entry.getKey(), entry.getValue());
420           }
421 
422           final Map<String, String> agentConfig = new HashMap<>();
423           JSONObject agentConfigObj = (JSONObject) json.get("agentConfig");
424           entrySet = agentConfigObj.entrySet();
425           for (Entry<String, String> entry : entrySet) {
426             agentConfig.put(entry.getKey(), entry.getValue());
427           }
428 
429           String status = (String) json.get("state");
430           String lastHeard = (String) json.get("lastHeardFrom");
431           Recording recording = null;
432           if (StringUtils.isNotBlank(status) && StringUtils.isNotBlank(lastHeard)) {
433             recording = new RecordingImpl(recordingId, status, DateTimeSupport.fromUTC(lastHeard));
434           }
435           final Optional<Recording> recordingOpt = Optional.ofNullable(recording);
436           logger.info("Successfully get the technical metadata of event '{}' from the remote scheduler service",
437                   eventId);
438           return new TechnicalMetadataImpl(recordingId, location, start, end, presenters, wfProperties,
439                   agentConfig, recordingOpt);
440         }
441       }
442     } catch (NotFoundException | UnauthorizedException e) {
443       throw e;
444     } catch (Exception e) {
445       throw new SchedulerException("Unable to parse the technical metadata from remote scheduler service", e);
446     } finally {
447       closeConnection(response);
448     }
449     throw new SchedulerException("Unable to get the technical metadata from remote scheduler service");
450   }
451 
452   @Override
453   public Map<String, String> getWorkflowConfig(String eventId)
454           throws NotFoundException, UnauthorizedException, SchedulerException {
455     HttpGet get = new HttpGet(eventId.concat("/workflow.properties"));
456     HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
457     try {
458       if (response != null) {
459         if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
460           throw new NotFoundException(
461                   "Event workflow configuration '" + eventId + "' not found on remote scheduler service!");
462         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
463           logger.info("Unauthorized to get workflow config of the event {}.", eventId);
464           throw new UnauthorizedException("Unauthorized to get workflow config of the event " + eventId);
465         } else {
466           Properties properties = new Properties();
467           properties.load(response.getEntity().getContent());
468           logger.info("Successfully get event workflow configuration {} from the remote scheduler service", eventId);
469           return new HashMap<String, String>((Map) properties);
470         }
471       }
472     } catch (NotFoundException | UnauthorizedException e) {
473       throw e;
474     } catch (Exception e) {
475       throw new SchedulerException("Unable to parse event workflow configuration from remote scheduler service", e);
476     } finally {
477       closeConnection(response);
478     }
479     throw new SchedulerException("Unable to get event workflow configuration from remote scheduler service");
480   }
481 
482   @Override
483   public Map<String, String> getCaptureAgentConfiguration(String eventId)
484           throws NotFoundException, UnauthorizedException, SchedulerException {
485     HttpGet get = new HttpGet(eventId.concat("/agent.properties"));
486     HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
487     try {
488       if (response != null) {
489         if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
490           throw new NotFoundException(
491                   "Event capture agent configuration '" + eventId + "' not found on remote scheduler service!");
492         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
493           logger.info("Unauthorized to get capture agent config of the event {}.", eventId);
494           throw new UnauthorizedException("Unauthorized to get capture agent config of the event " + eventId);
495         } else {
496           Properties properties = new Properties();
497           properties.load(response.getEntity().getContent());
498           logger.info("Successfully get event capture agent configuration {} from the remote scheduler service",
499                   eventId);
500           return new HashMap<String, String>((Map) properties);
501         }
502       }
503     } catch (NotFoundException | UnauthorizedException e) {
504       throw e;
505     } catch (Exception e) {
506       throw new SchedulerException(
507               "Unable to parse event capture agent configuration from remote scheduler service", e);
508     } finally {
509       closeConnection(response);
510     }
511     throw new SchedulerException("Unable to get event capture agent configuration from remote scheduler service");
512   }
513 
514   @Override
515   public int getEventCount() throws SchedulerException, UnauthorizedException {
516     final HttpGet get = new HttpGet(UrlSupport.concat("eventCount"));
517     final HttpResponse response = getResponse(get, SC_OK, SC_UNAUTHORIZED);
518     try {
519       if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
520         logger.info("Unauthorized to get event count");
521         throw new UnauthorizedException("Unauthorized to get event count");
522       }
523       final String countString = EntityUtils.toString(response.getEntity(), UTF_8);
524       return Integer.parseInt(countString);
525     } catch (UnauthorizedException e) {
526       throw e;
527     } catch (Exception e) {
528       throw new SchedulerException("Unable to get event count from remote scheduler service", e);
529     } finally {
530       closeConnection(response);
531     }
532   }
533 
534   @Override
535   public String getScheduleLastModified(String agentId) throws SchedulerException {
536     HttpGet get = new HttpGet(UrlSupport.concat(agentId, "lastmodified"));
537     HttpResponse response = getResponse(get, SC_OK);
538     try {
539       if (response != null) {
540         if (SC_OK == response.getStatusLine().getStatusCode()) {
541           String agentHash = EntityUtils.toString(response.getEntity(), UTF_8);
542           logger.info("Successfully get agent last modified hash of agent with id {} from the remote scheduler service",
543                   agentId);
544           return agentHash;
545         }
546       }
547     } catch (Exception e) {
548       throw new SchedulerException("Unable to get agent last modified hash from remote scheduler service", e);
549     } finally {
550       closeConnection(response);
551     }
552     throw new SchedulerException("Unable to get agent last modified hash from remote scheduler service");
553   }
554 
555   @Override
556   public List<MediaPackage> search(Optional<String> captureAgentId, Optional<Date> startsFrom, Optional<Date> startsTo,
557           Optional<Date> endFrom, Optional<Date> endTo) throws UnauthorizedException, SchedulerException {
558     List<NameValuePair> queryStringParams = new ArrayList<NameValuePair>();
559     if (captureAgentId.isPresent()) {
560       queryStringParams.add(new BasicNameValuePair("agent", captureAgentId.get()));
561     }
562     if (startsFrom.isPresent()) {
563       queryStringParams.add(new BasicNameValuePair("startsfrom", Long.toString(startsFrom.get().getTime())));
564     }
565     if (startsTo.isPresent()) {
566       queryStringParams.add(new BasicNameValuePair("startsto", Long.toString(startsTo.get().getTime())));
567     }
568     if (endFrom.isPresent()) {
569       queryStringParams.add(new BasicNameValuePair("endsfrom", Long.toString(endFrom.get().getTime())));
570     }
571     if (endTo.isPresent()) {
572       queryStringParams.add(new BasicNameValuePair("endsto", Long.toString(endTo.get().getTime())));
573     }
574     HttpGet get = new HttpGet("recordings.xml?".concat(URLEncodedUtils.format(queryStringParams, UTF_8)));
575     HttpResponse response = getResponse(get, SC_OK, SC_UNAUTHORIZED);
576     try {
577       if (response != null) {
578         if (SC_OK == response.getStatusLine().getStatusCode()) {
579           String mediaPackageXml = EntityUtils.toString(response.getEntity(), UTF_8);
580           List<MediaPackage> events = MediaPackageParser.getArrayFromXml(mediaPackageXml);
581           logger.info("Successfully get recordings from the remote scheduler service");
582           return events;
583         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
584           logger.info("Unauthorized to search for events");
585           throw new UnauthorizedException("Unauthorized to search for events");
586         }
587       }
588     } catch (UnauthorizedException e) {
589       throw e;
590     } catch (Exception e) {
591       throw new SchedulerException("Unable to get recordings from remote scheduler service", e);
592     } finally {
593       closeConnection(response);
594     }
595     throw new SchedulerException("Unable to get recordings from remote scheduler service");
596   }
597 
598   @Override
599   public Optional<MediaPackage> getCurrentRecording(String captureAgentId)
600           throws SchedulerException, UnauthorizedException {
601     HttpGet get = new HttpGet(UrlSupport.concat("currentRecording", captureAgentId));
602     HttpResponse response = getResponse(get, SC_OK, SC_NO_CONTENT, SC_UNAUTHORIZED);
603     try {
604       if (SC_OK == response.getStatusLine().getStatusCode()) {
605         String mediaPackageXml = EntityUtils.toString(response.getEntity(), UTF_8);
606         MediaPackage event = MediaPackageParser.getFromXml(mediaPackageXml);
607         logger.info("Successfully get current recording of agent {} from the remote scheduler service", captureAgentId);
608         return Optional.of(event);
609       } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
610         logger.info("Unauthorized to get current recording of agent {}", captureAgentId);
611         throw new UnauthorizedException("Unauthorized to get current recording of agent " + captureAgentId);
612       } else {
613         return Optional.empty();
614       }
615     } catch (UnauthorizedException e) {
616       throw e;
617     } catch (Exception e) {
618       throw new SchedulerException("Unable to get current recording from remote scheduler service", e);
619     } finally {
620       closeConnection(response);
621     }
622   }
623 
624   @Override
625   public Optional<MediaPackage> getUpcomingRecording(String captureAgentId)
626           throws SchedulerException, UnauthorizedException {
627     HttpGet get = new HttpGet(UrlSupport.concat("upcomingRecording", captureAgentId));
628     HttpResponse response = getResponse(get, SC_OK, SC_NO_CONTENT, SC_UNAUTHORIZED);
629     try {
630       if (SC_OK == response.getStatusLine().getStatusCode()) {
631         String mediaPackageXml = EntityUtils.toString(response.getEntity(), UTF_8);
632         MediaPackage event = MediaPackageParser.getFromXml(mediaPackageXml);
633         logger.info("Successfully get upcoming recording of agent {} from the remote scheduler service",
634             captureAgentId);
635         return Optional.of(event);
636       } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
637         logger.info("Unauthorized to get upcoming recording of agent {}", captureAgentId);
638         throw new UnauthorizedException("Unauthorized to get upcoming recording of agent " + captureAgentId);
639       } else {
640         return Optional.empty();
641       }
642     } catch (UnauthorizedException e) {
643       throw e;
644     } catch (Exception e) {
645       throw new SchedulerException("Unable to get upcoming recording from remote scheduler service", e);
646     } finally {
647       closeConnection(response);
648     }
649   }
650 
651   @Override
652   public List<MediaPackage> findConflictingEvents(String captureDeviceID, Date startDate, Date endDate)
653           throws UnauthorizedException, SchedulerException {
654     List<NameValuePair> queryStringParams = new ArrayList<NameValuePair>();
655     queryStringParams.add(new BasicNameValuePair("agent", captureDeviceID));
656     queryStringParams.add(new BasicNameValuePair("start", Long.toString(startDate.getTime())));
657     queryStringParams.add(new BasicNameValuePair("end", Long.toString(endDate.getTime())));
658     HttpGet get = new HttpGet("conflicts.xml?".concat(URLEncodedUtils.format(queryStringParams, UTF_8)));
659     HttpResponse response = getResponse(get, SC_OK, SC_NO_CONTENT);
660     try {
661       if (response != null) {
662         if (SC_OK == response.getStatusLine().getStatusCode()) {
663           String mediaPackageXml = EntityUtils.toString(response.getEntity(), UTF_8);
664           List<MediaPackage> events = MediaPackageParser.getArrayFromXml(mediaPackageXml);
665           logger.info("Successfully get conflicts from the remote scheduler service");
666           return events;
667         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
668           logger.info("Unauthorized to search for conflicting events");
669           throw new UnauthorizedException("Unauthorized to search for conflicting events");
670         } else if (SC_NO_CONTENT == response.getStatusLine().getStatusCode()) {
671           return Collections.<MediaPackage> emptyList();
672         }
673       }
674     } catch (UnauthorizedException e) {
675       throw e;
676     } catch (Exception e) {
677       throw new SchedulerException("Unable to get conflicts from remote scheduler service", e);
678     } finally {
679       closeConnection(response);
680     }
681     throw new SchedulerException("Unable to get conflicts from remote scheduler service");
682   }
683 
684   @Override
685   public List<MediaPackage> findConflictingEvents(String captureAgentId, RRule rrule, Date startDate, Date endDate,
686           long duration, TimeZone timezone) throws UnauthorizedException, SchedulerException {
687     List<NameValuePair> queryStringParams = new ArrayList<NameValuePair>();
688     queryStringParams.add(new BasicNameValuePair("agent", captureAgentId));
689     queryStringParams.add(new BasicNameValuePair("rrule", rrule.getRecur().toString()));
690     queryStringParams.add(new BasicNameValuePair("start", Long.toString(startDate.getTime())));
691     queryStringParams.add(new BasicNameValuePair("end", Long.toString(endDate.getTime())));
692     queryStringParams.add(new BasicNameValuePair("duration", Long.toString(duration)));
693     queryStringParams.add(new BasicNameValuePair("timezone", timezone.getID()));
694     HttpGet get = new HttpGet("conflicts.xml?".concat(URLEncodedUtils.format(queryStringParams, UTF_8)));
695     HttpResponse response = getResponse(get, SC_OK, SC_NO_CONTENT);
696     try {
697       if (response != null) {
698         if (SC_OK == response.getStatusLine().getStatusCode()) {
699           String mediaPackageXml = EntityUtils.toString(response.getEntity(), UTF_8);
700           List<MediaPackage> events = MediaPackageParser.getArrayFromXml(mediaPackageXml);
701           logger.info("Successfully get conflicts from the remote scheduler service");
702           return events;
703         } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
704           logger.info("Unauthorized to search for conflicting events");
705           throw new UnauthorizedException("Unauthorized to search for conflicting events");
706         } else if (SC_NO_CONTENT == response.getStatusLine().getStatusCode()) {
707           return Collections.<MediaPackage> emptyList();
708         }
709       }
710     } catch (UnauthorizedException e) {
711       throw e;
712     } catch (Exception e) {
713       throw new SchedulerException("Unable to get conflicts from remote scheduler service", e);
714     } finally {
715       closeConnection(response);
716     }
717     throw new SchedulerException("Unable to get conflicts from remote scheduler service");
718   }
719 
720   @Override
721   public String getCalendar(Optional<String> captureAgentId, Optional<String> seriesId, Optional<Date> cutoff)
722           throws SchedulerException {
723     List<NameValuePair> queryStringParams = new ArrayList<NameValuePair>();
724     if (captureAgentId.isPresent()) {
725       queryStringParams.add(new BasicNameValuePair("agentid", captureAgentId.get()));
726     }
727     if (seriesId.isPresent()) {
728       queryStringParams.add(new BasicNameValuePair("seriesid", seriesId.get()));
729     }
730     if (cutoff.isPresent()) {
731       queryStringParams.add(new BasicNameValuePair("cutoff", Long.toString(cutoff.get().getTime())));
732     }
733     HttpGet get = new HttpGet("calendars?".concat(URLEncodedUtils.format(queryStringParams, UTF_8)));
734     HttpResponse response = getResponse(get, SC_OK);
735     try {
736       if (response != null) {
737         if (SC_OK == response.getStatusLine().getStatusCode()) {
738           String calendar = EntityUtils.toString(response.getEntity(), UTF_8);
739           logger.info("Successfully get calendar of agent with id {} from the remote scheduler service",
740                   captureAgentId);
741           return calendar;
742         }
743       }
744     } catch (Exception e) {
745       throw new SchedulerException("Unable to get calendar from remote scheduler service", e);
746     } finally {
747       closeConnection(response);
748     }
749     throw new SchedulerException("Unable to get calendar from remote scheduler service");
750   }
751 
752   @Override
753   public void removeScheduledRecordingsBeforeBuffer(long buffer) throws UnauthorizedException, SchedulerException {
754     HttpPost post = new HttpPost("/removeOldScheduledRecordings");
755     logger.debug("Start removing old schedules before buffer {} through remote Schedule Service", buffer);
756 
757     List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
758     params.add(new BasicNameValuePair("buffer", Long.toString(buffer)));
759     post.setEntity(new UrlEncodedFormEntity(params, UTF_8));
760 
761     HttpResponse response = getResponse(post, SC_OK, SC_UNAUTHORIZED);
762     try {
763       if (response != null && SC_OK == response.getStatusLine().getStatusCode()) {
764         logger.info("Successfully started removing old schedules before butter {} to the scheduler service", buffer);
765         return;
766       } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
767         logger.info("Unauthorized to remove old schedules before buffer {}.", buffer);
768         throw new UnauthorizedException("Unauthorized to remove old schedules");
769       }
770     } catch (UnauthorizedException e) {
771       throw e;
772     } catch (Exception e) {
773       throw new SchedulerException("Unable to remove old schedules from the scheduler service", e);
774     } finally {
775       closeConnection(response);
776     }
777     throw new SchedulerException("Unable to remove old schedules from the scheduler service");
778   }
779 
780   @Override
781   public boolean updateRecordingState(String mediapackageId, String state)
782           throws NotFoundException, SchedulerException {
783     HttpPut put = new HttpPut(UrlSupport.concat(mediapackageId, "recordingStatus"));
784 
785     List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
786     params.add(new BasicNameValuePair("state", state));
787     put.setEntity(new UrlEncodedFormEntity(params, UTF_8));
788 
789     HttpResponse response = getResponse(put, SC_OK, SC_NOT_FOUND, SC_BAD_REQUEST);
790     try {
791       if (response != null) {
792         if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
793           logger.warn("Event with mediapackage id {} was not found by the scheduler service", mediapackageId);
794           throw new NotFoundException(
795                   "Event with mediapackage id '" + mediapackageId + "' not found on remote scheduler service!");
796         } else if (SC_BAD_REQUEST == response.getStatusLine().getStatusCode()) {
797           logger.info("Unable to update event with mediapackage id {}, invalid recording state: {}.", mediapackageId,
798                   state);
799           return false;
800         } else if (SC_OK == response.getStatusLine().getStatusCode()) {
801           logger.info("Event with mediapackage id {} successfully updated with recording status.", mediapackageId);
802           return true;
803         } else {
804           throw new SchedulerException("Unexpected status code " + response.getStatusLine());
805         }
806       }
807     } catch (NotFoundException e) {
808       throw e;
809     } catch (Exception e) {
810       throw new SchedulerException("Unable to update recording state of event with mediapackage id " + mediapackageId
811               + " to the scheduler service", e);
812     } finally {
813       closeConnection(response);
814     }
815     throw new SchedulerException("Unable to update recording state of event with mediapackage id " + mediapackageId);
816   }
817 
818   @Override
819   public Recording getRecordingState(String id) throws NotFoundException, SchedulerException {
820     HttpGet get = new HttpGet(UrlSupport.concat(id, "recordingStatus"));
821     HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND);
822     try {
823       if (response != null) {
824         if (SC_OK == response.getStatusLine().getStatusCode()) {
825           String recordingStateJson = EntityUtils.toString(response.getEntity(), UTF_8);
826           JSONObject json = (JSONObject) parser.parse(recordingStateJson);
827           String recordingId = (String) json.get("id");
828           String status = (String) json.get("state");
829           Long lastHeard = (Long) json.get("lastHeardFrom");
830           logger.info("Successfully get calendar of agent with id {} from the remote scheduler service", id);
831           return new RecordingImpl(recordingId, status, lastHeard);
832         } else if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
833           logger.warn("Event with mediapackage id {} was not found by the scheduler service", id);
834           throw new NotFoundException("Event with mediapackage id '" + id + "' not found on remote scheduler service!");
835         }
836       }
837     } catch (NotFoundException e) {
838       throw e;
839     } catch (Exception e) {
840       throw new SchedulerException("Unable to get calendar from remote scheduler service", e);
841     } finally {
842       closeConnection(response);
843     }
844     throw new SchedulerException("Unable to get calendar from remote scheduler service");
845   }
846 
847   @Override
848   public void removeRecording(String eventId) throws NotFoundException, SchedulerException {
849     HttpDelete delete = new HttpDelete(UrlSupport.concat(eventId, "recordingStatus"));
850 
851     HttpResponse response = getResponse(delete, SC_OK, SC_NOT_FOUND);
852     try {
853       if (response != null && SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
854         logger.info("Event {} was not found by the scheduler service", eventId);
855         throw new NotFoundException("Event '" + eventId + "' not found on remote scheduler service!");
856       } else if (response != null && SC_OK == response.getStatusLine().getStatusCode()) {
857         logger.info("Recording status of event {} removed from scheduling service.", eventId);
858         return;
859       }
860     } catch (NotFoundException e) {
861       throw e;
862     } catch (Exception e) {
863       throw new SchedulerException(
864               "Unable to remove recording status of event " + eventId + " from the scheduler service", e);
865     } finally {
866       closeConnection(response);
867     }
868     throw new SchedulerException("Unable to remove  recording status of event " + eventId);
869   }
870 
871   @Override
872   public Map<String, Recording> getKnownRecordings() throws SchedulerException {
873     HttpGet get = new HttpGet("recordingStatus");
874     HttpResponse response = getResponse(get, SC_OK);
875     try {
876       if (response != null) {
877         if (SC_OK == response.getStatusLine().getStatusCode()) {
878           String recordingStates = EntityUtils.toString(response.getEntity(), UTF_8);
879           JSONArray recordings = (JSONArray) parser.parse(recordingStates);
880           Map<String, Recording> recordingsMap = new HashMap<String, Recording>();
881           for (int i = 0; i < recordings.size(); i++) {
882             JSONObject recording = (JSONObject) recordings.get(i);
883             String recordingId = (String) recording.get("id");
884             String status = (String) recording.get("state");
885             Long lastHeard = (Long) recording.get("lastHeardFrom");
886             recordingsMap.put(recordingId, new RecordingImpl(recordingId, status, lastHeard));
887           }
888           logger.info("Successfully get recording states from the remote scheduler service");
889           return recordingsMap;
890         }
891       }
892     } catch (Exception e) {
893       throw new SchedulerException("Unable to get recording states from remote scheduler service", e);
894     } finally {
895       closeConnection(response);
896     }
897     throw new SchedulerException("Unable to get recording states from remote scheduler service");
898   }
899 
900   private String toPropertyString(Map<String, String> properties) {
901     StringBuilder wfPropertiesString = new StringBuilder();
902     for (Map.Entry<String, String> entry : properties.entrySet()) {
903       wfPropertiesString.append(entry.getKey() + "=" + entry.getValue() + "\n");
904     }
905     return wfPropertiesString.toString();
906   }
907 
908   @Reference
909   @Override
910   public void setTrustedHttpClient(TrustedHttpClient trustedHttpClient) {
911     super.setTrustedHttpClient(trustedHttpClient);
912   }
913   @Reference
914   @Override
915   public void setRemoteServiceManager(ServiceRegistry serviceRegistry) {
916     super.setRemoteServiceManager(serviceRegistry);
917   }
918 
919 }