blob: 2d2ce580cd266147127842592b88269d26f80dd2 [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.tasks.fogbugz;
import com.intellij.openapi.diagnostic.Logger;
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.NotNullFunction;
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.methods.PostMethod;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import java.util.Date;
import java.util.List;
/**
* @author mkennedy
*/
@Tag("FogBugz")
public class FogBugzRepository extends BaseRepositoryImpl {
private static final Logger LOG = Logger.getInstance("#com.intellij.tasks.fogbugz.FogBugzRepository");
private transient String token;
public FogBugzRepository(TaskRepositoryType type) {
super(type);
setUrl("https://example.fogbugz.com");
}
private FogBugzRepository(FogBugzRepository other) {
super(other);
}
@SuppressWarnings({"UnusedDeclaration"})
public FogBugzRepository() {
}
@Override
public Task[] getIssues(@Nullable String query, int max, final long since) throws Exception {
return getCases(StringUtil.notNullize(query));
}
private Task[] getCases(String q) throws Exception {
HttpClient client = login(getLoginMethod());
PostMethod method = new PostMethod(getUrl() + "/api.asp");
method.addParameter("token", token);
method.addParameter("cmd", "search");
method.addParameter("q", q);
method.addParameter("cols", "sTitle,fOpen,dtOpened,dtLastUpdated,ixCategory");
int status = client.executeMethod(method);
if (status != 200) {
throw new Exception("Error listing cases: " + method.getStatusLine());
}
Document document = new SAXBuilder(false).build(method.getResponseBodyAsStream()).getDocument();
XPath path = XPath.newInstance("/response/cases/case");
final XPath commentPath = XPath.newInstance("events/event");
@SuppressWarnings("unchecked") final List<Element> nodes = (List<Element>)path.selectNodes(document);
final List<Task> tasks = ContainerUtil.mapNotNull(nodes, new NotNullFunction<Element, Task>() {
@NotNull
@Override
public Task fun(Element element) {
return createCase(element, commentPath);
}
});
return tasks.toArray(new Task[tasks.size()]);
}
private static TaskType getType(Element element) {
String category = element.getChildText("ixCategory");
if ("1".equals(category)) {
return TaskType.BUG;
}
else if ("2".equals(category)) {
return TaskType.FEATURE;
}
return TaskType.OTHER;
}
@NotNull
private Task createCase(final Element element, final XPath commentPath) {
final String id = element.getAttributeValue("ixBug");
final String title = element.getChildTextTrim("sTitle");
final TaskType type = getType(element);
return new Task() {
@NotNull
@Override
public String getId() {
return id;
}
@NotNull
@Override
public String getSummary() {
return title;
}
@Nullable
@Override
public String getDescription() {
return null;
}
@NotNull
@Override
@SuppressWarnings("unchecked")
public Comment[] getComments() {
List<Element> nodes;
try {
nodes = commentPath.selectNodes(element);
}
catch (Exception e) {
throw new RuntimeException("Error selecting comment nodes", e);
}
List<Comment> comments = ContainerUtil.mapNotNull(nodes, new NotNullFunction<Element, Comment>() {
@NotNull
@Override
public Comment fun(Element element) {
return createComment(element);
}
private Comment createComment(final Element element) {
return new Comment() {
@Override
public String getText() {
return element.getChildTextTrim("s");
}
@Nullable
@Override
public String getAuthor() {
return element.getChildTextTrim("sPerson");
}
@Nullable
@Override
public Date getDate() {
return parseDate(element.getChildTextTrim("dt"));
}
};
}
});
return comments.toArray(new Comment[comments.size()]);
}
@NotNull
@Override
public Icon getIcon() {
return TasksIcons.Fogbugz;
}
@NotNull
@Override
public TaskType getType() {
return type;
}
@Nullable
@Override
public Date getUpdated() {
return parseDate(element.getChildText("dtLastUpdated"));
}
@Nullable
@Override
public Date getCreated() {
return parseDate(element.getChildTextTrim("dtOpened"));
}
@Override
public boolean isClosed() {
return !Boolean.valueOf(element.getChildTextTrim("fOpen"));
}
@Override
public boolean isIssue() {
return true;
}
@Nullable
@Override
public String getIssueUrl() {
return getUrl() + "/default.asp?" + getId();
}
@Nullable
@Override
public TaskRepository getRepository() {
return FogBugzRepository.this;
}
};
}
@Nullable
@Override
public Task findTask(@NotNull String id) throws Exception {
Task[] tasks = getCases(id);
switch (tasks.length) {
case 0:
return null;
case 1:
return tasks[0];
default:
LOG.warn("Expected unique case for case id: " + id + ", got " + tasks.length + " instead. Using the first one.");
return tasks[0];
}
}
@NotNull
@Override
public BaseRepository clone() {
return new FogBugzRepository(this);
}
private HttpClient login(PostMethod method) throws Exception {
HttpClient client = getHttpClient();
int status = client.executeMethod(method);
if (status != 200) {
throw new Exception("Error logging in: " + method.getStatusLine());
}
Document document = new SAXBuilder(false).build(method.getResponseBodyAsStream()).getDocument();
XPath path = XPath.newInstance("/response/token");
Element result = (Element)path.selectSingleNode(document);
if (result == null) {
Element error = (Element)XPath.newInstance("/response/error").selectSingleNode(document);
throw new Exception(error == null ? "Error logging in" : error.getText());
}
token = result.getTextTrim();
return client;
}
private PostMethod getLoginMethod() {
PostMethod method = new PostMethod(getUrl() + "/api.asp");
method.addParameter("cmd", "logon");
method.addParameter("email", getUsername());
method.addParameter("password", getPassword());
return method;
}
@Nullable
@Override
public CancellableConnection createCancellableConnection() {
return new HttpTestConnection<PostMethod>(getLoginMethod()) {
@Override
protected void doTest(PostMethod method) throws Exception {
login(method);
}
};
}
@NotNull
private static Date parseDate(@NotNull String string) {
try {
return DatatypeFactory.newInstance().newXMLGregorianCalendar(string).toGregorianCalendar().getTime();
}
catch (DatatypeConfigurationException e) {
throw new RuntimeException("Error configuring datatype factory", e);
}
}
@Override
public String getComment() {
return "{id} (e.g. 2344245), {summary}";
}
}