/*
* RHQ Management Platform
* Copyright (C) 2005-2013 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.enterprise.server.rest;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.ws.rs.Consumes;
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.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.rhq.core.domain.authz.Permission;
import org.rhq.core.util.stream.StreamUtil;
import org.rhq.enterprise.server.authz.PermissionException;
import org.rhq.enterprise.server.core.plugin.PluginDeploymentScannerMBean;
import org.rhq.enterprise.server.rest.domain.IntegerValue;
import org.rhq.enterprise.server.rest.domain.StringValue;
import org.rhq.enterprise.server.util.LookupUtil;
/**
* Deal with content
* @author Heiko W. Rupp
*/
@Path("/content")
@Api(value="Content related", description = "This endpoint deals with content (upload)")
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
@Interceptors(SetCallerInterceptor.class)
@Stateless
public class ContentHandlerBean extends AbstractRestBean {
private static final String TMP_FILE_PREFIX = "rhq-rest-";
private static final String TMP_FILE_SUFFIX = ".bin";
@POST
@Path("/fresh")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
@ApiOperation("Upload content to the server. This will return a handle that can be used later to retrieve and further process the content")
public Response uploadContent(
InputStream contentStream,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) throws IOException
{
String tmpDirName = System.getProperty("java.io.tmpdir");
File tmpDir = new File(tmpDirName);
File outFile = File.createTempFile(TMP_FILE_PREFIX, TMP_FILE_SUFFIX,tmpDir);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile));
StreamUtil.copy(contentStream, bos, true);
String fileHandle = outFile.getName();
StringValue sv = new StringValue(fileHandle);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/content/{handle}");
URI uri = uriBuilder.build(fileHandle);
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder = Response.created(uri);
builder.entity(sv);
builder.type(mediaType);
return builder.build();
}
@GET
@Path("/{handle}/info")
@ApiOperation("Retrieve the length of the content with the passed handle")
public IntegerValue getInfo(
@PathParam("handle") String handle
)
{
File content = getFileForHandle(handle);
if (!content.exists() || !content.canRead())
throw new StuffNotFoundException("Content with handle " + handle);
long len = content.length();
IntegerValue iv = new IntegerValue((int)len); // TODO
return iv;
}
@PUT
@Path("/{handle}/plugins")
@ApiErrors({
@ApiError(code = 406, reason = "No name provided or invalid combination of parameters supplied"),
@ApiError(code = 404, reason = "No content for handle found"),
@ApiError(code = 403, reason = "Caller has not rights to upload plugins")
})
@ApiOperation(value="Put the uploaded content into the plugin drop box. ",
notes = "This endpoint allows to deploy previously uploaded content as a plugin. You need to provide" +
"a valid plugin (file) name in order for the plugin processing to succeed. Optionally you can" +
"request that a plugin scan will be started and the plugin be registered in the system. You can also" +
"specify a delay in milliseconds after which the plugin will be automatically pushed out to the agents." +
"Note that a non-negative \"pushOutDelay\" only makes sense when the \"scan\" is set to true, otherwise" +
"no update on the agents can occur because there will be no updated plugins on the server. If a " +
"non-negative \"pushOutDelay\" is given together with \"scan\" set to false a 406 error is returned." +
"The content identified by the handle is not removed. Note that this method is deprecated - use a PUT to /plugins.")
@Deprecated
public Response provideAsPlugin(
@ApiParam("Name of the handle retrieved from upload") @PathParam("handle") String handle,
@ApiParam("Name of the plugin file") @QueryParam("name") String name,
@ApiParam("Should a discovery scan be started?") @QueryParam("scan") @DefaultValue("false") boolean startScan,
@ApiParam(value = "The delay in millis before the agents update their plugins. Any negative value disables " +
"the automatic update of agents", defaultValue = "-1")
@QueryParam("pushOutDelay") @DefaultValue("-1") long pushOutDelay,
@Context HttpHeaders headers
) {
if (name==null || name.isEmpty()) {
throw new BadArgumentException("A valid 'name' must be given");
}
File content = getFileForHandle(handle);
if (!content.exists() || !content.canRead()) {
throw new StuffNotFoundException("Content with handle " + handle);
}
Response.ResponseBuilder builder;
try {
boolean isAllowed = LookupUtil.getAuthorizationManager().hasGlobalPermission(caller,
Permission.MANAGE_SETTINGS);
if (!isAllowed) {
log.error("An unauthorized user [" + caller + "] attempted to upload a plugin");
throw new PermissionException("You are not authorized to do this");
}
//sanity checks before we do any real work
if (pushOutDelay >= 0 && !startScan) {
throw new BadArgumentException(
"Pushing changes to agents without starting a scan for changes first doesn't make sense.");
}
File dir = LookupUtil.getPluginManager().getPluginDropboxDirectory();
File targetFile = new File(dir,name);
FileOutputStream fos = new FileOutputStream(targetFile);
try {
FileInputStream fis = new FileInputStream(content);
try {
StreamUtil.copy(fis, fos);
} finally {
fis.close();
}
} finally {
fos.close();
}
if (startScan) {
PluginDeploymentScannerMBean scanner = LookupUtil.getPluginDeploymentScanner();
scanner.scanAndRegister();
}
if (pushOutDelay >= 0) {
LookupUtil.getPluginManager().schedulePluginUpdateOnAgents(caller, pushOutDelay);
}
builder=Response.ok();
}
catch (Exception e) {
builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR);
builder.entity(e.getMessage());
}
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
builder.type(mediaType);
return builder.build();
}
@DELETE
@Path("/{handle}")
@ApiOperation(value = "Remove the content with the passed handle", notes = "This operation is by default idempotent, returning 204." +
"If you want to check if the content existed at all, you need to pass the 'validate' query parameter.")
@ApiErrors({
@ApiError(code = 204, reason = "Content was deleted or did not exist with validation not set"),
@ApiError(code = 404, reason = "Content did not exist and validate was set")
})
public Response removeUploadedContent(
@PathParam("handle") String handle,
@ApiParam("Validate if the content exists") @QueryParam("validate") @DefaultValue("false") boolean validate) {
File content = getFileForHandle(handle);
Response.ResponseBuilder builder;
if (!content.exists()) {
if (validate) {
throw new StuffNotFoundException("Content with handle " + handle);
}
builder = Response.noContent();
}
else {
boolean deleted = content.delete();
if (deleted)
builder = Response.noContent();
else {
builder = Response.serverError();
System.err.println("Deletion of " + content.getAbsolutePath() + " failed");
}
}
return builder.build();
}
private File getFileForHandle(String handle) {
String tmpDirName = System.getProperty("java.io.tmpdir");
File tmpDir = new File(tmpDirName);
return new File(tmpDir,handle);
}
}