blob: bb60b57b429c411d9cdce4a9a3403ba4998154f4 [file] [log] [blame]
package com.intellij.tasks.lighthouse;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.tasks.*;
import com.intellij.tasks.impl.BaseRepository;
import com.intellij.tasks.impl.BaseRepositoryImpl;
import com.intellij.util.NullableFunction;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.annotations.Tag;
import icons.TasksIcons;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Dennis.Ushakov
*/
@Tag("Lighthouse")
public class LighthouseRepository extends BaseRepositoryImpl {
private static final Logger LOG = Logger.getInstance("#com.intellij.tasks.lighthouse.LighthouseRepository");
private static final Pattern DATE_PATTERN = Pattern.compile("(\\d\\d\\d\\d\\-\\d\\d\\-\\d\\d).*(\\d\\d:\\d\\d:\\d\\d).*");
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private Pattern myPattern;
private String myProjectId;
private String myAPIKey;
/** for serialization */
@SuppressWarnings({"UnusedDeclaration"})
public LighthouseRepository() {
}
public LighthouseRepository(TaskRepositoryType type) {
super(type);
}
@NotNull
@Override
public BaseRepository clone() {
return new LighthouseRepository(this);
}
private LighthouseRepository(LighthouseRepository other) {
super(other);
setProjectId(other.myProjectId);
setAPIKey(myAPIKey = other.myAPIKey);
}
@Override
public void testConnection() throws Exception {
getIssues(null, 10, 0);
}
@Override
public boolean isConfigured() {
return super.isConfigured() &&
StringUtil.isNotEmpty(getProjectId()) &&
StringUtil.isNotEmpty(getAPIKey());
}
@Override
public Task[] getIssues(@Nullable String query, int max, long since) throws Exception {
String url = "/projects/" + myProjectId + "/tickets.xml";
url += "?q=" + encodeUrl("state:open sort:updated ");
if (!StringUtil.isEmpty(query)) {
url += encodeUrl(query);
}
final List<Task> tasks = new ArrayList<Task>();
int page = 1;
final HttpClient client = login();
while (tasks.size() < max) {
HttpMethod method = doREST(url + "&page=" + page, false, client);
InputStream stream = method.getResponseBodyAsStream();
Element element = new SAXBuilder(false).build(stream).getRootElement();
if ("nil-classes".equals(element.getName())) break;
if (!"tickets".equals(element.getName())) {
LOG.warn("Error fetching issues for: " + url + ", HTTP status code: " + method.getStatusCode());
throw new Exception("Error fetching issues for: " + url + ", HTTP status code: " + method.getStatusCode() +
"\n" + element.getText());
}
List<Element> children = element.getChildren("ticket");
List<Task> taskList = ContainerUtil.mapNotNull(children, new NullableFunction<Element, Task>() {
public Task fun(Element o) {
return createIssue(o);
}
});
tasks.addAll(taskList);
page++;
}
return tasks.toArray(new Task[tasks.size()]);
}
@Nullable
private Task createIssue(Element element) {
final String id = element.getChildText("number");
if (id == null) {
return null;
}
final String summary = element.getChildText("title");
if (summary == null) {
return null;
}
final String description = element.getChildText("original-body");
final boolean isClosed = "true".equals(element.getChildText("closed"));
final Ref<Date> updated = new Ref<Date>();
final Ref<Date> created = new Ref<Date>();
try {
updated.set(parseDate(element, "updated-at"));
created.set(parseDate(element, "created-at"));
} catch (ParseException e) {
LOG.warn(e);
}
return new Task() {
@Override
public boolean isIssue() {
return true;
}
@Override
public String getIssueUrl() {
return getUrl() + "/projects/" + myProjectId + "/tickets/" + getId() + ".xml";
}
@NotNull
@Override
public String getId() {
return myProjectId + "-" + id;
}
@NotNull
@Override
public String getSummary() {
return summary;
}
public String getDescription() {
return description;
}
@NotNull
@Override
public Comment[] getComments() {
return new Comment[0];
}
@NotNull
@Override
public Icon getIcon() {
return TasksIcons.Lighthouse;
}
@NotNull
@Override
public TaskType getType() {
return TaskType.BUG;
}
@Override
public Date getUpdated() {
return updated.get();
}
@Override
public Date getCreated() {
return created.get();
}
@Override
public boolean isClosed() {
return isClosed;
}
@Override
public TaskRepository getRepository() {
return LighthouseRepository.this;
}
@Override
public String getPresentableName() {
return getId() + ": " + getSummary();
}
};
}
@Nullable
private static Date parseDate(Element element, String name) throws ParseException {
final Matcher m = DATE_PATTERN.matcher(element.getChildText(name));
if (m.find()) {
return DATE_FORMAT.parse(m.group(1) + " " + m.group(2));
}
return null;
}
@Nullable
public String extractId(@NotNull String taskName) {
Matcher matcher = myPattern.matcher(taskName);
return matcher.find() ? matcher.group(1) : null;
}
private HttpMethod doREST(String request, boolean post, HttpClient client) throws Exception {
String uri = getUrl() + request;
HttpMethod method = post ? new PostMethod(uri) : new GetMethod(uri);
configureHttpMethod(method);
client.executeMethod(method);
return method;
}
private HttpClient login() throws Exception {
HttpClient client = getHttpClient();
GetMethod method = new GetMethod(getUrl() + "/projects.xml");
configureHttpMethod(method);
client.getParams().setContentCharset("UTF-8");
client.executeMethod(method);
if (method.getStatusCode() != 200) {
throw new Exception("Cannot login: HTTP returned " + method.getStatusCode());
}
String response = method.getResponseBodyAsString();
if (response == null) {
throw new NullPointerException();
}
if (response.contains("<errors>")) {
int pos = response.indexOf("</errors>");
int length = "<errors>".length();
if (pos > length) {
response = response.substring(length, pos);
}
throw new Exception("Cannot login: " + response);
}
return client;
}
@Override
protected void configureHttpMethod(HttpMethod method) {
method.addRequestHeader("X-LighthouseToken", myAPIKey);
}
@Nullable
@Override
public Task findTask(@NotNull String id) throws Exception {
final String[] split = id.split("\\-");
final String projectId = split[0];
final String realId = split[1];
if (!Comparing.strEqual(projectId, myProjectId)) return null;
HttpMethod method = doREST("/projects/" + myProjectId + "/tickets/" + realId +".xml", false, login());
InputStream stream = method.getResponseBodyAsStream();
Element element = new SAXBuilder(false).build(stream).getRootElement();
return element.getName().equals("ticket") ? createIssue(element) : null;
}
public String getProjectId() {
return myProjectId;
}
public void setProjectId(String projectId) {
myProjectId = projectId;
myPattern = Pattern.compile("(" + projectId + "\\-\\d+):\\s+");
}
public String getAPIKey() {
return myAPIKey;
}
public void setAPIKey(String APIKey) {
myAPIKey = APIKey;
}
@Override
public boolean equals(Object o) {
if (!super.equals(o)) return false;
if (!(o instanceof LighthouseRepository)) return false;
LighthouseRepository that = (LighthouseRepository)o;
if (getAPIKey() != null ? !getAPIKey().equals(that.getAPIKey()) : that.getAPIKey() != null) return false;
if (getProjectId() != null ? !getProjectId().equals(that.getProjectId()) : that.getProjectId() != null) return false;
return true;
}
}