/*******************************************************************************
* Copyright (c) 2006 - 2006 Mylar eclipse.org project and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Mylar project committers - initial API and implementation
*******************************************************************************/
/*******************************************************************************
* Copyright (c) 2007, 2008 - 2007 IT Solutions, Inc. and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Chris Hane - adapted Trac implementation for Mantis
* David Carver - STAR - fixed issue with background synchronization of repository.
* David Carver - STAR - Migrated to Mylyn 3.0
*******************************************************************************/
package com.itsolut.mantis.core;
import static com.itsolut.mantis.core.MantisAttributeMapper.Attribute.PROJECT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.net.Policy;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.tasks.core.*;
import org.eclipse.mylyn.tasks.core.data.*;
import org.eclipse.mylyn.tasks.core.data.TaskRevision.Change;
import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.itsolut.mantis.core.exception.MantisException;
import com.itsolut.mantis.core.model.MantisIssueHistory;
import com.itsolut.mantis.core.model.MantisIssueHistoryEntry;
import com.itsolut.mantis.core.model.MantisTicket;
import com.itsolut.mantis.core.model.MantisUser;
import com.itsolut.mantis.core.util.MantisUtils;
/**
* @author Dave Carver - STAR - Standards for Technology in Automotive Retail
* @author Chris Hane
*/
@SuppressWarnings("restriction")
public class MantisRepositoryConnector extends AbstractRepositoryConnector {
private final static String CLIENT_LABEL = "MantisBT (supports 1.1 or later)";
private static final String TASK_ATTRIBUTE_MANTIS_VERSION = "mantis.version";
// increment when the ITask structure changes in an incompatible way to force tasks to be refreshed
private static final String TASK_VALUE_MANTIS_VERSION_CURRENT = "1";
@Inject
private IMantisClientManager clientManager;
@Inject
private MantisTaskDataHandler offlineTaskHandler;
@Inject
private MantisAttachmentHandler attachmentHandler;
@Inject
private Tracer tracer;
@Inject
private StatusFactory statusFactory;
public MantisRepositoryConnector() {
Injector injector = Guice.createInjector(new MantisCorePluginModule(this));
injector.injectMembers(this);
injector.injectMembers(MantisCorePlugin.getDefault());
}
protected MantisRepositoryConnector(final Module dummy) {
}
/**
* <b>Visible for testing only</b>
*/
public MantisRepositoryConnector(IMantisClientManager clientManager, MantisTaskDataHandler taskDataHandler,
MantisAttachmentHandler attachmentHandler, StatusFactory statusFactory, Tracer tracer) {
this.clientManager = clientManager;
offlineTaskHandler = taskDataHandler;
this.attachmentHandler = attachmentHandler;
this.statusFactory = statusFactory;
this.tracer = tracer;
}
@Override
public boolean canCreateNewTask(TaskRepository repository) {
return true;
}
@Override
public boolean canCreateTaskFromKey(TaskRepository repository) {
return true;
}
@Override
public String getLabel() {
return CLIENT_LABEL;
}
@Override
public String getConnectorKind() {
return MantisCorePlugin.REPOSITORY_KIND;
}
@Override
public String getRepositoryUrlFromTaskUrl(String url) {
// There is no way of knowing the proper URL for the repository
// so we return at least a common prefix which should be good
// enough for TaskRepositoryManager#getConnectorForRepositoryTaskUrl
if (url == null)
return null;
return MantisRepositoryLocations.create(url).getBaseRepositoryLocation();
}
@Override
public String getTaskIdFromTaskUrl(String url) {
if (url == null)
return null;
Integer taskId = MantisRepositoryLocations.extractTaskId(url);
if ( taskId == null )
return null;
return taskId.toString();
}
@Override
public String getTaskUrl(String repositoryUrl, String taskId) {
if ( repositoryUrl == null || taskId == null )
return null;
try {
return MantisRepositoryLocations.create(repositoryUrl).getTaskLocation(Integer.valueOf(taskId));
} catch (NumberFormatException e) {
return null;
}
}
@Override
public AbstractTaskAttachmentHandler getTaskAttachmentHandler() {
return this.attachmentHandler;
}
@Override
public AbstractTaskDataHandler getTaskDataHandler() {
return offlineTaskHandler;
}
@Override
public IStatus performQuery(TaskRepository repository, IRepositoryQuery query, TaskDataCollector resultCollector,
ISynchronizationSession event, IProgressMonitor monitor) {
try {
final List<MantisTicket> tickets = new ArrayList<MantisTicket>();
IMantisClient client;
try {
client = clientManager.getRepository(repository);
client.search(MantisUtils.getMantisSearch(query), tickets, monitor);
for (MantisTicket ticket : tickets) {
ticket.setLastChanged(null); // XXX Remove once we have a fix for
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=331733
resultCollector.accept(offlineTaskHandler.createTaskDataFromPartialTicket(client, repository,
ticket, monitor));
}
} catch (MantisException e) {
return statusFactory.toStatus(null, e, repository);
} catch (CoreException e) {
return e.getStatus();
}
return Status.OK_STATUS;
} finally {
monitor.done();
}
}
@Override
public String getTaskIdPrefix() {
return "#";
}
// For the repositories, perform the queries to get the latest information
// about the
// tasks. This allows the connector to get a limited list of items instead
// of every
// item in the repository. Next check to see if the tasks have changed since
// the
// last synchronization. If so, add their ids to a List.
/**
* Gets the changed tasks for a given query
*
* <p>For the <tt>repository</tt>, run the <tt>query</tt> to get the latest information about the
* tasks. This allows the connector to get a limited list of items instead of every item in the
* repository. Next check to see if the tasks have changed since the last synchronization. If
* so, add their ids to a List.</p>
*
* @param monitor
* @return the ids of the changed tasks
* @throws CoreException
*/
private List<Integer> getChangedTasksByQuery(IRepositoryQuery query, TaskRepository repository, Date since,
IProgressMonitor monitor) throws CoreException {
tracer.trace(TraceLocation.SYNC, "Looking for tasks changed in query {0} since {1} .", query.getSummary(), since);
final List<MantisTicket> tickets = new ArrayList<MantisTicket>();
List<Integer> changedTickets = new ArrayList<Integer>();
IMantisClient client;
try {
client = clientManager.getRepository(repository);
client.search(MantisUtils.getMantisSearch(query), tickets, Policy.subMonitorFor(monitor, 1));
for (MantisTicket ticket : tickets)
if (ticket.getLastChanged() != null && ticket.getLastChanged().compareTo(since) > 0)
changedTickets.add(Integer.valueOf(ticket.getId()));
} catch (MantisException e) {
throw new CoreException(statusFactory.toStatus("Failed getting changed tasks.", e, repository));
}
tracer.trace(TraceLocation.SYNC, "Found {0} changed tickets.", changedTickets.size());
return changedTickets;
}
@Override
public void updateRepositoryConfiguration(TaskRepository repository, IProgressMonitor monitor) throws CoreException {
try {
IMantisClient client = clientManager.getRepository(repository);
client.updateAttributes(monitor);
MantisRepositoryConfiguration.setSupportsSubTasks(repository, client.getCache(monitor).getRepositoryVersion().isHasProperTaskRelations());
} catch (MantisException e) {
throw new CoreException(statusFactory.toStatus("Could not update attributes", e, repository));
}
}
@Override
public void updateRepositoryConfiguration(TaskRepository taskRepository, ITask task, IProgressMonitor monitor) throws CoreException {
if ( task == null ) {
updateRepositoryConfiguration(taskRepository, monitor);
return;
}
try {
clientManager.getRepository(taskRepository).updateAttributesForTask(monitor, Integer.valueOf(task.getTaskId()));
} catch (MantisException e) {
throw new CoreException(statusFactory.toStatus("Could not update attributes", e, taskRepository));
}
}
@Override
public TaskData getTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor)
throws CoreException {
try {
monitor.beginTask("", IProgressMonitor.UNKNOWN);
return offlineTaskHandler.getTaskData(repository, taskId, monitor);
} finally {
monitor.done();
}
}
// Based off of Trac Implementation.
@Override
public boolean hasTaskChanged(TaskRepository taskRepository, ITask task, TaskData taskData) {
TaskAttribute attrModification = taskData.getRoot().getMappedAttribute(TaskAttribute.DATE_MODIFICATION);
if (!MantisUtils.hasValue(attrModification))
return false;
// detect if any of the tasks has and old version
boolean taskVersionIsCurrent = TASK_VALUE_MANTIS_VERSION_CURRENT.equals(task.getAttribute(TASK_ATTRIBUTE_MANTIS_VERSION));
Date lastKnownUpdated = task.getModificationDate();
Date modified = taskData.getAttributeMapper().getDateValue(attrModification);
boolean lastChangeIsDifferent = !MantisUtils.equal(lastKnownUpdated, modified);
boolean hasChanged = lastChangeIsDifferent || !taskVersionIsCurrent;
tracer.trace(TraceLocation.SYNC, "Checking if task {0} has changed: {1}", task.getTaskId(), hasChanged);
return hasChanged;
}
@Override
public void updateTaskFromTaskData(TaskRepository repository, ITask task, TaskData taskData) {
TaskMapper scheme = getTaskMapper(taskData);
scheme.applyTo(task);
task.setCompletionDate(scheme.getCompletionDate());
task.setUrl(getTaskUrl(repository.getRepositoryUrl(), taskData.getTaskId()));
task.setAttribute(PROJECT.getKey(), taskData.getRoot().getAttribute(PROJECT.getKey()).getValue());
task.setAttribute(TASK_ATTRIBUTE_MANTIS_VERSION, TASK_VALUE_MANTIS_VERSION_CURRENT);
}
public TaskMapper getTaskMapper(final TaskData taskData) {
return new MantisTaskMapper(taskData);
}
/**
* Returns the client manager, for internal use in the UI module only.
*
* <p><b>For internal use only</b></p>
*
* @return the client manager instance
*/
public IMantisClientManager getClientManager() {
return clientManager;
}
/**
* Returns the repository change listener, for internal use in the UI module only.
*
* <p><b>For internal use only</b></p>
*
* @return the client manager instance
*/
public IRepositoryListener getRepositoryListener() {
return (IRepositoryListener) clientManager;
}
@Override
public ITaskMapping getTaskMapping(TaskData taskData) {
return getTaskMapper(taskData);
}
@Override
public void preSynchronization(ISynchronizationSession event, IProgressMonitor monitor) throws CoreException {
// No Tasks, don't contact the repository
if (event.getTasks().isEmpty()) {
return;
}
TaskRepository repository = event.getTaskRepository();
if (repository.getSynchronizationTimeStamp() == null || repository.getSynchronizationTimeStamp().length() == 0) {
for (ITask task : event.getTasks())
event.markStale(task);
return;
}
Date since = new Date(0);
try {
if (repository.getSynchronizationTimeStamp().length() > 0)
since = MantisUtils.parseDate(Long.valueOf(repository.getSynchronizationTimeStamp()));
} catch (NumberFormatException e) {
MantisCorePlugin.warn("Failed parsing repository synchronisationTimestamp " + repository.getSynchronizationTimeStamp() + " .", e);
}
// Run the queries to get the list of tasks currently meeting the query
// criteria. The result returned are only the ids that have changed.
// Next checkt to see if any of these ids matches the ones in the
// task list. If so, then set it to stale.
//
// The prior implementation retireved each id individually, and
// checked it's date, this caused unnecessary SOAP traffic during
// synchronization.
event.setNeedsPerformQueries(false);
List<IRepositoryQuery> queries = getMantisQueriesFor(repository);
monitor.beginTask("", queries.size() * 2); // 1 for query, 1 for search call
for (IRepositoryQuery query : queries) {
for (Integer taskId : getChangedTasksByQuery(query, repository, since, monitor)) {
for (ITask task : event.getTasks()) {
if (Integer.parseInt(task.getTaskId()) == taskId.intValue()) {
event.setNeedsPerformQueries(true);
event.markStale(task);
tracer.trace(TraceLocation.SYNC, "Marking task {0} as stale.", task);
}
}
}
monitor.worked(1);
}
monitor.done();
}
private List<IRepositoryQuery> getMantisQueriesFor(TaskRepository taskRespository) {
List<IRepositoryQuery> queries = new ArrayList<IRepositoryQuery>();
for (IRepositoryQuery query : TasksUiInternal.getTaskList().getQueries()) {
boolean isMantisQuery = MantisCorePlugin.REPOSITORY_KIND.equals(query.getConnectorKind());
boolean belongsToThisRepository = query.getRepositoryUrl().equals(taskRespository.getUrl());
if (isMantisQuery && belongsToThisRepository) {
queries.add(query);
}
}
return queries;
}
@Override
public void postSynchronization(ISynchronizationSession event, IProgressMonitor monitor) throws CoreException {
try {
monitor.beginTask("", 1);
if (event.isFullSynchronization()) {
Date date = getSynchronizationTimestamp(event);
tracer.trace(TraceLocation.SYNC, "Synchronisation timestamp from event for {0} is {1} .", event.getTaskRepository(), date);
if (date != null) {
event.getTaskRepository().setSynchronizationTimeStamp(MantisUtils.toMantisTime(date) + "");
} else {
event.getTaskRepository().setSynchronizationTimeStamp(MantisUtils.toMantisTime(new Date()) + "");
}
}
} catch (RuntimeException e) {
event.getTaskRepository().setSynchronizationTimeStamp(MantisUtils.toMantisTime(new Date()) + "");
throw new CoreException(statusFactory.toStatus(null, e, event.getTaskRepository()));
} finally {
monitor.done();
}
}
private Date getSynchronizationTimestamp(ISynchronizationSession event) {
Date mostRecent = new Date(0);
Date mostRecentTimeStamp = null;
if (event.getTaskRepository().getSynchronizationTimeStamp() == null) {
mostRecentTimeStamp = mostRecent;
} else {
mostRecentTimeStamp = MantisUtils.parseDate(Long.parseLong(event.getTaskRepository()
.getSynchronizationTimeStamp()));
}
for (ITask task : event.getChangedTasks()) {
Date taskModifiedDate = task.getModificationDate();
if (taskModifiedDate != null && taskModifiedDate.after(mostRecent)) {
mostRecent = taskModifiedDate;
mostRecentTimeStamp = task.getModificationDate();
}
}
return mostRecentTimeStamp;
}
@Override
public Collection<TaskRelation> getTaskRelations(TaskData taskData) {
TaskAttribute parentTasksAttribute = taskData.getRoot().getAttribute(
MantisAttributeMapper.Attribute.PARENT_OF.getKey());
TaskAttribute childTasksAttribute = taskData.getRoot().getAttribute(
MantisAttributeMapper.Attribute.CHILD_OF.getKey());
if (parentTasksAttribute == null && childTasksAttribute == null)
return null;
List<TaskRelation> relations = new ArrayList<TaskRelation>();
if (parentTasksAttribute != null)
for (String taskId : parentTasksAttribute.getValues())
relations.add(TaskRelation.subtask(taskId));
if (childTasksAttribute != null)
for (String taskId : childTasksAttribute.getValues())
relations.add(TaskRelation.parentTask(taskId));
return relations;
}
@Override
public boolean hasRepositoryDueDate(TaskRepository taskRepository, ITask task, TaskData taskData) {
return taskData.getRoot().getAttribute(MantisAttributeMapper.Attribute.DUE_DATE.getKey()) != null;
}
@Override
public boolean canDeleteTask(TaskRepository repository, ITask task) {
return true;
}
@Override
public IStatus deleteTask(TaskRepository repository, ITask task, IProgressMonitor monitor) throws CoreException {
monitor = Policy.subMonitorFor(monitor, 1);
monitor.beginTask("Deleting task with id " + task.getTaskId(), 1);
try {
clientManager.getRepository(repository).deleteTicket(Integer.parseInt(task.getTaskId()), monitor);
return Status.OK_STATUS;
} catch (MantisException e) {
return statusFactory.toStatus("Failed deleting task with id " + task.getTaskId() + " : " + e.getMessage(), e, repository);
} finally {
monitor.done();
}
}
@Override
public RepositoryInfo validateRepository(TaskRepository repository, IProgressMonitor monitor) throws CoreException {
try {
IMantisClient client = clientManager.getRepository(repository);
RepositoryValidationResult result = client.validate(monitor);
RepositoryVersion version = RepositoryVersion.fromVersionString(result.getVersion());
if ( !version.getMissingCapabilities().isEmpty() ) {
StringBuilder message = new StringBuilder();
message.append("You are using a version in the range ").append(version.getDescription()).append(" which has known problems : ");
for ( RepositoryCapability capability : version.getMissingCapabilities() )
message.append(capability.getDescriptionForMissingCapability()).append(" ,");
message.deleteCharAt(message.length() -1);
message.append(". Please consider upgrading to the latest stable version.");
throw new CoreException(RepositoryStatus.createStatus(repository.getRepositoryUrl(), IStatus.WARNING, MantisCorePlugin.PLUGIN_ID, message.toString()));
}
return new RepositoryInfo(new org.eclipse.mylyn.tasks.core.RepositoryVersion(result.getVersion()));
} catch (MantisException e) {
throw new CoreException(statusFactory.toStatus("Failed validating connection to the task repository : " + e.getMessage(), e, repository));
}
}
@Override
public boolean canGetTaskHistory(TaskRepository repository, ITask task) {
try {
IMantisClient client = clientManager.getRepository(repository);
// use the quick/unsafe version since we can't block the UI at this point
MantisCacheData cacheData = client.getCacheData();
if ( cacheData == null ) {
return false;
}
RepositoryVersion version = cacheData.repositoryVersion;
return version.isHasIssueHistorySupport();
} catch (MantisException e) {
MantisCorePlugin.getDefault().getLog().log(statusFactory.toStatus("Error while trying running 'canGetTaskHistory'", e, repository));
return false;
}
}
@Override
public TaskHistory getTaskHistory(TaskRepository repository, ITask task,
IProgressMonitor monitor) throws CoreException {
try {
IMantisClient client = clientManager.getRepository(repository);
MantisIssueHistory mantisHistory = client.getHistory(Integer.parseInt(task.getTaskId()), monitor);
TaskHistory history = new TaskHistory(repository, task);
for ( int i = 0 ; i < mantisHistory.getEntries().size(); i++) {
MantisIssueHistoryEntry historyEntry = mantisHistory.getEntries().get(i);
IRepositoryPerson author = repository.createPerson(historyEntry.getAuthor());
MantisUser mantisUser = client.getCache(monitor).getUserByUsername(author.getPersonId());
if ( mantisUser != null ) {
author.setName(mantisUser.getRealName());
}
TaskRevision rev = new TaskRevision(String.valueOf(i), historyEntry.getDate(), author);
// TODO - proper label
rev.add(new Change(historyEntry.getField(), historyEntry.getField(), historyEntry.getOldValue(), historyEntry.getNewValue()));
history.add(rev);
}
return history;
} catch (MantisException e) {
throw new CoreException(statusFactory.toStatus("Failed retrieving issue history: " + e.getMessage(), e, repository));
}
}
}