/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.adminui.endpoint;
import static com.entwinemedia.fn.data.json.Jsons.f;
import static com.entwinemedia.fn.data.json.Jsons.obj;
import static com.entwinemedia.fn.data.json.Jsons.v;
import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
import org.opencastproject.index.service.resources.list.query.ServicesListQuery;
import org.opencastproject.index.service.util.RestUtils;
import org.opencastproject.matterhorn.search.SearchQuery;
import org.opencastproject.matterhorn.search.SortCriterion;
import org.opencastproject.serviceregistry.api.ServiceRegistry;
import org.opencastproject.serviceregistry.api.ServiceState;
import org.opencastproject.serviceregistry.api.ServiceStatistics;
import org.opencastproject.util.SmartIterator;
import org.opencastproject.util.data.Option;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestQuery;
import org.opencastproject.util.doc.rest.RestResponse;
import org.opencastproject.util.doc.rest.RestService;
import com.entwinemedia.fn.data.json.JValue;
import com.entwinemedia.fn.data.json.Jsons;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONAware;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/")
@RestService(name = "ServicesProxyService", title = "UI Services",
abstractText = "This service provides the services data for the UI.",
notes = { "These Endpoints deliver informations about the services required for the UI.",
"<strong>Important:</strong> "
+ "<em>This service is for exclusive use by the module matterhorn-admin-ui-ng. Its API might change "
+ "anytime without prior notice. Any dependencies other than the admin UI will be strictly ignored. "
+ "DO NOT use this for integration of third-party applications.<em>"})
public class ServicesEndpoint {
private static final Logger logger = LoggerFactory.getLogger(ServicesEndpoint.class);
private ServiceRegistry serviceRegistry;
@GET
@Path("services.json")
@Produces(MediaType.APPLICATION_JSON)
@RestQuery(description = "Returns the list of services", name = "services", restParameters = {
@RestParameter(name = "limit", description = "The maximum number of items to return per page", isRequired = false, type = RestParameter.Type.INTEGER),
@RestParameter(name = "offset", description = "The offset", isRequired = false, type = RestParameter.Type.INTEGER),
@RestParameter(name = "filter", description = "Filter results by name, host, actions, status or free text query", isRequired = false, type = STRING),
@RestParameter(name = "sort", description = "The sort order. May include any "
+ "of the following: host, name, running, queued, completed, meanRunTime, meanQueueTime, "
+ "status. The sort suffix must be :asc for ascending sort order and :desc for descending.", isRequired = false, type = STRING)
}, reponses = { @RestResponse(description = "Returns the list of services from Opencast", responseCode = HttpServletResponse.SC_OK) }, returnDescription = "The list of services")
public Response getServices(@QueryParam("limit") final int limit, @QueryParam("offset") final int offset,
@QueryParam("filter") String filter, @QueryParam("sort") String sort) throws Exception {
Option<String> sortOpt = Option.option(StringUtils.trimToNull(sort));
ServicesListQuery query = new ServicesListQuery();
EndpointUtil.addRequestFiltersToQuery(filter, query);
String fName = null;
if (query.getName().isSome())
fName = StringUtils.trimToNull(query.getName().get());
String fHostname = null;
if (query.getHostname().isSome())
fHostname = StringUtils.trimToNull(query.getHostname().get());
String fStatus = null;
if (query.getStatus().isSome())
fStatus = StringUtils.trimToNull(query.getStatus().get());
String fFreeText = null;
if (query.getFreeText().isSome())
fFreeText = StringUtils.trimToNull(query.getFreeText().get());
List<Service> services = new ArrayList<Service>();
for (ServiceStatistics stats : serviceRegistry.getServiceStatistics()) {
Service service = new Service(stats);
if (fName != null && !StringUtils.equalsIgnoreCase(service.getName(), fName))
continue;
if (fHostname != null && !StringUtils.equalsIgnoreCase(service.getHost(), fHostname))
continue;
if (fStatus != null && !StringUtils.equalsIgnoreCase(service.getStatus().toString(), fStatus))
continue;
if (query.getActions().isSome()) {
ServiceState serviceState = service.getStatus();
if (query.getActions().get()) {
if (ServiceState.NORMAL == serviceState)
continue;
} else {
if (ServiceState.NORMAL != serviceState)
continue;
}
}
if (fFreeText != null && !StringUtils.containsIgnoreCase(service.getName(), fFreeText)
&& !StringUtils.containsIgnoreCase(service.getHost(), fFreeText)
&& !StringUtils.containsIgnoreCase(service.getStatus().toString(), fFreeText))
continue;
services.add(service);
}
int total = services.size();
if (sortOpt.isSome()) {
Set<SortCriterion> sortCriteria = RestUtils.parseSortQueryParameter(sortOpt.get());
if (!sortCriteria.isEmpty()) {
try {
SortCriterion sortCriterion = sortCriteria.iterator().next();
Collections.sort(services, new ServiceStatisticsComparator(
sortCriterion.getFieldName(),
sortCriterion.getOrder() == SearchQuery.Order.Ascending));
} catch (Exception ex) {
logger.warn("Failed to sort services collection.", ex);
}
}
}
List<JValue> jsonList = new ArrayList<JValue>();
for (Service s : new SmartIterator<Service>(limit, offset).applyLimitAndOffset(services)) {
jsonList.add(s.toJSON());
}
return RestUtils.okJsonList(jsonList, offset, limit, total);
}
/**
* Service UI model. Wrapper class for a {@code ServiceStatistics} class.
*/
class Service implements JSONAware {
/** Completed model field name. */
public static final String COMPLETED_NAME = "completed";
/** Host model field name. */
public static final String HOST_NAME = "hostname";
/** MeanQueueTime model field name. */
public static final String MEAN_QUEUE_TIME_NAME = "meanQueueTime";
/** MeanRunTime model field name. */
public static final String MEAN_RUN_TIME_NAME = "meanRunTime";
/** (Service-) Name model field name. */
public static final String NAME_NAME = "name";
/** Queued model field name. */
public static final String QUEUED_NAME = "queued";
/** Running model field name. */
public static final String RUNNING_NAME = "running";
/** Status model field name. */
public static final String STATUS_NAME = "status";
/** Wrapped {@code ServiceStatistics} instance. */
private final ServiceStatistics serviceStatistics;
/** Constructor, set {@code ServiceStatistics} instance to a final private property. */
Service(ServiceStatistics serviceStatistics) {
this.serviceStatistics = serviceStatistics;
}
/**
* Returns completed jobs count.
* @return completed jobs count
*/
public int getCompletedJobs() {
return serviceStatistics.getFinishedJobs();
}
/**
* Returns service host name.
* @return service host name
*/
public String getHost() {
return serviceStatistics.getServiceRegistration().getHost();
}
/**
* Returns service mean queue time in seconds.
* @return service mean queue time in seconds
*/
public long getMeanQueueTime() {
return TimeUnit.MILLISECONDS.toSeconds(serviceStatistics.getMeanQueueTime());
}
/**
* Returns service mean run time in seconds.
* @return service mean run time in seconds
*/
public long getMeanRunTime() {
return TimeUnit.MILLISECONDS.toSeconds(serviceStatistics.getMeanRunTime());
}
/**
* Returns service name.
* @return service name
*/
public String getName() {
return serviceStatistics.getServiceRegistration().getServiceType();
}
/**
* Returns queued jobs count.
* @return queued jobs count
*/
public int getQueuedJobs() {
return serviceStatistics.getQueuedJobs();
}
/**
* Returns running jobs count.
* @return running jobs count
*/
public int getRunningJobs() {
return serviceStatistics.getRunningJobs();
}
/**
* Returns service status.
* @return service status
*/
public ServiceState getStatus() {
return serviceStatistics.getServiceRegistration().getServiceState();
}
/**
* Returns a map of all service fields.
* @return a map of all service fields
*/
public Map<String, String> toMap() {
Map<String, String> serviceMap = new HashMap<String, String>();
serviceMap.put(COMPLETED_NAME, Integer.toString(getCompletedJobs()));
serviceMap.put(HOST_NAME, getHost());
serviceMap.put(MEAN_QUEUE_TIME_NAME, Long.toString(getMeanQueueTime()));
serviceMap.put(MEAN_RUN_TIME_NAME, Long.toString(getMeanRunTime()));
serviceMap.put(NAME_NAME, getName());
serviceMap.put(QUEUED_NAME, Integer.toString(getQueuedJobs()));
serviceMap.put(RUNNING_NAME, Integer.toString(getRunningJobs()));
serviceMap.put(STATUS_NAME, getStatus().name());
return serviceMap;
}
/**
* Returns a json representation of a service as {@code String}.
* @return a json representation of a service as {@code String}
*/
@Override
public String toJSONString() {
return JSONObject.toJSONString(toMap());
}
/**
* Returns a json representation of a service as {@code JValue}.
* @return a json representation of a service as {@code JValue}
*/
public JValue toJSON() {
return obj(f(COMPLETED_NAME, v(getCompletedJobs())), f(HOST_NAME, v(getHost(), Jsons.BLANK)),
f(MEAN_QUEUE_TIME_NAME, v(getMeanQueueTime())), f(MEAN_RUN_TIME_NAME, v(getMeanRunTime())),
f(NAME_NAME, v(getName(), Jsons.BLANK)), f(QUEUED_NAME, v(getQueuedJobs())),
f(RUNNING_NAME, v(getRunningJobs())),
f(STATUS_NAME, v(getStatus().name(), Jsons.BLANK)));
}
}
/**
* {@code Service} comparator. Can compare service instances based on the given sort criterion and sort order.
*/
class ServiceStatisticsComparator implements Comparator<Service> {
/** Sort criterion. */
private final String sortBy;
/** Sort order (true if ascending, false otherwise). */
private final boolean ascending;
/** Constructor. */
ServiceStatisticsComparator(String sortBy, boolean ascending) {
if (StringUtils.equalsIgnoreCase(Service.COMPLETED_NAME, sortBy)) {
this.sortBy = Service.COMPLETED_NAME;
} else if (StringUtils.equalsIgnoreCase(Service.HOST_NAME, sortBy)) {
this.sortBy = Service.HOST_NAME;
} else if (StringUtils.equalsIgnoreCase(Service.MEAN_QUEUE_TIME_NAME, sortBy)) {
this.sortBy = Service.MEAN_QUEUE_TIME_NAME;
} else if (StringUtils.equalsIgnoreCase(Service.MEAN_RUN_TIME_NAME, sortBy)) {
this.sortBy = Service.MEAN_RUN_TIME_NAME;
} else if (StringUtils.equalsIgnoreCase(Service.NAME_NAME, sortBy)) {
this.sortBy = Service.NAME_NAME;
} else if (StringUtils.equalsIgnoreCase(Service.QUEUED_NAME, sortBy)) {
this.sortBy = Service.QUEUED_NAME;
} else if (StringUtils.equalsIgnoreCase(Service.RUNNING_NAME, sortBy)) {
this.sortBy = Service.RUNNING_NAME;
} else if (StringUtils.equalsIgnoreCase(Service.STATUS_NAME, sortBy)) {
this.sortBy = Service.STATUS_NAME;
} else {
throw new IllegalArgumentException(String.format("Can't sort services by %s.", sortBy));
}
this.ascending = ascending;
}
/**
* Compare two service instances.
* @param s1 first {@code Service} instance to compare
* @param s2 second {@code Service} instance to compare
* @return
*/
@Override
public int compare(Service s1, Service s2) {
int result = 0;
switch (sortBy) {
case Service.COMPLETED_NAME:
result = s1.getCompletedJobs() - s2.getCompletedJobs();
break;
case Service.HOST_NAME:
result = s1.getHost().compareToIgnoreCase(s2.getHost());
break;
case Service.MEAN_QUEUE_TIME_NAME:
result = (int) (s1.getMeanQueueTime() - s2.getMeanQueueTime());
break;
case Service.MEAN_RUN_TIME_NAME:
result = (int) (s1.getMeanRunTime() - s2.getMeanRunTime());
break;
case Service.QUEUED_NAME:
result = s1.getQueuedJobs() - s2.getQueuedJobs();
break;
case Service.RUNNING_NAME:
result = s1.getRunningJobs() - s2.getRunningJobs();
break;
case Service.STATUS_NAME:
result = s1.getStatus().compareTo(s2.getStatus());
break;
case Service.NAME_NAME: // default sorting criterium
default:
result = s1.getName().compareToIgnoreCase(s2.getName());
}
return ascending ? result : 0 - result;
}
}
/** OSGI activate method. */
public void activate() {
logger.info("ServicesEndpoint is activated!");
}
/**
* @param serviceRegistry
* the serviceRegistry to set
*/
public void setServiceRegistry(ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
}