/*
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
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.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
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.rhq.core.domain.criteria.EventCriteria;
import org.rhq.core.domain.event.Event;
import org.rhq.core.domain.event.EventDefinition;
import org.rhq.core.domain.event.EventSeverity;
import org.rhq.core.domain.event.EventSource;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
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.event.EventManagerLocal;
import org.rhq.enterprise.server.rest.domain.EventDefinitionRest;
import org.rhq.enterprise.server.rest.domain.EventRest;
import org.rhq.enterprise.server.rest.domain.EventSourceRest;
/**
* Handle event related things
* @author Heiko W. Rupp
*/
@Path("/event")
@Api("Api that deals with Events (e.g snmp traps, logfile lines)")
@Stateless
@Interceptors(SetCallerInterceptor.class)
public class EventHandlerBean extends AbstractRestBean {
@EJB
EventManagerLocal eventManager;
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
EntityManager em;
@GET
@Path("/{id}/sources")
@ApiOperation(value = "List the defined event sources for the resource", responseClass = "EventSourceRest", multiValueResponse = true)
public Response listEventSourcesForResource(@ApiParam("id of the resource") @PathParam("id") int resourceId,
@Context HttpHeaders headers) {
Resource res = fetchResource(resourceId);
Set<EventSource> eventSources = res.getEventSources();
List<EventSourceRest> restSources = new ArrayList<EventSourceRest>(eventSources.size());
for (EventSource source : eventSources) {
EventSourceRest esr = convertEventSource(source);
restSources.add(esr);
}
Response.ResponseBuilder builder;
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
if (mediaType.equals(MediaType.APPLICATION_XML_TYPE)) {
GenericEntity<List<EventSourceRest>> list = new GenericEntity<List<EventSourceRest>>(restSources) {};
builder = Response.ok(list, mediaType);
}
else {
builder = Response.ok(restSources, mediaType);
}
return builder.build();
}
@GET
@Path("/{id}/definitions")
@ApiOperation(value = "List the defined event source definitions for the resource", responseClass = "EventDefintionRest", multiValueResponse = true)
public Response listEventDefinitionsForResource(@ApiParam("id of the resource") @PathParam("id") int resourceId,
@Context HttpHeaders headers) {
Resource res = fetchResource(resourceId);
ResourceType resourceType = res.getResourceType();
em.refresh(resourceType);
Set<EventDefinition> eventDefinitions = resourceType.getEventDefinitions();
List<EventDefinitionRest> definitionsRest = new ArrayList<EventDefinitionRest>(eventDefinitions.size());
for (EventDefinition source : eventDefinitions) {
EventDefinitionRest esr = new EventDefinitionRest();
esr.setDescription(source.getDescription());
esr.setId(source.getId());
esr.setDisplayName(source.getDisplayName());
esr.setName(source.getName());
definitionsRest.add(esr);
}
Response.ResponseBuilder builder;
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
if (mediaType.equals(MediaType.APPLICATION_XML_TYPE)) {
GenericEntity<List<EventDefinitionRest>> list = new GenericEntity<List<EventDefinitionRest>>(definitionsRest) {};
builder = Response.ok(list, mediaType);
}
else {
builder = Response.ok(definitionsRest, mediaType);
}
return builder.build();
}
@GET
@Path("/source/{id}")
@ApiOperation(value = "Retrieve the event source with the passed id", responseClass = "EventSourceRest")
@ApiError(code = 404, reason = "There is no event source with the passed id")
public EventSourceRest getEventSource(@ApiParam("Id of the source to retrieve") @PathParam("id") int sourceId) {
EventSource source = findEventSourceById(sourceId);
EventSourceRest esr = convertEventSource(source);
return esr;
}
@POST
@Path("/{id}/sources")
@ApiOperation("Add a new event source for a resource. This can e.g. be a different logfile. " +
"The source.name must match an existing definition fo this resource. " +
"If an event source for the definition name and resource with the same location already exists, no new source is created. " +
"NOTE: An Event source added this way will not show up in the connection properties.")
@ApiErrors({
@ApiError(code = 404, reason = "Resource with the passed id does not exist"),
@ApiError(code = 404, reason = "Event definition with the passed name not found"),
@ApiError(code = 406, reason = "Tried to create an event source on the same definition with the same location")
})
public EventSourceRest addEventSource(@ApiParam("id of the resource") @PathParam("id") int resourceId,
EventSourceRest esr) {
Resource resource = fetchResource(resourceId);
ResourceType rt = resource.getResourceType();
Set<EventDefinition> eventDefinitions = rt.getEventDefinitions();
EventDefinition eventDefinition=null;
for (EventDefinition ed : eventDefinitions) {
if (ed.getName().equals(esr.getName())) {
eventDefinition = ed;
break;
}
}
if (eventDefinition==null) {
throw new StuffNotFoundException("eventDefinition with name " + esr.getName());
}
// check if a source with the given location already exists for the definition and resource
Query q = em.createQuery("SELECT es FROM EventSource es WHERE es.location = :location AND es.eventDefinition = :definition AND es.resourceId = :resourceId");
q.setParameter("location",esr.getLocation());
q.setParameter("definition",eventDefinition);
q.setParameter("resourceId",resourceId);
List<EventSource> sources = q.getResultList();
EventSource source;
if (sources.isEmpty()) {
source = new EventSource(esr.getLocation(),eventDefinition,resource);
em.persist(source);
} else if (sources.size()==1) {
source = sources.get(0);
} else {
throw new IllegalStateException("We have more than one EventSource on the same Definition with the same location - must not happen");
}
EventSourceRest result = convertEventSource(source);
return result;
}
@DELETE
@Path("/source/{id}")
@ApiOperation(value = "Delete the event source with the passed id", notes = "This operation is by default idempotent, returning 204." +
"If you want to check if the source existed at all, you need to pass the 'validate' query parameter.")
@ApiErrors({
@ApiError(code = 204, reason = "Source was deleted or did not exist with validation not set"),
@ApiError(code = 404, reason = "Source did not exist and validate was set")
})
public Response deleteEventSource(@ApiParam("Id of the source to delete") @PathParam("id") int sourceId,
@ApiParam("Validate if the content exists") @QueryParam("validate") @DefaultValue("false") boolean validate) {
EventSource source = em.find(EventSource.class,sourceId);
if (source!=null) {
em.remove(source); // We have a cascade delete on the events TODO make operation async ?
}
else {
if (validate) {
throw new StuffNotFoundException("Event source with id " + sourceId);
}
}
return Response.noContent().build();
}
@GET @GZIP
@Path("/source/{id}/events")
@ApiOperation(value = "List the events for the event source with the passed id. If no time range is given, the last 200 entries will be displayed",
responseClass = "EventRest", multiValueResponse = true)
public Response getEventsForSource(@PathParam("id") int sourceId,
@QueryParam("startTime") long startTime,
@QueryParam("endTime") long endTime,
@ApiParam(value="Select the severity to display. Default is to show all",
allowableValues = "DEBUG, INFO, WARN, ERROR, FATAL") @QueryParam("severity") String severity,
@ApiParam("Page size for paging") @QueryParam("ps") @DefaultValue("20") int pageSize,
@ApiParam("Page for paging, 0-based") @QueryParam("page") Integer page,
@Context UriInfo uriInfo,
@Context HttpHeaders headers) {
if (severity!=null) {
try {
EventSeverity.valueOf(severity.toUpperCase());
} catch (Exception e) {
throw new BadArgumentException("severity",severity + " is bad. Allowed values are DEBUG, INFO, WARN, ERROR, FATAL");
}
}
EventSource source = findEventSourceById(sourceId);
EventCriteria criteria = new EventCriteria();
criteria.addSortId(PageOrdering.ASC);
criteria.addFilterSourceId(source.getId());
if (startTime>0) {
criteria.addFilterStartTime(startTime);
}
if (endTime>0) {
criteria.addFilterEndTime(endTime);
}
if (page!=null) {
criteria.setPaging(page,pageSize);
}
else if (startTime==0 && endTime==0) {
PageControl pageControl = new PageControl();
pageControl.setPageSize(200);
criteria.setPageControl(pageControl);
}
if (severity!=null) {
criteria.addFilterSeverities(EventSeverity.valueOf(severity.toUpperCase()));
}
Response.ResponseBuilder builder = getEventsAsBuilderForCriteria(headers, criteria, uriInfo);
return builder.build();
}
@GET @GZIP
@Path("/{id}/events")
@ApiOperation(value="List the events for the resource with the passed id. If no time range is given, the last 200 entries will be displayed",
responseClass = "EventRest", multiValueResponse = true)
public Response getEventsForResource(@PathParam("id") int resourceId,
@QueryParam("startTime") long startTime,
@QueryParam("endTime") long endTime,
@ApiParam("Page size for paging") @QueryParam("ps") @DefaultValue("20") int pageSize,
@ApiParam("Page for paging, 0-based") @QueryParam("page") Integer page,
@ApiParam(value="Select the severity to display. Default is to show all",
allowableValues = "DEBUG, INFO, WARN, ERROR, FATAL") @QueryParam("severity") String severity,
@Context UriInfo uriInfo,
@Context HttpHeaders headers) {
if (severity!=null) {
try {
EventSeverity.valueOf(severity.toUpperCase());
} catch (Exception e) {
throw new BadArgumentException("severity",severity + " is bad. Allowed values are DEBUG, INFO, WARN, ERROR, FATAL");
}
}
EventCriteria criteria = new EventCriteria();
criteria.addSortId(PageOrdering.ASC);
criteria.addFilterResourceId(resourceId);
if (startTime>0) {
criteria.addFilterStartTime(startTime);
}
if (endTime>0) {
criteria.addFilterEndTime(endTime);
}
if (page!=null) {
criteria.setPaging(page,pageSize);
}
else if (startTime==0 && endTime==0) {
PageControl pageControl = new PageControl();
pageControl.setPageSize(200);
criteria.setPageControl(pageControl);
}
if (severity!=null) {
criteria.addFilterSeverities(EventSeverity.valueOf(severity.toUpperCase()));
}
Response.ResponseBuilder builder = getEventsAsBuilderForCriteria(headers, criteria, uriInfo);
return builder.build();
}
@POST
@Path("/source/{id}/events")
@ApiOperation("Submit multiple events for one given event source; the event source in the passed Events is ignored. "+
"Make sure your events are ordered by timestamp to get alerts fired correctly.")
public Response addEventsToSource(@ApiParam("Id of the source to add data to") @PathParam("id") int sourceId,
List<EventRest> eventRest) {
EventSource source = findEventSourceById(sourceId);
Map<EventSource,Set<Event>> eventMap = new HashMap<EventSource, Set<Event>>();
Set<Event> events = new LinkedHashSet<Event>(eventRest.size());
for (EventRest eRest : eventRest) {
EventSeverity eventSeverity = EventSeverity.valueOf(eRest.getSeverity());
Event event = new Event(eRest.getTimestamp(),eventSeverity,source,eRest.getDetail());
events.add(event);
}
eventMap.put(source,events);
eventManager.addEventData(eventMap);
return Response.noContent().build();
}
private Response.ResponseBuilder getEventsAsBuilderForCriteria(HttpHeaders headers, EventCriteria criteria, UriInfo uriInfo) {
PageList<Event> eventList = eventManager.findEventsByCriteria(caller, criteria);
List<EventRest> restEvents = new ArrayList<EventRest>(eventList.size());
for (Event event : eventList) {
restEvents.add(convertEvent(event));
}
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder = Response.ok();
builder.type(mediaType);
if (mediaType.equals(MediaType.APPLICATION_XML_TYPE)) {
GenericEntity<List<EventRest>> list = new GenericEntity<List<EventRest>>(restEvents) {};
builder.entity(list);
createPagingHeader(builder,uriInfo,eventList);
} else if (mediaType.equals(MediaType.APPLICATION_JSON_TYPE)) {
builder.entity(restEvents);
createPagingHeader(builder,uriInfo,eventList);
}
else {
wrapForPaging(builder,uriInfo,eventList,restEvents);
}
return builder;
}
private EventSourceRest convertEventSource(EventSource source) {
EventSourceRest esr = new EventSourceRest();
esr.setId(source.getId());
esr.setDescription(source.getEventDefinition().getDescription());
esr.setDisplayName(source.getEventDefinition().getDisplayName());
esr.setName(source.getEventDefinition().getName());
esr.setLocation(source.getLocation());
esr.setResourceId(source.getResourceId());
return esr;
}
private EventRest convertEvent(Event event) {
EventRest er = new EventRest();
er.setDetail(event.getDetail());
er.setId(event.getId());
er.setSeverity(event.getSeverity().toString());
er.setTimestamp(event.getTimestamp());
er.setSourceId(event.getSource().getId());
return er;
}
private EventSource findEventSourceById(int sourceId) {
EventSource source = em.find(EventSource.class,sourceId);
if (source==null)
throw new StuffNotFoundException("Event source with id " + sourceId);
return source;
}
}