blob: 416a7158eb260716d360f4e93bef8577fe0695d1 [file] [log] [blame]
/*
* Copyright 2000-2010 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;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.xmlb.annotations.Attribute;
import com.intellij.util.xmlb.annotations.Tag;
import com.intellij.util.xmlb.annotations.Transient;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.concurrent.Callable;
/**
* This class describes bug-tracking server.
* Do not forget to mark your implementation with {@link Tag} annotation to make it persistent.
*
* @author Dmitry Avdeev
* @see TaskRepositoryType
* @see com.intellij.tasks.impl.BaseRepository
*/
@Tag("server")
public abstract class TaskRepository {
protected static final int NO_FEATURES = 0;
public static final int BASIC_HTTP_AUTHORIZATION = 0x0001;
public static final int LOGIN_ANONYMOUSLY = 0x0002;
public static final int TIME_MANAGEMENT = 0x0004;
public static final int STATE_UPDATING = 0x0008;
/**
* Supporting this feature means that server implements some kind of issues filtering.
* It may be special query language like the one used in YouTrack or mere plain
* text search.
* <p/>
* If server supports this feature it MUST return tasks already filtered according
* to {@code query} parameter from {@link #getIssues}} method, otherwise they will
* be filtered internally in {@link TaskManager#getIssues}.
*/
public static final int NATIVE_SEARCH = 0x0010;
/**
* URL of the server to be used in requests. For more human-readable name of repository (e.g. some imaginary URL containing name of
* selected project), that will be used in settings, use {@link #getPresentableName()}.
*
* @return URL of the server
* @see #getPresentableName()
*/
@Attribute("url")
public String getUrl() {
return trimTrailingSlashes(myUrl);
}
public void setUrl(String url) {
myUrl = trimTrailingSlashes(url);
}
public boolean isConfigured() {
return StringUtil.isNotEmpty(getUrl());
}
/**
* Shared repositories will be visible in visible in other projects, but only their URL will be initialized there.
*
* @return whether repository is shared
*/
@Attribute("shared")
public boolean isShared() {
return myShared;
}
public void setShared(boolean shared) {
myShared = shared;
}
/**
* @return name of this repository, that will be shown in settings
*/
public String getPresentableName() {
return StringUtil.isEmpty(getUrl()) ? "<undefined>" : getUrl();
}
public Icon getIcon() {
return getRepositoryType().getIcon();
}
/**
* @see #createCancellableConnection()
* @deprecated
*/
public void testConnection() throws Exception {
}
/**
* Returns an object that can test connection.
* {@link com.intellij.openapi.vcs.impl.CancellableRunnable#cancel()} should cancel the process.
*
* @return null if not supported
*/
@Nullable
public CancellableConnection createCancellableConnection() {
return null;
}
/**
* Get issues from the repository. If query is null, return issues should assigned to current user only.
* If server supports {@link #NATIVE_SEARCH} feature, tasks returned MUST be filtered by specified query.
*
* @param query repository specific.
* @param max maximum issues number to return
* @param since last updated timestamp. If 0, all issues should be returned.
* @return found issues
* @throws Exception
* @deprecated To be removed in IDEA 14. Use {@link #getIssues(String, int, int, boolean)} instead.
*/
@Deprecated
public Task[] getIssues(@Nullable String query, int max, long since) throws Exception {
throw new UnsupportedOperationException("Deprecated: should not be called");
}
/**
* @deprecated To be removed in IDEA 14. Use {@link #getIssues(String, int, int, boolean, ProgressIndicator)} instead.
*/
@Deprecated
public Task[] getIssues(@Nullable String query, int max, long since, @NotNull ProgressIndicator cancelled) throws Exception {
return getIssues(query, max, since);
}
/**
* Retrieve tasks from server using its own pagination capabilities and also filtering out closed issues.
* <p/>
* Previously used approach with filtering tasks on client side leads to non-filled up popup in "Open Task" action and, as result,
* missing tasks and various issues with caching.
*
* @param query arbitrary search query, possibly provided by user for search. It may utilize server specific query language.
* @param offset index of the first issue to return
* @param limit maximum number of issues returned by server in this request (or number of issues per page in some interpretations)
* @param withClosed whether to include closed (e.g. fixed/resolved) issues to response
* @return found tasks
* @throws Exception
*/
public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed) throws Exception {
return getIssues(query, offset + limit, 0);
}
public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed, @NotNull ProgressIndicator cancelled)
throws Exception {
return getIssues(query, offset, limit, withClosed);
}
/**
* @param id task ID. Don't forget to define {@link #extractId(String)}, if your server uses not <tt>PROJECT-123</tt> format for task IDs.
* @return found task or {@code null} otherwise. Basically you should return {@code null} on e.g. 404 error and throw exception with
* information about failure in other cases.
* @throws Exception
*/
@Nullable
public abstract Task findTask(@NotNull String id) throws Exception;
@NotNull
public abstract TaskRepository clone();
@Nullable
public abstract String extractId(@NotNull String taskName);
/**
* Update state of the task on server. Don't forget to add {@link #STATE_UPDATING} in {@link #getFeatures()} and
* supported states in {@link TaskRepositoryType#getPossibleTaskStates()}.
*
* @param task issue to update
* @param state new state of the issue
* @see com.intellij.tasks.TaskRepositoryType#getPossibleTaskStates()
* @see com.intellij.tasks.TaskRepository#getFeatures()
*/
public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception {
throw new UnsupportedOperationException("Setting task to state " + state + " is not supported");
}
// for serialization
public TaskRepository() {
myType = null;
}
public TaskRepository(TaskRepositoryType type) {
myType = type;
}
protected TaskRepository(TaskRepository other) {
myType = other.myType;
myShared = other.isShared();
myUrl = other.getUrl();
setShouldFormatCommitMessage(other.myShouldFormatCommitMessage);
setCommitMessageFormat(other.myCommitMessageFormat);
}
private boolean myShared;
private String myUrl = "";
private TaskRepositoryType myType;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TaskRepository)) return false;
TaskRepository that = (TaskRepository)o;
if (!Comparing.equal(myType, that.myType)) return false;
if (isShared() != that.isShared()) return false;
if (getUrl() != null ? !getUrl().equals(that.getUrl()) : that.getUrl() != null) return false;
if (getCommitMessageFormat() != null
? !getCommitMessageFormat().equals(that.getCommitMessageFormat())
: that.getCommitMessageFormat() != null) {
return false;
}
return isShouldFormatCommitMessage() == that.isShouldFormatCommitMessage();
}
@Transient
public final TaskRepositoryType getRepositoryType() {
return myType;
}
public final void setRepositoryType(TaskRepositoryType type) {
myType = type;
}
public boolean isShouldFormatCommitMessage() {
return myShouldFormatCommitMessage;
}
public void setShouldFormatCommitMessage(final boolean shouldFormatCommitMessage) {
myShouldFormatCommitMessage = shouldFormatCommitMessage;
}
@Tag("commitMessageFormat")
public String getCommitMessageFormat() {
return myCommitMessageFormat;
}
public void setCommitMessageFormat(@NotNull String commitMessageFormat) {
myCommitMessageFormat = commitMessageFormat;
}
protected boolean myShouldFormatCommitMessage;
protected String myCommitMessageFormat = "{id} {summary}";
private static String trimTrailingSlashes(String url) {
if (url == null) return "";
for (int i = url.length() - 1; i >= 0; i--) {
if (url.charAt(i) != '/') {
return url.substring(0, i + 1);
}
}
return "";
}
@Nullable
public String getTaskComment(@NotNull Task task) {
return isShouldFormatCommitMessage()
? myCommitMessageFormat.replace("{id}", task.getId()).replace("{summary}", task.getSummary())
: null;
}
public String getComment() {
return "{id} (e.g. FOO-001), {summary}, {number} (e.g. 001), {project} (e.g. FOO)";
}
public void updateTimeSpent(@NotNull LocalTask task, @NotNull String timeSpent, @NotNull String comment) throws Exception {
throw new UnsupportedOperationException();
}
public abstract static class CancellableConnection implements Callable<Exception> {
@Nullable
@Override
public final Exception call() {
try {
doTest();
return null;
}
catch (Exception e) {
return e;
}
}
protected abstract void doTest() throws Exception;
public abstract void cancel();
}
public boolean isSupported(
@MagicConstant(
flags = {
NO_FEATURES,
BASIC_HTTP_AUTHORIZATION,
LOGIN_ANONYMOUSLY,
STATE_UPDATING,
TIME_MANAGEMENT,
NATIVE_SEARCH}
) int feature) {
return (getFeatures() & feature) != 0;
}
@MagicConstant(
flags = {
NO_FEATURES,
BASIC_HTTP_AUTHORIZATION,
LOGIN_ANONYMOUSLY,
STATE_UPDATING,
TIME_MANAGEMENT,
NATIVE_SEARCH})
protected int getFeatures() {
return NATIVE_SEARCH;
}
}