/*
* RHQ Management Platform
* Copyright (C) 2005-2012 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.enterprise.server.rest;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.ejb.EJB;
import javax.ejb.EJBTransactionRolledbackException;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.interceptor.Interceptors;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiError;
import com.wordnik.swagger.annotations.ApiErrors;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import org.jboss.resteasy.annotations.GZIP;
import org.jboss.resteasy.annotations.cache.Cache;
import org.jboss.resteasy.links.AddLinks;
import org.jboss.resteasy.links.LinkResource;
import org.rhq.core.clientapi.agent.PluginContainerException;
import org.rhq.core.clientapi.agent.discovery.InvalidPluginConfigurationClientException;
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.criteria.AlertCriteria;
import org.rhq.core.domain.criteria.AvailabilityCriteria;
import org.rhq.core.domain.criteria.ResourceCriteria;
import org.rhq.core.domain.criteria.ResourceTypeCriteria;
import org.rhq.core.domain.discovery.AvailabilityReport;
import org.rhq.core.domain.measurement.Availability;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementSchedule;
import org.rhq.core.domain.resource.Agent;
import org.rhq.core.domain.resource.CreateResourceHistory;
import org.rhq.core.domain.resource.CreateResourceStatus;
import org.rhq.core.domain.resource.InventoryStatus;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceCategory;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.resource.composite.ResourceAvailabilitySummary;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.domain.util.PageOrdering;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.alert.AlertManagerLocal;
import org.rhq.enterprise.server.core.AgentManagerLocal;
import org.rhq.enterprise.server.discovery.DiscoveryBossLocal;
import org.rhq.enterprise.server.measurement.AvailabilityManagerLocal;
import org.rhq.enterprise.server.resource.ResourceAlreadyExistsException;
import org.rhq.enterprise.server.resource.ResourceFactoryManagerLocal;
import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal;
import org.rhq.enterprise.server.rest.domain.AvailabilityRest;
import org.rhq.enterprise.server.rest.domain.AvailabilitySummary;
import org.rhq.enterprise.server.rest.domain.CreateCBResourceRequest;
import org.rhq.enterprise.server.rest.domain.Link;
import org.rhq.enterprise.server.rest.domain.MetricSchedule;
import org.rhq.enterprise.server.rest.domain.ResourceWithChildren;
import org.rhq.enterprise.server.rest.domain.ResourceWithType;
import org.rhq.enterprise.server.rest.domain.StringValue;
import org.rhq.enterprise.server.rest.helper.ConfigurationHelper;
import org.rhq.enterprise.server.rest.helper.ResourceCriteriaHelper;
/**
* Class that deals with getting data about resources
* @author Heiko W. Rupp
*/
@Path("/resource")
@Api(value="Resource related", description = "This endpoint deals with individual resources, not resource groups")
@Interceptors(SetCallerInterceptor.class)
@Stateless
public class ResourceHandlerBean extends AbstractRestBean {
private static final String NO_RESOURCE_FOR_ID = "If no resource with the passed id exists";
private static final String DEFAULT_PACKAGE = "default.rest.package";
// Name prefix for synthetic/dummy agents created with the rest api. See #createPlatformInternal
public static final String DUMMY_AGENT_NAME_PREFIX = "dummy-agent:name";
public static final String DUMMY_AGENT_TOKEN_PREFIX = "abc-";
@EJB
AvailabilityManagerLocal availMgr;
@EJB
AlertManagerLocal alertManager;
@EJB
ResourceTypeManagerLocal resourceTypeManager;
@EJB
AgentManagerLocal agentMgr;
@EJB
ResourceFactoryManagerLocal resourceFactory;
@EJB
DiscoveryBossLocal discoveryBoss;
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
@GET
@Path("/{id:\\d+}")
@Cache(isPrivate = true,maxAge = 120)
@ApiOperation(value = "Retrieve a single resource", responseClass = "ResourceWithType")
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID)
public Response getResource(@ApiParam("Id of the resource to retrieve") @PathParam("id") int id,
@Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo) {
Resource res;
res = fetchResource(id);
long mtime = res.getMtime();
EntityTag eTag = new EntityTag(Long.toOctalString(res.hashCode() + mtime)); // factor in mtime in etag
Response.ResponseBuilder builder = request.evaluatePreconditions(new Date(mtime), eTag);
if (builder != null) {
return builder.build();
}
ResourceWithType rwt = fillRWT(res, uriInfo);
// What media type does the user request?
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
builder = Response.ok(renderTemplate("resourceWithType", rwt), mediaType);
} else {
builder = Response.ok(rwt);
}
return builder.build();
}
@PUT
@Path("/{id:\\d+}")
@ApiOperation(value = "Update a single resource or import a new resource from the discovery queue.",
notes = "You can either update a resource that is already in inventory, in which case the fields" +
"name, description and location can be updated. Or you can import a Platform or Server resource that is in state NEW." +
"To do this you need to PUT the resource retrieved with a COMMITTED state",
responseClass = "ResourceWithType")
@ApiErrors({
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID),
@ApiError(code = 406, reason = "Tried to update a resource that is not COMMITTED")
})
public Response updateResource(@ApiParam("Id of the resource to import") @PathParam("id") int resourceId,
@ApiParam("Resource to update" ) ResourceWithType resourceWithType,
@Context UriInfo uriInfo) {
Resource res = fetchResource(resourceId);
if (res.getInventoryStatus()==InventoryStatus.NEW && res.getResourceType().getCategory()!=ResourceCategory.SERVICE && resourceWithType.getStatus().equalsIgnoreCase("COMMITTED")) {
// Import
discoveryBoss.importResources(caller,new int[] { resourceId});
res = fetchResource(resourceId);
ResourceWithType outWithType = fillRWT(res,uriInfo);
return Response.ok(outWithType).build();
}
// Import was handled above, so we require committed state now
if (res.getInventoryStatus()!=InventoryStatus.COMMITTED) {
throw new BadArgumentException("Can only update resources in committed state");
}
// No import, so update some of the allowed items
Resource in = new Resource(res.getId());
in.setName(resourceWithType.getResourceName());
in.setDescription(resourceWithType.getDescription());
in.setLocation(resourceWithType.getLocation());
Resource out = resMgr.updateResource(caller,in);
ResourceWithType outWithType = fillRWT(out,uriInfo);
return Response.ok(outWithType).build();
}
@GET
@GZIP
@Path("/search")
@ApiError(code = 406, reason = "The passed inventory status was invalid")
@ApiOperation(value = "Search for resources based on query parameters", notes = "You can use any parameters based on org.rhq.core.domain.criteria.ResourceCriteria#addFilter*,"
+ " , but note that only one value is passed to filter method (even if it may support multiple values)."
+ " For example parameter name=value transforms to ResourceCriteria#addFilterName(value),"
+ " parameter pluginName=value transforms to ResourceCriteria#addFilterPluginName(value)."
+ " For some parameter names, following are equivalent : "
+ ResourceCriteriaHelper.PARAM_SHORTCUTS_TEXT
+ ". For example, to find all running AS7 Standalone Servers on a platform do GET /resource/search?parentId=10001&type=JBossAS7 Standalone Server&availability=UP", responseClass = "ResourceWithType")
public Response searchResourcesByQuery(
@ApiParam("Page size for paging") @QueryParam("ps") @DefaultValue("20") int pageSize,
@ApiParam("Page for paging, 0-based") @QueryParam("page") @DefaultValue("0") Integer page,
@ApiParam("Enable strict filtering") @QueryParam("strict") @DefaultValue("false") boolean strict,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) {
ResourceCriteria criteria = ResourceCriteriaHelper.create(uriInfo.getQueryParameters());
criteria.addSortName(PageOrdering.ASC);
if (page != null) {
criteria.setPaging(page, pageSize);
}
PageList<Resource> ret = resMgr.findResourcesByCriteria(caller, criteria);
Response.ResponseBuilder builder = getResponseBuilderForResourceList(headers, uriInfo, ret);
return builder.build();
}
@GET @GZIP
@Path("/")
@ApiError(code = 406, reason = "The passed inventory status was invalid")
@ApiOperation(value = "Search for resources by the given search string, possibly limited by category and paged", responseClass = "ResourceWithType")
public Response getResourcesByQuery(@ApiParam("Limit results to param in the resource name") @QueryParam("q") String q,
@ApiParam("Limit to category (PLATFORM, SERVER, SERVICE") @QueryParam("category") String category,
@ApiParam("Page size for paging") @QueryParam("ps") @DefaultValue("20") int pageSize,
@ApiParam("Page for paging, 0-based") @QueryParam("page") @DefaultValue("0") Integer page,
@ApiParam(value = "Limit to Inventory status of the resources", allowableValues = "ALL, NEW, IGNORED, COMMITTED, UNINVENTORIED")
@DefaultValue("COMMITTED") @QueryParam("status") String status,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) {
ResourceCriteria criteria = new ResourceCriteria();
criteria.addSortName(PageOrdering.ASC);
if (!status.toLowerCase().equals("all")) {
try {
criteria.addFilterInventoryStatus(InventoryStatus.valueOf(status.toUpperCase()));
} catch (IllegalArgumentException iae) {
throw new BadArgumentException("status", "Value " + status
+ " is not in the list of allowed values: ALL, NEW, IGNORED, COMMITTED, UNINVENTORIED");
}
} else {
// JavaDoc says to explicitly set to null in order to get all Status
criteria.addFilterInventoryStatus(null);
}
if (q!=null) {
criteria.addFilterName(q);
}
if (category!=null) {
criteria.addFilterResourceCategories(ResourceCategory.valueOf(category.toUpperCase()));
}
if (page!=null) {
criteria.setPaging(page,pageSize);
}
PageList<Resource> ret = resMgr.findResourcesByCriteria(caller,criteria);
Response.ResponseBuilder builder = getResponseBuilderForResourceList(headers,uriInfo,ret);
return builder.build();
}
@GZIP
@GET
@Path("/platforms")
@Cache(isPrivate = true,maxAge = 300)
@ApiOperation(value = "List all platforms in the system", multiValueResponse = true, responseClass = "ResourceWithType")
public Response getPlatforms(
@ApiParam("Page size for paging") @QueryParam("ps") @DefaultValue("20") int pageSize,
@ApiParam("Page for paging, 0-based") @QueryParam("page") @DefaultValue("0") Integer page,
@Context HttpHeaders headers, @Context UriInfo uriInfo) {
PageControl pc = new PageControl(page, pageSize);
pc.setPrimarySort("id",PageOrdering.ASC);
PageList<Resource> ret = resMgr.findResourcesByCategory(caller, ResourceCategory.PLATFORM,
InventoryStatus.COMMITTED, pc);
Response.ResponseBuilder builder = getResponseBuilderForResourceList(headers, uriInfo, ret);
return builder.build();
}
/**
* Translate the passed list of resources into a response according to the acceptable mime types etc.
*
*
* @param headers HttpHeaders from the request
* @param uriInfo Uri from the request
* @param resources List of resources
* @return An initialized ResponseBuilder
*/
private Response.ResponseBuilder getResponseBuilderForResourceList(HttpHeaders headers, UriInfo uriInfo,
PageList<Resource> resources) {
List<ResourceWithType> rwtList = new ArrayList<ResourceWithType>(resources.size());
for (Resource r : resources) {
putToCache(r.getId(), Resource.class, r);
ResourceWithType rwt = fillRWT(r, uriInfo);
rwtList.add(rwt);
}
// What media type does the user request?
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder = Response.ok();
builder.type(mediaType);
if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
builder.entity(renderTemplate("listResourceWithType", rwtList));
} else {
if (mediaType.equals(wrappedCollectionJsonType)) {
wrapForPaging(builder, uriInfo, resources, rwtList);
} else {
GenericEntity<List<ResourceWithType>> list = new GenericEntity<List<ResourceWithType>>(rwtList) {
};
builder.entity(list);
createPagingHeader(builder,uriInfo,resources);
}
}
return builder;
}
@GET @GZIP
@Path("/{id}/hierarchy")
@Produces({"application/json","application/xml"})
@ApiOperation(value = "Retrieve the hierarchy of resources starting with the passed one", multiValueResponse = true, responseClass = "ResourceWithType")
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID)
public ResourceWithChildren getHierarchy(@ApiParam("Id of the resource to start with") @PathParam("id")int baseResourceId) {
// TODO optimize to do less recursion
Resource start = fetchResource(baseResourceId);
return getHierarchy(start);
}
private ResourceWithChildren getHierarchy(Resource baseResource) {
ResourceWithChildren rwc = new ResourceWithChildren("" + baseResource.getId(), baseResource.getName());
PageControl pc = new PageControl();
List<Resource> ret = resMgr.findResourceByParentAndInventoryStatus(caller, baseResource,
InventoryStatus.COMMITTED, pc);
if (!ret.isEmpty()) {
List<ResourceWithChildren> resList = new ArrayList<ResourceWithChildren>(ret.size());
for (Resource res : ret) {
ResourceWithChildren child = getHierarchy(res);
resList.add(child);
putToCache(res.getId(), Resource.class, res);
}
if (!resList.isEmpty())
rwc.setChildren(resList);
}
return rwc;
}
@GET
@Path("/{id}/availability")
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID)
@ApiOperation(value = "Return the current availability for the passed resource", responseClass = "AvailabilityRest")
public Response getAvailability(@ApiParam("Id of the resource to query") @PathParam("id") int resourceId, @Context HttpHeaders headers) {
Availability avail = availMgr.getCurrentAvailabilityForResource(caller, resourceId);
AvailabilityRest availabilityRest;
if (avail.getAvailabilityType() != null)
availabilityRest = new AvailabilityRest(avail.getAvailabilityType(), avail.getStartTime(), avail
.getResource().getId());
else
availabilityRest = new AvailabilityRest(avail.getStartTime(), resourceId);
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder;
if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
builder = Response.ok(renderTemplate("availability.ftl",availabilityRest), mediaType);
} else {
builder = Response.ok(availabilityRest);
}
return builder.build();
}
@GZIP
@GET
@Path("/{id}/availability/history")
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID)
@ApiOperation(value = "Return the availability history for the passed resource", responseClass = "AvailabilityRest", multiValueResponse = true)
public Response getAvailabilityHistory(
@ApiParam("Id of the resource to query") @PathParam("id") int resourceId,
@ApiParam(value="Start time", defaultValue = "30 days ago") @QueryParam("start") long start,
@ApiParam(value="End time", defaultValue = "Now") @QueryParam("end") long end,
@Context HttpHeaders headers) {
// Vaildate it the resource exists
fetchResource(resourceId);
if (end==0)
end = System.currentTimeMillis();
if (start==0)
start = end - (30*86400*1000L); // 30 days
AvailabilityCriteria criteria = new AvailabilityCriteria();
criteria.addFilterInterval(start,end);
criteria.addFilterResourceId(resourceId);
criteria.addSortStartTime(PageOrdering.DESC);
List<Availability> points = availMgr.findAvailabilityByCriteria(caller, criteria);
List<AvailabilityRest> ret = new ArrayList<AvailabilityRest>(points.size());
for (Availability avail : points) {
AvailabilityRest availabilityRest;
if (avail.getAvailabilityType() != null) {
availabilityRest = new AvailabilityRest(avail.getAvailabilityType(), avail.getStartTime(), avail
.getResource().getId());
}
else {
availabilityRest = new AvailabilityRest(avail.getStartTime(), resourceId);
}
if (avail.getEndTime()!=null)
availabilityRest.setUntil(avail.getEndTime());
ret.add(availabilityRest);
}
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder;
if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
builder = Response.ok(renderTemplate("listAvailability.ftl",ret), mediaType);
} else {
GenericEntity<List<AvailabilityRest>> availabilityRest = new GenericEntity<List<AvailabilityRest>>(ret) {};
builder = Response.ok(availabilityRest);
}
return builder.build();
}
@GET
@Path("/{id}/availability/summary")
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID)
@ApiOperation(value = "Return the availability history for the passed resource", responseClass = "AvailabilitySummary", multiValueResponse = false)
public Response getAvailabilitySummary(
@ApiParam("Id of the resource to query") @PathParam("id") int resourceId,
@Context HttpHeaders headers) {
fetchResource(resourceId);
ResourceAvailabilitySummary summary = resMgr.getAvailabilitySummary(caller,resourceId);
AvailabilitySummary as = new AvailabilitySummary(resourceId,summary);
Response.ResponseBuilder builder = Response.ok(as);
MediaType type = headers.getAcceptableMediaTypes().get(0);
builder.type(type);
return builder.build();
}
@PUT
@Path("/{id}/availability")
@ApiOperation("Set the current availability of the passed resource")
@TransactionAttribute(TransactionAttributeType.NEVER)
public void reportAvailability(@ApiParam("Id of the resource to update") @PathParam("id") int resourceId,
@ApiParam(value= "New Availability setting", required = true) AvailabilityRest avail) {
if (avail.getResourceId() != resourceId)
throw new IllegalArgumentException("Resource Ids do not match");
Resource resource = fetchResource(resourceId);
AvailabilityType at;
at = AvailabilityType.valueOf(avail.getType());
// According to jshaughn, plaforms must not be set to DISABLED, so catch this case here.
if (resource.getResourceType().getCategory()==ResourceCategory.PLATFORM && at==AvailabilityType.DISABLED) {
throw new BadArgumentException("Availability","Platforms must not be set to DISABLED");
}
Agent agent = agentMgr.getAgentByResourceId(caller,resourceId);
AvailabilityReport report = new AvailabilityReport(true, agent.getName());
Availability availability = new Availability(resource, avail.getSince(), at);
report.addAvailability(availability);
availMgr.mergeAvailabilityReport(report);
}
@GZIP
@GET
@Path("/{id}/schedules")
@LinkResource(rel="schedules",value = MetricSchedule.class)
@Cache(isPrivate = true,maxAge = 60)
@ApiOperation(value ="Get the metric schedules of the passed resource id", multiValueResponse = true, responseClass = "MetricSchedule")
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID)
public Response getSchedules(
@ApiParam("Id of the resource to obtain the schedules for") @PathParam("id") int resourceId,
@ApiParam(value = "Limit by type",
allowableValues = "<empty>, all, metric, trait, measurement") @QueryParam("type") @DefaultValue(
"all") String scheduleType,
@ApiParam(value = "Limit by enabled schedules") @QueryParam("enabledOnly") @DefaultValue(
"true") boolean enabledOnly,
@ApiParam(value = "Limit by name") @QueryParam("name") String name,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) {
// allow metric as input
if (scheduleType.equals("metric"))
scheduleType = DataType.MEASUREMENT.toString().toLowerCase();
Resource res = resMgr.getResource(caller, resourceId); // Don't fetch(), as this would yield a LazyLoadException
Set<MeasurementSchedule> schedules = res.getSchedules();
List<MetricSchedule> ret = new ArrayList<MetricSchedule>(schedules.size());
for (MeasurementSchedule schedule : schedules) {
putToCache(schedule.getId(), MeasurementSchedule.class, schedule);
MeasurementDefinition definition = schedule.getDefinition();
// user can opt to e.g. only get "measurement" or "trait" metrics
if ("all".equals(scheduleType)
|| scheduleType.toLowerCase().equals(definition.getDataType().toString().toLowerCase())) {
if (!enabledOnly || (enabledOnly && schedule.isEnabled())) {
if (name == null || (name != null && name.equals(definition.getName()))) {
MetricSchedule ms = getMetricScheduleInternal(uriInfo, schedule, definition);
ret.add(ms);
}
}
}
}
// What media type does the user request?
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder;
if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
builder = Response.ok(renderTemplate("listMetricSchedule", ret), mediaType);
} else {
GenericEntity<List<MetricSchedule>> list = new GenericEntity<List<MetricSchedule>>(ret) {
};
builder = Response.ok(list, mediaType);
}
return builder.build();
}
@GZIP
@GET
@Path("/{id}/children")
@LinkResource(rel="children", value = ResourceWithType.class)
@ApiOperation(value = "Get the direct children of the passed resource")
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID)
public Response getChildren(
@ApiParam("Id of the resource to get children") @PathParam("id") int id,
@ApiParam("Page size for paging") @QueryParam("ps") @DefaultValue("20") int pageSize,
@ApiParam("Page for paging, 0-based") @QueryParam("page") @DefaultValue("0") Integer page,
@ApiParam("Limit results to param in the resource name") @QueryParam("q") String q,
@ApiParam("Limit to category (PLATFORM, SERVER, SERVICE)") @QueryParam("category") String category,
@ApiParam(value = "Limit to Inventory status of the resources", allowableValues = "ALL, NEW, IGNORED, COMMITTED, DELETED, UNINVENTORIED")
@DefaultValue("COMMITTED") @QueryParam("status") String status,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) {
// just check if resource exists
fetchResource(id);
ResourceCriteria criteria = new ResourceCriteria();
criteria.addSortName(PageOrdering.ASC);
criteria.addFilterParentResourceId(id);
if (!status.toLowerCase().equals("all")) {
try {
criteria.addFilterInventoryStatus(InventoryStatus.valueOf(status.toUpperCase()));
} catch (IllegalArgumentException iae) {
throw new BadArgumentException("status", "Value " + status
+ " is not in the list of allowed values: ALL, NEW, IGNORED, COMMITTED, DELETED, UNINVENTORIED");
}
} else {
// JavaDoc says to explicitly set to null in order to get all Status
criteria.addFilterInventoryStatus(null);
}
if (q != null) {
criteria.addFilterName(q);
}
if (category != null) {
criteria.addFilterResourceCategories(ResourceCategory.valueOf(category.toUpperCase()));
}
if (page != null) {
criteria.setPaging(page, pageSize);
}
PageList<Resource> ret = resMgr.findResourcesByCriteria(caller, criteria);
List<ResourceWithType> rwtList = new ArrayList<ResourceWithType>(ret.size());
for (Resource r : ret) {
ResourceWithType rwt = fillRWT(r, uriInfo);
rwtList.add(rwt);
}
// What media type does the user request?
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder;
if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
builder = Response.ok(renderTemplate("listResourceWithType", rwtList), mediaType);
} else {
GenericEntity<List<ResourceWithType>> list = new GenericEntity<List<ResourceWithType>>(rwtList) {
};
builder = Response.ok(list);
}
return builder.build();
}
@GZIP
@AddLinks
@GET
@Path(("/{id}/alerts"))
@ApiError(code = 404, reason = NO_RESOURCE_FOR_ID)
@ApiOperation("Get a list of links to the alerts for the passed resource")
public List<Link> getAlertsForResource(@ApiParam("Id of the resource to query") @PathParam("id") int resourceId) {
AlertCriteria criteria = new AlertCriteria();
// Check for resource existence
fetchResource(resourceId);
criteria.addFilterResourceIds(resourceId);
PageList<Alert> alerts = alertManager.findAlertsByCriteria(caller, criteria);
List<Link> links = new ArrayList<Link>(alerts.size());
for (Alert al : alerts) {
Link link = new Link();
link.setRel("alert");
link.setHref("/alert/" + al.getId());
links.add(link);
}
return links;
}
@ApiOperation(value = "Create a new platform in the Server. If the platform already exists, this is a no-op." +
"The platform internally has a special name so that it will not clash with one that was generated" +
"via a normal RHQ agent. DEPRECATED Use POST /platforms instead")
@POST
@Path("platform/{name}")
public Response createPlatformOLD(
@ApiParam(value = "Name of the platform") @PathParam("name") String name,
@ApiParam(value = "Type of the platform", allowableValues = "Linux,Windows,... TODO") StringValue typeValue,
@Context UriInfo uriInfo) {
String typeName = typeValue.getValue();
return createPlatformInternal(name, typeName, uriInfo);
}
@POST
@Path("platforms")
@ApiOperation(value = "Create a new platform in the Server. If the platform already exists, this is a no-op." +
"The platform internally has a special name so that it will not clash with one that was generated" +
"via a normal RHQ agent. Only resourceName and typeName need to be supplied in the passed object")
public Response createPlatform(
@ApiParam("The info about the platform. Only type name and resource name need to be supplied") ResourceWithType resource,
@Context UriInfo uriInfo)
{
String typeName = resource.getTypeName();
String resourceName = resource.getResourceName();
return createPlatformInternal(resourceName, typeName, uriInfo);
}
private Response createPlatformInternal(String name, String typeName, UriInfo uriInfo) {
ResourceType type = resourceTypeManager.getResourceTypeByNameAndPlugin(typeName,"Platforms");
if (type==null) {
throw new StuffNotFoundException("Platform with type [" + typeName + "]");
}
String resourceKey = "p:" + name;
Resource r = resMgr.getResourceByParentAndKey(caller,null,resourceKey,"Platforms",typeName);
if (r!=null) {
// platform exists - return it
ResourceWithType rwt = fillRWT(r,uriInfo);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/{id}");
URI uri = uriBuilder.build(r.getId());
Response.ResponseBuilder builder = Response.created(uri);
builder.entity(rwt);
return builder.build();
}
// Create a dummy agent per platform - otherwise we can't delete the platform later
// See also https://docs.jboss.org/author/display/RHQ/Virtual+platforms+and+synthetic+agents
Agent agent ;
agent = new Agent(DUMMY_AGENT_NAME_PREFIX + name, "-dummy-p:" + name, 12345, "http://foo.com/p:name/" + name,
DUMMY_AGENT_TOKEN_PREFIX + name);
agentMgr.createAgent(agent);
Resource platform = new Resource(resourceKey,name,type);
platform.setUuid(UUID.randomUUID().toString());
platform.setAgent(agent);
platform.setInventoryStatus(InventoryStatus.COMMITTED);
platform.setModifiedBy(caller.getName());
platform.setDescription(type.getDescription() + ". Created via REST-api");
platform.setItime(System.currentTimeMillis());
try {
resMgr.createResource(caller,platform,-1);
createSchedules(platform);
ResourceWithType rwt = fillRWT(platform,uriInfo);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/{id}");
URI uri = uriBuilder.build(platform.getId());
Response.ResponseBuilder builder = Response.created(uri);
builder.entity(rwt);
return builder.build();
} catch (ResourceAlreadyExistsException e) {
throw new IllegalArgumentException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@ApiOperation(value = "Create a resource with a given type below a certain parent. DEPRECATED Use POST / instead")
@POST
@Path("{name}")
public Response createResourceOLD(
@ApiParam("Name of the new resource") @PathParam("name") String name,
@ApiParam("Name of the Resource type") StringValue typeValue,
@ApiParam("Name of the plugin providing the type") @QueryParam("plugin") String plugin,
@ApiParam("Id of the future parent to attach this to") @QueryParam("parentId") int parentId,
@Context UriInfo uriInfo) {
String typeName = typeValue.getValue();
return createResourceInternalOld(name, plugin, parentId, typeName, uriInfo);
}
@POST
@Path("/")
@ApiErrors({
@ApiError(code = 302, reason = "Creation is still happening. Check back with a GET on the Location.")
})
@ApiOperation(value = "Create a new resource as a child of an existing resource. ",
notes= "If a handle is given, a content based resource is created; the content identified by the handle is not removed from the content store." +
"If no handle is given, a resource is created from the data of the passed 'resource' object.")
public Response createResource(
@ApiParam("The info about the resource. You need to supply resource name, resource type name, plugin name, id of the parent") CreateCBResourceRequest resource,
@ApiParam("A handle that identifies content that has been uploaded to the server before.") @QueryParam("handle") String handle,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) throws IOException
{
if (handle!=null) {
return createContentBackedResource(resource,handle,headers,uriInfo);
} else {
return createResourceInternal(resource,headers,uriInfo);
}
}
/**
* creates regular child resource similar to {@link ResourceHandlerBean#createContentBackedResource(CreateCBResourceRequest, String, HttpHeaders, UriInfo)}
* except no content is given and
* @param request
* @param headers
* @param uriInfo
* @return
*/
private Response createResourceRegularChild(CreateCBResourceRequest request, ResourceType resType, HttpHeaders headers, UriInfo uriInfo) {
int parentId = request.getParentId();
String name = request.getResourceName();
// fetch default plugin and resource configs
ResourceTypeCriteria rtc = new ResourceTypeCriteria();
rtc.addFilterId(resType.getId());
rtc.fetchPluginConfigurationDefinition(true);
rtc.fetchResourceConfigurationDefinition(true);
resType = resourceTypeManager.findResourceTypesByCriteria(caller, rtc).get(0);
Configuration defaultPc = new Configuration();
Configuration pluginConfig = ConfigurationHelper.mapToConfiguration(request.getPluginConfig());
if (resType.getPluginConfigurationDefinition().getDefaultTemplate() != null) {
defaultPc = resType.getPluginConfigurationDefinition().getDefaultTemplate().getConfiguration().deepCopyWithoutProxies();
}
for (Property p : pluginConfig.getProperties()) {
defaultPc.put(p);
}
Configuration defaultRc= new Configuration();
Configuration resourceConfig = ConfigurationHelper.mapToConfiguration(request.getResourceConfig());
if (resType.getResourceConfigurationDefinition().getDefaultTemplate() != null) {
defaultRc = resType.getResourceConfigurationDefinition().getDefaultTemplate().getConfiguration().deepCopyWithoutProxies();
}
for (Property p : resourceConfig.getProperties()) {
defaultRc.put(p);
}
CreateResourceHistory history = resourceFactory.createResource(caller,parentId, resType.getId(),name,defaultPc,
defaultRc, null);
CreateResourceStatus status = history.getStatus();
try {
Thread.sleep(2000L); // give the agent time to do the work
} catch (InterruptedException e) {
; // nothing
}
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder;
if ( status == CreateResourceStatus.SUCCESS) {
ResourceWithType rwt = findCreatedResource(history.getParentResource().getId(),history.getCreatedResourceName(),uriInfo);
if (rwt!=null) {
builder = Response.ok();
builder.entity(rwt);
} else {
// History says we had success but due to internal timing
// the resource is not yet visible, so switch to in_progress
status = CreateResourceStatus.IN_PROGRESS;
}
}
if (status==CreateResourceStatus.IN_PROGRESS) {
try {
Thread.sleep(2000L); // give the agent time to do the work
} catch (InterruptedException e) {
; // nothing
}
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/creationStatus/{id}");
URI uri = uriBuilder.build(history.getId());
builder = Response.status(302);
builder.entity("Creation is running - please check back later");
builder.location(uri); // redirect to self
}
else { // All kinds of failures
builder = Response.serverError();
builder.entity(new StringValue(history.getErrorMessage()));
}
builder.type(mediaType);
return builder.build();
}
/**
* creates syntetic resource. This way we can create resources that would normally be discovered by agent
* and there's no way in UI/CLI to create them.
* @param request
* @param resType
* @param headers
* @param uriInfo
* @return
*/
private Response createResourceSyntetic(CreateCBResourceRequest request, ResourceType resType, HttpHeaders headers, UriInfo uriInfo) {
int parentId = request.getParentId();
String typeName = request.getTypeName();
String plugin = request.getPluginName();
String name = request.getResourceName();
Resource parent = resMgr.getResourceById(caller,parentId);
String resourceKey = "res:" + name + ":" + parentId;
Resource r = resMgr.getResourceByParentAndKey(caller,parent,resourceKey,plugin,typeName);
if (r!=null) {
// resource exists - return it
ResourceWithType rwt = fillRWT(r,uriInfo);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/{id}");
URI uri = uriBuilder.build(r.getId());
Response.ResponseBuilder builder = Response.created(uri);
builder.entity(rwt);
return builder.build();
}
Resource res = new Resource(resourceKey,name,resType);
res.setUuid(UUID.randomUUID().toString());
res.setAgent(parent.getAgent());
res.setParentResource(parent);
res.setInventoryStatus(InventoryStatus.COMMITTED);
res.setDescription(resType.getDescription() + ". Created via REST-api");
try {
resMgr.createResource(caller,res,parent.getId());
createSchedules(res);
ResourceWithType rwt = fillRWT(res,uriInfo);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/{id}");
URI uri = uriBuilder.build(res.getId());
Response.ResponseBuilder builder = Response.created(uri);
builder.entity(rwt);
return builder.build();
} catch (ResourceAlreadyExistsException e) {
throw new IllegalArgumentException(e);
}
}
private Response createResourceManualImport(CreateCBResourceRequest request, ResourceType resType, HttpHeaders headers, UriInfo uriInfo) {
// fetch default plugin config
ResourceTypeCriteria rtc = new ResourceTypeCriteria();
rtc.addFilterId(resType.getId());
rtc.fetchPluginConfigurationDefinition(true);
resType = resourceTypeManager.findResourceTypesByCriteria(caller, rtc).get(0);
Configuration defaultPc = new Configuration();
if (resType.getPluginConfigurationDefinition().getDefaultTemplate() != null) {
defaultPc = resType.getPluginConfigurationDefinition().getDefaultTemplate().getConfiguration().deepCopyWithoutProxies();
}
Configuration pluginConfig = ConfigurationHelper.mapToConfiguration(request.getPluginConfig());
for (Property p : pluginConfig.getProperties()) {
defaultPc.put(p);
}
Response.ResponseBuilder builder;
try {
long now = System.currentTimeMillis();
Resource r = discoveryBoss.manuallyAddResource(caller, resType.getId(), request.getParentId(), defaultPc);
if (now > r.getCtime()) {
builder = Response.serverError();
builder.entity(new StringValue("Duplicate resource: manuallyAdded resource under same parent having same properties already exists on server. Note that 'resourceName' is ignored."));
return builder.build();
}
if (request.getResourceName()!=null) {
// resourceName is optional, but if specified, let's update it
r.setName(request.getResourceName());
r = resMgr.updateResource(caller, r);
}
ResourceWithType rwt = fillRWT(r, uriInfo);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/{id}");
URI uri = uriBuilder.build(r.getId());
builder = Response.created(uri);
builder.entity(rwt);
return builder.build();
} catch (InvalidPluginConfigurationClientException e) {
builder = Response.serverError();
builder.entity(new StringValue(e.getMessage()));
e.printStackTrace();
} catch (PluginContainerException e) {
builder = Response.serverError();
builder.entity(new StringValue(e.getMessage()));
e.printStackTrace();
} catch (Exception e) {
builder = Response.serverError();
builder.entity(new StringValue(e.getMessage()));
e.printStackTrace();
}
return builder.build();
}
private Response createResourceInternal(CreateCBResourceRequest request, HttpHeaders headers, UriInfo uriInfo) throws IOException
{
int parentId = request.getParentId();
// Check for valid parent
fetchResource(parentId);
ResourceType resType = resourceTypeManager.getResourceTypeByNameAndPlugin(request.getTypeName(),request.getPluginName());
if (resType == null) {
throw new StuffNotFoundException("ResourceType with name [" + request.getTypeName() + "] and plugin [" + request.getPluginName() + "]");
}
if (resType.isSupportsManualAdd()) {
return createResourceManualImport(request, resType, headers, uriInfo);
}
else if (resType.isCreatable()) {
return createResourceRegularChild(request, resType, headers, uriInfo);
}
else {
return createResourceSyntetic(request, resType, headers, uriInfo);
}
}
private Response createResourceInternalOld(String name, String plugin, int parentId, String typeName,
UriInfo uriInfo) {
Resource parent = resMgr.getResourceById(caller,parentId);
ResourceType resType = resourceTypeManager.getResourceTypeByNameAndPlugin(typeName,plugin);
if (resType==null)
throw new StuffNotFoundException("ResourceType with name [" + typeName + "] and plugin [" + plugin + "]");
String resourceKey = "res:" + name + ":" + parentId;
Resource r = resMgr.getResourceByParentAndKey(caller,parent,resourceKey,plugin,typeName);
if (r!=null) {
// resource exists - return it
ResourceWithType rwt = fillRWT(r,uriInfo);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/{id}");
URI uri = uriBuilder.build(r.getId());
Response.ResponseBuilder builder = Response.created(uri);
builder.entity(rwt);
return builder.build();
}
Resource res = new Resource(resourceKey,name,resType);
res.setUuid(UUID.randomUUID().toString());
res.setAgent(parent.getAgent());
res.setParentResource(parent);
res.setInventoryStatus(InventoryStatus.COMMITTED);
res.setDescription(resType.getDescription() + ". Created via REST-api");
try {
resMgr.createResource(caller,res,parent.getId());
createSchedules(res);
ResourceWithType rwt = fillRWT(res,uriInfo);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/{id}");
URI uri = uriBuilder.build(res.getId());
Response.ResponseBuilder builder = Response.created(uri);
builder.entity(rwt);
return builder.build();
} catch (ResourceAlreadyExistsException e) {
throw new IllegalArgumentException(e);
}
}
private Response createContentBackedResource(CreateCBResourceRequest request, String handle, HttpHeaders headers, UriInfo uriInfo) throws IOException
{
int parentId = request.getParentId();
String typeName = request.getTypeName();
String plugin = request.getPluginName();
String name = request.getResourceName();
String tmpDirName = System.getProperty("java.io.tmpdir");
File tmpDir = new File(tmpDirName);
File content = new File(tmpDir,handle);
if (!content.exists() || !content.canRead())
throw new StuffNotFoundException("Content for handle " + handle);
BufferedInputStream resourceBits = new BufferedInputStream(new FileInputStream(content));
// Check for valid parent
fetchResource(parentId);
ResourceType resType = resourceTypeManager.getResourceTypeByNameAndPlugin(typeName,plugin);
if (resType==null)
throw new StuffNotFoundException("ResourceType with name [" + typeName + "] and plugin [" + plugin + "]");
Configuration pluginConfig = ConfigurationHelper.mapToConfiguration(request.getPluginConfig());
Configuration deployConfig = ConfigurationHelper.mapToConfiguration(request.getResourceConfig());
String packageName = DEFAULT_PACKAGE;
CreateResourceHistory history = resourceFactory.createResource(caller,parentId, resType.getId(),name,pluginConfig,
packageName, null,null,deployConfig,resourceBits);
CreateResourceStatus status = history.getStatus();
try {
Thread.sleep(2000L); // give the agent time to do the work
} catch (InterruptedException e) {
; // nothing
}
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder;
if ( status == CreateResourceStatus.SUCCESS) {
ResourceWithType rwt = findCreatedResource(history.getParentResource().getId(),history.getCreatedResourceName(),uriInfo);
if (rwt!=null) {
builder = Response.ok();
builder.entity(rwt);
} else {
// History says we had success but due to internal timing
// the resource is not yet visible, so switch to in_progress
status = CreateResourceStatus.IN_PROGRESS;
}
}
if (status==CreateResourceStatus.IN_PROGRESS) {
try {
Thread.sleep(2000L); // give the agent time to do the work
} catch (InterruptedException e) {
; // nothing
}
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/resource/creationStatus/{id}");
URI uri = uriBuilder.build(history.getId());
builder = Response.status(302);
builder.entity("Creation is running - please check back later");
builder.location(uri); // redirect to self
}
else { // All kinds of failures
builder = Response.serverError();
builder.entity(new StringValue(history.getErrorMessage()));
}
builder.type(mediaType);
return builder.build();
}
@GET
@Path("/creationStatus/{id}")
@ApiOperation("Get the status of a resource creation for content based resources.")
@ApiError(code = 302, reason = "Creation is still going on. Check back later with the same URL.")
public Response getHistoryItem(@PathParam("id") int historyId, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
CreateResourceHistory history;
try {
history = resourceFactory.getCreateHistoryItem(historyId);
} catch (EJBTransactionRolledbackException e) {
if (e.getCause() instanceof NoResultException)
throw new StuffNotFoundException("Resource creation status with id " + historyId);
else
return Response.serverError().entity(e.getMessage()).build();
}
CreateResourceStatus status = history.getStatus();
Response.ResponseBuilder builder;
try {
Thread.sleep(2000L); // give the agent time to do the work
} catch (InterruptedException e) {
; // nothing
}
if (status== CreateResourceStatus.SUCCESS) {
ResourceWithType rwt = findCreatedResource(history.getParentResource().getId(),history.getCreatedResourceName(),uriInfo);
if (rwt!=null) {
builder = Response.ok();
setCachingHeader(builder, 600);
builder.entity(rwt);
} else {
// History says we had success but due to internal timing
// the resource is not yet visible, so switch to in_progress
UriBuilder uriBuilder = uriInfo.getRequestUriBuilder();
URI uri = uriBuilder.build();
builder = Response.status(302);
builder.entity("Creation is still running - please check back later");
builder.location(uri); // redirect to self
}
}
else if (status==CreateResourceStatus.IN_PROGRESS) {
// Creation is still running, so let the user know to check back later
UriBuilder uriBuilder = uriInfo.getRequestUriBuilder();
URI uri = uriBuilder.build();
builder = Response.status(302);
builder.entity("Creation is still running - please check back later");
builder.location(uri); // redirect to self
}
else {
builder = Response.serverError();
StringValue errorMessage = new StringValue(status + ": " + history.getErrorMessage());
builder.entity(errorMessage);
}
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
builder.type(mediaType);
return builder.build();
}
/**
* Find the created resource by its name and parent. Will only return it
* if the resource is already committed.
* @param parentId Id of the parent
* @param name Name of the resource to find
* @param uriInfo UriInfo object to fill links in the returned resource
* @return A ResourceWithType if found, null otherwise.
*/
private ResourceWithType findCreatedResource(int parentId, String name, UriInfo uriInfo) {
ResourceCriteria criteria = new ResourceCriteria();
criteria.setStrict(true);
criteria.addFilterParentResourceId(parentId);
criteria.addFilterName(name);
criteria.addFilterInventoryStatus(InventoryStatus.COMMITTED);
List<Resource> resources = resMgr.findResourcesByCriteria(caller,criteria);
if (resources.size()==0) {
return null;
}
Resource res = resources.get(0);
return fillRWT(res,uriInfo);
}
@DELETE
@Path("/{id}")
@ApiOperation(value = "Remove a resource from inventory", notes = "This operation is by default idempotent, returning 204." +
"If you want to check if the resource existed at all, you need to pass the 'validate' query parameter.")
@ApiErrors({
@ApiError(code = 204, reason = "Resource was removed or did not exist with validation not set"),
@ApiError(code = 404, reason = "Resource did not exist and validate was set")
})
public Response uninventoryOrDeleteResource(
@PathParam("id") int resourceId,
@ApiParam@DefaultValue("false") @QueryParam("physical") boolean delete,
@ApiParam("Validate that the resource exists") @QueryParam("validate") @DefaultValue("false") boolean validate) {
try {
fetchResource(resourceId);
} catch (Exception e) {
if (validate) {
throw new StuffNotFoundException("Resource with id " + resourceId);
} else {
return Response.noContent().build();
}
}
if (delete==false) {
resMgr.uninventoryResource(caller,resourceId);
}
else {
resourceFactory.deleteResource(caller,resourceId);
}
return Response.noContent().build();
}
private void createSchedules(Resource resource) {
ResourceType rt = resource.getResourceType();
Set<MeasurementDefinition> definitions = rt.getMetricDefinitions ();
for (MeasurementDefinition definition : definitions) {
MeasurementSchedule schedule = new MeasurementSchedule(definition,resource);
schedule.setEnabled(definition.isDefaultOn());
schedule.setInterval(definition.getDefaultInterval());
entityManager.persist(schedule);
}
}
}