Skip to content

Instantly share code, notes, and snippets.

@kmila
Created November 29, 2012 15:49
Show Gist options
  • Save kmila/4169933 to your computer and use it in GitHub Desktop.
Save kmila/4169933 to your computer and use it in GitHub Desktop.
CRUCIBLE REST API
import static java.lang.Math.min;
import static java.lang.String.format;
import static java.lang.System.getProperty;
import static java.nio.charset.Charset.defaultCharset;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.google.common.base.Throwables;
import com.google.common.io.CharStreams;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
public class Main {
/**
* @param args Username and password. Use eclipse's variable ${password_prompt}.
*/
public static void main(String[] args) throws Exception {
Crucible.restV1(args[0], args[1]).maxRequests(1).run();
}
}
enum Cmd {
CREATE("PUT"),
READ("GET"),
UPDATE("POST"),
DELETE("DELETE");
public final String cmd;
private Cmd(String cmd) {
this.cmd = cmd;
}
}
class Crucible implements Runnable {
private static final String REST_SERVICE_URL = "http://crucible/rest-service";
// Crucible REST API
private static final String AUTH_URI = REST_SERVICE_URL + "/auth-v1/login?userName=%s&password=%s";
private static final String REVIEW_URI = REST_SERVICE_URL + "/reviews-v1";
private static final String GET_REVIEW = REVIEW_URI + "/filter/toReview?FEAUTH=%s"; // TODO by date?
private static final String POST_COMPLETE = REVIEW_URI + "/%s/complete?FEAUTH=%s";
private final String user;
private final String pass;
private int maxRequests = 100;
private Crucible(String user, String pass) {
this.user = user;
this.pass = pass;
}
public static Crucible restV1(String user, String pass) throws Exception {
return new Crucible(user, pass);
}
/**
* Calls crucible REST API, should run asynchronously.
*/
public void run() {
String authToken = getAuthData();
if (authToken == null) {
throw new IllegalArgumentException("Invalid auth for " + user);
}
String[] cruIds = getPendingReviews(authToken);
if (cruIds.length == 0 || cruIds[0] == null) {
throw new UnsupportedOperationException("To review: not found.");
}
completeReviews(authToken, cruIds);
}
/**
* @return No reviews found for filter "To Review"
*/
private String completeReviews(String authToken, String... ids) {
StringBuilder result = new StringBuilder();
for (String id : ids) {
result.append(curl.httpRequest(Cmd.UPDATE, format(POST_COMPLETE, id, authToken)));
}
return result.toString();
}
private String[] getPendingReviews(String authToken) {
try {
return parseXml("id", maxRequests, GET_REVIEW, authToken);
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
private String getAuthData() {
try {
return parseXml("token", 1, AUTH_URI, user, pass)[0];
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
private static String[] parseXml(String tag, int maxRequests, String uri, Object... args) throws Exception {
Document xml = getXml(format(uri, args));
String[] requests = parseXauth(xml, tag, maxRequests);
System.out.println(format("tag=%s, uri=%s, data=%s, charset=%s", tag, format(uri, args), Arrays.toString(requests), xml.getInputEncoding()));
return requests;
}
private static String[] parseXauth(Document xml, String tag, int maxRequests) throws XPathExpressionException {
Object result = XPathFactory.newInstance().newXPath().evaluate(format("//%s/text()", tag), xml, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
String[] ids = new String[min(nodes.getLength(), maxRequests)];
for (int i = 0; i < ids.length; i++) {
String nodeValue = nodes.item(i).getNodeValue();
if (nodeValue == null) {
throw new IllegalStateException("No text for node " + tag);
}
ids[i] = nodeValue;
}
return ids;
}
private static Document getXml(String uri) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
return domFactory.newDocumentBuilder().parse(uri);
}
public Crucible maxRequests(int maxRequests) {
this.maxRequests = maxRequests;
return this;
}
}
class curl {
public static final String CURL_CMD = getProperty("curl", "curl");
private static final long TIMEOUT = 5;
private static final ThreadPoolExecutor EXECUTOR_SERVICE = (ThreadPoolExecutor) Executors.newFixedThreadPool(1, new ThreadFactoryBuilder()
.setNameFormat("CMD-%d").build());
public static String httpRequest(Cmd cmd, String uri) {
try {
return exec(CURL_CMD, Boolean.getBoolean("noop") ? "--help" : "-v", "-X", cmd.cmd, uri);
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
public static String exec(String... cmd) throws Exception {
System.out.println("Running " + Arrays.toString(cmd));
Process p = new ProcessBuilder().redirectErrorStream(true).command(cmd).start();
return parseResponse(p, new InputStreamReader(p.getInputStream(), defaultCharset()));
}
private static String parseResponse(final Process p, final InputStreamReader response) throws InterruptedException, ExecutionException,
TimeoutException, IOException {
try {
String result = EXECUTOR_SERVICE.submit(new Callable<String>() {
public String call() throws Exception {
String result = CharStreams.toString(new BufferedReader(response));
p.waitFor();
return result;
}
}).get(TIMEOUT, TimeUnit.SECONDS);
System.out.println("Process exited with status " + p.exitValue());
return result;
}
finally {
response.close();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment