org.eclipse.che.api.project.server.EditorWorkingCopyManager.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.api.project.server.EditorWorkingCopyManager.java

Source

/*
 * Copyright (c) 2012-2017 Red Hat, Inc.
 * 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:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.api.project.server;

import static java.lang.String.format;
import static java.nio.charset.Charset.defaultCharset;
import static org.eclipse.che.api.project.shared.Constants.CHE_DIR;

import com.google.common.hash.Hashing;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.eclipse.che.api.project.shared.dto.EditorChangesDto;
import org.eclipse.che.api.project.shared.dto.ServerError;
import org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto;
import org.eclipse.che.api.vfs.impl.file.event.detectors.FileTrackingOperationEvent;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.dto.server.DtoFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The class contains methods to simplify the work with editor working copies.
 *
 * @author Roman Nikitenko
 */
@Singleton
public class EditorWorkingCopyManager {
    private static final Logger LOG = LoggerFactory.getLogger(EditorWorkingCopyManager.class);
    private static final String WORKING_COPIES_DIR = "/" + CHE_DIR + "/workingCopies";
    private static final String WORKING_COPY_ERROR_METHOD = "track:editor-working-copy-error";

    private Provider<ProjectManager> projectManagerProvider;
    private EventService eventService;
    private RequestTransmitter transmitter;
    private EventSubscriber<FileTrackingOperationEvent> fileOperationEventSubscriber;

    private final Map<String, EditorWorkingCopy> workingCopiesStorage = new HashMap<>();

    @Inject
    public EditorWorkingCopyManager(Provider<ProjectManager> projectManagerProvider, EventService eventService,
            RequestTransmitter transmitter) {
        this.projectManagerProvider = projectManagerProvider;
        this.eventService = eventService;
        this.transmitter = transmitter;

        fileOperationEventSubscriber = new EventSubscriber<FileTrackingOperationEvent>() {
            @Override
            public void onEvent(FileTrackingOperationEvent event) {
                onFileOperation(event.getEndpointId(), event.getFileTrackingOperation());
            }
        };
        eventService.subscribe(fileOperationEventSubscriber);
    }

    /**
     * Gets in-memory working copy by path to the original file. Note: returns {@code null} when
     * working copy is not found
     *
     * @param filePath path to the original file
     * @return in-memory working copy for the file which corresponds given {@code filePath} or {@code
     *     null} when working copy is not found
     */
    @Nullable
    public EditorWorkingCopy getWorkingCopy(String filePath) {
        return workingCopiesStorage.get(filePath);
    }

    void onEditorContentUpdated(String endpointId, EditorChangesDto changes) {
        String filePath = changes.getFileLocation();
        String projectPath = changes.getProjectPath();

        try {
            if (filePath.isEmpty() || projectPath.isEmpty()) {
                throw new NotFoundException("Paths for file and project should be defined");
            }

            EditorWorkingCopy workingCopy = workingCopiesStorage.get(filePath);
            if (workingCopy == null) {
                workingCopy = createWorkingCopy(filePath);
            }

            workingCopy.applyChanges(changes);
            eventService.publish(new EditorWorkingCopyUpdatedEvent(endpointId, changes));

        } catch (IOException | ForbiddenException | ConflictException | ServerException e) {
            String errorMessage = "Can not handle editor changes: " + e.getLocalizedMessage();

            LOG.error(errorMessage);

            transmitError(500, errorMessage, endpointId);
        } catch (NotFoundException e) {
            String errorMessage = "Can not handle editor changes: " + e.getLocalizedMessage();

            LOG.error(errorMessage);

            transmitError(400, errorMessage, endpointId);
        }
    }

    private void onFileOperation(String endpointId, FileTrackingOperationDto operation) {
        try {
            FileTrackingOperationDto.Type type = operation.getType();
            switch (type) {
            case START: {
                String path = operation.getPath();
                EditorWorkingCopy workingCopy = workingCopiesStorage.get(path);
                if (workingCopy == null) {
                    createWorkingCopy(path);
                }
                //TODO At opening file we can have persistent working copy when user has unsaved data
                // at this case we need provide ability to recover unsaved data
                break;
            }
            case STOP: {
                String path = operation.getPath();
                EditorWorkingCopy workingCopy = workingCopiesStorage.get(path);
                if (workingCopy == null) {
                    return;
                }

                if (isWorkingCopyHasUnsavedData(path)) {
                    createPersistentWorkingCopy(path); //to have ability to recover unsaved data when the file will be open later
                } else {
                    VirtualFileEntry persistentWorkingCopy = getPersistentWorkingCopy(path,
                            workingCopy.getProjectPath());
                    if (persistentWorkingCopy != null) {
                        persistentWorkingCopy.remove();
                    }
                }
                workingCopiesStorage.remove(path);
                break;
            }

            case MOVE: {
                String oldPath = operation.getOldPath();
                String newPath = operation.getPath();

                EditorWorkingCopy workingCopy = workingCopiesStorage.remove(oldPath);
                if (workingCopy == null) {
                    return;
                }

                String workingCopyNewPath = toWorkingCopyPath(newPath);
                workingCopy.setPath(workingCopyNewPath);
                workingCopiesStorage.put(newPath, workingCopy);

                String projectPath = workingCopy.getProjectPath();
                VirtualFileEntry persistentWorkingCopy = getPersistentWorkingCopy(oldPath, projectPath);
                if (persistentWorkingCopy != null) {
                    persistentWorkingCopy.remove();
                }
                break;
            }

            default: {
                break;
            }
            }
        } catch (ServerException | IOException | ForbiddenException | ConflictException e) {
            String errorMessage = "Can not handle file operation: " + e.getMessage();

            LOG.error(errorMessage);

            transmitError(500, errorMessage, endpointId);
        } catch (NotFoundException e) {
            String errorMessage = "Can not handle file operation: " + e.getMessage();

            LOG.error(errorMessage);

            transmitError(400, errorMessage, endpointId);
        }
    }

    private void transmitError(int code, String errorMessage, String endpointId) {
        DtoFactory dtoFactory = DtoFactory.getInstance();
        ServerError error = dtoFactory.createDto(ServerError.class).withCode(code).withMessage(errorMessage);
        transmitter.newRequest().endpointId(endpointId).methodName(WORKING_COPY_ERROR_METHOD).paramsAsDto(error)
                .sendAndSkipResult();
    }

    private boolean isWorkingCopyHasUnsavedData(String originalFilePath) {
        try {
            EditorWorkingCopy workingCopy = workingCopiesStorage.get(originalFilePath);
            if (workingCopy == null) {
                return false;
            }

            FileEntry originalFile = projectManagerProvider.get().asFile(originalFilePath);
            if (originalFile == null) {
                return false;
            }

            String workingCopyContent = workingCopy.getContentAsString();
            String originalFileContent = originalFile.getVirtualFile().getContentAsString();
            if (workingCopyContent == null || originalFileContent == null) {
                return false;
            }

            String workingCopyHash = Hashing.md5().hashString(workingCopyContent, defaultCharset()).toString();
            String originalFileHash = Hashing.md5().hashString(originalFileContent, defaultCharset()).toString();

            return !Objects.equals(workingCopyHash, originalFileHash);
        } catch (NotFoundException | ServerException | ForbiddenException e) {
            LOG.error(e.getLocalizedMessage());
        }

        return false;
    }

    private EditorWorkingCopy createWorkingCopy(String filePath)
            throws NotFoundException, ServerException, ConflictException, ForbiddenException, IOException {

        FileEntry file = projectManagerProvider.get().asFile(filePath);
        if (file == null) {
            throw new NotFoundException(format("Item '%s' isn't found. ", filePath));
        }

        String projectPath = file.getProject();
        String workingCopyPath = toWorkingCopyPath(filePath);

        EditorWorkingCopy workingCopy = new EditorWorkingCopy(workingCopyPath, projectPath, file.contentAsBytes());
        workingCopiesStorage.put(filePath, workingCopy);

        return workingCopy;
    }

    private void createPersistentWorkingCopy(String originalFilePath)
            throws ServerException, ForbiddenException, ConflictException {
        try {
            EditorWorkingCopy workingCopy = workingCopiesStorage.get(originalFilePath);
            if (workingCopy == null) {
                throw new ServerException("Can not create recovery file for " + originalFilePath);
            }

            byte[] content = workingCopy.getContentAsBytes();
            String projectPath = workingCopy.getProjectPath();

            VirtualFileEntry persistentWorkingCopy = getPersistentWorkingCopy(originalFilePath, projectPath);
            if (persistentWorkingCopy != null) {
                persistentWorkingCopy.getVirtualFile().updateContent(content);
                return;
            }

            FolderEntry persistentWorkingCopiesStorage = getPersistentWorkingCopiesStorage(projectPath);
            if (persistentWorkingCopiesStorage == null) {
                persistentWorkingCopiesStorage = createPersistentWorkingCopiesStorage(projectPath);
            }

            persistentWorkingCopiesStorage.createFile(workingCopy.getPath(), content);
        } catch (ConflictException | ForbiddenException e) {
            LOG.error(e.getLocalizedMessage());
            throw new ServerException("Can not create recovery file for " + originalFilePath);
        }
    }

    private VirtualFileEntry getPersistentWorkingCopy(String originalFilePath, String projectPath) {
        try {
            FolderEntry persistentWorkingCopiesStorage = getPersistentWorkingCopiesStorage(projectPath);
            if (persistentWorkingCopiesStorage == null) {
                return null;
            }

            String workingCopyPath = toWorkingCopyPath(originalFilePath);
            return persistentWorkingCopiesStorage.getChild(workingCopyPath);
        } catch (ServerException e) {
            LOG.error(e.getLocalizedMessage());
            return null;
        }
    }

    private FolderEntry getPersistentWorkingCopiesStorage(String projectPath) {
        try {
            RegisteredProject project = projectManagerProvider.get().getProject(projectPath);
            FolderEntry baseFolder = project.getBaseFolder();
            if (baseFolder == null) {
                return null;
            }

            String tempDirectoryPath = baseFolder.getPath().toString() + WORKING_COPIES_DIR;
            return projectManagerProvider.get().asFolder(tempDirectoryPath);
        } catch (Exception e) {
            LOG.error(e.getLocalizedMessage());
            return null;
        }
    }

    private FolderEntry createPersistentWorkingCopiesStorage(String projectPath) throws ServerException {
        try {
            RegisteredProject project = projectManagerProvider.get().getProject(projectPath);
            FolderEntry baseFolder = project.getBaseFolder();
            if (baseFolder == null) {
                throw new ServerException("Can not create storage for recovery data");
            }

            return baseFolder.createFolder(WORKING_COPIES_DIR);
        } catch (NotFoundException | ConflictException | ForbiddenException e) {
            LOG.error(e.getLocalizedMessage());
            throw new ServerException("Can not create storage for recovery data " + e.getMessage());
        }
    }

    private String toWorkingCopyPath(String path) {
        if (path.startsWith("/")) {
            path = path.substring(1, path.length());
        }
        return path.replace('/', '.');
    }

    @PreDestroy
    private void unsubscribe() {
        eventService.unsubscribe(fileOperationEventSubscriber);
    }
}