/*
* The MIT License
*
* Copyright (c) 2009, verit Informationssysteme GmbH, Caroline Albuquerque, Torsten Stolpmann
*
* 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 hudson.plugins.klaros;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.FilePath.FileCallable;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.remoting.VirtualChannel;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.FormValidation;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONObject;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.FileRequestEntity;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
/**
* Klaros test result publisher class. When a publish is performed, the
* {@link #perform(AbstractBuild, Launcher, BuildListener)} method will be
* invoked.
*
* @author Caroline Albuquerque (albuquerque@verit.de)
* @author Torsten Stolpmann (stolpmann@verit.de)
*/
public class KlarosTestResultPublisher extends Recorder {
/** The config. */
private String config;
/** The env. */
private String env;
/** The sut. */
private String sut;
/** The type. */
private String type = "junit";
/** The path test results. */
private String pathTestResults;
/** The url. */
private String url;
/** The username. */
private String username;
/** The password. */
private String password;
/**
* Instantiates a new klaros test result publisher.
*
* @param config
* the Klaros project configuration to use
* @param env
* the Klaros test environment to use
* @param sut
* the Klaros system under test to use
* @param type
* the type of test result to import
* @param pathTestResults
* the path to the test results
* @param url
* the Klaros application url
* @param username
* the optional Klaros login user name
* @param password
* the optional Klaros login password
*/
@DataBoundConstructor
public KlarosTestResultPublisher(final String config, final String env,
final String sut, final String type, final String pathTestResults,
final String url, final String username, final String password) {
this.config = config;
this.env = env;
this.sut = sut;
this.pathTestResults = pathTestResults;
this.url = url;
this.username = username;
this.password = password;
}
/**
* Descriptor.
*
* @return the descriptor implementation
*/
public static DescriptorImpl descriptor() {
return Hudson.getInstance().getDescriptorByType(
KlarosTestResultPublisher.DescriptorImpl.class);
}
/**
* Gets the config.
*
* @return the config
*/
public String getConfig() {
return config;
}
/**
* Sets the config.
*
* @param value
* the new config
*/
public void setConfig(final String value) {
config = value;
}
/**
* Gets the env.
*
* @return the env
*/
public String getEnv() {
return env;
}
/**
* Sets the env.
*
* @param value
* the new env
*/
public void setEnv(final String value) {
env = value;
}
/**
* Gets the url.
*
* @return the url
*/
public String getUrl() {
return url;
}
/**
* Sets the url.
*
* @param value
* the new url
*/
public void setUrl(final String value) {
url = value;
}
/**
* Gets the username.
*
* @return the username
*/
public String getUsername() {
return username;
}
/**
* Sets the username.
*
* @param value
* the new username
*/
public void setUsername(final String value) {
username = value;
}
/**
* Gets the password.
*
* @return the password
*/
public String getPassword() {
return password;
}
/**
* Sets the password.
*
* @param value
* the new password
*/
public void setPassword(final String value) {
password = value;
}
/**
* Gets the sut.
*
* @return the sut
*/
public String getSut() {
return sut;
}
/**
* Sets the sut.
*
* @param value
* the new sut
*/
public void setSut(final String value) {
sut = value;
}
/**
* Gets the path test results.
*
* @return the path test results
*/
public String getPathTestResults() {
return pathTestResults;
}
/**
* Sets the path test results.
*
* @param value
* the new path test results
*/
public void setPathTestResults(final String value) {
pathTestResults = value;
}
/**
* Gets the urls.
*
* @return the urls
*/
public List<String> getUrls() {
return descriptor().getUrls();
}
/**
* Runs the step over the given build and reports the progress to the
* listener.
*
* @param build
* the current build
* @param launcher
* the launcher
* @param listener
* the listener
*
* @return null
*/
@Override
public boolean perform(final AbstractBuild<?, ?> build,
final Launcher launcher, final BuildListener listener) {
final boolean result;
if (pathTestResults == null) {
listener.getLogger().println("There are no test result to import!");
result = false;
} else {
listener.getLogger().println(
"The test result(s) contained in target " + pathTestResults
+ " will be exported to the "
+ "Klaros-Testmanagement Server at " + getUrl(url)
+ ".");
listener.getLogger().println(
"With parameters Project[" + config + "], Environment["
+ env + "], SUT[" + sut + "] and Type[" + type
+ "].");
FilePath ws = build.getWorkspace();
if (ws == null) {
listener.error("No workspace defined!");
build.setResult(Result.FAILURE);
result = false;
} else {
try {
FileCallableImplementation exporter = new FileCallableImplementation(
listener);
ws.act(exporter);
} catch (IOException e) {
listener.getLogger().println(
"Failure to export test result(s).");
e.printStackTrace(listener.getLogger());
} catch (InterruptedException e) {
listener.getLogger().println(
"Failure to export test result(s).");
e.printStackTrace(listener.getLogger());
}
listener.getLogger().println(
"Test result(s) successfully exported.");
result = true;
}
}
return result;
}
/**
* Gets the URL of the given name, or returns null.
*
* @param sourceURL
* the URL of the klaros server import servlet
*
* @return the klaros server import servlet URL
*/
public String getUrl(final String sourceURL) {
String result = null;
if (sourceURL == null) {
// if only one URL is configured, "default URL" should mean that
// URL.
List<String> urls = descriptor().getUrls();
if (urls.size() >= 1) {
result = urls.get(0);
}
return result;
}
for (String j : descriptor().getUrls()) {
if (j.equals(sourceURL)) {
result = j;
break;
}
}
return result;
}
/**
* {@inheritDoc}
*/
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
/**
* {@inheritDoc}
*/
@Override
public BuildStepDescriptor<Publisher> getDescriptor() {
return descriptor();
}
/**
* Builds the servlet url. Try to honor URL's with trailing slashes.
*
* @param applicationURL
* the application url
*
* @return the servlet url
*/
private static String buildServletURL(final String applicationURL) {
final String result;
if (applicationURL.endsWith("/")) {
result = new StringBuffer(applicationURL.substring(0,
applicationURL.length() - 1)).append(
"/seam/resource/rest/importer").toString();
} else {
result = new StringBuffer(applicationURL).append(
"/seam/resource/rest/importer").toString();
}
return result;
}
/**
* The Class FileCallableImplementation.
*/
private final class FileCallableImplementation implements
FileCallable<List<Integer>> {
/** The serial version UID. */
private static final long serialVersionUID = 1560913900801548965L;
/** The listener. */
private final BuildListener listener;
/**
* Instantiates a new file callable implementation.
*
* @param listener
* the listener
*/
private FileCallableImplementation(final BuildListener listener) {
this.listener = listener;
}
/**
* Invoke.
*
* @param baseDir
* the base directory
* @param channel
* the channel
*
* @return the list of http return codes
*
* @throws IOException
* Signals that an I/O exception has occurred.
*
* @see hudson.FilePath.FileCallable#invoke(File,
* hudson.remoting.VirtualChannel)
*/
public List<Integer> invoke(final File baseDir,
final VirtualChannel channel) throws IOException {
List<Integer> results = new ArrayList<Integer>();
FileSet src = Util.createFileSet(baseDir, pathTestResults);
DirectoryScanner ds = src.getDirectoryScanner();
ds.scan();
if (ds.getIncludedFilesCount() == 0) {
listener.getLogger().println("No exportable files found");
return results;
}
// Get target URL
String targetUrl = getUrl(url);
if (targetUrl != null) {
String strURL = buildServletURL(targetUrl);
// Prepare HTTP PUT
for (String f : ds.getIncludedFiles()) {
PutMethod put = new PutMethod(strURL);
StringBuffer query = new StringBuffer("config=").append(
config).append("&env=").append(env).append("&sut=")
.append(sut).append("&type=").append(type);
if (username != null) {
query.append("&username=").append(username).append(
"&password=").append(password);
}
put.setQueryString(query.toString());
File file = new File(baseDir, f);
int result;
try {
RequestEntity entity = new FileRequestEntity(file,
"text/xml; charset=ISO-8859-1");
put.setRequestEntity(entity);
// Get HTTP client
HttpClient httpclient = new HttpClient();
// Execute request
try {
result = httpclient.executeMethod(put);
if (result != HttpServletResponse.SC_OK) {
StringBuffer msg = new StringBuffer()
.append("Export of ")
.append(file.getName())
.append(
" failed - Response status code: ")
.append(result).append(
" for request URL: ").append(
strURL).append("?").append(
query);
String response = new String(put
.getResponseBody());
if (response != null && response.length() > 0) {
msg.append("\nReason: ").append(response);
}
listener.getLogger().println(msg.toString());
} else {
results.add(result);
listener
.getLogger()
.println(
"Test result file "
+ file.getName()
+ " has been successfully exported.");
}
} catch (Exception e) {
e.printStackTrace(listener.getLogger());
}
} finally {
// Release current connection to the connection pool
// once you
// are
// done
put.releaseConnection();
}
}
} else {
listener.getLogger().println(
url + ": unable to locate this Klaros URL");
}
return results;
}
}
/**
* Descriptor for KlarosImportPublisher class. Used as a singleton. The
* class is marked as public so that it can be accessed from views.
*/
@Extension
public static final class DescriptorImpl extends
BuildStepDescriptor<Publisher> {
/** The Constant PROJECT_CONFIG_HTML. */
private static final String PROJECT_CONFIG_HTML = //
"/plugin/klaros-testmanagement/help-projectConfig.html";
/** The Constant URL_NAME. */
private static final String URL_NAME = "url.name";
/** Global configuration information. */
private List<String> urls = new ArrayList<String>();
/**
* Instantiates a new descriptor impl.
*/
public DescriptorImpl() {
load();
}
/**
* This human readable name is used in the configuration screen.
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return Messages.DisplayName();
}
/**
* {@inheritDoc}
*/
@Override
public boolean configure(final StaplerRequest req, final JSONObject json)
throws hudson.model.Descriptor.FormException {
urls.clear();
if (req.getParameterValues(URL_NAME) != null) {
for (int i = 0; i < req.getParameterValues(URL_NAME).length; i++) {
urls.add(req.getParameterValues(URL_NAME)[i]);
save();
}
}
return super.configure(req, json);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isApplicable(
final Class<? extends AbstractProject> jobType) {
return true; // for all types
}
/**
* {@inheritDoc}
*/
@Override
public String getHelpFile() {
return PROJECT_CONFIG_HTML;
}
/**
* Gets the urls.
*
* @return the urls
*/
public List<String> getUrls() {
return urls;
}
/**
* Sets the URLs.
*
* @param setUrls
* the new URLs
*/
public void setUrls(final List<String> setUrls) {
urls.clear();
for (String url : setUrls) {
urls.add(url);
}
}
/**
* Performs on-the-fly validation on a Klaros application URL.
*
* @param value
* the url value to check
*
* @return the form validation result
*
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws ServletException
* the servlet exception
*/
public FormValidation doCheckUrl(final String value)
throws IOException, ServletException {
return new FormValidation.URLCheck() {
@Override
protected FormValidation check() throws IOException,
ServletException {
String cooked = Util.fixEmpty(value);
if (cooked == null) { // nothing entered yet
return FormValidation.ok();
}
if (!value.endsWith("/")) {
cooked += '/';
}
FormValidation result = FormValidation.ok();
try {
if (findText(open(new URL(cooked)), "Klaros")) {
result = FormValidation.ok();
} else {
result = FormValidation.error( //
"This is a valid URL but it doesn't look like Klaros-Testmanagement");
}
} catch (IOException e) {
result = handleIOException(value, e);
}
return result;
}
}.check();
}
/**
* Performs on-the-fly validation on the file mask wildcard.
*
* @param project
* the current project
* @param value
* the mask value to check
*
* @return the form validation result
*
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws ServletException
* the servlet exception
*/
public FormValidation doCheck(
@AncestorInPath final AbstractProject<?, ?> project,
@QueryParameter final String value) throws IOException,
ServletException {
FilePath ws = project.getSomeWorkspace();
return ws != null ? ws.validateFileMask(value, false)
: FormValidation.ok();
}
/**
* Performs on-the-fly validation on the server installation.
*
* @param value
* the mask value to check
*
* @return the form validation result
*
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws ServletException
* the servlet exception
*/
public FormValidation doCheckInstallation(
@QueryParameter final String value) throws IOException,
ServletException {
if (Util.fixEmpty(value) != null) {
return FormValidation.ok();
} else {
return FormValidation
.error(Messages.ErrorMissingInstallation());
}
}
/**
* Test the connection with the given parameters.
*
* @param url
* the url
* @param username
* the username
* @param password
* the password
*
* @return the form validation
*
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws ServletException
* the servlet exception
*/
public FormValidation doTestConnection(
@QueryParameter final String url,
@QueryParameter final String username,
@QueryParameter final String password) throws IOException,
ServletException {
final String strURL = buildServletURL(url);
PutMethod put = new PutMethod(strURL);
StringBuffer query = new StringBuffer();
if (username != null) {
query.append("username=").append(username).append("&password=")
.append(password).append("&type=").append("check");
}
System.out.println(strURL + '?' + query.toString());
put.setQueryString(query.toString());
try {
RequestEntity entity = new StringRequestEntity("",
"text/xml; charset=UTF-8", "UTF-8");
put.setRequestEntity(entity);
int result;
try {
HttpClient client = new HttpClient();
result = client.executeMethod(put);
String response = "";
if (result != HttpServletResponse.SC_OK) {
StringBuffer msg = new StringBuffer();
response = new String(put.getResponseBody());
if (response != null && response.length() > 0) {
msg.append("Connection failed: ").append(response);
System.out.println(msg.toString());
}
return FormValidation.error(msg.toString());
} else {
if (response != null && response.length() > 0) {
return FormValidation.ok(Messages
.ConnectionEstablished()
+ ": " + response);
} else {
return FormValidation.ok(Messages
.ConnectionEstablished());
}
}
} finally {
put.releaseConnection();
}
} catch (Exception e) {
return FormValidation.error(e.getMessage());
}
}
}
}