blob: c6f6d7ff775037f38622f7006e1f2d7b50f8094c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2013 Ericsson AB 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
*
* Description:
*
* Contributors:
* Miles Parker, Tasktop Technologies - Initial API and Implementation
* Others (includes code modified from GerritConnector in org.eclipse.mylyn.gerit, see author annotations.)
*******************************************************************************/
package org.eclipse.mylyn.reviews.connector;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.mylyn.internal.reviews.connector.EmfCorePlugin;
import org.eclipse.mylyn.internal.tasks.core.RepositoryTaskHandleUtil;
import org.eclipse.mylyn.reviews.connector.EmfTaskSchema.FieldFeature;
import org.eclipse.mylyn.reviews.connector.client.EmfClient;
import org.eclipse.mylyn.reviews.connector.query.EmfQueryEngine;
import org.eclipse.mylyn.reviews.connector.query.QueryClause;
import org.eclipse.mylyn.reviews.connector.query.QueryException;
import org.eclipse.mylyn.reviews.connector.query.SimpleQueryEngine;
import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.ITaskMapping;
import org.eclipse.mylyn.tasks.core.RepositoryClientManager;
import org.eclipse.mylyn.tasks.core.RepositoryResponse;
import org.eclipse.mylyn.tasks.core.RepositoryResponse.ResponseKind;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema.Field;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskDataCollector;
import org.eclipse.mylyn.tasks.core.data.TaskMapper;
import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession;
import org.osgi.framework.Bundle;
/**
* Generic support for connectors to Emf models. Connector implementors should override, providing all meta-data.
*
* @author Miles Parker
*/
@SuppressWarnings("restriction")
public abstract class AbstractEmfConnector extends AbstractRepositoryConnector {
/**
* Unicode en-dash.
*/
public static final String EN_DASH = "\u2013"; //$NON-NLS-1$
public static final String ITEM_DELIM = "#"; //$NON-NLS-1$
public enum KeyStrategy {
EMF_SCHEMA_FIELD, OBJECT_ID, MEMBER_INDEX, XMI_ID
};
class EmfClientManager extends RepositoryClientManager<EmfClient, EmfConfiguration> {
public EmfClientManager() {
super(getCacheFile(), getConfigurationClass());
}
@Override
protected EmfClient createClient(TaskRepository repository, EmfConfiguration configuration) {
return AbstractEmfConnector.this.createClient(repository, configuration);
}
}
/**
* By delegating back to the EmfConnector for this case, we can greatly simplify API for consumers.
*/
class EmfTaskDataHandler extends AbstractTaskDataHandler {
@Override
public TaskAttributeMapper getAttributeMapper(TaskRepository repository) {
return AbstractEmfConnector.this.getAttributeMapper(repository);
}
@Override
public boolean initializeTaskData(TaskRepository repository, TaskData taskData,
ITaskMapping initializationData, IProgressMonitor monitor) {
return AbstractEmfConnector.this.initializeTaskData(repository, taskData, initializationData, monitor);
}
@Override
public RepositoryResponse postTaskData(TaskRepository repository, TaskData taskData,
Set<TaskAttribute> oldAttributes, IProgressMonitor monitor) throws CoreException {
return AbstractEmfConnector.this.postTaskData(repository, taskData, oldAttributes, monitor);
}
}
/**
* By delegating back to the EmfConnector for this case, we can greatly simplify API for consumers.
*/
public class EmfTaskSchemaDelegator extends EmfTaskSchema {
@Override
protected EClass[] getSchemaEClasses() {
return AbstractEmfConnector.this.getTaskClasses();
}
@Override
protected FieldFeature[] getSchemaPairs() {
return AbstractEmfConnector.this.getTaskFeatures();
}
}
public static final String EMF_ITEM_DELIM = "#"; //$NON-NLS-1$
private final EmfTaskDataHandler taskDataHandler = new EmfTaskDataHandler();
private RepositoryClientManager<EmfClient, EmfConfiguration> clientManager;
private EmfTaskSchema taskSchema;
private final File configurationCacheFile;
private final Set<QueryClause> queryClauses = new HashSet<QueryClause>();
public AbstractEmfConnector() {
IPath stateLocation = Platform.getStateLocation(EmfCorePlugin.getDefault().getBundle());
IPath cache = stateLocation.append("repositoryConfigurations"); //$NON-NLS-1$
configurationCacheFile = cache.toFile();
}
public Class<EmfConfiguration> getConfigurationClass() {
return EmfConfiguration.class;
}
/**
* Not supported yet.
*/
@Override
public boolean canCreateNewTask(TaskRepository arg0) {
return true;
}
@Override
public boolean canCreateTaskFromKey(TaskRepository arg0) {
return true;
}
public EmfClient getClient(TaskRepository repository) {
return getClientManager().getClient(repository);
}
public TaskAttributeMapper getAttributeMapper(TaskRepository repository) {
return getSchema().getAttributeMapper(repository);
}
public EmfAttributeMapper getEmfMapper(TaskRepository repository) {
return (EmfAttributeMapper) getAttributeMapper(repository);
}
public EmfQueryEngine getQueryEngine(TaskRepository repository) {
return new SimpleQueryEngine(this, repository);
}
public boolean initializeTaskData(TaskRepository repository, TaskData data, ITaskMapping initializationData,
IProgressMonitor monitor) {
getSchema().initialize(data);
String taskId = data.getTaskId();
data.getRoot()
.createAttribute(TaskAttribute.TASK_URL)
.setValue(getTaskUrl(repository.getRepositoryUrl(), taskId));
data.getRoot().createAttribute(TaskAttribute.TASK_KEY).setValue(encodeTaskKey(taskId));
return true;
}
public RepositoryResponse postTaskData(TaskRepository repository, TaskData taskData,
Set<TaskAttribute> oldAttributes, IProgressMonitor monitor) throws CoreException {
EmfClient client = getClient(repository);
client.open();
String taskId = taskData.getTaskId();
EObject emfTask;
if (taskId.equals("")) { //$NON-NLS-1$
EClass containedClass = getTaskClasses()[0];
@SuppressWarnings("unchecked")
EList<? super EObject> taskContainment = (EList<? super EObject>) client.getRootContainer().eGet(
getContainmentReference());
emfTask = client.create(containedClass, taskData);
taskContainment.add(emfTask);
//Emf implementation may create a key at model save time (e.g. XMLResource uuid implementation)
client.save();
taskId = getTaskKey(repository, emfTask);
if (StringUtils.isEmpty(taskId) || taskId.equals("0")) { //$NON-NLS-1$
taskId = getNextTaskId(client.getRootContainer());
if (taskId != null) {
TaskAttribute keyAttribute = taskData.getRoot().getAttribute(TaskAttribute.TASK_KEY);
keyAttribute.setValue(taskId);
oldAttributes.add(keyAttribute);
}
}
for (Field field : getSchema().getFields()) {
oldAttributes.add(taskData.getRoot().getAttribute(field.getKey()));
}
} else {
emfTask = getTaskObjectChecked(repository, taskId, monitor);
}
boolean emfDirty = false;
for (TaskAttribute staleAttribute : oldAttributes) {
String id = staleAttribute.getId();
TaskAttribute newAttribute = taskData.getRoot().getAttribute(id);
boolean isSet = newAttribute.getValues().size() > 0;
if (isSet) {
boolean setValueForKey = getEmfMapper(repository).copyTaskToEmf(newAttribute, emfTask);
emfDirty |= setValueForKey;
}
}
if (emfDirty) {
Date time = Calendar.getInstance().getTime();
EStructuralFeature dateModificationFeature = getSchema().getFeature(TaskAttribute.DATE_MODIFICATION);
emfTask.eSet(dateModificationFeature, time);
client.notifyChanged(emfTask);
}
return new RepositoryResponse(ResponseKind.TASK_UPDATED, taskId);
}
public TaskData createTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor) {
TaskData data = new TaskData(getAttributeMapper(repository), getConnectorKind(), repository.getRepositoryUrl(),
taskId);
initializeTaskData(repository, data, null, monitor);
return data;
}
public TaskData createPartialTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor) {
TaskData data = new TaskData(getAttributeMapper(repository), getConnectorKind(), repository.getRepositoryUrl(),
taskId);
//Only create summary and other mapped attributes now
getSchema().initialize(data);
data.setPartial(true);
return data;
}
/**
* Retrieves task data for the given review from repository.
*/
@Override
public TaskData getTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor)
throws CoreException {
EmfClient client = getClient(repository);
client.updateConfiguration(monitor);
EObject emfObject = getTaskObjectChecked(repository, taskId, monitor);
String id = getTaskKey(repository, emfObject);
TaskData data = createTaskData(repository, id, monitor);
EmfTaskSchema schema = getSchema();
for (Field field : schema.getFields()) {
EStructuralFeature feature = schema.getFeature(field.getKey());
//TODO support object references
if (feature instanceof EAttribute && !((EAttribute) feature).isMany()) {
EAttribute emfAttribute = (EAttribute) feature;
Object emfValue = emfObject.eGet(feature);
TaskAttribute taskAttribute = field.createAttribute(data.getRoot());
if (emfAttribute.getEAttributeType() instanceof EEnum) {
EEnum enumerator = (EEnum) emfAttribute.getEAttributeType();
for (EEnumLiteral literal : enumerator.getELiterals()) {
taskAttribute.putOption(literal.getLiteral(), literal.getName());
}
}
getEmfMapper(repository).copyEmfToTask(emfObject, taskAttribute);
if (emfValue != null) {
EFactory factory = emfAttribute.getEAttributeType().getEPackage().getEFactoryInstance();
String stringValue = factory.convertToString(emfAttribute.getEAttributeType(), emfValue);
taskAttribute.setValue(stringValue);
} else if (!emfObject.eIsSet(feature)) {
taskAttribute.clearValues();
} else {
taskAttribute.setValue(""); //$NON-NLS-1$
}
}
}
return data;
}
protected KeyStrategy getKeyStrategy() {
return KeyStrategy.EMF_SCHEMA_FIELD;
}
protected String getTaskKey(TaskRepository repository, EObject emfObject) {
String key = null;
switch (getKeyStrategy()) {
case EMF_SCHEMA_FIELD:
key = getEmfMapper(repository).getEmfString(emfObject, TaskAttribute.TASK_KEY);
break;
case OBJECT_ID:
key = EcoreUtil.getID(emfObject);
break;
case XMI_ID:
Resource resource = emfObject.eResource();
if (resource instanceof XMLResource) {
XMLResource xmlResource = (XMLResource) resource;
key = xmlResource.getID(emfObject);
}
break;
case MEMBER_INDEX:
EObject rootContainer = EcoreUtil.getRootContainer(emfObject);
String uriFragment = rootContainer.eResource().getURIFragment(emfObject);
key = uriFragment;
int dotIndex = key.indexOf('.');
if (dotIndex >= 0) {
key = key.substring(dotIndex + 1);
}
break;
}
return encodeTaskKey(key);
}
public EObject getTaskObject(TaskRepository repository, String taskId, IProgressMonitor monitor)
throws CoreException {
taskId = encodeTaskKey(taskId);
EmfClient client = getClient(repository);
client.open();
EObject container = client.getRootContainer();
EObject rootContainer = EcoreUtil.getRootContainer(container);
EObject referencedObject = rootContainer.eResource().getEObject(taskId);
taskId = encodeTaskKey(taskId);
if (referencedObject == null) {
for (Object object : ((List<?>) container.eGet(getContainmentReference()))) {
if (object instanceof EObject) {
EObject eObject = (EObject) object;
String stringValueForKey = getTaskKey(repository, eObject);
if (ObjectUtils.equals(stringValueForKey, taskId)) {
referencedObject = eObject;
break;
}
}
}
}
return referencedObject;
}
public EObject getTaskObjectChecked(TaskRepository repository, String taskId, IProgressMonitor monitor)
throws CoreException {
EObject emfObject = getTaskObject(repository, taskId, monitor);
if (emfObject == null) {
throw new CoreException(new Status(IStatus.WARNING, EmfCorePlugin.PLUGIN_ID,
"Couldn't locate task object for taskId: " + taskId //$NON-NLS-1$
));
}
return emfObject;
}
@Override
public AbstractTaskDataHandler getTaskDataHandler() {
return taskDataHandler;
}
/**
* Encode a task id to prevent use of handle delimiters.
*
* @param taskId
* @return
*/
public String encodeTaskKey(String taskId) {
return StringUtils.replace(taskId, RepositoryTaskHandleUtil.HANDLE_DELIM, EN_DASH);
}
/**
* Decode a task id to restore any handle delimiters.
*
* @param taskId
* @return
*/
public String decodeTaskKey(String taskId) {
return StringUtils.replace(taskId, EN_DASH, RepositoryTaskHandleUtil.HANDLE_DELIM);
}
@Override
public String getRepositoryUrlFromTaskUrl(String url) {
if (url == null) {
return null;
}
int i = url.indexOf(ITEM_DELIM);
if (i != -1) {
return url.substring(0, i);
}
return null;
}
@Override
public String getTaskIdFromTaskUrl(String url) {
if (url == null) {
return null;
}
int index = url.indexOf(ITEM_DELIM);
if (index > 0) {
String taskId = url.substring(index + ITEM_DELIM.length());
try {
taskId = URLDecoder.decode(taskId, "utf-8"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return taskId;
}
return null;
}
@Override
public String getTaskUrl(String repositoryUrl, String taskId) {
String encodedId = taskId;
try {
encodedId = URLEncoder.encode(taskId, "utf-8"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return repositoryUrl + ITEM_DELIM + encodedId;
}
@Override
public ITaskMapping getTaskMapping(TaskData taskData) {
return new TaskMapper(taskData);
}
@Override
public boolean hasTaskChanged(TaskRepository repository, ITask task, TaskData taskData) {
ITaskMapping taskMapping = getTaskMapping(taskData);
if (taskData.isPartial() && task.getModificationDate() != null) {
return false;
}
Date repositoryDate = taskMapping.getModificationDate();
Date localDate = task.getModificationDate();
boolean hasChanged = repositoryDate != null && repositoryDate.equals(localDate);
return hasChanged;
}
@Override
public IStatus performQuery(TaskRepository repository, IRepositoryQuery query, TaskDataCollector resultCollector,
ISynchronizationSession session, IProgressMonitor monitor) {
try {
EmfClient client = getClient(repository);
client.open();
List<EObject> results;
try {
results = getQueryEngine(repository).performQuery(query, monitor);
for (EObject taskObject : results) {
String id = encodeTaskKey(getTaskKey(repository, taskObject));
if (!StringUtils.isEmpty(id)) {
TaskData taskData = createPartialTaskData(repository, id, monitor);
resultCollector.accept(taskData);
}
}
return Status.OK_STATUS;
} catch (QueryException e) {
return new Status(IStatus.ERROR, EmfCorePlugin.PLUGIN_ID, "Problem occurred while executing query.", e); //$NON-NLS-1$
}
} catch (CoreException e) {
return e.getStatus();
} finally {
monitor.done();
}
}
@Override
public void updateRepositoryConfiguration(TaskRepository repository, IProgressMonitor monitor) throws CoreException {
getClient(repository).updateConfiguration(monitor);
}
@Override
public void updateTaskFromTaskData(TaskRepository taskRepository, ITask task, TaskData taskData) {
Date oldModificationDate = task.getModificationDate();
TaskMapper mapper = (TaskMapper) getTaskMapping(taskData);
// retain modification date to force an update when full task data is received
if (taskData.isPartial()) {
task.setModificationDate(oldModificationDate);
} else {
mapper.applyTo(task);
}
}
public IStatus validate(TaskRepository repository, IProgressMonitor monitor) throws CoreException {
return Status.OK_STATUS;
}
public RepositoryClientManager<EmfClient, EmfConfiguration> getClientManager() {
if (clientManager == null) {
clientManager = new EmfClientManager();
}
return clientManager;
}
public final EmfTaskSchema getSchema() {
if (taskSchema == null) {
taskSchema = createTaskSchema();
taskSchema.initialize();
}
return taskSchema;
}
/**
* Override to provide a custom task schema implementation. (The default Schema will provide values based on the
* connector meta-data .)
*
* @return
*/
public EmfTaskSchema createTaskSchema() {
return new EmfTaskSchemaDelegator();
}
public File getCacheFile() {
return configurationCacheFile;
}
/**
* Override to implement a custom EMF client. In most cases this should not be necessary.
*
* @param repository
* @param configuration
* @return
*/
protected EmfClient createClient(TaskRepository repository, EmfConfiguration configuration) {
return new EmfClient(repository, configuration) {
@Override
public AbstractEmfConnector getConnector() {
return AbstractEmfConnector.this;
}
};
}
public Set<QueryClause> getQueryClauses() {
return queryClauses;
}
public abstract String getNextTaskId(EObject taskContainer);
public abstract EAttribute getContentsNameAttribute();
public abstract EReference getContainmentReference();
/**
* The class to be used for task container. By default this is the clss defined by the containment reference.
*
* @return
*/
public EClass getContainerClass() {
return getContainmentReference().getEContainingClass();
}
public abstract EAttribute[] getSearchAttributes();
public abstract Bundle getConnectorBundle();
public abstract EClass[] getTaskClasses();
public abstract FieldFeature[] getTaskFeatures();
}