/*
* The MIT License
*
* Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.sonyericsson.hudson.plugins.metadata.cli;
import com.sonyericsson.hudson.plugins.metadata.model.JsonUtils;
import com.sonyericsson.hudson.plugins.metadata.model.MetadataContainer;
import com.sonyericsson.hudson.plugins.metadata.model.PluginImpl;
import com.sonyericsson.hudson.plugins.metadata.model.values.MetadataValue;
import com.sonyericsson.hudson.plugins.metadata.model.values.ParentUtil;
import hudson.Extension;
import hudson.model.Queue;
import hudson.model.RootAction;
import net.sf.json.JSON;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.Type;
import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.sendError;
import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.sendOk;
import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.createResponse;
import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.CONTENT_TYPE;
import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.sendResponse;
import static java.net.HttpURLConnection.HTTP_OK;
/**
* Http interface for the CLI commands.
* <p/>
* As some systems prefer to have a bit more intimate call API towards other systems than what
* {@link hudson.cli.CLICommand}s provide.
* This action exposes {@link GetMetadataCommand} and {@link UpdateMetadataCommand} to
* a standard HTTP post or GET.
*
* @author Robert Sandell <robert.sandell@sonyericsson.com>
*/
@Extension
@SuppressWarnings("unused")
public class HttpCliRootAction implements RootAction {
/**
* The URL to this action.
*/
protected static final String URL = "metadata-httpcli";
@Override
public String getIconFileName() {
//Should not be displayed on the main page.
return null;
}
@Override
public String getDisplayName() {
//Should not be displayed on the main page.
return null;
}
@Override
public String getUrlName() {
return URL;
}
/**
* Update the metadata in a container. Valid http parameters are : data and (node or job or (job and build)). Ex:
* <code> http://jenkinshost/metadata-httpcli/update?node=bobby&data={metadata-type: "metadata-string" name="owner"
* value="bobby"} </code> would update the metadata in on the node named "bobby" with a metadata string with the
* name owner and the value "bobby".
*
* @param request the request.
* @param response the response
* @throws Exception if something unknown happened.
* @see UpdateMetadataCommand
*/
@SuppressWarnings("unused")
public void doUpdate(StaplerRequest request, StaplerResponse response) throws Exception {
ContainerParams params = new ContainerParams(request, response).invoke();
if (!params.isUsable()) {
return;
}
MetadataContainer<MetadataValue> container;
String dataDocument = request.getParameter("data");
if (dataDocument == null || dataDocument.isEmpty()) {
sendError(CliUtils.Status.ERR_BAD_CMD, "No metadata provided!", response);
return;
}
try {
container = CliUtils.getContainer(params.getNode(), params.getJob(), params.getBuild(), true);
} catch (CmdLineException e) {
sendError(CliUtils.Status.ERR_BAD_CMD, e.getMessage(), response);
return;
} catch (CliUtils.NoItemException e) {
sendError(CliUtils.Status.ERR_NO_ITEM, e.getMessage(), response);
return;
} catch (CliUtils.NoMetadataException e) {
sendError(CliUtils.Status.ERR_NO_METADATA, e.getMessage(), response);
return;
}
if (container != null) {
container.getACL().checkPermission(PluginImpl.UPDATE_METADATA);
if (params.isReplace()) {
container.getACL().checkPermission(PluginImpl.REPLACE_METADATA);
}
JSON json = JSONSerializer.toJSON(dataDocument);
try {
List<MetadataValue> values = JsonUtils.toValues(json, container);
if (params.isReplace()) {
ParentUtil.replaceChildren(container, values);
sendOk(response);
} else {
Collection<MetadataValue> leftOvers = container.addChildren(values);
if (leftOvers != null && !leftOvers.isEmpty()) {
JSONObject jsonMessage = createResponse(Type.warning, 0, "Warning",
"The following data could not be replaced because"
+ "it already existed. Use the replace parameter to try and force it in.");
jsonMessage.put("leftOvers", JsonUtils.toJson(leftOvers));
sendResponse(response, jsonMessage, HTTP_OK);
} else {
sendOk(response);
}
}
container.save();
Queue.getInstance().scheduleMaintenance();
} catch (JsonUtils.ParseException e) {
sendError(CliUtils.Status.ERR_BAD_DATA, e.getMessage(), response);
} catch (IOException ex) {
sendError(CliUtils.Status.WARN_NO_SAVE,
"Warning Could not save the data to disk, the data is added in memory.\n"
+ ex.getMessage(), response);
}
} else {
sendError(CliUtils.Status.ERR_BAD_CMD, "No metadata container found.", response);
}
}
/**
* Get the metadata in a container. Valid http parameters are : node or job or (job and build). Ex:
* <code>http://jenkinshost/metadata-httpcli/get?node=bobby</code> would give you the metadata in JSON format for
* the node named "bobby"
*
* @param request the request.
* @param response the response
* @throws Exception if something unknown happened.
* @see GetMetadataCommand
*/
@SuppressWarnings("unused")
public void doGet(StaplerRequest request, StaplerResponse response) throws Exception {
ContainerParams params = new ContainerParams(request, response).invoke();
if (!params.isUsable()) {
return;
}
MetadataContainer<MetadataValue> container;
try {
container = CliUtils.getContainer(params.getNode(), params.getJob(), params.getBuild(), false);
} catch (CmdLineException e) {
sendError(CliUtils.Status.ERR_BAD_CMD, e.getMessage(), response);
return;
} catch (CliUtils.NoItemException e) {
sendError(CliUtils.Status.ERR_NO_ITEM, e.getMessage(), response);
return;
} catch (CliUtils.NoMetadataException e) {
sendError(CliUtils.Status.ERR_NO_METADATA, e.getMessage(), response);
return;
}
if (container != null) {
container.getACL().checkPermission(PluginImpl.READ_METADATA);
JSON json = container.toJson();
response.setContentType(CONTENT_TYPE);
response.getOutputStream().print(json.toString());
} else {
sendError(CliUtils.Status.ERR_BAD_CMD, "No metadata container found.", response);
}
}
/**
* Helper class for the common HTTP parameters. Actually nicely auto generated by the IDE.
*/
static class ContainerParams {
private boolean myResult;
private StaplerRequest request;
private StaplerResponse response;
private String node;
private String job;
private Integer build;
private boolean replace;
/**
* Standard constructor.
*
* @param request the request
* @param response the response for sending bad param results.
*/
public ContainerParams(StaplerRequest request, StaplerResponse response) {
this.request = request;
this.response = response;
}
/**
* Are there some usable parameters?
*
* @return true if so.
*/
boolean isUsable() {
return myResult;
}
/**
* The node param.
*
* @return the node.
*/
public String getNode() {
return node;
}
/**
* The Job param.
*
* @return the job.
*/
public String getJob() {
return job;
}
/**
* The build param.
*
* @return the build.
*/
public Integer getBuild() {
return build;
}
/**
* The replace param.
*
* @return true if replace.
*/
public boolean isReplace() {
return replace;
}
/**
* Get the parameters.
*
* @return itself.
*
* @throws IOException if so.
*/
public ContainerParams invoke() throws IOException {
node = request.getParameter("node");
job = request.getParameter("job");
build = null;
String buildStr = request.getParameter("build");
if (buildStr != null) {
try {
build = Integer.parseInt(buildStr);
} catch (NumberFormatException e) {
sendError(CliUtils.Status.ERR_BAD_CMD, "Not a number: " + buildStr, response);
myResult = false;
return this;
}
}
String replaceStr = request.getParameter("replace");
if ("on".equalsIgnoreCase(replaceStr)
|| "yes".equalsIgnoreCase(replaceStr)
|| "true".equalsIgnoreCase(replaceStr)) {
replace = true;
}
myResult = true;
return this;
}
}
}