1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.adminui.endpoint;
23
24 import static org.apache.commons.lang3.StringUtils.trimToNull;
25 import static org.apache.http.HttpStatus.SC_OK;
26 import static org.opencastproject.index.service.util.JSONUtils.safeString;
27 import static org.opencastproject.index.service.util.RestUtils.okJson;
28 import static org.opencastproject.index.service.util.RestUtils.okJsonList;
29 import static org.opencastproject.util.DateTimeSupport.toUTC;
30 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
31
32 import org.opencastproject.adminui.util.TextFilter;
33 import org.opencastproject.capture.CaptureParameters;
34 import org.opencastproject.capture.admin.api.Agent;
35 import org.opencastproject.capture.admin.api.AgentState;
36 import org.opencastproject.capture.admin.api.CaptureAgentStateService;
37 import org.opencastproject.index.service.resources.list.query.AgentsListQuery;
38 import org.opencastproject.index.service.util.RestUtils;
39 import org.opencastproject.security.api.SecurityService;
40 import org.opencastproject.security.api.UnauthorizedException;
41 import org.opencastproject.security.util.SecurityUtil;
42 import org.opencastproject.util.NotFoundException;
43 import org.opencastproject.util.SmartIterator;
44 import org.opencastproject.util.doc.rest.RestParameter;
45 import org.opencastproject.util.doc.rest.RestQuery;
46 import org.opencastproject.util.doc.rest.RestResponse;
47 import org.opencastproject.util.doc.rest.RestService;
48 import org.opencastproject.util.requests.SortCriterion;
49 import org.opencastproject.util.requests.SortCriterion.Order;
50
51 import com.google.gson.JsonArray;
52 import com.google.gson.JsonObject;
53
54 import org.apache.commons.lang3.StringUtils;
55 import org.osgi.service.component.annotations.Component;
56 import org.osgi.service.component.annotations.Reference;
57 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.Comparator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Map.Entry;
67 import java.util.Optional;
68 import java.util.Properties;
69
70 import javax.servlet.http.HttpServletResponse;
71 import javax.ws.rs.DELETE;
72 import javax.ws.rs.GET;
73 import javax.ws.rs.Path;
74 import javax.ws.rs.PathParam;
75 import javax.ws.rs.Produces;
76 import javax.ws.rs.QueryParam;
77 import javax.ws.rs.core.MediaType;
78 import javax.ws.rs.core.Response;
79 import javax.ws.rs.core.Response.Status;
80
81 @Path("/admin-ng/capture-agents")
82 @RestService(name = "captureAgents", title = "Capture agents façade service",
83 abstractText = "Provides operations for the capture agents",
84 notes = { "This service offers the default capture agents CRUD Operations for the admin UI.",
85 "<strong>Important:</strong> "
86 + "<em>This service is for exclusive use by the module admin-ui. Its API might change "
87 + "anytime without prior notice. Any dependencies other than the admin UI will be strictly ignored. "
88 + "DO NOT use this for integration of third-party applications.<em>"})
89 @Component(
90 immediate = true,
91 service = CaptureAgentsEndpoint.class,
92 property = {
93 "service.description=Admin UI - Capture agents facade Endpoint",
94 "opencast.service.type=org.opencastproject.adminui.endpoint.UsersEndpoint",
95 "opencast.service.path=/admin-ng/capture-agents"
96 }
97 )
98 @JaxrsResource
99 public class CaptureAgentsEndpoint {
100
101 private static final String TRANSLATION_KEY_PREFIX = "CAPTURE_AGENT.DEVICE.";
102
103
104 private static final Logger logger = LoggerFactory.getLogger(CaptureAgentsEndpoint.class);
105
106
107 private CaptureAgentStateService service;
108
109 private SecurityService securityService;
110
111
112
113
114
115
116
117 @Reference
118 public void setCaptureAgentService(CaptureAgentStateService service) {
119 this.service = service;
120 }
121
122 @Reference
123 public void setSecurityService(SecurityService securityService) {
124 this.securityService = securityService;
125 }
126
127 @GET
128 @Produces({ MediaType.APPLICATION_JSON })
129 @Path("agents.json")
130 @RestQuery(name = "getAgents", description = "Return all of the known capture agents on the system", restParameters = {
131 @RestParameter(name = "filter", isRequired = false, description = "The filter used for the query. They should be formated like that: 'filter1:value1,filter2:value2'", type = STRING),
132 @RestParameter(defaultValue = "100", description = "The maximum number of items to return per page.", isRequired = false, name = "limit", type = RestParameter.Type.STRING),
133 @RestParameter(defaultValue = "0", description = "The page number.", isRequired = false, name = "offset", type = RestParameter.Type.STRING),
134 @RestParameter(defaultValue = "false", description = "Define if the inputs should or not returned with the capture agent.", isRequired = false, name = "inputs", type = RestParameter.Type.BOOLEAN),
135 @RestParameter(name = "sort", isRequired = false, description = "The sort order. May include any of the following: STATUS, NAME OR LAST_UPDATED. Add '_DESC' to reverse the sort order (e.g. STATUS_DESC).", type = STRING) }, responses = { @RestResponse(description = "An XML representation of the agent capabilities", responseCode = HttpServletResponse.SC_OK) }, returnDescription = "")
136 public Response getAgents(@QueryParam("limit") int limit, @QueryParam("offset") int offset,
137 @QueryParam("inputs") boolean inputs, @QueryParam("filter") String filter, @QueryParam("sort") String sort) {
138 Optional<String> filterName = Optional.empty();
139 Optional<String> filterStatus = Optional.empty();
140 Optional<Long> filterLastUpdated = Optional.empty();
141 Optional<String> filterText = Optional.empty();
142 Optional<String> optSort = Optional.ofNullable(trimToNull(sort));
143
144 Map<String, String> filters = RestUtils.parseFilter(filter);
145 for (String name : filters.keySet()) {
146 if (AgentsListQuery.FILTER_NAME_NAME.equals(name))
147 filterName = Optional.of(filters.get(name));
148 if (AgentsListQuery.FILTER_STATUS_NAME.equals(name))
149 filterStatus = Optional.of(filters.get(name));
150 if (AgentsListQuery.FILTER_LAST_UPDATED.equals(name)) {
151 try {
152 filterLastUpdated = Optional.of(Long.parseLong(filters.get(name)));
153 } catch (NumberFormatException e) {
154 logger.info("Unable to parse long {}", filters.get(name));
155 return Response.status(Status.BAD_REQUEST).build();
156 }
157 }
158 if (AgentsListQuery.FILTER_TEXT_NAME.equals(name) && StringUtils.isNotBlank(filters.get(name)))
159 filterText = Optional.of(filters.get(name));
160 }
161
162
163 List<Agent> filteredAgents = new ArrayList<>();
164 for (Entry<String, Agent> entry : service.getKnownAgents().entrySet()) {
165 Agent agent = entry.getValue();
166
167
168 if ((filterName.isPresent() && !filterName.get().equals(agent.getName()))
169 || (filterStatus.isPresent() && !filterStatus.get().equals(agent.getState()))
170 || (filterLastUpdated.isPresent() && filterLastUpdated.get() != agent.getLastHeardFrom())
171 || (filterText.isPresent() && !TextFilter.match(filterText.get(), agent.getName(), agent.getState())))
172 continue;
173 filteredAgents.add(agent);
174 }
175 int total = filteredAgents.size();
176
177
178 if (optSort.isPresent()) {
179 final ArrayList<SortCriterion> sortCriteria = RestUtils.parseSortQueryParameter(optSort.get());
180 Collections.sort(filteredAgents, new Comparator<Agent>() {
181 @Override
182 public int compare(Agent agent1, Agent agent2) {
183 for (SortCriterion criterion : sortCriteria) {
184 Order order = criterion.getOrder();
185 switch (criterion.getFieldName()) {
186 case "status":
187 if (order.equals(Order.Descending))
188 return agent2.getState().compareTo(agent1.getState());
189 return agent1.getState().compareTo(agent2.getState());
190 case "name":
191 if (order.equals(Order.Descending))
192 return agent2.getName().compareTo(agent1.getName());
193 return agent1.getName().compareTo(agent2.getName());
194 case "updated":
195 if (order.equals(Order.Descending))
196 return agent2.getLastHeardFrom().compareTo(agent1.getLastHeardFrom());
197 return agent1.getLastHeardFrom().compareTo(agent2.getLastHeardFrom());
198 default:
199 logger.info("Unknown sort type: {}", criterion.getFieldName());
200 return 0;
201 }
202 }
203 return 0;
204 }
205 });
206 }
207
208
209 filteredAgents = new SmartIterator<Agent>(limit, offset).applyLimitAndOffset(filteredAgents);
210
211
212 List<JsonObject> agentsJSON = new ArrayList<>();
213 for (Agent agent : filteredAgents) {
214 agentsJSON.add(generateJsonAgent(agent, inputs, false));
215 }
216
217 return okJsonList(agentsJSON, offset, limit, total);
218 }
219
220 @DELETE
221 @Path("{name}")
222 @Produces({ MediaType.APPLICATION_JSON })
223 @RestQuery(name = "removeAgent", description = "Remove record of a given capture agent", pathParameters = { @RestParameter(name = "name", description = "The name of a given capture agent", isRequired = true, type = RestParameter.Type.STRING) }, restParameters = {}, responses = {
224 @RestResponse(description = "{agentName} removed", responseCode = HttpServletResponse.SC_OK),
225 @RestResponse(description = "The agent {agentname} does not exist", responseCode = HttpServletResponse.SC_NOT_FOUND) }, returnDescription = "")
226 public Response removeAgent(@PathParam("name") String agentName) throws NotFoundException, UnauthorizedException {
227 if (service == null)
228 return Response.serverError().status(Response.Status.SERVICE_UNAVAILABLE).build();
229
230 SecurityUtil.checkAgentAccess(securityService, agentName);
231
232 service.removeAgent(agentName);
233
234 logger.debug("The agent {} was successfully removed", agentName);
235 return Response.status(SC_OK).build();
236 }
237
238 @GET
239 @Path("{name}")
240 @Produces({ MediaType.APPLICATION_JSON })
241 @RestQuery(
242 name = "getAgent",
243 description = "Return the capture agent including its configuration and capabilities",
244 pathParameters = {
245 @RestParameter(description = "Name of the capture agent", isRequired = true, name = "name", type = RestParameter.Type.STRING),
246 }, restParameters = {}, responses = {
247 @RestResponse(description = "A JSON representation of the capture agent", responseCode = HttpServletResponse.SC_OK),
248 @RestResponse(description = "The agent {name} does not exist in the system", responseCode = HttpServletResponse.SC_NOT_FOUND)
249 }, returnDescription = "")
250 public Response getAgent(@PathParam("name") String agentName)
251 throws NotFoundException {
252 if (service != null) {
253 Agent agent = service.getAgent(agentName);
254 if (agent != null) {
255 return okJson(generateJsonAgent(agent, true, true));
256 } else {
257 return Response.status(Status.NOT_FOUND).build();
258 }
259 } else {
260 return Response.serverError().status(Response.Status.SERVICE_UNAVAILABLE).build();
261 }
262 }
263
264
265
266
267
268
269
270
271
272
273
274
275 private JsonObject generateJsonAgent(Agent agent, boolean withInputs, boolean details) {
276 JsonObject json = new JsonObject();
277 String status = AgentState.TRANSLATION_PREFIX + agent.getState().toUpperCase();
278 json.addProperty("Status", safeString(status));
279 json.addProperty("Name", agent.getName());
280 json.addProperty("Update", safeString(toUTC(agent.getLastHeardFrom())));
281 json.addProperty("URL", safeString(agent.getUrl()));
282
283 if (withInputs) {
284 String devices = (String) agent.getCapabilities().get(CaptureParameters.CAPTURE_DEVICE_NAMES);
285 if (devices == null || devices.isEmpty()) {
286 json.add("inputs", new JsonArray());
287 } else {
288 json.add("inputs", generateJsonDevice(devices.split(",")));
289 }
290 }
291
292 if (details) {
293 json.add("configuration", generateJsonProperties(agent.getConfiguration()));
294 json.add("capabilities", generateJsonProperties(agent.getCapabilities()));
295 }
296
297 return json;
298 }
299
300
301
302
303
304
305
306
307 private JsonArray generateJsonProperties(Properties properties) {
308 JsonArray jsonFields = new JsonArray();
309
310 if (properties != null) {
311 for (String key : properties.stringPropertyNames()) {
312 JsonObject jsonField = new JsonObject();
313 jsonField.addProperty("key", key);
314 jsonField.addProperty("value", properties.getProperty(key));
315 jsonFields.add(jsonField);
316 }
317 }
318
319 return jsonFields;
320 }
321
322
323
324
325
326
327
328
329 private JsonArray generateJsonDevice(String[] devices) {
330 JsonArray jsonDevices = new JsonArray();
331
332 for (String device : devices) {
333 JsonObject jsonDevice = new JsonObject();
334 jsonDevice.addProperty("id", device);
335 jsonDevice.addProperty("value", TRANSLATION_KEY_PREFIX + device.toUpperCase());
336 jsonDevices.add(jsonDevice);
337 }
338
339 return jsonDevices;
340 }
341 }