/********************************************************************************
* CruiseControl, a Continuous Integration Toolkit
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* + Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************************/
package net.sourceforge.cruisecontrol.publishers;
import net.sourceforge.cruisecontrol.Publisher;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.gendoc.annotations.ManualChildName;
import net.sourceforge.cruisecontrol.util.NamedXPathAwareChild;
import net.sourceforge.cruisecontrol.util.ValidationHelper;
import org.jdom.Element;
import org.apache.log4j.Logger;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Used to execute an HTTP request
*
* @author <a href="jonathan@indiekid.org">Jonathan Gerrish</a>
*/
public class HTTPPublisher implements Publisher {
private static final Logger LOG = Logger.getLogger(ExecutePublisher.class);
private static final Set<String> METHODS = new HashSet<String>();
static {
METHODS.add("GET");
METHODS.add("POST");
METHODS.add("HEAD");
METHODS.add("OPTIONS");
METHODS.add("PUT");
METHODS.add("DELETE");
METHODS.add("TRACE");
}
private final Collection<NamedXPathAwareChild> parameters = new ArrayList<NamedXPathAwareChild>();
private String urlString;
private String requestMethod;
private URL url;
/**
* Publish the results to a URL.
*
* @param cruisecontrolLog of the current build
* @throws CruiseControlException - on any error
*/
public void publish(final Element cruisecontrolLog) throws CruiseControlException {
HttpURLConnection conn = null;
try {
if ("GET".equals(this.requestMethod)) {
// On a get request, we need to build a new URL with the encoded parameters in the URL.
String dataSet = createDataSet(cruisecontrolLog);
if (dataSet.length() > 0) {
dataSet = "?" + dataSet;
}
url = new URL(this.urlString + dataSet);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(this.requestMethod);
} else {
// On a non-get request, we encode the parameters in the message body
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(this.requestMethod);
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(createDataSet(cruisecontrolLog));
wr.flush();
}
LOG.info("Sending " + this.requestMethod + " to " + this.urlString);
conn.connect();
LOG.info("Returned: " + conn.getResponseCode() + " : " + conn.getResponseMessage());
} catch (IOException e) {
throw new CruiseControlException(e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
/**
* Called after the configuration is read to make sure that all the mandatory parameters
* were specified..
*
* @throws CruiseControlException if there was a configuration error.
*/
public void validate() throws CruiseControlException {
ValidationHelper.assertIsSet(this.urlString, "urlString", this.getClass());
try {
url = new URL(this.urlString);
} catch (MalformedURLException e) {
ValidationHelper.fail("URL: " + this.urlString + " is not a valid URL", e);
}
ValidationHelper.assertTrue(METHODS.contains(this.requestMethod), "Request Method: " + this.requestMethod
+ " is mot a valid HTTP Request method");
for (final NamedXPathAwareChild parameter : parameters) {
parameter.validate();
}
}
/**
* Method to specify the URL used to publish the build result
* @param url URL to which to publish build result
*/
public void setUrl(String url) {
this.urlString = url;
}
/**
* Method to specify the HTTP Request method to use for this request
* @param requestMethod String representing the HTTP Request method
*/
public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
}
/**
* An HTTP request attribute-value parameter pair, represented by a NamedXPathAwareChild to provide
* for dymanic parameter value resolution from the either the cruisecontrol log or other file.
* @return NamedXPathAwareChild representing attribute-value HTTP parameter pair.
* @see NamedXPathAwareChild
*/
@ManualChildName("parameter")
public NamedXPathAwareChild createParameter() {
NamedXPathAwareChild parameter = new NamedXPathAwareChild();
parameters.add(parameter);
return parameter;
}
/**
* This method will build up a URL encoded string of all the parameters specified
* for the HTTP request
* @param cruisecontrolLog The cruise control log document.
* @return URL Encoded string of the parameters
* @throws CruiseControlException On any error
*/
private String createDataSet(final Element cruisecontrolLog) throws CruiseControlException {
final StringBuilder data = new StringBuilder();
final Iterator<NamedXPathAwareChild> it = this.parameters.iterator();
while (it.hasNext()) {
final NamedXPathAwareChild parameter = it.next();
final String name = parameter.getName();
final String value = parameter.lookupValue(cruisecontrolLog);
LOG.info("Adding request property: " + name + " = " + value);
try {
data.append(URLEncoder.encode(name, "UTF-8")).append("=").append(URLEncoder.encode(value, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new CruiseControlException("UTF-8 encoding not available", e);
}
if (it.hasNext()) {
data.append("&");
}
}
return data.toString();
}
}