blob: 70825a9b7266b5430317f67ded0986dddc66ebe9 [file] [log] [blame]
package com.intellij.tasks.mantis;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.KeyValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.tasks.Task;
import com.intellij.tasks.TaskBundle;
import com.intellij.tasks.TaskRepositoryType;
import com.intellij.tasks.impl.BaseRepository;
import com.intellij.tasks.impl.BaseRepositoryImpl;
import com.intellij.tasks.mantis.model.*;
import com.intellij.util.Function;
import com.intellij.util.NullableFunction;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.proxy.JavaProxyProperty;
import com.intellij.util.text.VersionComparatorUtil;
import com.intellij.util.xmlb.annotations.Tag;
import org.apache.axis.AxisFault;
import org.apache.axis.AxisProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.math.BigInteger;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.intellij.tasks.actions.GotoTaskAction.PAGE_SIZE;
/**
* @author Dmitry Avdeev
*/
@Tag("Mantis")
public class MantisRepository extends BaseRepositoryImpl {
private static final boolean DEBUG_ALL_PROJECTS = Boolean.getBoolean("tasks.mantis.debug.all.projects");
private static final String SOAP_API_LOCATION = "/api/soap/mantisconnect.php";
private static final Pattern ID_PATTERN = Pattern.compile("\\d+");
private static final Logger LOG = Logger.getInstance(MantisRepository.class);
// Projects fetched from server last time is cached, so workaround for IDEA-105413 could work.
private List<MantisProject> myProjects = null;
// It means that special pseudo-project "All Projects" is supported on server side.
// false if Mantis version < 1.2.9, because of http://www.mantisbt.org/bugs/view.php?id=13526
private boolean myAllProjectsAvailable = true;
private MantisProject myCurrentProject;
private MantisFilter myCurrentFilter;
@SuppressWarnings({"UnusedDeclaration"})
public MantisRepository() {
// empty
}
public MantisRepository(TaskRepositoryType type) {
super(type);
}
public MantisRepository(MantisRepository other) {
super(other);
myCurrentProject = other.getCurrentProject();
myCurrentFilter = other.getCurrentFilter();
myProjects = other.myProjects;
// deep copy isn't needed because, new list will be assigned to field on update in configurable
myAllProjectsAvailable = other.myAllProjectsAvailable;
}
@NotNull
@Override
public BaseRepository clone() {
return new MantisRepository(this);
}
@Nullable
@Override
public String extractId(@NotNull String taskName) {
Matcher matcher = ID_PATTERN.matcher(taskName);
return matcher.find() ? matcher.group() : null;
}
@Override
public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed, @NotNull ProgressIndicator cancelled)
throws Exception {
if (myCurrentProject == null || myCurrentFilter == null) {
throw new Exception(TaskBundle.message("failure.configuration"));
}
ensureProjectsRefreshed();
MantisConnectPortType soap = createSoap();
List<Task> tasks = new ArrayList<Task>(limit);
int pageNumber = offset / PAGE_SIZE + 1;
// what the heck does it suppose to mean?
while (tasks.size() < limit) {
cancelled.checkCanceled();
int pageSize = Math.min(PAGE_SIZE, limit - tasks.size());
List<Task> issuesFromPage = getIssuesFromPage(soap, pageNumber, pageSize);
tasks.addAll(issuesFromPage);
if (issuesFromPage.size() < pageSize) {
break;
}
pageNumber++;
}
return tasks.toArray(new Task[tasks.size()]);
}
private List<Task> getIssuesFromPage(@NotNull MantisConnectPortType soap, int pageNumber, int pageSize) throws Exception {
List<IssueHeaderData> collectedHeaders = new ArrayList<IssueHeaderData>();
boolean isWorkaround = myCurrentProject.isUnspecified() && !myAllProjectsAvailable;
// Projects to iterate over, actually needed only when "All Projects" pseudo-project is selected
// and is unsupported on server side.
List<MantisProject> projects = isWorkaround ? myProjects : Collections.singletonList(myCurrentProject);
for (MantisProject project : projects) {
if (isWorkaround && project.isUnspecified()) {
continue;
}
assert !project.isUnspecified() || myAllProjectsAvailable;
IssueHeaderData[] headers = fetchProjectIssues(soap, project, myCurrentFilter, pageNumber, pageSize);
ContainerUtil.addAll(collectedHeaders, headers);
}
return ContainerUtil.mapNotNull(collectedHeaders, new NullableFunction<IssueHeaderData, Task>() {
public Task fun(IssueHeaderData issueData) {
if (issueData.getId() == null || issueData.getSummary() == null) {
return null;
}
return new MantisTask(issueData, MantisRepository.this);
}
});
}
@Nullable
@Override
public Task findTask(@NotNull String id) throws Exception {
IssueData data = fetchIssueById(createSoap(), id);
// sanity check
if (data == null || data.getId() == null || data.getSummary() == null) {
return null;
}
return new MantisTask(data, this);
}
@Nullable
@Override
public CancellableConnection createCancellableConnection() {
return new CancellableConnection() {
@Override
protected void doTest() throws Exception {
myProjects = null;
try {
createSoap().mc_enum_access_levels(getUsername(), getPassword());
}
catch (Exception e) {
throw handleException(e);
}
}
@Override
public void cancel() {
}
};
}
@NotNull
public List<MantisProject> getProjects() throws Exception {
ensureProjectsRefreshed();
return myProjects == null ? Collections.<MantisProject>emptyList() : myProjects;
}
private void ensureProjectsRefreshed() throws Exception {
if (myProjects == null) {
refreshProjects();
}
}
void refreshProjects() throws Exception {
MantisConnectPortType soap = createSoap();
myAllProjectsAvailable = checkAllProjectsAvailable(soap);
List<MantisProject> projects =
new ArrayList<MantisProject>(ContainerUtil.map(fetchUserProjects(soap), new Function<ProjectData, MantisProject>() {
@Override
public MantisProject fun(final ProjectData data) {
return new MantisProject(data);
}
}));
List<MantisFilter> commonFilters = new LinkedList<MantisFilter>();
for (MantisProject project : projects) {
FilterData[] rawFilters = fetchProjectFilters(soap, project);
List<MantisFilter> projectFilters = new LinkedList<MantisFilter>();
for (FilterData data : rawFilters) {
MantisFilter filter = new MantisFilter(data);
if (data.getProject_id().intValue() == 0) {
commonFilters.add(filter);
}
projectFilters.add(filter);
}
projectFilters.add(0, MantisFilter.newUndefined());
project.setFilters(projectFilters);
}
Collections.sort(commonFilters, new Comparator<MantisFilter>() {
@Override
public int compare(MantisFilter f1, MantisFilter f2) {
return f1.getName().compareTo(f2.getName());
}
});
commonFilters.add(0, MantisFilter.newUndefined());
MantisProject undefined = MantisProject.newUndefined();
undefined.setFilters(commonFilters);
projects.add(0, undefined);
myProjects = projects;
}
private boolean checkAllProjectsAvailable(MantisConnectPortType soap) throws Exception {
// Check whether All Projects is available supported by server
try {
String version = soap.mc_version();
boolean available = !DEBUG_ALL_PROJECTS && VersionComparatorUtil.compare(version, "1.2.9") >= 0;
if (!available) {
LOG.info("Using Mantis version without 'All Projects' support: " + version);
}
return available;
}
catch (Exception e) {
throw handleException(e);
}
}
private Exception handleException(@NotNull Exception e) throws Exception {
if (e instanceof AxisFault) {
resetConfiguration();
throw new Exception(TaskBundle.message("failure.server.message", ((AxisFault)e).getFaultString()), e);
}
throw e;
}
@NotNull
private MantisConnectPortType createSoap() throws Exception {
if (isUseProxy()) {
for (KeyValue<String, String> pair : HttpConfigurable.getJvmPropertiesList(false, null)) {
String key = pair.getKey(), value = pair.getValue();
// Axis uses another names for username and password properties
// see http://axis.apache.org/axis/java/client-side-axis.html for complete list
if (key.equals(JavaProxyProperty.HTTP_USERNAME)) {
AxisProperties.setProperty("http.proxyUser", value);
}
else if (key.equals(JavaProxyProperty.HTTP_PASSWORD)) {
AxisProperties.setProperty("http.proxyPassword", value);
}
else {
AxisProperties.setProperty(key, value);
}
}
}
return new MantisConnectLocator().getMantisConnectPort(new URL(getUrl() + SOAP_API_LOCATION));
}
@Nullable
private IssueData fetchIssueById(@NotNull MantisConnectPortType soap, @NotNull String id) throws Exception {
try {
return soap.mc_issue_get(getUsername(), getPassword(), BigInteger.valueOf(Integer.valueOf(id)));
}
catch (RemoteException e) {
throw handleException(e);
}
}
@NotNull
private ProjectData[] fetchUserProjects(@NotNull MantisConnectPortType soap) throws Exception {
try {
return soap.mc_projects_get_user_accessible(getUsername(), getPassword());
}
catch (RemoteException e) {
throw handleException(e);
}
}
@NotNull
private FilterData[] fetchProjectFilters(@NotNull MantisConnectPortType soap, @NotNull MantisProject project) throws Exception {
try {
return soap.mc_filter_get(getUsername(), getPassword(), BigInteger.valueOf(project.getId()));
}
catch (RemoteException e) {
throw handleException(e);
}
}
@NotNull
private IssueHeaderData[] fetchProjectIssues(@NotNull MantisConnectPortType soap, @NotNull MantisProject project,
@NotNull MantisFilter filter, int pageNumber, int pageSize) throws Exception {
try {
if (filter.isUnspecified()) {
return soap.mc_project_get_issue_headers(getUsername(), getPassword(),
BigInteger.valueOf(project.getId()), BigInteger.valueOf(pageNumber),
BigInteger.valueOf(pageSize));
}
else {
return soap.mc_filter_get_issue_headers(getUsername(), getPassword(),
BigInteger.valueOf(project.getId()), BigInteger.valueOf(filter.getId()),
BigInteger.valueOf(pageNumber), BigInteger.valueOf(pageSize));
}
}
catch (RemoteException e) {
throw handleException(e);
}
}
private void resetConfiguration() {
if (myProjects != null) {
for (MantisProject project : myProjects) {
if (project.isUnspecified()) {
myCurrentProject = project;
for (MantisFilter filter : project.getFilters()) {
if (filter.isUnspecified()) {
myCurrentFilter = filter;
break;
}
}
break;
}
}
} else {
myCurrentProject = null;
myCurrentFilter = null;
}
}
@Nullable
public MantisProject getCurrentProject() {
return myCurrentProject;
}
public void setCurrentProject(@Nullable MantisProject currentProject) {
myCurrentProject = currentProject;
}
@Nullable
public MantisFilter getCurrentFilter() {
return myCurrentFilter;
}
public void setCurrentFilter(@Nullable MantisFilter currentFilter) {
myCurrentFilter = currentFilter;
}
@Override
public boolean isConfigured() {
return super.isConfigured() && StringUtil.isNotEmpty(myUsername) && StringUtil.isNotEmpty(myPassword);
}
@Override
public boolean equals(Object o) {
if (!super.equals(o)) return false;
MantisRepository repository = (MantisRepository)o;
if (!Comparing.equal(getCurrentProject(), repository.getCurrentProject())) return false;
if (!Comparing.equal(getCurrentFilter(), repository.getCurrentFilter())) return false;
if (!Comparing.equal(myProjects, repository.myProjects)) return false;
return myAllProjectsAvailable == repository.myAllProjectsAvailable;
}
@Override
protected int getFeatures() {
return super.getFeatures() & ~NATIVE_SEARCH;
}
}