/* * 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 as published by * the Free Software Foundation version 2 of the License. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.rest; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.interceptor.Interceptors; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; 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.rhq.core.domain.alert.Alert; import org.rhq.core.domain.alert.AlertCondition; import org.rhq.core.domain.alert.AlertConditionLog; import org.rhq.core.domain.alert.AlertDefinition; import org.rhq.core.domain.alert.AlertPriority; import org.rhq.core.domain.alert.notification.AlertNotificationLog; import org.rhq.core.domain.criteria.AlertCriteria; import org.rhq.core.domain.criteria.Criteria; import org.rhq.core.domain.resource.Resource; 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.alert.AlertManagerLocal; import org.rhq.enterprise.server.rest.domain.AlertDefinitionRest; import org.rhq.enterprise.server.rest.domain.AlertRest; import org.rhq.enterprise.server.rest.domain.IntegerValue; import org.rhq.enterprise.server.rest.domain.Link; import org.rhq.enterprise.server.rest.domain.ResourceWithType; import org.rhq.enterprise.server.rest.domain.StringValue; /** * Deal with alert related stuff * @author Heiko W. Rupp */ @Path("/alert") @Api(value = "Deal with Alerts",description = "This api deals with alerts that have fired.") @Stateless @Interceptors(SetCallerInterceptor.class) public class AlertHandlerBean extends AbstractRestBean { @EJB private AlertManagerLocal alertManager; @GZIP @GET @Path("/") @ApiOperation(value = "List all alerts, possibly limiting by resource or alert definition, priority and start time", multiValueResponse = true, responseClass = "List<AlertRest>") @ApiErrors({ @ApiError(code = 406, reason = "There are 'resourceId' and 'definitionId' passed as query parameters"), @ApiError(code = 406, reason = "Page size was 0"), @ApiError(code = 406, reason = "Page number was < 0") }) public Response listAlerts( @ApiParam(value = "Page number") @QueryParam("page") @DefaultValue("0") int page, @ApiParam(value = "Page size; use -1 for 'unlimited'") @QueryParam("size") @DefaultValue("100")int size, @ApiParam(value = "Limit to priority", allowableValues = "High, Medium, Low, All") @DefaultValue("All") @QueryParam("prio") String prio, @ApiParam(value = "Should full resources and definitions be sent") @QueryParam("slim") @DefaultValue("false") boolean slim, @ApiParam(value = "If non-null only send alerts that have fired after this time, time is millisecond since epoch") @QueryParam("since") Long since, @ApiParam(value = "Id of a resource to limit search for") @QueryParam("resourceId") Integer resourceId, @ApiParam(value = "If of an alert definition to search for") @QueryParam("definitionId") Integer definitionId, @ApiParam(value = "Should only unacknowledged alerts be sent") @QueryParam("unacknowledgedOnly") @DefaultValue("false") boolean unacknowledgedOnly, @ApiParam(value = "Should not display any recovered alerts") @QueryParam("filter_recovered") @DefaultValue("false") boolean noRecovered, @ApiParam(value = "Should not display any recovery alerts") @QueryParam("filter_recoverytypes") @DefaultValue("false") boolean noRecoveryType, @ApiParam(value = "Display only alerts matching this name filter") @QueryParam("filter_name") @DefaultValue("") String name, @Context UriInfo uriInfo, @Context HttpHeaders headers) { if (resourceId!=null && definitionId!=null) { throw new BadArgumentException("At most one of 'resourceId' and 'definitionId' may be given"); } if (size==0) { throw new BadArgumentException("size","Must not be 0"); } if (page<0) { throw new BadArgumentException("page","Must be >=1"); } AlertCriteria criteria = new AlertCriteria(); if (size==-1) { PageControl pageControl = PageControl.getUnlimitedInstance(); pageControl.setPageNumber(page); criteria.setPageControl(pageControl); } else { criteria.setPaging(page, size); } if (since!=null) { criteria.addFilterStartTime(since); } if (resourceId!=null) { criteria.addFilterResourceIds(resourceId); } if (definitionId!=null) { criteria.addFilterAlertDefinitionIds(definitionId); } if (!prio.equals("All")) { AlertPriority alertPriority = AlertPriority.valueOf(prio.toUpperCase()); criteria.addFilterPriorities(alertPriority); } if(name != null && name.length() > 0) { criteria.addFilterName(name); } if (unacknowledgedOnly) { criteria.addFilterUnacknowledgedOnly(Boolean.TRUE); } criteria.addFilterRecovered(noRecovered); if(noRecoveryType) { criteria.addFilterRecoveryIds(Integer.valueOf(0)); } criteria.addSortCtime(PageOrdering.DESC); PageList<Alert> alerts = alertManager.findAlertsByCriteria(caller,criteria); List<AlertRest> ret = new ArrayList<AlertRest>(alerts.size()); for (Alert al : alerts) { AlertRest ar = alertToDomain(al, uriInfo, slim); ret.add(ar); } MediaType type = headers.getAcceptableMediaTypes().get(0); Response.ResponseBuilder builder = Response.ok(); builder.type(type); if (type.equals(MediaType.TEXT_HTML_TYPE)) { builder.entity(renderTemplate("listAlerts.ftl",ret)); } else { if (type.equals(wrappedCollectionJsonType)) { wrapForPaging(builder,uriInfo,alerts,ret); } else { GenericEntity<List<AlertRest>> entity = new GenericEntity<List<AlertRest>>(ret) {}; builder.entity(entity); createPagingHeader(builder,uriInfo,alerts); } } return builder.build(); } @GET @Path("count") @ApiOperation("Return a count of alerts in the system depending on criteria") public IntegerValue countAlerts(@ApiParam(value = "If non-null only send alerts that have fired after this time, time is millisecond since epoch") @QueryParam("since") Long since) { AlertCriteria criteria = new AlertCriteria(); criteria.setPageControl(PageControl.getUnlimitedInstance()); criteria.fetchAlertDefinition(false); criteria.fetchConditionLogs(false); criteria.fetchRecoveryAlertDefinition(false); criteria.fetchNotificationLogs(false); criteria.setRestriction(Criteria.Restriction.COUNT_ONLY); if (since!=null) { criteria.addFilterStartTime(since); } PageList<Alert> alerts = alertManager.findAlertsByCriteria(caller,criteria); int count = alerts.getTotalSize(); return new IntegerValue(count); } @GET @Cache(maxAge = 60) @Path("/{id}") @ApiOperation(value = "Get one alert with the passed id", responseClass = "AlertRest") public Response getAlert( @ApiParam("Id of the alert to retrieve") @PathParam("id") int id, @ApiParam(value = "Should full resources and definitions be sent") @QueryParam("slim") @DefaultValue("false") boolean slim, @Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders headers) { Alert al = findAlertWithId(id); MediaType type = headers.getAcceptableMediaTypes().get(0); EntityTag eTag = new EntityTag(Integer.toHexString(al.hashCode())); Response.ResponseBuilder builder = request.evaluatePreconditions(eTag); if (builder==null) { AlertRest ar = alertToDomain(al, uriInfo, slim); if (type.equals(MediaType.TEXT_HTML_TYPE)) { builder = Response.ok(renderTemplate("alert.ftl",ar),type); } else { builder = Response.ok(ar); } } builder.tag(eTag); return builder.build(); } @GET @Path("/{id}/conditions") @Cache(maxAge = 300) @ApiOperation(value = "Return the condition logs for the given alert") public Response getConditionLogs(@ApiParam("Id of the alert to retrieve") @PathParam("id") int id, @Context Request request, @Context UriInfo uriInfo, @Context HttpHeaders headers) { Alert al = findAlertWithId(id); Set<AlertConditionLog> conditions = al.getConditionLogs(); MediaType type = headers.getAcceptableMediaTypes().get(0); Response.ResponseBuilder builder; if (type.equals(MediaType.APPLICATION_XML_TYPE)) { List<StringValue> result = new ArrayList<StringValue>(conditions.size()); for (AlertConditionLog log : conditions) { AlertCondition condition = log.getCondition(); String entry = String.format("category='%s', name='%s', comparator='%s', threshold='%s', option='%s' : %s", condition.getCategory(), condition.getName(), condition.getComparator(), condition.getThreshold(), condition.getOption(), log.getValue() ); StringValue sv = new StringValue(entry); result.add(sv); } GenericEntity<List<StringValue>> entity = new GenericEntity<List<StringValue>>(result){}; builder = Response.ok(entity); } else { List<String> result = new ArrayList<String>(conditions.size()); for (AlertConditionLog log : conditions) { AlertCondition condition = log.getCondition(); String entry = String.format("category='%s', name='%s', comparator='%s', threshold='%s', option='%s' : %s", condition.getCategory(), condition.getName(), condition.getComparator(), condition.getThreshold(), condition.getOption(), log.getValue() ); result.add(entry); } if (type.equals(MediaType.TEXT_HTML_TYPE)) { builder = Response.ok(renderTemplate("genericStringList.ftl",result),type); } else { builder = Response.ok(result); } } return builder.build(); } @GET @Path("/{id}/notifications") @Cache(maxAge = 60) @ApiOperation(value = "Return the notification logs for the given alert") public Response getNotificationLogs(@ApiParam("Id of the alert to retrieve") @PathParam("id") int id, @Context Request request, @Context UriInfo uriInfo, @Context HttpHeaders headers) { Alert al = findAlertWithId(id); MediaType type = headers.getAcceptableMediaTypes().get(0); Response.ResponseBuilder builder; List<AlertNotificationLog> notifications = al.getAlertNotificationLogs(); if (type.equals(MediaType.APPLICATION_XML_TYPE)) { List<StringValue> result = new ArrayList<StringValue>(notifications.size()); for (AlertNotificationLog log : notifications) { String entry = log.getSender() + ": " + log.getResultState() + ": " + log.getMessage(); StringValue sv = new StringValue(entry); result.add(sv); } GenericEntity<List<StringValue>> entity = new GenericEntity<List<StringValue>>(result){}; builder = Response.ok(entity); } else { List<String> result = new ArrayList<String>(notifications.size()); for (AlertNotificationLog log : notifications) { String entry = log.getSender() + ": " + log.getResultState() + ": " + log.getMessage(); result.add(entry); } if (type.equals(MediaType.TEXT_HTML_TYPE)) { builder = Response.ok(renderTemplate("genericStringList.ftl",result),type); } else { builder = Response.ok(result); } } return builder.build(); } @PUT @Path("/{id}") @ApiOperation(value = "Mark the alert as acknowledged (by the caller)", notes = "Returns a slim version of the alert") public AlertRest ackAlert(@ApiParam(value = "Id of the alert to acknowledge") @PathParam("id") int id, @Context UriInfo uriInfo) { findAlertWithId(id); // Ensure the alert exists int count = alertManager.acknowledgeAlerts(caller, new int[]{id}); // TODO this is not reliable due to Tx constraints ( the above may only run after this ackAlert() method has finished ) Alert al = findAlertWithId(id); AlertRest ar = alertToDomain(al, uriInfo, true); return ar; } @DELETE @Path("/{id}") @ApiOperation(value = "Remove the alert from the list of alerts", notes = "This operation is by default idempotent, returning 204." + "If you want to check if the alert existed at all, you need to pass the 'validate' query parameter.") @ApiErrors({ @ApiError(code = 204, reason = "Alert was deleted or did not exist with validation not set"), @ApiError(code = 404, reason = "Alert did not exist and validate was set") }) public Response purgeAlert(@ApiParam(value = "Id of the alert to remove") @PathParam("id") int id, @ApiParam("Validate if the alert exists") @QueryParam("validate") @DefaultValue("false") boolean validate) { int count = alertManager.deleteAlerts(caller, new int[]{id}); if (count == 0 && validate) { throw new StuffNotFoundException("Alert with id " + id); } return Response.noContent().build(); } @GET @Cache(maxAge = 300) @Path("/{id}/definition") @ApiOperation("Get the alert definition (basics) for the alert") public AlertDefinitionRest getDefinitionForAlert(@ApiParam("Id of the alert to show the definition") @PathParam("id") int alertId, @Context UriInfo uriInfo) { Alert al = findAlertWithId(alertId); AlertDefinition def = al.getAlertDefinition(); AlertDefinitionHandlerBean adhb = new AlertDefinitionHandlerBean(); AlertDefinitionRest ret = adhb.definitionToDomain(def, false, uriInfo); // TODO allow 'full' parameter? return ret; } /** * Retrieve the alert with id id. * @param id Primary key of the alert * @return Alert domain object * @throws StuffNotFoundException if no such alert exists in the system. */ private Alert findAlertWithId(int id) { AlertCriteria criteria = new AlertCriteria(); criteria.addFilterId(id); List<Alert> alerts = alertManager.findAlertsByCriteria(caller,criteria); if (alerts.isEmpty()) { throw new StuffNotFoundException("Alert with id " + id); } return alerts.get(0); } public AlertRest alertToDomain(Alert al, UriInfo uriInfo, boolean slim) { AlertRest ret = new AlertRest(); ret.setId(al.getId()); AlertDefinition alertDefinition = al.getAlertDefinition(); ret.setName(alertDefinition.getName()); AlertDefinitionRest alertDefinitionRest; if (slim) { alertDefinitionRest = new AlertDefinitionRest(alertDefinition.getId()); } else { AlertDefinitionHandlerBean adhb = new AlertDefinitionHandlerBean(); alertDefinitionRest = adhb.definitionToDomain(alertDefinition, false, uriInfo); } ret.setAlertDefinition(alertDefinitionRest); ret.setDefinitionEnabled(alertDefinition.getEnabled()); if (al.getAcknowledgingSubject()!=null) { ret.setAckBy(al.getAcknowledgingSubject()); ret.setAckTime(al.getAcknowledgeTime()); } ret.setAlertTime(al.getCtime()); ret.setDescription(alertManager.prettyPrintAlertConditions(al,false)); ret.setRecoveryTime(al.getRecoveryTime()); Resource r = fetchResource(alertDefinition.getResource().getId()); ResourceWithType rwt; if (slim) { rwt = new ResourceWithType(r.getName(),r.getId()); } else { rwt = fillRWT(r,uriInfo); } ret.setResource(rwt); // add some links UriBuilder builder = uriInfo.getBaseUriBuilder(); builder.path("/alert/{id}/conditions"); URI uri = builder.build(al.getId()); Link link = new Link("conditions",uri.toString()); ret.addLink(link); builder = uriInfo.getBaseUriBuilder(); builder.path("/alert/{id}/notifications"); uri = builder.build(al.getId()); link = new Link("notification",uri.toString()); ret.addLink(link); builder = uriInfo.getBaseUriBuilder(); builder.path("/alert/{id}/definition"); uri = builder.build(al.getId()); link = new Link("definition",uri.toString()); ret.addLink(link); int resourceId = alertDefinition.getResource().getId(); ret.addLink(createUILink(uriInfo,UILinkTemplate.RESOURCE_ALERT,resourceId,al.getId())); return ret; } }