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