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.capture.admin.impl;
23  
24  import org.opencastproject.capture.CaptureParameters;
25  import org.opencastproject.capture.admin.api.Agent;
26  
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import java.io.IOException;
31  import java.io.StringReader;
32  import java.io.StringWriter;
33  import java.util.HashMap;
34  import java.util.HashSet;
35  import java.util.Properties;
36  import java.util.Set;
37  
38  import javax.persistence.CollectionTable;
39  import javax.persistence.Column;
40  import javax.persistence.ElementCollection;
41  import javax.persistence.Entity;
42  import javax.persistence.Id;
43  import javax.persistence.IdClass;
44  import javax.persistence.Index;
45  import javax.persistence.JoinColumn;
46  import javax.persistence.Lob;
47  import javax.persistence.NamedQueries;
48  import javax.persistence.NamedQuery;
49  import javax.persistence.PostLoad;
50  import javax.persistence.Table;
51  import javax.persistence.Transient;
52  import javax.persistence.UniqueConstraint;
53  
54  /**
55   * An in-memory construct to represent the state of a capture agent, and when it was last heard from.
56   */
57  @Entity @IdClass(AgentImplId.class)
58  @Table(name = "oc_capture_agent_state")
59  @NamedQueries({
60      @NamedQuery(name = "Agent.get", query = "select a from AgentImpl a where a.name = :id and a.organization = :org"),
61      @NamedQuery(name = "Agent.byOrganization", query = "SELECT a FROM AgentImpl a where a.organization = :org")
62  })
63  public class AgentImpl implements Agent {
64  
65    private static final Logger log = LoggerFactory.getLogger(AgentImpl.class);
66  
67    /**
68     * The name of the agent.
69     */
70    @Id
71    @Column(name = "id", length = 128)
72    protected String name;
73  
74    /**
75     * The state of the agent. This should be defined from the constants in AgentState.
76     */
77    @Lob
78    @Column(name = "state", nullable = false, length = 65535)
79    protected String state;
80  
81    /**
82     * The URL of the agent. This is determined from the referer header parameter when the agent is registered.
83     */
84    @Lob
85    @Column(name = "url", length = 65535)
86    protected String url;
87  
88    @Id
89    @Column(name = "organization", length = 128)
90    protected String organization;
91  
92    /**
93     * The time at which the agent last checked in with this service. Note that this is an absolute timestamp (ie,
94     * milliseconds since 1970) rather than a relative timestamp (ie, it's been 3000 ms since it last checked in).
95     */
96    @Column(name = "last_heard_from", nullable = false)
97    protected Long lastHeardFrom;
98  
99    /** The roles allowed to schedule this agent */
100   @ElementCollection
101   @Column(name = "role", nullable = false)
102   @CollectionTable(
103       name = "oc_capture_agent_role",
104       indexes = {
105           @Index(name = "IX_oc_capture_agent_role_pk", columnList = "id, organization")
106       }, uniqueConstraints = {
107           @UniqueConstraint(name = "UNQ_capture_agent_role_id_org_role", columnNames = {"id", "organization", "role"})
108       }, joinColumns = {
109           @JoinColumn(name = "id", referencedColumnName = "id", nullable = false),
110           @JoinColumn(name = "organization", referencedColumnName = "organization", nullable = false)
111       })
112   protected Set<String> schedulerRoles = new HashSet<>();
113 
114   /**
115    * The capabilities the agent has Capabilities are the devices this agent can record from, with a friendly name
116    * associated to determine their nature (e.g. PRESENTER --&gt; dev/video0)
117    */
118   @Lob
119   @Column(name = "configuration", nullable = true, length = 65535)
120   protected String configurationString;
121 
122   // Private var to store the caps as a properties object.
123   @Transient
124   private Properties capabilitiesProperties;
125 
126   // Private var to store the configuration as a properties object.
127   @Transient
128   private Properties configurationProperties;
129 
130   /**
131    * Required 0-arg constructor for JAXB, creates a blank agent.
132    */
133   public AgentImpl() {
134   };
135 
136   /**
137    * Builds a representation of the agent.
138    *
139    * @param agentName
140    *          The name of the agent.
141    * @param organization
142    *          The organization identifier to which this agent belongs
143    * @param agentState
144    *          The state of the agent. This should be defined from the constants in AgentState
145    * @param configuration
146    *          The configuration of the agent.
147    */
148   public AgentImpl(String agentName, String organization, String agentState, String agentUrl,
149       Properties configuration) {
150     name = agentName;
151     this.setState(agentState);
152     this.setUrl(agentUrl);
153     this.setOrganization(organization);
154     // Agents with no capabilities are allowed. These can/will be updated after the agent is built if necessary.
155     setConfiguration(configuration);
156   }
157 
158   /**
159    * {@inheritDoc}
160    *
161    * @see org.opencastproject.capture.admin.api.Agent#getName()
162    */
163   public String getName() {
164     return name;
165   }
166 
167   /**
168    * {@inheritDoc}
169    *
170    * @see org.opencastproject.capture.admin.api.Agent#setState(java.lang.String)
171    */
172   public void setState(String newState) {
173     state = newState;
174     setLastHeardFrom(System.currentTimeMillis());
175   }
176 
177   /**
178    * {@inheritDoc}
179    *
180    * @see org.opencastproject.capture.admin.api.Agent#getState()
181    */
182   public String getState() {
183     return state;
184   }
185 
186   /**
187    * {@inheritDoc}
188    *
189    * @see org.opencastproject.capture.admin.api.Agent#setUrl(java.lang.String)
190    */
191   public void setUrl(String agentUrl) {
192     url = agentUrl;
193   }
194 
195   /**
196    * {@inheritDoc}
197    *
198    * @see org.opencastproject.capture.admin.api.Agent#getUrl()
199    */
200   public String getUrl() {
201     return url;
202   }
203 
204   /**
205    * {@inheritDoc}
206    *
207    * @see org.opencastproject.capture.admin.api.Agent#setLastHeardFrom(java.lang.Long)
208    */
209   public void setLastHeardFrom(Long time) {
210     lastHeardFrom = time;
211   }
212 
213   /**
214    * {@inheritDoc}
215    *
216    * @see org.opencastproject.capture.admin.api.Agent#getLastHeardFrom()
217    */
218   public Long getLastHeardFrom() {
219     return lastHeardFrom;
220   }
221 
222   /**
223    * {@inheritDoc}
224    *
225    * @see org.opencastproject.capture.admin.api.Agent#getCapabilities()
226    */
227   public Properties getCapabilities() {
228     return capabilitiesProperties;
229   }
230 
231   /**
232    * {@inheritDoc}
233    *
234    * @see org.opencastproject.capture.admin.api.Agent#getConfiguration()
235    */
236   public Properties getConfiguration() {
237     return configurationProperties;
238   }
239 
240   /**
241    * @return the schedulerRoles
242    */
243   public Set<String> getSchedulerRoles() {
244     return schedulerRoles;
245   }
246 
247   /**
248    * @param schedulerRoles
249    *          the schedulerRoles to set
250    */
251   public void setSchedulerRoles(Set<String> schedulerRoles) {
252     this.schedulerRoles = schedulerRoles;
253   }
254 
255   /**
256    * @return the organization
257    */
258   public String getOrganization() {
259     return organization;
260   }
261 
262   /**
263    * @param organization
264    *          the organization to set
265    */
266   public void setOrganization(String organization) {
267     this.organization = organization;
268   }
269 
270   /**
271    * {@inheritDoc}
272    *
273    * @see org.opencastproject.capture.admin.api.Agent#setConfiguration(java.util.Properties)
274    */
275   public void setConfiguration(Properties configuration) {
276     if (configuration == null) {
277       return;
278     }
279 
280     // Set the configuration variables
281     configurationProperties = configuration;
282     try {
283       StringWriter sw = new StringWriter();
284       configuration.store(sw, "");
285       this.configurationString = sw.toString();
286     } catch (IOException e) {
287       log.warn("Unable to store agent " + "'s capabilities to the database, IO exception occurred.", e);
288     }
289 
290     // Figure out the capabilities variables
291 
292     capabilitiesProperties = new Properties();
293 
294     String names = configuration.getProperty(CaptureParameters.CAPTURE_DEVICE_NAMES);
295     if (names == null) {
296       log.debug("Capture agent '{}' failed to provide device names ({})", name, CaptureParameters.CAPTURE_DEVICE_NAMES);
297       return;
298     }
299 
300     capabilitiesProperties.put(CaptureParameters.CAPTURE_DEVICE_NAMES, names);
301     // Get the names and setup a hash map of them
302     String[] friendlyNames = names.split(",");
303     HashMap<String, Integer> propertyCounts = new HashMap<String, Integer>();
304     for (String name : friendlyNames) {
305       propertyCounts.put(name, 0);
306     }
307 
308     // For each key
309     for (String key : configuration.stringPropertyNames()) {
310       // For each device
311       for (String name : friendlyNames) {
312         String check = CaptureParameters.CAPTURE_DEVICE_PREFIX + name;
313         // If the key looks like a device prefix + the name, copy it
314         if (key.contains(check)) {
315           String property = configuration.getProperty(key);
316           if (property == null) {
317             log.error("Unable to expand variable in value for key {}, returning null!", key);
318             capabilitiesProperties = null;
319             return;
320           }
321           capabilitiesProperties.setProperty(key, property);
322           propertyCounts.put(name, propertyCounts.get(name) + 1);
323         }
324       }
325     }
326   }
327 
328   /**
329    * Post load method to load the capabilities from a string to the properties object
330    */
331   @SuppressWarnings("unused")
332   @PostLoad
333   private void loadCaps() {
334     configurationProperties = new Properties();
335     if (configurationString == null) {
336       log.info("No configuration properties set yet for '{}'", name);
337     } else {
338       try {
339         configurationProperties.load(new StringReader(this.configurationString));
340         this.setConfiguration(configurationProperties);
341       } catch (IOException e) {
342         log.warn("Unable to load agent " + name + "'s capabilities, IO exception occurred.", e);
343       }
344     }
345   }
346 
347   /**
348    * {@inheritDoc}
349    *
350    * @see java.lang.Object#equals(java.lang.Object)
351    */
352   @Override
353   public boolean equals(Object o) {
354     if (this == o) {
355       return true;
356     }
357     if (o instanceof AgentImpl) {
358       return name.equals(((AgentImpl) o).name);
359     } else {
360       return false;
361     }
362   }
363 
364   /**
365    * {@inheritDoc}
366    *
367    * @see java.lang.Object#hashCode()
368    */
369   @Override
370   public int hashCode() {
371     return name.hashCode();
372   }
373 }