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.serviceregistry.impl.endpoint;
23
24 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
25 import static javax.servlet.http.HttpServletResponse.SC_CREATED;
26 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
27 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
28 import static javax.servlet.http.HttpServletResponse.SC_OK;
29 import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
30 import static org.apache.commons.lang3.StringUtils.isBlank;
31 import static org.apache.commons.lang3.StringUtils.isNotBlank;
32
33 import org.opencastproject.job.api.JaxbJob;
34 import org.opencastproject.job.api.JaxbJobList;
35 import org.opencastproject.job.api.Job;
36 import org.opencastproject.job.api.JobParser;
37 import org.opencastproject.rest.RestConstants;
38 import org.opencastproject.security.api.SecurityService;
39 import org.opencastproject.serviceregistry.api.HostRegistration;
40 import org.opencastproject.serviceregistry.api.JaxbHostRegistration;
41 import org.opencastproject.serviceregistry.api.JaxbHostRegistrationList;
42 import org.opencastproject.serviceregistry.api.JaxbServiceHealth;
43 import org.opencastproject.serviceregistry.api.JaxbServiceRegistration;
44 import org.opencastproject.serviceregistry.api.JaxbServiceRegistrationList;
45 import org.opencastproject.serviceregistry.api.JaxbServiceStatisticsList;
46 import org.opencastproject.serviceregistry.api.ServiceRegistration;
47 import org.opencastproject.serviceregistry.api.ServiceRegistry;
48 import org.opencastproject.serviceregistry.api.ServiceRegistryException;
49 import org.opencastproject.serviceregistry.api.ServiceState;
50 import org.opencastproject.serviceregistry.api.SystemLoad;
51 import org.opencastproject.serviceregistry.impl.ServiceRegistryJpaImpl;
52 import org.opencastproject.systems.OpencastConstants;
53 import org.opencastproject.util.NotFoundException;
54 import org.opencastproject.util.UrlSupport;
55 import org.opencastproject.util.doc.rest.RestParameter;
56 import org.opencastproject.util.doc.rest.RestParameter.Type;
57 import org.opencastproject.util.doc.rest.RestQuery;
58 import org.opencastproject.util.doc.rest.RestResponse;
59 import org.opencastproject.util.doc.rest.RestService;
60
61 import org.apache.commons.lang3.StringUtils;
62 import org.json.simple.JSONArray;
63 import org.json.simple.JSONValue;
64 import org.osgi.service.component.ComponentContext;
65 import org.osgi.service.component.annotations.Activate;
66 import org.osgi.service.component.annotations.Component;
67 import org.osgi.service.component.annotations.Reference;
68 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
69
70 import java.net.MalformedURLException;
71 import java.net.URL;
72 import java.util.Arrays;
73 import java.util.Collections;
74 import java.util.LinkedList;
75 import java.util.List;
76 import java.util.Map;
77
78 import javax.servlet.http.HttpServletRequest;
79 import javax.servlet.http.HttpServletResponse;
80 import javax.ws.rs.DELETE;
81 import javax.ws.rs.FormParam;
82 import javax.ws.rs.GET;
83 import javax.ws.rs.POST;
84 import javax.ws.rs.PUT;
85 import javax.ws.rs.Path;
86 import javax.ws.rs.PathParam;
87 import javax.ws.rs.Produces;
88 import javax.ws.rs.QueryParam;
89 import javax.ws.rs.WebApplicationException;
90 import javax.ws.rs.core.Context;
91 import javax.ws.rs.core.MediaType;
92 import javax.ws.rs.core.Response;
93 import javax.ws.rs.core.Response.Status;
94
95
96
97
98 @Path("/services")
99 @RestService(name = "serviceregistry", title = "Service Registry", notes = { "All paths above are relative to the REST endpoint base" }, abstractText = "Provides registration and management functions for servers and services in this Opencast instance or cluster.")
100 @Component(
101 property = {
102 "service.description=Service Registry REST Endpoint",
103 "opencast.service.type=org.opencastproject.serviceregistry",
104 "opencast.service.path=/services"
105 },
106 immediate = true,
107 service = { ServiceRegistryEndpoint.class }
108 )
109 @JaxrsResource
110 public class ServiceRegistryEndpoint {
111
112
113 protected ServiceRegistry serviceRegistry = null;
114
115 private SecurityService securityService = null;
116
117
118 protected String serverUrl = UrlSupport.DEFAULT_BASE_URL;
119
120
121 protected String servicePath = "/";
122
123
124 @Reference
125 public void setServiceRegistry(ServiceRegistry serviceRegistry) {
126 this.serviceRegistry = serviceRegistry;
127 }
128
129 @Reference
130 public void setSecurityService(SecurityService securityService) {
131 this.securityService = securityService;
132 }
133
134
135
136
137
138
139
140 @Activate
141 public void activate(ComponentContext cc) {
142 serverUrl = cc.getBundleContext().getProperty(OpencastConstants.SERVER_URL_PROPERTY);
143 servicePath = (String) cc.getProperties().get(RestConstants.SERVICE_PATH_PROPERTY);
144 }
145
146 @GET
147 @Path("statistics.json")
148 @Produces(MediaType.APPLICATION_JSON)
149 @RestQuery(name = "statisticsasjson", description = "List the service registrations in the cluster, along with some simple statistics", returnDescription = "The service statistics.", responses = { @RestResponse(responseCode = SC_OK, description = "A JSON representation of the service statistics") })
150 public Response getStatisticsAsJson() {
151 try {
152 return Response.ok(new JaxbServiceStatisticsList(serviceRegistry.getServiceStatistics())).build();
153 } catch (ServiceRegistryException e) {
154 throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
155 }
156 }
157
158 @GET
159 @Path("statistics.xml")
160 @Produces(MediaType.TEXT_XML)
161 @RestQuery(name = "statisticsasxml", description = "List the service registrations in the cluster, along with some simple statistics", returnDescription = "The service statistics.", responses = { @RestResponse(responseCode = SC_OK, description = "An XML representation of the service statistics") })
162 public Response getStatisticsAsXml() throws ServiceRegistryException {
163 return getStatisticsAsJson();
164 }
165
166 @POST
167 @Path("sanitize")
168 @RestQuery(name = "sanitize", description = "Sets the given service to NORMAL state", returnDescription = "No content", restParameters = {
169 @RestParameter(name = "serviceType", isRequired = true, description = "The service type identifier", type = Type.STRING, defaultValue = ""),
170 @RestParameter(name = "host", isRequired = true, description = "The host providing the service, including the http(s) protocol", type = Type.STRING, defaultValue = "") }, responses = {
171 @RestResponse(responseCode = SC_NO_CONTENT, description = "The service was successfully sanitized"),
172 @RestResponse(responseCode = SC_NOT_FOUND, description = "No service of that type on that host is registered.") })
173 public Response sanitize(@FormParam("serviceType") String serviceType, @FormParam("host") String host)
174 throws NotFoundException {
175 serviceRegistry.sanitize(serviceType, host);
176 return Response.status(Status.NO_CONTENT).build();
177 }
178
179 @POST
180 @Path("register")
181 @Produces(MediaType.TEXT_XML)
182 @RestQuery(name = "register", description = "Add a new service registration to the cluster.", returnDescription = "The service registration.", restParameters = {
183 @RestParameter(name = "serviceType", isRequired = true, description = "The service type identifier", type = Type.STRING, defaultValue = ""),
184 @RestParameter(name = "host", isRequired = true, description = "The host providing the service, including the http(s) protocol", type = Type.STRING, defaultValue = ""),
185 @RestParameter(name = "path", isRequired = true, description = "The service path on the host", type = Type.STRING, defaultValue = ""),
186 @RestParameter(name = "jobProducer", isRequired = true, description = "Whether this service is a producer of long running jobs requiring dispatch", type = Type.STRING, defaultValue = "false") }, responses = { @RestResponse(responseCode = SC_OK, description = "An XML representation of the new service registration") })
187 public JaxbServiceRegistration register(@FormParam("serviceType") String serviceType, @FormParam("host") String host,
188 @FormParam("path") String path, @FormParam("jobProducer") boolean jobProducer) {
189 try {
190 return new JaxbServiceRegistration(serviceRegistry.registerService(serviceType, host, path, jobProducer));
191 } catch (ServiceRegistryException e) {
192 throw new WebApplicationException(e);
193 }
194 }
195
196 @POST
197 @Path("unregister")
198 @RestQuery(name = "unregister", description = "Removes a service registration.", returnDescription = "No content", restParameters = {
199 @RestParameter(name = "serviceType", isRequired = true, description = "The service type identifier", type = Type.STRING),
200 @RestParameter(name = "host", isRequired = true, description = "The host providing the service, including the http(s) protocol", type = Type.STRING) }, responses = { @RestResponse(responseCode = SC_NO_CONTENT, description = "The service was unregistered successfully") })
201 public Response unregister(@FormParam("serviceType") String serviceType, @FormParam("host") String host) {
202 try {
203 serviceRegistry.unRegisterService(serviceType, host);
204 return Response.status(Status.NO_CONTENT).build();
205 } catch (ServiceRegistryException e) {
206 throw new WebApplicationException(e);
207 }
208 }
209
210 @POST
211 @Path("enablehost")
212 @RestQuery(name = "enablehost", description = "Enable a server from the cluster.", returnDescription = "No content.", restParameters = { @RestParameter(name = "host", isRequired = true, description = "The host name, including the http(s) protocol", type = Type.STRING) }, responses = {
213 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was enabled successfully"),
214 @RestResponse(responseCode = SC_NOT_FOUND, description = "The host does not exist") })
215 public Response enableHost(@FormParam("host") String host) throws NotFoundException {
216 try {
217 serviceRegistry.enableHost(host);
218 return Response.status(Status.NO_CONTENT).build();
219 } catch (ServiceRegistryException e) {
220 throw new WebApplicationException(e);
221 }
222 }
223
224 @POST
225 @Path("disablehost")
226 @RestQuery(name = "disablehost", description = "Disable a server from the cluster.", returnDescription = "No content.", restParameters = { @RestParameter(name = "host", isRequired = true, description = "The host name, including the http(s) protocol", type = Type.STRING) }, responses = {
227 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was disabled successfully"),
228 @RestResponse(responseCode = SC_NOT_FOUND, description = "The host does not exist") })
229 public Response disableHost(@FormParam("host") String host) throws NotFoundException {
230 try {
231 serviceRegistry.disableHost(host);
232 return Response.status(Status.NO_CONTENT).build();
233 } catch (ServiceRegistryException e) {
234 throw new WebApplicationException(e);
235 }
236 }
237
238 @POST
239 @Path("registerhost")
240 @RestQuery(name = "registerhost", description = "Add a new server to the cluster.", returnDescription = "No content.", restParameters = {
241 @RestParameter(name = "host", isRequired = true, description = "The host name, including the http(s) protocol", type = Type.STRING),
242 @RestParameter(name = "address", isRequired = true, description = "The IP address", type = Type.STRING),
243 @RestParameter(name = "nodeName", isRequired = true, description = "Descriptive node name", type = Type.STRING),
244 @RestParameter(name = "memory", isRequired = true, description = "The allocated memory", type = Type.STRING),
245 @RestParameter(name = "cores", isRequired = true, description = "The available cores", type = Type.STRING),
246 @RestParameter(name = "maxLoad", isRequired = true, description = "The maximum load this host support", type = Type.STRING) }, responses = { @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was registered successfully") })
247 public void register(@FormParam("host") String host, @FormParam("address") String address, @FormParam("nodeName") String nodeName,
248 @FormParam("memory") long memory, @FormParam("cores") int cores, @FormParam("maxLoad") float maxLoad) {
249 try {
250 serviceRegistry.registerHost(host, address, nodeName, memory, cores, maxLoad);
251 } catch (ServiceRegistryException e) {
252 throw new WebApplicationException(e);
253 }
254 }
255
256 @POST
257 @Path("unregisterhost")
258 @RestQuery(name = "unregisterhost", description = "Removes a server from the cluster.",
259 returnDescription = "No content.",
260 restParameters = {
261 @RestParameter(name = "host", isRequired = true, description = "The host name, including the http(s) protocol", type = Type.STRING)
262 }, responses = {
263 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was removed successfully") })
264 public Response unregister(@FormParam("host") String host) {
265 try {
266 serviceRegistry.unregisterHost(host);
267 return Response.status(Status.NO_CONTENT).build();
268 } catch (ServiceRegistryException e) {
269 if (e.getCause() instanceof IllegalArgumentException) {
270 return Response.status(Status.NOT_FOUND).entity(e.getMessage()).build();
271 }
272 throw new WebApplicationException(e);
273 }
274 }
275
276 @POST
277 @Path("maintenance")
278 @RestQuery(name = "maintenance", description = "Sets the maintenance status for a server in the cluster.", returnDescription = "No content.", restParameters = {
279 @RestParameter(name = "host", isRequired = true, type = Type.STRING, description = "The host name, including the http(s) protocol"),
280 @RestParameter(name = "maintenance", isRequired = true, type = Type.BOOLEAN, description = "Whether this host should be put into maintenance mode (true) or not") }, responses = {
281 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was registered successfully"),
282 @RestResponse(responseCode = SC_NOT_FOUND, description = "Host not found") })
283 public Response setMaintenanceMode(@FormParam("host") String host, @FormParam("maintenance") boolean maintenance)
284 throws NotFoundException {
285 try {
286 serviceRegistry.setMaintenanceStatus(host, maintenance);
287 return Response.status(Status.NO_CONTENT).build();
288 } catch (ServiceRegistryException e) {
289 throw new WebApplicationException(e);
290 }
291 }
292
293 @GET
294 @Path("available.xml")
295 @Produces(MediaType.TEXT_XML)
296 @RestQuery(name = "availableasxml", description = "Lists available services by service type identifier, ordered by load.", returnDescription = "The services list as XML", restParameters = { @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING, description = "The service type identifier") }, responses = {
297 @RestResponse(responseCode = SC_OK, description = "Returned the available services."),
298 @RestResponse(responseCode = SC_BAD_REQUEST, description = "No service type specified, bad request.") })
299 public Response getAvailableServicesAsXml(@QueryParam("serviceType") String serviceType) {
300
301 if (isBlank(serviceType))
302 throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("Service type must be specified")
303 .build());
304
305 Map<String, String> properties = securityService.getOrganization().getProperties();
306
307 JaxbServiceRegistrationList registrations = new JaxbServiceRegistrationList();
308 try {
309 for (ServiceRegistration reg : serviceRegistry.getServiceRegistrationsByLoad(serviceType)) {
310 JaxbServiceRegistration jaxbReg = new JaxbServiceRegistration(reg);
311 URL internalHostUrl = new URL(jaxbReg.getHost());
312 String tenantSpecificHost = StringUtils.trimToNull(properties.get("org.opencastproject.host." + internalHostUrl.getHost()));
313 if (StringUtils.isNotBlank(tenantSpecificHost)) {
314 jaxbReg.setHost(tenantSpecificHost);
315 }
316 registrations.add(jaxbReg);
317 }
318 return Response.ok(registrations).build();
319 } catch (ServiceRegistryException | MalformedURLException e) {
320 throw new WebApplicationException(e);
321 }
322 }
323
324 @GET
325 @Path("available.json")
326 @Produces(MediaType.APPLICATION_JSON)
327 @RestQuery(name = "availableasjson", description = "Lists available services by service type identifier, ordered by load.", returnDescription = "The services list as JSON", restParameters = { @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING, description = "The service type identifier") }, responses = {
328 @RestResponse(responseCode = SC_OK, description = "Returned the available services."),
329 @RestResponse(responseCode = SC_BAD_REQUEST, description = "No service type specified, bad request.") })
330 public Response getAvailableServicesAsJson(@QueryParam("serviceType") String serviceType) {
331 return getAvailableServicesAsXml(serviceType);
332 }
333
334 @GET
335 @Path("health.json")
336 @Produces(MediaType.APPLICATION_JSON)
337 @RestQuery(name = "health", description = "Checks the status of the registered services", returnDescription = "Returns NO_CONTENT if services are in a proper state", restParameters = {
338 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING, description = "The service type identifier"),
339 @RestParameter(name = "host", isRequired = false, type = Type.STRING, description = "The host, including the http(s) protocol") }, responses = {
340 @RestResponse(responseCode = SC_OK, description = "Service states returned"),
341 @RestResponse(responseCode = SC_NOT_FOUND, description = "No service of that type on that host is registered."),
342 @RestResponse(responseCode = SC_SERVICE_UNAVAILABLE, description = "An error has occurred during stats processing") })
343 public Response getHealthStatusAsJson(@QueryParam("serviceType") String serviceType, @QueryParam("host") String host)
344 throws NotFoundException {
345 return getHealthStatus(serviceType, host);
346 }
347
348 @GET
349 @Path("health.xml")
350 @Produces(MediaType.APPLICATION_XML)
351 @RestQuery(name = "health", description = "Checks the status of the registered services", returnDescription = "Returns NO_CONTENT if services are in a proper state", restParameters = {
352 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING, description = "The service type identifier"),
353 @RestParameter(name = "host", isRequired = false, type = Type.STRING, description = "The host, including the http(s) protocol") }, responses = {
354 @RestResponse(responseCode = SC_OK, description = "Service states returned"),
355 @RestResponse(responseCode = SC_NOT_FOUND, description = "No service of that type on that host is registered."),
356 @RestResponse(responseCode = SC_SERVICE_UNAVAILABLE, description = "An error has occurred during stats processing") })
357 public Response getHealthStatus(@QueryParam("serviceType") String serviceType, @QueryParam("host") String host)
358 throws NotFoundException {
359 try {
360 List<ServiceRegistration> services = null;
361 if (isNotBlank(serviceType) && isNotBlank(host)) {
362
363 ServiceRegistration reg = serviceRegistry.getServiceRegistration(serviceType, host);
364 if (reg == null)
365 throw new NotFoundException();
366
367 services = new LinkedList<ServiceRegistration>();
368 services.add(reg);
369 } else if (isBlank(serviceType) && isBlank(host)) {
370
371 services = serviceRegistry.getServiceRegistrations();
372 } else if (isNotBlank(serviceType)) {
373
374 services = serviceRegistry.getServiceRegistrationsByType(serviceType);
375 } else if (isNotBlank(host)) {
376
377 services = serviceRegistry.getServiceRegistrationsByHost(host);
378 }
379
380 int healthy = 0;
381 int warning = 0;
382 int error = 0;
383 for (ServiceRegistration reg : services) {
384 if (ServiceState.NORMAL == reg.getServiceState()) {
385 healthy++;
386 } else if (ServiceState.WARNING == reg.getServiceState()) {
387 warning++;
388 } else if (ServiceState.ERROR == reg.getServiceState()) {
389 error++;
390 } else {
391 error++;
392 }
393 }
394 JaxbServiceHealth stats = new JaxbServiceHealth(healthy, warning, error);
395 return Response.ok(stats).build();
396 } catch (ServiceRegistryException e) {
397 throw new WebApplicationException(e);
398 }
399 }
400
401 @GET
402 @Path("services.xml")
403 @Produces(MediaType.TEXT_XML)
404 @RestQuery(name = "servicesasxml", description = "Returns a service registraton or list of available service registrations as XML.", returnDescription = "The services list as XML", restParameters = {
405 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING, description = "The service type identifier"),
406 @RestParameter(name = "host", isRequired = false, type = Type.STRING, description = "The host, including the http(s) protocol") }, responses = {
407 @RestResponse(responseCode = SC_OK, description = "Returned the available service."),
408 @RestResponse(responseCode = SC_NOT_FOUND, description = "No service of that type on that host is registered.") })
409 public JaxbServiceRegistrationList getRegistrationsAsXml(@QueryParam("serviceType") String serviceType,
410 @QueryParam("host") String host) throws NotFoundException {
411 JaxbServiceRegistrationList registrations = new JaxbServiceRegistrationList();
412 try {
413 if (isNotBlank(serviceType) && isNotBlank(host)) {
414
415 ServiceRegistration reg = serviceRegistry.getServiceRegistration(serviceType, host);
416 if (reg == null) {
417 throw new NotFoundException();
418 } else {
419 return new JaxbServiceRegistrationList(new JaxbServiceRegistration(reg));
420 }
421 } else if (isBlank(serviceType) && isBlank(host)) {
422
423 for (ServiceRegistration reg : serviceRegistry.getServiceRegistrations()) {
424 registrations.add(new JaxbServiceRegistration(reg));
425 }
426 } else if (isNotBlank(serviceType)) {
427
428 for (ServiceRegistration reg : serviceRegistry.getServiceRegistrationsByType(serviceType)) {
429 registrations.add(new JaxbServiceRegistration(reg));
430 }
431 } else if (isNotBlank(host)) {
432
433 for (ServiceRegistration reg : serviceRegistry.getServiceRegistrationsByHost(host)) {
434 registrations.add(new JaxbServiceRegistration(reg));
435 }
436 }
437 return registrations;
438 } catch (ServiceRegistryException e) {
439 throw new WebApplicationException(e);
440 }
441 }
442
443 @GET
444 @Path("services.json")
445 @Produces(MediaType.APPLICATION_JSON)
446 @RestQuery(name = "servicesasjson", description = "Returns a service registraton or list of available service registrations as JSON.", returnDescription = "The services list as XML", restParameters = {
447 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING, description = "The service type identifier"),
448 @RestParameter(name = "host", isRequired = false, type = Type.STRING, description = "The host, including the http(s) protocol") }, responses = {
449 @RestResponse(responseCode = SC_OK, description = "Returned the available service."),
450 @RestResponse(responseCode = SC_NOT_FOUND, description = "No service of that type on that host is registered.") })
451 public JaxbServiceRegistrationList getRegistrationsAsJson(@QueryParam("serviceType") String serviceType,
452 @QueryParam("host") String host) throws NotFoundException {
453 return getRegistrationsAsXml(serviceType, host);
454 }
455
456 @GET
457 @Path("hosts.xml")
458 @Produces(MediaType.TEXT_XML)
459 @RestQuery(name = "hostsasxml", description = "Returns a host registraton or list of available host registrations as XML.", returnDescription = "The host list as XML", responses = { @RestResponse(responseCode = SC_OK, description = "Returned the available hosts.") })
460 public JaxbHostRegistrationList getHostsAsXml() throws NotFoundException {
461 JaxbHostRegistrationList registrations = new JaxbHostRegistrationList();
462 try {
463 for (HostRegistration reg : serviceRegistry.getHostRegistrations())
464 registrations.add(new JaxbHostRegistration(reg));
465 return registrations;
466 } catch (ServiceRegistryException e) {
467 throw new WebApplicationException(e);
468 }
469 }
470
471 @GET
472 @Path("hosts.json")
473 @Produces(MediaType.APPLICATION_JSON)
474 @RestQuery(name = "hostsasjson", description = "Returns a host registraton or list of available host registrations as JSON.", returnDescription = "The host list as JSON", responses = { @RestResponse(responseCode = SC_OK, description = "Returned the available hosts.") })
475 public JaxbHostRegistrationList getHostsAsJson() throws NotFoundException {
476 return getHostsAsXml();
477 }
478
479 @POST
480 @Path("job")
481 @Produces(MediaType.TEXT_XML)
482 @RestQuery(name = "createjob", description = "Creates a new job.", returnDescription = "An XML representation of the job.", restParameters = {
483 @RestParameter(name = "jobType", isRequired = true, type = Type.STRING, description = "The job type identifier"),
484 @RestParameter(name = "host", isRequired = true, type = Type.STRING, description = "The creating host, including the http(s) protocol"),
485 @RestParameter(name = "operation", isRequired = true, type = Type.STRING, description = "The operation this job should execute"),
486 @RestParameter(name = "payload", isRequired = false, type = Type.TEXT, description = "The job type identifier"),
487 @RestParameter(name = "start", isRequired = false, type = Type.BOOLEAN, description = "Whether the job should be queued for dispatch and execution"),
488 @RestParameter(name = "jobLoad", isRequired = false, type = Type.STRING, description = "The load this job will incur on the system"),
489 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT, description = "An argument for the operation"),
490 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT, description = "An argument for the operation"),
491 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT, description = "An argument for the operation"),
492 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT, description = "An argument for the operation"),
493 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT, description = "An argument for the operation") }, responses = {
494 @RestResponse(responseCode = SC_CREATED, description = "Job created."),
495 @RestResponse(responseCode = SC_BAD_REQUEST, description = "The required parameters were not supplied, bad request.") })
496 public Response createJob(@Context HttpServletRequest request) {
497 String[] argArray = request.getParameterValues("arg");
498 List<String> arguments = null;
499 if (argArray != null && argArray.length > 0) {
500 arguments = Arrays.asList(argArray);
501 }
502 String jobType = request.getParameter("jobType");
503 String operation = request.getParameter("operation");
504 String host = request.getParameter("host");
505 String payload = request.getParameter("payload");
506 boolean start = StringUtils.isBlank(request.getParameter("start"))
507 || Boolean.TRUE.toString().equalsIgnoreCase(request.getParameter("start"));
508 try {
509 Job job = null;
510 if (StringUtils.isNotBlank(request.getParameter("jobLoad"))) {
511 Float jobLoad = Float.parseFloat(request.getParameter("jobLoad"));
512 job = ((ServiceRegistryJpaImpl) serviceRegistry).createJob(host, jobType, operation, arguments, payload,
513 start, serviceRegistry.getCurrentJob(), jobLoad);
514 } else {
515 job = ((ServiceRegistryJpaImpl) serviceRegistry).createJob(host, jobType, operation, arguments, payload,
516 start, serviceRegistry.getCurrentJob());
517 }
518 return Response.created(job.getUri()).entity(new JaxbJob(job)).build();
519 } catch (IllegalArgumentException e) {
520 throw new WebApplicationException(Status.BAD_REQUEST);
521 } catch (Exception e) {
522 throw new WebApplicationException(e);
523 }
524 }
525
526 @PUT
527 @Path("job/{id}.xml")
528 @Produces(MediaType.TEXT_XML)
529 @RestQuery(name = "updatejob", description = "Updates an existing job", returnDescription = "No content", pathParameters = { @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The job identifier") }, restParameters = { @RestParameter(name = "job", isRequired = true, type = Type.TEXT, description = "The updated job as XML") }, responses = {
530 @RestResponse(responseCode = SC_NO_CONTENT, description = "Job updated."),
531 @RestResponse(responseCode = SC_NOT_FOUND, description = "Job not found.") })
532 public Response updateJob(@PathParam("id") String id, @FormParam("job") String jobXml) throws NotFoundException {
533 try {
534 Job job = JobParser.parseJob(jobXml);
535 serviceRegistry.updateJob(job);
536 return Response.status(Status.NO_CONTENT).build();
537 } catch (Exception e) {
538 throw new WebApplicationException(e);
539 }
540 }
541
542 @GET
543 @Path("job/{id}.xml")
544 @Produces(MediaType.TEXT_XML)
545 @RestQuery(name = "jobasxml", description = "Returns a job as XML.", returnDescription = "The job as XML", pathParameters = { @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The job identifier") }, responses = {
546 @RestResponse(responseCode = SC_OK, description = "Job found."),
547 @RestResponse(responseCode = SC_NOT_FOUND, description = "No job with that identifier exists.") })
548 public JaxbJob getJobAsXml(@PathParam("id") long id) throws NotFoundException {
549 return getJobAsJson(id);
550 }
551
552 @GET
553 @Path("job/{id}.json")
554 @Produces(MediaType.APPLICATION_JSON)
555 @RestQuery(name = "jobasjson", description = "Returns a job as JSON.", returnDescription = "The job as JSON", pathParameters = { @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The job identifier") }, responses = {
556 @RestResponse(responseCode = SC_OK, description = "Job found."),
557 @RestResponse(responseCode = SC_NOT_FOUND, description = "No job with that identifier exists.") })
558 public JaxbJob getJobAsJson(@PathParam("id") long id) throws NotFoundException {
559 try {
560 return new JaxbJob(serviceRegistry.getJob(id));
561 } catch (ServiceRegistryException e) {
562 throw new WebApplicationException(e);
563 }
564 }
565
566 @GET
567 @Path("job/{id}/children.xml")
568 @Produces(MediaType.TEXT_XML)
569 @RestQuery(name = "childrenjobsasxml", description = "Returns all children from a job as XML.", returnDescription = "A list of children jobs as XML", pathParameters = { @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The parent job identifier") }, responses = { @RestResponse(responseCode = SC_OK, description = "Jobs found.") })
570 public JaxbJobList getChildrenJobsAsXml(@PathParam("id") long id) {
571 return getChildrenJobsAsJson(id);
572 }
573
574 @GET
575 @Path("job/{id}/children.json")
576 @Produces(MediaType.APPLICATION_JSON)
577 @RestQuery(name = "childrenjobsasjson", description = "Returns all children from a job as JSON.", returnDescription = "A list of children jobs as JSON", pathParameters = { @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The parent job identifier") }, responses = { @RestResponse(responseCode = SC_OK, description = "Jobs found.") })
578 public JaxbJobList getChildrenJobsAsJson(@PathParam("id") long id) {
579 try {
580 return new JaxbJobList(serviceRegistry.getChildJobs(id));
581 } catch (ServiceRegistryException e) {
582 throw new WebApplicationException(e);
583 }
584 }
585
586 @GET
587 @Path("jobs.xml")
588 @Produces(MediaType.TEXT_XML)
589 public JaxbJobList getJobsAsXml(@QueryParam("serviceType") String serviceType, @QueryParam("status") Job.Status status) {
590 try {
591 return new JaxbJobList(serviceRegistry.getJobs(serviceType, status));
592 } catch (ServiceRegistryException e) {
593 throw new WebApplicationException(e);
594 }
595
596 }
597
598 @GET
599 @Path("activeJobs.xml")
600 @Produces(MediaType.TEXT_XML)
601 @RestQuery(name = "activejobsasxml",
602 description = "Returns all active jobs as XML.",
603 returnDescription = "A list of active jobs as XML",
604 responses = { @RestResponse(responseCode = SC_OK, description = "Active jobs found.") })
605 public JaxbJobList getActiveJobsAsXml() {
606 try {
607 return new JaxbJobList(serviceRegistry.getActiveJobs());
608 } catch (ServiceRegistryException e) {
609 throw new WebApplicationException(e);
610 }
611 }
612
613 @GET
614 @Path("activeJobs.json")
615 @Produces(MediaType.APPLICATION_JSON)
616 @RestQuery(name = "activejobsasjson",
617 description = "Returns all active jobs as JSON.",
618 returnDescription = "A list of active jobs as JSON",
619 responses = { @RestResponse(responseCode = SC_OK, description = "Active jobs found.") })
620 public JaxbJobList getActiveJobsAsJson() {
621 try {
622 return new JaxbJobList(serviceRegistry.getActiveJobs());
623 } catch (ServiceRegistryException e) {
624 throw new WebApplicationException(e);
625 }
626 }
627
628 @GET
629 @Path("count")
630 @Produces(MediaType.TEXT_PLAIN)
631 @RestQuery(name = "count", description = "Returns the number of jobs matching the query parameters as plain text.", returnDescription = "The number of matching jobs", restParameters = {
632 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING, description = "The service type identifier"),
633 @RestParameter(name = "status", isRequired = false, type = Type.STRING, description = "The job status"),
634 @RestParameter(name = "host", isRequired = false, type = Type.STRING, description = "The host executing the job"),
635 @RestParameter(name = "operation", isRequired = false, type = Type.STRING, description = "The job's operation") }, responses = { @RestResponse(responseCode = SC_OK, description = "Job count returned.") })
636 public long count(@QueryParam("serviceType") String serviceType, @QueryParam("status") Job.Status status,
637 @QueryParam("host") String host, @QueryParam("operation") String operation) {
638 try {
639 if (isNotBlank(host) && isNotBlank(operation)) {
640 if (isBlank(serviceType))
641 throw new WebApplicationException(Response.serverError().entity("Service type must not be null").build());
642 return serviceRegistry.count(serviceType, host, operation, status);
643 } else if (isNotBlank(host)) {
644 if (isBlank(serviceType))
645 throw new WebApplicationException(Response.serverError().entity("Service type must not be null").build());
646 return serviceRegistry.countByHost(serviceType, host, status);
647 } else if (isNotBlank(operation)) {
648 if (isBlank(serviceType))
649 throw new WebApplicationException(Response.serverError().entity("Service type must not be null").build());
650 return serviceRegistry.countByOperation(serviceType, operation, status);
651 } else {
652 return serviceRegistry.count(serviceType, status);
653 }
654 } catch (ServiceRegistryException e) {
655 throw new WebApplicationException(e);
656 }
657 }
658
659 @GET
660 @Path("maxconcurrentjobs")
661 @Produces(MediaType.TEXT_PLAIN)
662 @RestQuery(name = "maxconcurrentjobs", description = "Returns the number of jobs that the servers in this service registry can execute concurrently. If there is only one server in this service registry this will be the number of jobs that one server is able to do at one time. If it is a distributed install across many servers then this number will be the total number of jobs the cluster can process concurrently.", returnDescription = "The maximum number of concurrent jobs", responses = { @RestResponse(responseCode = SC_OK, description = "Maximum number of concurrent jobs returned.") })
663 @Deprecated
664 public Response getMaximumConcurrentWorkflows() {
665 return Response.status(Status.MOVED_PERMANENTLY).type(MediaType.TEXT_PLAIN)
666 .header("Location", servicePath + "/maxload").build();
667 }
668
669 @GET
670 @Path("maxload")
671 @Produces(MediaType.TEXT_XML)
672 @RestQuery(name = "maxload", description = "Returns the maximum load that servers in this service registry can execute concurrently. "
673 + "If there is only one server in this service registry this will be the maximum load that one server is able to handle at one time. "
674 + "If it is a distributed install across many servers then this number will be the maximum load the cluster can process concurrently.",
675 returnDescription = "The maximum load of the cluster or server", restParameters = {
676 @RestParameter(name = "host", isRequired = false, type = Type.STRING, description = "The host you want to know the maximum load for.")
677 }, responses = { @RestResponse(responseCode = SC_OK, description = "Maximum load for the cluster.") })
678 public Response getMaxLoadOnNode(@QueryParam("host") String host) throws NotFoundException {
679 try {
680 if (StringUtils.isEmpty(host)) {
681 return Response.ok(serviceRegistry.getMaxLoads()).build();
682 } else {
683 SystemLoad systemLoad = new SystemLoad();
684 systemLoad.addNodeLoad(serviceRegistry.getMaxLoadOnNode(host));
685 return Response.ok(systemLoad).build();
686 }
687 } catch (ServiceRegistryException e) {
688 throw new WebApplicationException(e);
689 }
690 }
691
692 @GET
693 @Path("currentload")
694 @Produces(MediaType.TEXT_XML)
695 @RestQuery(name = "currentload", description = "Returns the current load on the servers in this service registry. "
696 + "If there is only one server in this service registry this will be the the load that one server. "
697 + "If it is a distributed install across many servers then this number will be a dictionary of the load on all nodes in the cluster.",
698 returnDescription = "The current load across the cluster", restParameters = {},
699 responses = { @RestResponse(responseCode = SC_OK, description = "Current load for the cluster.") })
700 public Response getCurrentLoad() {
701 try {
702 return Response.ok(serviceRegistry.getCurrentHostLoads()).build();
703 } catch (ServiceRegistryException e) {
704 throw new WebApplicationException(e);
705 }
706 }
707
708 @GET
709 @Path("ownload")
710 @Produces(MediaType.TEXT_PLAIN)
711 @RestQuery(name = "ownload", description = "Returns the current load on this service registry's node.",
712 returnDescription = "The current load across the cluster", restParameters = {},
713 responses = { @RestResponse(responseCode = SC_OK, description = "Current load for the cluster.") })
714 public Response getOwnLoad() {
715 try {
716 return Response.ok(serviceRegistry.getOwnLoad()).build();
717 } catch (ServiceRegistryException e) {
718 throw new WebApplicationException(e);
719 }
720 }
721
722
723 @DELETE
724 @Path("job/{id}")
725 @RestQuery(name = "deletejob", description = "Deletes a job from the service registry", returnDescription = "No data is returned, just the HTTP status code", pathParameters = { @RestParameter(isRequired = true, name = "id", type = Type.INTEGER, description = "ID of the job to delete") }, responses = {
726 @RestResponse(responseCode = SC_NO_CONTENT, description = "Job successfully deleted"),
727 @RestResponse(responseCode = SC_NOT_FOUND, description = "Job with given id could not be found") })
728 public Response deleteJob(@PathParam("id") long id) throws NotFoundException {
729 try {
730 serviceRegistry.removeJobs(Collections.singletonList(id));
731 return Response.noContent().build();
732 } catch (ServiceRegistryException e) {
733 throw new WebApplicationException(e);
734 }
735 }
736
737
738 @POST
739 @Path("removejobs")
740 @RestQuery(name = "removejobs", description = "Removes all given jobs and their child jobs", returnDescription = "No data is returned, just the HTTP status code", restParameters = { @RestParameter(name = "jobIds", isRequired = true, description = "The IDs of the jobs to delete", type = Type.TEXT), }, responses = {
741 @RestResponse(responseCode = SC_NO_CONTENT, description = "Jobs successfully removed"),
742 @RestResponse(responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR, description = "Error while removing jobs") })
743 public Response removeParentlessJobs(@FormParam("jobIds") String jobIds) throws NotFoundException {
744 try {
745 final JSONArray array = (JSONArray) JSONValue.parse(jobIds);
746 final List<Long> jobIdList = Arrays.asList((Long[]) array.toArray(new Long[0]));
747 serviceRegistry.removeJobs(jobIdList);
748 return Response.noContent().build();
749 } catch (ServiceRegistryException e) {
750 throw new WebApplicationException(e);
751 }
752 }
753
754
755 @POST
756 @Path("removeparentlessjobs")
757 @RestQuery(name = "removeparentlessjobs", description = "Removes all jobs without a parent job which have passed their lifetime", returnDescription = "No data is returned, just the HTTP status code", restParameters = { @RestParameter(name = "lifetime", isRequired = true, type = Type.INTEGER, description = "Lifetime of parentless jobs") }, responses = {
758 @RestResponse(responseCode = SC_NO_CONTENT, description = "Parentless jobs successfully removed"),
759 @RestResponse(responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR, description = "Error while removing parentless jobs") })
760 public Response removeParentlessJobs(@FormParam("lifetime") int lifetime) {
761 try {
762 serviceRegistry.removeParentlessJobs(lifetime);
763 return Response.noContent().build();
764 } catch (ServiceRegistryException e) {
765 throw new WebApplicationException(e);
766 }
767 }
768
769 }