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