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(name = "oc_capture_agent_role", indexes = {
103       @Index(name = "IX_oc_capture_agent_role_pk", columnList = "id, organization")
104     }, uniqueConstraints = {
105       @UniqueConstraint(name = "UNQ_capture_agent_role_id_org_role", columnNames = {"id", "organization", "role"})
106     }, joinColumns = {
107       @JoinColumn(name = "id", referencedColumnName = "id", nullable = false),
108       @JoinColumn(name = "organization", referencedColumnName = "organization", nullable = false) })
109   protected Set<String> schedulerRoles = new HashSet<>();
110 
111   /**
112    * The capabilities the agent has Capabilities are the devices this agent can record from, with a friendly name
113    * associated to determine their nature (e.g. PRESENTER --&gt; dev/video0)
114    */
115   @Lob
116   @Column(name = "configuration", nullable = true, length = 65535)
117   protected String configurationString;
118 
119   // Private var to store the caps as a properties object.
120   @Transient
121   private Properties capabilitiesProperties;
122 
123   // Private var to store the configuration as a properties object.
124   @Transient
125   private Properties configurationProperties;
126 
127   /**
128    * Required 0-arg constructor for JAXB, creates a blank agent.
129    */
130   public AgentImpl() {
131   };
132 
133   /**
134    * Builds a representation of the agent.
135    *
136    * @param agentName
137    *          The name of the agent.
138    * @param organization
139    *          The organization identifier to which this agent belongs
140    * @param agentState
141    *          The state of the agent. This should be defined from the constants in AgentState
142    * @param configuration
143    *          The configuration of the agent.
144    */
145   public AgentImpl(String agentName, String organization, String agentState, String agentUrl, Properties configuration) {
146     name = agentName;
147     this.setState(agentState);
148     this.setUrl(agentUrl);
149     this.setOrganization(organization);
150     // Agents with no capabilities are allowed. These can/will be updated after the agent is built if necessary.
151     setConfiguration(configuration);
152   }
153 
154   /**
155    * {@inheritDoc}
156    *
157    * @see org.opencastproject.capture.admin.api.Agent#getName()
158    */
159   public String getName() {
160     return name;
161   }
162 
163   /**
164    * {@inheritDoc}
165    *
166    * @see org.opencastproject.capture.admin.api.Agent#setState(java.lang.String)
167    */
168   public void setState(String newState) {
169     state = newState;
170     setLastHeardFrom(System.currentTimeMillis());
171   }
172 
173   /**
174    * {@inheritDoc}
175    *
176    * @see org.opencastproject.capture.admin.api.Agent#getState()
177    */
178   public String getState() {
179     return state;
180   }
181 
182   /**
183    * {@inheritDoc}
184    *
185    * @see org.opencastproject.capture.admin.api.Agent#setUrl(java.lang.String)
186    */
187   public void setUrl(String agentUrl) {
188     url = agentUrl;
189   }
190 
191   /**
192    * {@inheritDoc}
193    *
194    * @see org.opencastproject.capture.admin.api.Agent#getUrl()
195    */
196   public String getUrl() {
197     return url;
198   }
199 
200   /**
201    * {@inheritDoc}
202    *
203    * @see org.opencastproject.capture.admin.api.Agent#setLastHeardFrom(java.lang.Long)
204    */
205   public void setLastHeardFrom(Long time) {
206     lastHeardFrom = time;
207   }
208 
209   /**
210    * {@inheritDoc}
211    *
212    * @see org.opencastproject.capture.admin.api.Agent#getLastHeardFrom()
213    */
214   public Long getLastHeardFrom() {
215     return lastHeardFrom;
216   }
217 
218   /**
219    * {@inheritDoc}
220    *
221    * @see org.opencastproject.capture.admin.api.Agent#getCapabilities()
222    */
223   public Properties getCapabilities() {
224     return capabilitiesProperties;
225   }
226 
227   /**
228    * {@inheritDoc}
229    *
230    * @see org.opencastproject.capture.admin.api.Agent#getConfiguration()
231    */
232   public Properties getConfiguration() {
233     return configurationProperties;
234   }
235 
236   /**
237    * @return the schedulerRoles
238    */
239   public Set<String> getSchedulerRoles() {
240     return schedulerRoles;
241   }
242 
243   /**
244    * @param schedulerRoles
245    *          the schedulerRoles to set
246    */
247   public void setSchedulerRoles(Set<String> schedulerRoles) {
248     this.schedulerRoles = schedulerRoles;
249   }
250 
251   /**
252    * @return the organization
253    */
254   public String getOrganization() {
255     return organization;
256   }
257 
258   /**
259    * @param organization
260    *          the organization to set
261    */
262   public void setOrganization(String organization) {
263     this.organization = organization;
264   }
265 
266   /**
267    * {@inheritDoc}
268    *
269    * @see org.opencastproject.capture.admin.api.Agent#setConfiguration(java.util.Properties)
270    */
271   public void setConfiguration(Properties configuration) {
272     if (configuration == null) {
273       return;
274     }
275 
276     // Set the configuration variables
277     configurationProperties = configuration;
278     try {
279       StringWriter sw = new StringWriter();
280       configuration.store(sw, "");
281       this.configurationString = sw.toString();
282     } catch (IOException e) {
283       log.warn("Unable to store agent " + "'s capabilities to the database, IO exception occurred.", e);
284     }
285 
286     // Figure out the capabilities variables
287 
288     capabilitiesProperties = new Properties();
289 
290     String names = configuration.getProperty(CaptureParameters.CAPTURE_DEVICE_NAMES);
291     if (names == null) {
292       log.debug("Capture agent '{}' failed to provide device names ({})", name, CaptureParameters.CAPTURE_DEVICE_NAMES);
293       return;
294     }
295 
296     capabilitiesProperties.put(CaptureParameters.CAPTURE_DEVICE_NAMES, names);
297     // Get the names and setup a hash map of them
298     String[] friendlyNames = names.split(",");
299     HashMap<String, Integer> propertyCounts = new HashMap<String, Integer>();
300     for (String name : friendlyNames) {
301       propertyCounts.put(name, 0);
302     }
303 
304     // For each key
305     for (String key : configuration.stringPropertyNames()) {
306       // For each device
307       for (String name : friendlyNames) {
308         String check = CaptureParameters.CAPTURE_DEVICE_PREFIX + name;
309         // If the key looks like a device prefix + the name, copy it
310         if (key.contains(check)) {
311           String property = configuration.getProperty(key);
312           if (property == null) {
313             log.error("Unable to expand variable in value for key {}, returning null!", key);
314             capabilitiesProperties = null;
315             return;
316           }
317           capabilitiesProperties.setProperty(key, property);
318           propertyCounts.put(name, propertyCounts.get(name) + 1);
319         }
320       }
321     }
322   }
323 
324   /**
325    * Post load method to load the capabilities from a string to the properties object
326    */
327   @SuppressWarnings("unused")
328   @PostLoad
329   private void loadCaps() {
330     configurationProperties = new Properties();
331     if (configurationString == null) {
332       log.info("No configuration properties set yet for '{}'", name);
333     } else {
334       try {
335         configurationProperties.load(new StringReader(this.configurationString));
336         this.setConfiguration(configurationProperties);
337       } catch (IOException e) {
338         log.warn("Unable to load agent " + name + "'s capabilities, IO exception occurred.", e);
339       }
340     }
341   }
342 
343   /**
344    * {@inheritDoc}
345    *
346    * @see java.lang.Object#equals(java.lang.Object)
347    */
348   @Override
349   public boolean equals(Object o) {
350     if (this == o) {
351       return true;
352     }
353     if (o instanceof AgentImpl) {
354       return name.equals(((AgentImpl) o).name);
355     } else {
356       return false;
357     }
358   }
359 
360   /**
361    * {@inheritDoc}
362    *
363    * @see java.lang.Object#hashCode()
364    */
365   @Override
366   public int hashCode() {
367     return name.hashCode();
368   }
369 }