/* * RHQ Management Platform * Copyright (C) 2014 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.Collections; import java.util.List; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.interceptor.Interceptors; 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.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.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import org.rhq.core.domain.criteria.PluginCriteria; import org.rhq.core.domain.plugin.Plugin; import org.rhq.core.domain.util.PageList; import org.rhq.enterprise.server.resource.metadata.PluginManagerLocal; import org.rhq.enterprise.server.rest.domain.BooleanValue; import org.rhq.enterprise.server.rest.domain.PluginRest; import org.rhq.enterprise.server.rest.domain.StringValue; /** * @author Lukas Krejci * @since 4.11 */ @Path("/plugins") @Api(value="Plugin related", description = "This endpoint deals with RHQ's plugins") @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) @Interceptors(SetCallerInterceptor.class) @Stateless public class PluginHandlerBean extends AbstractRestBean { //using remote iface here seems to be causing some weird problems with //transactions.. I get HeuristicMixedException in integration tests when //declaring this as PluginManagerRemote. //That said, we only use methods also available in the remote iface so for now //we can be sure, the same things can be done by the REST API and remote API. @EJB private PluginManagerLocal pluginManager; @GET @Path("/") @ApiOperation("Looks for the plugins currently present in the system, optionally reloading them from the server's " + "filesystem") @TransactionAttribute(TransactionAttributeType.NEVER) public Response findPlugins(@QueryParam("name") String name, @ApiParam("Look for enabled, disabled or both kinds of plugins?") @QueryParam("enabled") Boolean enabled, @ApiParam("The version of the plugin to look for") @QueryParam("version") String version, @ApiParam("Whether to look for deleted, installed or both plugins") @DefaultValue("false") @QueryParam("deleted") Boolean deleted, @ApiParam("Whether to reload the plugins from the filesystem before performing the search. This can be used to " + "take into effect the plugins that have been manually put into server's plugin \"dropbox\" directory.") @QueryParam("reload") boolean reload, @ApiParam("Page size for paging") @QueryParam("ps") @DefaultValue("20") int pageSize, @ApiParam("Page for paging, 0-based") @QueryParam("page") Integer page, @Context HttpHeaders headers, @Context UriInfo uriInfo) throws Exception { PluginCriteria crit = new PluginCriteria(); if (name != null) { crit.addFilterName(name); } if (enabled != null) { crit.addFilterEnabled(enabled); } if (version != null) { crit.addFilterVersion(version); } if (deleted != null) { crit.addFilterDeleted(deleted); } if (reload) { pluginManager.update(caller); } if (page != null) { crit.setPaging(page, pageSize); } PageList<Plugin> plugins = pluginManager.findPluginsByCriteria(caller, crit); return paginate(headers, uriInfo, plugins, PluginRest.list(plugins), PluginRest.class).build(); } @GET @Path("{id}") @ApiOperation("Gets info about a single plugin") public Response getPluginInfo( @ApiParam("The id of the plugin") @PathParam("id") int id, @Context HttpHeaders headers) { PluginCriteria crit = new PluginCriteria(); crit.addFilterId(id); PageList<Plugin> plugins = pluginManager.findPluginsByCriteria(caller, crit); if (plugins.isEmpty()) { throw new StuffNotFoundException("Plugin"); } return withMediaType(Response.ok(PluginRest.from(plugins.get(0))), headers).build(); } @PUT @Path("{id}") @ApiOperation("Updates the enablement of a plugin.") public Response updatePluginState( @ApiParam("The id of the plugin") @PathParam("id") int id, @ApiParam("true means plugin is enabled, false means plugin is disabled") @QueryParam("enabled") boolean enabled, @Context HttpHeaders headers) throws Exception { if (enabled) { pluginManager.enablePlugins(caller, Collections.singletonList(id)); } else { pluginManager.disablePlugins(caller, Collections.singletonList(id)); } return getPluginInfo(id, headers); } @DELETE @Path("{id}") @ApiOperation("Deletes a single plugin, optionally purging it.") @TransactionAttribute(TransactionAttributeType.NEVER) public Response deletePlugin( @ApiParam("The id of the plugin") @PathParam("id") int id, @Context HttpHeaders headers) throws Exception { pluginManager.deletePlugins(caller, Collections.singletonList(id)); return getPluginInfo(id, headers); } @POST @Path("/") @ApiOperation("Puts the plugin provided using a content handle into a dropbox and scans the dropbox for changes. " + "In another words, this can result in more than just the provided plugin to become registered in the server " + "if there were some unregistered plugins waiting in the dropbox directory. The content identified by the handle " + "is NOT deleted afterwards.") @TransactionAttribute(TransactionAttributeType.NEVER) public Response register( @ApiParam("The handle retrieved from upload") @QueryParam("handle") String handle, @ApiParam("Name of the plugin file") @QueryParam("name") String name, @Context HttpHeaders headers, @Context UriInfo uriInfo) throws Exception { List<Plugin> newOnes = pluginManager.deployUsingContentHandle(caller, name, handle); Plugin myPlugin = null; for (Plugin p : newOnes) { if (p.getPath().equals(name)) { myPlugin = p; break; } } if (myPlugin == null) { //this may happen and is actually not an error... it just means that the plugin most possibly already //was installed.. return withMediaType(Response.ok(PluginRest.list(newOnes)), headers).build(); } else { URI myPluginUri = uriInfo.getBaseUri().resolve(uriInfo.getPath()) .resolve(Integer.toString(myPlugin.getId())); return withMediaType(Response.created(myPluginUri), headers).entity(PluginRest.list(newOnes)).build(); } } @POST @Path("/deploy") @ApiOperation("Pushes out all the enabled plugins to all the agents running at that point in time. Defaults to " + "start that process immediately. The returned string is a handle that can be used to check whether all the " + "agents received the updated plugins.") public Response deployOnAgents( @ApiParam("The delay in milliseconds before triggering the update on the agents") @QueryParam("delay") @DefaultValue("0") long delay, @Context HttpHeaders headers) throws Exception { StringValue handle = new StringValue(pluginManager.schedulePluginUpdateOnAgents(caller, delay)); return withMediaType(Response.ok(handle), headers).build(); } @GET @Path("/deploy/{handle}") @ApiOperation("Checks whether the deployment to the agents identified by the provided handle has finished or not.") public Response isUpdateFinished(@ApiParam("The deploy handle") @PathParam("handle") String handle, @Context HttpHeaders headers) { BooleanValue ret = new BooleanValue(pluginManager.isPluginUpdateOnAgentsFinished(caller, handle)); return withMediaType(Response.ok(ret), headers).build(); } }