/*
* 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();
}
}