org.eclipse.mylyn.reviews.connector.AbstractEmfConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.mylyn.reviews.connector.AbstractEmfConnector.java

Source

/*******************************************************************************
 * 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.", //$NON-NLS-1$
                        e);
            }
        } 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();
}