/*******************************************************************************
* Copyright (c) 2013 IBM Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
*
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
*
* Samuel Padgett - initial implementation
*******************************************************************************/
package org.eclipse.lyo.server.hudson.auto;
import hudson.Extension;
import hudson.model.ParameterValue;
import hudson.model.Result;
import hudson.model.RootAction;
import hudson.model.SimpleParameterDefinition;
import hudson.model.AbstractProject;
import hudson.model.BooleanParameterDefinition;
import hudson.model.BooleanParameterValue;
import hudson.model.Cause;
import hudson.model.ChoiceParameterDefinition;
import hudson.model.Hudson;
import hudson.model.Job;
import hudson.model.ParameterDefinition;
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.PasswordParameterDefinition;
import hudson.model.Run;
import hudson.model.StringParameterDefinition;
import hudson.model.StringParameterValue;
import hudson.security.csrf.CrumbIssuer;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import org.eclipse.lyo.core.query.ComparisonTerm;
import org.eclipse.lyo.core.query.InTerm;
import org.eclipse.lyo.core.query.PName;
import org.eclipse.lyo.core.query.ParseException;
import org.eclipse.lyo.core.query.QueryUtils;
import org.eclipse.lyo.core.query.SimpleTerm;
import org.eclipse.lyo.core.query.UriRefValue;
import org.eclipse.lyo.core.query.Value;
import org.eclipse.lyo.core.query.WhereClause;
import org.eclipse.lyo.core.utils.marshallers.MarshallerConstants;
import org.eclipse.lyo.core.utils.marshallers.OSLC4JContext;
import org.eclipse.lyo.core.utils.marshallers.OSLC4JMarshaller;
import org.eclipse.lyo.core.utils.marshallers.OSLC4JUnmarshaller;
import org.eclipse.lyo.oslc4j.automation.AutomationConstants;
import org.eclipse.lyo.oslc4j.automation.AutomationPlan;
import org.eclipse.lyo.oslc4j.automation.AutomationRequest;
import org.eclipse.lyo.oslc4j.automation.AutomationResult;
import org.eclipse.lyo.oslc4j.automation.ParameterInstance;
import org.eclipse.lyo.oslc4j.core.model.Compact;
import org.eclipse.lyo.oslc4j.core.model.CreationFactory;
import org.eclipse.lyo.oslc4j.core.model.Dialog;
import org.eclipse.lyo.oslc4j.core.model.Link;
import org.eclipse.lyo.oslc4j.core.model.Occurs;
import org.eclipse.lyo.oslc4j.core.model.OslcConstants;
import org.eclipse.lyo.oslc4j.core.model.OslcMediaType;
import org.eclipse.lyo.oslc4j.core.model.PrefixDefinition;
import org.eclipse.lyo.oslc4j.core.model.Preview;
import org.eclipse.lyo.oslc4j.core.model.Property;
import org.eclipse.lyo.oslc4j.core.model.Publisher;
import org.eclipse.lyo.oslc4j.core.model.QueryCapability;
import org.eclipse.lyo.oslc4j.core.model.Service;
import org.eclipse.lyo.oslc4j.core.model.ServiceProvider;
import org.eclipse.lyo.oslc4j.utils.AcceptUtil;
import org.eclipse.lyo.server.hudson.auto.resource.HudsonAutoConstants;
import org.eclipse.lyo.server.hudson.auto.resource.QueryResponse;
import org.eclipse.lyo.server.hudson.auto.resource.ResponseInfo;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.framework.io.WriterOutputStream;
import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
/**
* Hudson and Jenkins OSLC Automation Provider.
*
* @author Samuel Padgett <spadgett@us.ibm.com>
*/
@Extension
public class OslcAutomationProvider implements RootAction {
/*
* Apache Wink doesn't work with a Hudson or Jenkins environment without
* this property. This static block must come before we instantiate a
* MediaType (or do anything with Wink).
*/
static {
System.setProperty("javax.ws.rs.ext.RuntimeDelegate",
"org.apache.wink.common.internal.runtime.RuntimeDelegateImpl");
}
/**
* Acceptable media types for our automation provider. This array may only
* contain types supported by {@link OSLC4JMarshaller}, except for
* {@code text/html} and {@code application/x-oslc-compact+xml},
* which we handle specially.
*/
public static final MediaType[] ACCEPTABLE = {
MediaType.TEXT_HTML_TYPE,
OslcMediaType.APPLICATION_RDF_XML_TYPE,
OslcMediaType.TEXT_TURTLE_TYPE,
OslcMediaType.APPLICATION_XML_TYPE,
OslcMediaType.APPLICATION_JSON_TYPE,
OslcMediaType.APPLICATION_X_OSLC_COMPACT_XML_TYPE
};
/**
* All prefixes and namespaces used by this provider.
*/
private static final Map<String, String> PREFIX_MAP;
static {
Map<String, String> prefixes = new HashMap<String, String>();
prefixes.put(OslcConstants.DCTERMS_NAMESPACE_PREFIX, OslcConstants.DCTERMS_NAMESPACE);
prefixes.put(OslcConstants.OSLC_CORE_NAMESPACE_PREFIX, OslcConstants.OSLC_CORE_NAMESPACE);
prefixes.put(OslcConstants.RDF_NAMESPACE_PREFIX, OslcConstants.RDF_NAMESPACE);
prefixes.put(OslcConstants.RDFS_NAMESPACE_PREFIX, OslcConstants.RDFS_NAMESPACE);
prefixes.put(AutomationConstants.FOAF_NAMESPACE_PREFIX, AutomationConstants.FOAF_NAMESPACE);
prefixes.put(AutomationConstants.AUTOMATION_PREFIX, AutomationConstants.AUTOMATION_NAMESPACE);
prefixes.put(HudsonAutoConstants.PREFIX, HudsonAutoConstants.NAMESPACE);
PREFIX_MAP = Collections.unmodifiableMap(prefixes);
}
private static final Logger LOG = Logger.getLogger(OslcAutomationProvider.class.getName());
/*
* You will need to rename the doXXX() methods below if you change these
* constants. Also update any JavaScript, which can't use these constants,
* and JUnit tests.
*/
private static final String PATH_AUTO = "auto";
private static final String PATH_JOB = "job";
private static final String PATH_RUN = "run";
private static final String PATH_REQUEST = "request";
private static final String PATH_PREVIEW = "preview";
@Override
public String getDisplayName() {
return "OSLC Automation Provider";
}
/*
* /plugin/hudson-oslc-auto -> src/main/webapp
*/
@Override
public String getIconFileName() {
return "/plugin/hudson-oslc-auto/images/oslc.png";
}
@Override
public String getUrlName() {
return PATH_AUTO;
}
/*
* You can test the plugin is running by going to /auto/hello
*/
public String doHello() {
return "Hello from the OSLC Automation plugin!";
}
/*
* Path: /auto/provider
*
* Generate the service provider description document.
*/
public void doProvider(StaplerRequest request, StaplerResponse response)
throws URISyntaxException, WebApplicationException, IOException {
requireGET();
ServiceProvider provider = new ServiceProvider();
provider.setAbout(getProviderURI());
provider.setTitle("OSLC Automation Provider for Hudson and Jenkins");
provider.setPublisher(new Publisher("Eclipse Lyo", "urn:oslc:ServiceProvider"));
final PrefixDefinition[] prefixDefinitions = getPrefixDefinitions();
provider.setPrefixDefinitions(prefixDefinitions);
Service service = new Service();
provider.addService(service);
service.setDomain(new URI(AutomationConstants.AUTOMATION_DOMAIN));
service.setUsages(new URI[] { new URI(AutomationConstants.AUTOMATION_NAMESPACE + "Build") });
QueryCapability queryJobs = new QueryCapability("Jobs",
getBaseUriBuilder().path("queryJobs").build());
queryJobs.addResourceType(new URI(AutomationConstants.TYPE_AUTOMATION_PLAN));
service.addQueryCapability(queryJobs);
QueryCapability queryRuns = new QueryCapability("Runs",
getBaseUriBuilder().path("queryRuns").build());
queryRuns.addResourceType(new URI(AutomationConstants.TYPE_AUTOMATION_RESULT));
service.addQueryCapability(queryRuns);
UriBuilder creationFactoryUriBuilder = getBaseUriBuilder().path("scheduleBuild");
/*
* Hudson uses crumbs to prevent CSRF attacks on POST requests. OSLC
* interfaces cannot support this, however. To workaround -- for now at
* least -- bake the crumb into the URL. Then we can use OAuth or
* another authentication mechanism to avoid CSRF problems.
*/
CrumbIssuer issuer = Hudson.getInstance().getCrumbIssuer();
if (issuer != null) {
String crumbName = issuer.getDescriptor().getCrumbRequestField();
String crumb = issuer.getCrumb(null);
creationFactoryUriBuilder.queryParam(crumbName, crumb);
}
CreationFactory scheduleBuild = new CreationFactory("Schedule Build", creationFactoryUriBuilder.build());
scheduleBuild.addResourceType(new URI(AutomationConstants.TYPE_AUTOMATION_REQUEST));
service.addCreationFactory(scheduleBuild);
Dialog selectJobs = new Dialog("Select Job", getBaseUriBuilder().path("selectJob").build());
selectJobs.addResourceType(new URI(AutomationConstants.TYPE_AUTOMATION_PLAN));
selectJobs.setHintHeight("400px");
selectJobs.setHintWidth("600px");
service.addSelectionDialog(selectJobs);
marshal(provider);
}
private PrefixDefinition[] getPrefixDefinitions() throws URISyntaxException {
final PrefixDefinition[] prefixDefinitions = new PrefixDefinition[PREFIX_MAP.size()];
int i = 0;
for (Map.Entry<String, String> entry : PREFIX_MAP.entrySet()) {
prefixDefinitions[i++] = new PrefixDefinition(entry.getKey(), new URI(entry.getValue()));
}
return prefixDefinitions;
}
/*
* Path: /auto/queryJobs
*
* Jobs query capability.
*/
public void doQueryJobs(StaplerRequest request, StaplerResponse response)
throws IOException, URISyntaxException {
requireGET();
@SuppressWarnings("rawtypes")
Collection<Job> jobs = Hudson.getInstance().getItems(Job.class);
ArrayList<AutomationPlan> plans = new ArrayList<AutomationPlan>();
// TODO: Add support for oslc.where and oslc.select.
for (Job<?, ?> job : jobs) {
plans.add(toAutomationPlan(job));
}
marshalQueryResult(request, response, plans);
}
/*
* Path: /auto/selectJob
*
* Jobs selection dialog
*/
public void doSelectJob(StaplerRequest request, StaplerResponse response)
throws ServletException, IOException {
requireGET();
response.forward(new JobSelectionDialog(), "dialog", request);
}
private Job<?, ?> getJob(String name) {
return (Job<?, ?>) Hudson.getInstance().getItem(name);
}
/*
* Path: /auto/job/*
*
* Handle requests for jobs and runs.
*/
// TODO: Break up this method and clean up URL handling.
public void doJob(StaplerRequest request, StaplerResponse response)
throws IOException, URISyntaxException, ServletException {
requireGET();
MediaType type = AcceptUtil.matchMediaType(request, ACCEPTABLE);
if (type == null) {
throw HttpResponses.status(HttpServletResponse.SC_NOT_ACCEPTABLE);
}
String restOfPath = request.getRestOfPath();
if (restOfPath == null) {
throw HttpResponses.notFound();
}
// Remove leading '/'
restOfPath = restOfPath.substring(1);
String segments[] = restOfPath.split("/");
// URI patterns:
// <job-name>
// <job-name>/preview
// <job-name>/run/<run-number>
// <job-name>/run/<run-number>/preview
// <job-name>/run/<run-number>/request
// Find the job.
String jobName = segments[0];
Job<?, ?> job = getJob(jobName);
if (job == null) {
throw HttpResponses.notFound();
}
// Is it a run?
// <job-name>/run/<run-number>
if (segments.length >= 3 && PATH_RUN.equals(segments[1])) {
String runNumber = segments[2];
int i;
try {
i = Integer.valueOf(Integer.parseInt(runNumber));
} catch (NumberFormatException e) {
throw HttpResponses.notFound();
}
Run<?, ?> run = job.getBuildByNumber(i);
if (run == null) {
throw HttpResponses.notFound();
}
if (segments.length == 4) {
// Is it a run preview?
// <job-name>/run/<run-number>/preview
if (PATH_PREVIEW.equals(segments[3])) {
/*
* See /hudson-oslc-auto/src/main/resources/hudson/model/Run/preview.jelly
*/
response.forward(run, "preview", request);
return;
}
// Is it an AutomationRequest?
// <job-name>/run/<run-number>/request
if (PATH_REQUEST.equals(segments[3])) {
AutomationRequest autoRequest = toAutomationRequest(request, job, run);
marshal(autoRequest);
}
if ("buildStatus".equals(segments[3])) {
throw HttpResponses.redirectViaContextPath(run.getUrl() + "/buildStatus");
}
} else if (segments.length == 3) { // <job-name>/run/<run-number>
if (MediaType.TEXT_HTML_TYPE.isCompatible(type)) {
throw HttpResponses.redirectViaContextPath(run.getUrl());
}
if (MarshallerConstants.MT_OSLC_COMPACT.isCompatible(type)) {
handleCompact(job, run);
} else {
AutomationResult result = toAutomationResult(request, job, run);
marshal(result);
}
} else {
throw HttpResponses.notFound();
}
} else {
// Is it a job preview?
// <job-name>/preview
if (segments.length == 2 && PATH_PREVIEW.equals(segments[1])) {
/*
* See /hudson-oslc-auto/src/main/resources/hudson/model/Job/preview.jelly
*/
response.forward(job, "preview", request);
return;
}
if (segments.length != 1) {
throw HttpResponses.notFound();
}
// Is it just a job name with no other segments?
// <job-name>
if (MediaType.TEXT_HTML_TYPE.isCompatible(type)) {
throw HttpResponses.redirectViaContextPath(job.getUrl());
}
if (MarshallerConstants.MT_OSLC_COMPACT.isCompatible(type)) {
handleCompact(job);
} else {
AutomationPlan plan = toAutomationPlan(job);
marshal(plan);
}
}
}
/*
* Handle the Compact representation of a job.
*/
private void handleCompact(Job<?, ?> job) throws IOException,
URISyntaxException {
Compact c = new Compact();
c.setAbout(getJobURI(job));
c.setTitle(job.getFullDisplayName());
String icon = Stapler.getCurrentRequest().getRootPath() + job.getBuildHealth().getIconUrl("16x16");
c.setIcon(new URI(icon));
Preview p = new Preview();
p.setHintHeight("200px");
p.setHintWidth("400px");
p.setDocument(getJobPreviewURI(job));
c.setSmallPreview(p);
marshal(c);
}
/*
* Handle the Compact representation of a run.
*/
private void handleCompact(Job<?, ?> job, Run<?, ?> run) throws IOException,
URISyntaxException {
Compact c = new Compact();
c.setAbout(getRunURI(job, run));
c.setTitle(run.getFullDisplayName());
c.setShortTitle(run.getDisplayName());
String relative = run.getIconColor().getImageOf("16x16");
// Remove context or it shows up twice since getRootPath() and getImageOf() both include it.
String icon = Stapler.getCurrentRequest().getRootPath() + relative.substring(Stapler.getCurrentRequest().getContextPath().length());
c.setIcon(new URI(icon));
Preview p = new Preview();
p.setHintHeight("300px");
p.setHintWidth("400px");
p.setDocument(getRunPreviewURI(job, run));
c.setSmallPreview(p);
marshal(c);
}
/*
* Path: /auto/scheduleBuild
*
* POST to create automation requests to schedule builds.
*/
public void doScheduleBuild(StaplerRequest request, StaplerResponse response)
throws Exception {
requirePOST();
OSLC4JUnmarshaller unmarshaller = OSLC4JContext.newInstance().createUnmarshaller();
String contentType = request.getContentType();
if (contentType == null) {
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
unmarshaller.setMediaType(MediaType.valueOf(contentType));
final AutomationRequest autoRequest =
unmarshaller.unmarshal(request.getInputStream(), AutomationRequest.class);
if (autoRequest == null) {
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
Link planLink = autoRequest.getExecutesAutomationPlan();
if (planLink == null) {
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
URI planURI = planLink.getValue();
String jobName = getJobNameFromURI(planURI);
if (jobName == null) {
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
Job<?, ?> job = getJob(jobName);
if (job == null) {
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
if (!job.isBuildable()) {
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
if (!(job instanceof AbstractProject)) {
LOG.log(Level.WARNING, "Cannot schedule builds for jobs that don't extend AbstractProject: " + jobName);
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
AbstractProject<?, ?> project = (AbstractProject<?, ?>) job;
int nextBuildNumber = project.getNextBuildNumber();
Cause cause = new Cause() {
@Override
public String getShortDescription() {
String description = autoRequest.getDescription();
return description != null ? description : "OSLC Automation Request";
}
};
ParameterInstance[] parameters = autoRequest.getInputParameters();
boolean suceeded;
if (parameters.length == 0) {
suceeded = project.scheduleBuild(cause);
} else {
List<ParameterValue> values = getParameterValues(project, parameters);
suceeded = project.scheduleBuild2(project.getQuietPeriod(), cause, new ParametersAction(values)) != null;
}
if (!suceeded) {
// Build already queued.
LOG.log(Level.WARNING, "Automation request rejected (409 conflict) since build is already queued: " + jobName);
throw HttpResponses.status(HttpServletResponse.SC_CONFLICT);
}
URI requestURI = getAutoRequestURI(job, nextBuildNumber);
response.setStatus(HttpServletResponse.SC_CREATED);
response.setHeader("Location", requestURI.toString());
}
/*
* Determine the Hudson parameter values from the OSLC parameter instances
* in the AutomationRequest
*/
private List<ParameterValue> getParameterValues(AbstractProject<?, ?> project, ParameterInstance[] parameters) {
ParametersDefinitionProperty pp = project.getProperty(ParametersDefinitionProperty.class);
if (pp == null) {
LOG.log(Level.FINE, "Job does not take parameters: " + project.getName());
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST); // This build is not parameterized.
}
HashMap<String, String> inputMap = new HashMap<String, String>();
for (ParameterInstance param : parameters) {
inputMap.put(param.getName(), param.getValue());
}
List<ParameterValue> values = new ArrayList<ParameterValue>();
for (ParameterDefinition def : pp.getParameterDefinitions()) {
String inputValue = inputMap.get(def.getName());
if (inputValue == null) {
ParameterValue defaultValue = def.getDefaultParameterValue();
if (defaultValue == null) {
LOG.log(Level.FINE, "Missing parameter " + def.getName() + " for job " + project.getName());
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
values.add(defaultValue);
} else {
if (def instanceof SimpleParameterDefinition) {
SimpleParameterDefinition simple = (SimpleParameterDefinition) def;
values.add(simple.createValue(inputValue));
} else {
LOG.log(Level.WARNING, "Unsupported parameter type with name " + def.getName() + " for project " + project.getName());
throw HttpResponses.status(HttpServletResponse.SC_NOT_IMPLEMENTED);
}
}
}
return values;
}
/*
* Convert a Hudson Job to an OSLC AutomationPlan
*/
public AutomationPlan toAutomationPlan(Job<?, ?> job) throws URISyntaxException {
StaplerRequest request = Stapler.getCurrentRequest();
AutomationPlan plan = new AutomationPlan();
plan.setAbout(getJobURI(job));
plan.setTitle(job.getDisplayName());
plan.setDescription(job.getDescription());
plan.setServiceProvider(getProviderURI());
if (job instanceof AbstractProject) {
AbstractProject<?, ?> project = (AbstractProject<?, ?>) job;
fillInParameters(request, plan, project);
}
return plan;
}
/*
* Create the OSLC parameter definitions form the Hudson parameter definitions.
*/
private void fillInParameters(StaplerRequest request, AutomationPlan plan, AbstractProject<?, ?> project) throws URISyntaxException {
ParametersDefinitionProperty pp = project.getProperty(ParametersDefinitionProperty.class);
if (pp == null) {
return;
}
Property[] autoParams = new Property[pp.getParameterDefinitions().size()];
int i = 0;
for (ParameterDefinition def : pp.getParameterDefinitions()) {
autoParams[i++] = toProperty(request, def);
}
plan.setParameterDefinitions(autoParams);
}
/*
* Convert an individual Hudson parameter definition to an OSLC Property.
*/
private Property toProperty(StaplerRequest request, ParameterDefinition def) throws URISyntaxException {
Property prop = new Property();
prop.setName(def.getName());
prop.setDescription(def.getDescription());
if (def instanceof BooleanParameterDefinition) {
prop.setValueType(new URI(XSDDatatype.XSDboolean.getURI()));
} else if (def instanceof StringParameterDefinition || def instanceof PasswordParameterDefinition) {
prop.setValueType(new URI(XSDDatatype.XSDstring.getURI()));
} else if (def instanceof ChoiceParameterDefinition) {
prop.setValueType(new URI(XSDDatatype.XSDstring.getURI()));
ChoiceParameterDefinition choices = (ChoiceParameterDefinition) def;
prop.setAllowedValuesCollection(choices.getChoices());
}
// TODO: Other types?
ParameterValue defaultValue = def.getDefaultParameterValue();
if (defaultValue == null) {
prop.setOccurs(Occurs.ExactlyOne);
} else {
prop.setOccurs(Occurs.ZeroOrOne);
if (!defaultValue.isSensitive()) {
if (defaultValue instanceof BooleanParameterValue) {
BooleanParameterValue bool = (BooleanParameterValue) defaultValue;
prop.setDefaultValue(bool.value);
} else if (defaultValue instanceof StringParameterValue) {
StringParameterValue str = (StringParameterValue) defaultValue;
prop.setDefaultValue(str.value);
}
// TODO: Other types?
}
}
return prop;
}
/*
* Throw an HttpResponseException (method not allowed) if this isn't a GET request.
*/
private void requireGET() {
requireMethod("GET");
}
/*
* Throw an HttpResponseException (method not allowed) if this isn't a POST request.
*/
private void requirePOST() {
requireMethod("POST");
}
private void requireMethod(String method) {
if (!method.equals(Stapler.getCurrentRequest().getMethod())) {
throw HttpResponses.status(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
}
/*
* Parse the where clause from the oslc.where and oslc.prefix parameters.
*/
private WhereClause parseWhere(String where, String prefixes) {
final Map<String, String> prefixMap;
if (prefixes == null) {
prefixMap = PREFIX_MAP;
} else {
try {
prefixMap = QueryUtils.parsePrefixes(prefixes);
} catch (ParseException e) {
LOG.log(Level.FINE, "Bad oslc.prefix", e);
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
}
try {
return QueryUtils.parseWhere(where, prefixMap);
} catch (ParseException e) {
LOG.log(Level.FINE, "Bad oslc.where", e);
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
}
private URI getJobURI(Job<?, ?> job) {
return getBaseUriBuilder().path(PATH_JOB).path(job.getName()).build();
}
private URI getJobPreviewURI(Job<?, ?> job) {
return getBaseUriBuilder().path(PATH_JOB).path(job.getName()).path(PATH_PREVIEW).build();
}
private String getJobNameFromURI(URI uri)
throws UnsupportedEncodingException {
String uriString = uri.toString();
String rootPath = Stapler.getCurrentRequest().getRootPath() + "/";
if (!uriString.startsWith(rootPath)) {
return null;
}
String segments[] = uriString.substring(rootPath.length()).split("/");
if (segments.length != 3 || !PATH_AUTO.equals(segments[0]) || !PATH_JOB.equals(segments[1])) {
return null;
}
return URLDecoder.decode(segments[2], "UTF-8");
}
@SuppressWarnings("rawtypes")
public void doQueryRuns(StaplerRequest request, StaplerResponse response,
@QueryParameter("oslc.where") String where, @QueryParameter("oslc.prefix") String prefixes) throws IOException,
URISyntaxException {
requireGET();
WhereClause whereClause = null;
if (where != null) {
whereClause = parseWhere(where, prefixes);
}
Collection<Job> jobs = Hudson.getInstance().getItems(Job.class);
ArrayList<AutomationResult> results = new ArrayList<AutomationResult>();
for (Job<?, ?> job : jobs) {
if (jobMatchesRunWhereClause(request, job, whereClause)) {
Iterator<?> i = job.getBuilds().iterator();
while (i.hasNext()) {
Run<?, ?> run = (Run<?, ?>) i.next();
results.add(toAutomationResult(request, job, run));
}
}
}
marshalQueryResult(request, response, results);
}
private boolean jobMatchesRunWhereClause(StaplerRequest request, Job<?, ?> job, WhereClause whereClause) {
if (whereClause == null) {
return true;
}
for (SimpleTerm term : whereClause.children()) {
PName pname = term.property();
String prop = pname.namespace + pname.local;
if (prop.equals(AutomationConstants.AUTOMATION_NAMESPACE + "reportsOnAutomationPlan")
&& !uriMatches(getJobURI(job), term)) {
return false;
}
}
return true;
}
private boolean uriMatches(URI actual, SimpleTerm term) {
switch (term.type()) {
case COMPARISON:
return uriMatchesComparison(actual, (ComparisonTerm) term);
case IN_TERM:
return uriMatchesInTerm(actual, (InTerm) term);
case NESTED:
throw HttpResponses.status(HttpServletResponse.SC_NOT_IMPLEMENTED);
default:
return true;
}
}
private boolean uriMatchesInTerm(URI actual, InTerm in) {
String actualString = actual.toString();
for (Object element : in.values()) {
Value value = (Value) element;
if (!(value instanceof UriRefValue)) {
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
UriRefValue uriRef = (UriRefValue) value;
String uri = uriRef.value();
if (uri.equals(actualString)) {
return true;
}
}
return false;
}
private boolean uriMatchesComparison(URI actual, ComparisonTerm comparison) {
String actualString = actual.toString();
Value value = comparison.operand();
if (!(value instanceof UriRefValue)) {
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
UriRefValue uriRef = (UriRefValue) value;
String uri = uriRef.value();
switch (comparison.operator()) {
case EQUALS:
return actualString.equals(uri);
case NOT_EQUALS:
return !actualString.equals(uri);
default:
throw HttpResponses.status(HttpServletResponse.SC_BAD_REQUEST);
}
}
private AutomationResult toAutomationResult(StaplerRequest request, Job<?, ?> job, Run<?, ?> run) throws URISyntaxException {
AutomationResult result = new AutomationResult();
result.setAbout(getRunURI(job, run));
result.setIdentifier(run.getId());
result.setServiceProvider(getProviderURI());
result.setTitle(run.getFullDisplayName());
Result hudsonResult = run.getResult();
if (hudsonResult == Result.SUCCESS) {
result.addState(new URI(AutomationConstants.STATE_COMPLETE));
result.addVerdict(new URI(AutomationConstants.VERDICT_PASSED));
} else if (hudsonResult == Result.FAILURE) {
result.addState(new URI(AutomationConstants.STATE_COMPLETE));
result.addVerdict(new URI(AutomationConstants.VERDICT_FAILED));
} else if (hudsonResult == Result.NOT_BUILT) {
result.addState(new URI(AutomationConstants.STATE_CANCELED));
} else if (hudsonResult == Result.ABORTED) {
result.addState(new URI(AutomationConstants.STATE_CANCELED));
} else if (hudsonResult == Result.UNSTABLE) {
result.addState(new URI(AutomationConstants.STATE_COMPLETE));
result.addVerdict(new URI(AutomationConstants.VERDICT_WARNING));
}
URI jobURI = getJobURI(job);
Link jobLink = new Link(jobURI, job.getDisplayName());
result.setReportsOnAutomationPlan(jobLink);
return result;
}
private AutomationRequest toAutomationRequest(StaplerRequest request,
Job<?, ?> job, Run<?, ?> run) {
AutomationRequest autoRequest = new AutomationRequest(getAutoRequestURI(job, run));
URI jobURI = getJobURI(job);
Link jobLink = new Link(jobURI, job.getDisplayName());
autoRequest.setExecutesAutomationPlan(jobLink);
return autoRequest;
}
private URI getRunURI(Job<?, ?> job, Run<?, ?> run) {
return getBaseUriBuilder().path(PATH_JOB).path(job.getName())
.path(PATH_RUN).path("" + run.getNumber()).build();
}
private URI getRunPreviewURI(Job<?, ?> job, Run<?, ?> run) {
return getBaseUriBuilder().path(PATH_JOB).path(job.getName())
.path(PATH_RUN).path("" + run.getNumber()).path(PATH_PREVIEW)
.build();
}
private URI getAutoRequestURI(Job<?, ?> job, Run<?, ?> run) {
return getAutoRequestURI(job, run.getNumber());
}
private URI getAutoRequestURI(Job<?, ?> job,
int buildNumber) {
return getBaseUriBuilder().path(PATH_JOB).path(job.getName())
.path(PATH_RUN).path("" + buildNumber).path(PATH_REQUEST)
.build();
}
private UriBuilder getBaseUriBuilder() {
return UriBuilder.fromPath(Stapler.getCurrentRequest().getRootPath()).path(PATH_AUTO);
}
private URI getProviderURI() {
return getBaseUriBuilder().path("provider").build();
}
private static String getFullRequestURI(StaplerRequest request) {
StringBuffer requestURL = request.getRequestURL();
String queryString = request.getQueryString();
if (queryString == null) {
return requestURL.toString();
} else {
return requestURL.append('?').append(queryString).toString();
}
}
/*
* Marshal a query response.
*/
private void marshalQueryResult(StaplerRequest request,
StaplerResponse response, Collection<? extends Object> resources) throws IOException,
URISyntaxException {
QueryResponse queryResponse = new QueryResponse();
queryResponse.setAbout(new URI(request.getRequestURL().toString()));
queryResponse.setResources(resources);
ResponseInfo responseInfo = new ResponseInfo();
String uri = getFullRequestURI(request);
responseInfo.setAbout(new URI(uri));
responseInfo.setTotalCount(resources.size());
Object objects[] = { responseInfo, queryResponse };
marshal(objects);
}
/*
* Marshal a single resource.
*/
private void marshal(Object object) throws IOException {
marshal(new Object[] { object });
}
/*
* Set the correct media based on the request Accept header and marshal the
* response. Set any common headers for all responses.
*/
private void marshal(Object objects[]) throws IOException {
OSLC4JMarshaller marshaller = OSLC4JContext.newInstance().createMarshaller();
MediaType type = AcceptUtil.matchMediaType(Stapler.getCurrentRequest());
if (type == null) {
throw HttpResponses.status(HttpServletResponse.SC_NOT_ACCEPTABLE);
}
marshaller.setMediaType(type);
StaplerResponse response = Stapler.getCurrentResponse();
response.setCharacterEncoding("UTF-8");
response.setHeader("OSLC-Core-Version", "2.0");
response.setHeader("Content-Type", type.toString());
response.setHeader("Vary", "Accept, Accept-Encoding");
// Use WriterOutputStream since Stapler has already called response.getWriter().
WriterOutputStream out = new WriterOutputStream(response.getWriter());
marshaller.marshal(objects, out);
}
}