[WIP] CHE-11: VFS event handling system (#1634)

CHE-11: VFS event handling system initial commit
6.19.x
Dmitry Kuleshov 2016-07-19 11:35:37 +03:00 committed by GitHub
parent e11c8e5406
commit 435bbd5505
32 changed files with 2319 additions and 14 deletions

View File

@ -74,6 +74,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-project</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-project-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace-shared</artifactId>

View File

@ -17,10 +17,11 @@ import com.google.inject.name.Named;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.eclipse.che.api.project.server.notification.ProjectItemModifiedEvent;
import org.eclipse.che.api.project.shared.dto.event.PomModifiedEventDto;
import org.eclipse.che.commons.schedule.executor.ThreadPullLauncher;
import org.eclipse.che.ide.maven.tools.Model;
import org.eclipse.che.plugin.maven.server.core.EclipseWorkspaceProvider;
import org.eclipse.che.plugin.maven.server.core.MavenWorkspace;
import org.eclipse.che.ide.maven.tools.Model;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.Path;
@ -71,6 +72,16 @@ public class PomChangeListener {
}
}
});
eventService.subscribe(new EventSubscriber<PomModifiedEventDto>() {
@Override
public void onEvent(PomModifiedEventDto event) {
String eventPath = event.getPath();
if (pomIsValid(eventPath)) {
projectToUpdate.add(new Path(eventPath).removeLastSegments(1).toOSString());
}
}
});
}
private boolean pomIsValid(String path) {

View File

@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.project.shared.dto.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.dto.shared.DTO;
/**
* To transfer branch name after git checkout operation
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@DTO
public interface GitBranchCheckoutEventDto {
String getBranchName();
GitBranchCheckoutEventDto withBranchName(String branchName);
}

View File

@ -0,0 +1,31 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.project.shared.dto.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.dto.shared.DTO;
/**
* To transfer modified POM path
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@DTO
public interface PomModifiedEventDto {
String getPath();
PomModifiedEventDto withPath(String path);
}

View File

@ -11,6 +11,7 @@
package org.eclipse.che.api.project.server;
import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
@ -26,6 +27,12 @@ import org.eclipse.che.api.vfs.VirtualFileSystemProvider;
import org.eclipse.che.api.vfs.impl.file.DefaultFileWatcherNotificationHandler;
import org.eclipse.che.api.vfs.impl.file.FileWatcherNotificationHandler;
import org.eclipse.che.api.vfs.impl.file.LocalVirtualFileSystemProvider;
import org.eclipse.che.api.vfs.impl.file.event.GitCheckoutHiEventDetector;
import org.eclipse.che.api.vfs.impl.file.event.HiEventDetector;
import org.eclipse.che.api.vfs.impl.file.event.HiEventService;
import org.eclipse.che.api.vfs.impl.file.event.LoEventListener;
import org.eclipse.che.api.vfs.impl.file.event.LoEventService;
import org.eclipse.che.api.vfs.impl.file.event.PomModifiedHiEventDetector;
import org.eclipse.che.api.vfs.search.MediaTypeFilter;
import org.eclipse.che.api.vfs.search.SearcherProvider;
import org.eclipse.che.api.vfs.search.impl.FSLuceneSearcherProvider;
@ -72,5 +79,15 @@ public class ProjectApiModule extends AbstractModule {
bind(VirtualFileSystemProvider.class).to(LocalVirtualFileSystemProvider.class);
bind(FileWatcherNotificationHandler.class).to(DefaultFileWatcherNotificationHandler.class);
bind(LoEventListener.class);
bind(LoEventService.class);
bind(HiEventService.class);
Multibinder<HiEventDetector<?>> highLevelVfsEventDetectorMultibinder =
Multibinder.newSetBinder(binder(), new TypeLiteral<HiEventDetector<?>>() {
});
highLevelVfsEventDetectorMultibinder.addBinding().to(PomModifiedHiEventDetector.class);
highLevelVfsEventDetectorMultibinder.addBinding().to(GitCheckoutHiEventDetector.class);
}
}

View File

@ -34,18 +34,16 @@ import org.eclipse.che.api.project.server.type.ProjectTypeRegistry;
import org.eclipse.che.api.project.server.type.ProjectTypeResolution;
import org.eclipse.che.api.project.server.type.ValueStorageException;
import org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType;
import org.eclipse.che.api.project.shared.dto.event.VfsWatchEvent;
import org.eclipse.che.api.vfs.Path;
import org.eclipse.che.api.vfs.VirtualFile;
import org.eclipse.che.api.vfs.VirtualFileFilter;
import org.eclipse.che.api.vfs.VirtualFileSystem;
import org.eclipse.che.api.vfs.VirtualFileSystemProvider;
import org.eclipse.che.api.vfs.impl.file.FileTreeWatcher;
import org.eclipse.che.api.vfs.impl.file.FileWatcherNotificationHandler;
import org.eclipse.che.api.vfs.impl.file.FileWatcherNotificationListener;
import org.eclipse.che.api.vfs.impl.file.event.LoEvent;
import org.eclipse.che.api.vfs.search.Searcher;
import org.eclipse.che.api.vfs.search.SearcherProvider;
import org.eclipse.che.dto.server.DtoFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -109,16 +107,22 @@ public final class ProjectManager {
@PostConstruct
void initWatcher() throws IOException {
FileWatcherNotificationListener defaultListener = new FileWatcherNotificationListener(VirtualFileFilter.ACCEPT_ALL) {
@Override
public void onFileWatcherEvent(VirtualFile virtualFile, FileWatcherEventType eventType) {
LOG.debug("FS event detected: " + eventType + " " + virtualFile.getPath().toString() + " " + virtualFile.isFile());
eventService.publish(DtoFactory.newDto(VfsWatchEvent.class)
.withPath(virtualFile.getPath().toString())
.withFile(virtualFile.isFile())
.withType(eventType));
}
};
FileWatcherNotificationListener defaultListener =
new FileWatcherNotificationListener(file -> !(file.getPath().toString().contains(".codenvy")
|| file.getPath().toString().contains(".#"))) {
@Override
public void onFileWatcherEvent(VirtualFile virtualFile, FileWatcherEventType eventType) {
LOG.debug("FS event detected: " + eventType + " " + virtualFile.getPath().toString() + " " + virtualFile.isFile());
eventService.publish(LoEvent.newInstance()
.withPath(virtualFile.getPath().toString())
.withName(virtualFile.getName())
.withItemType(virtualFile.isFile()
? LoEvent.ItemType.FILE
: LoEvent.ItemType.DIR)
.withTime(System.currentTimeMillis())
.withEventType(eventType));
}
};
fileWatchNotifier.addNotificationListener(defaultListener);
try {
fileWatcher.startup();

View File

@ -0,0 +1,93 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.api.vfs.Path;
import java.util.Optional;
import static java.io.File.separator;
import static org.eclipse.che.api.vfs.impl.file.event.EventTreeNode.newInstance;
/**
* Helper for event tree related operations.
*
* @see {@link EventTreeNode}
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
public class EventTreeHelper {
/**
* Adds corresponding event to an event tree.
* <p>
* If event's tree parents are not yet in the tree they are implicitly created
* with default values and no events. For tree nodes created in such way call
* of {@link EventTreeNode#modificationOccurred()} method always return false.
* </p>
* @param root root node of the tree, node where event's absolute path starts
*
* @param loEvent event to be added
*/
public static void addEventAndCreatePrecedingNodes(EventTreeNode root, LoEvent loEvent) {
traverseAndCreate(root, Path.of(loEvent.getPath()))
.withEvent(loEvent)
.withPath(loEvent.getPath())
.withType(loEvent.getItemType());
}
/**
* Get a tree node according to a relative path starting from a predefined tree node.
*
* @param root node where relative path starts
* @param relativePath relative path
*
* @return node if node with such path exists otherwise {@code null}
*/
public static Optional<EventTreeNode> getTreeNode(EventTreeNode root, String relativePath) {
Optional<EventTreeNode> current = Optional.of(root);
if (relativePath.startsWith(separator)) {
relativePath = relativePath.substring(1);
}
if (relativePath.endsWith(separator)) {
relativePath = relativePath.substring(0, relativePath.length() - 1);
}
for (String segment : relativePath.split(separator)) {
if (current.isPresent()) {
current = current.get().getChild(segment);
}
}
return current;
}
private static EventTreeNode traverseAndCreate(EventTreeNode root, Path path) {
EventTreeNode current = root;
for (int i = 0; i < path.length(); i++) {
final String name = path.element(i);
final EventTreeNode parent = current;
final Optional<EventTreeNode> childOptional = current.getChild(name);
current = childOptional.orElseGet(() -> newInstance().withName(name).withParent(parent));
}
return current;
}
}

View File

@ -0,0 +1,186 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType;
import org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import static java.util.Optional.empty;
import static java.util.stream.Stream.concat;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType.DIR;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType.FILE;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType.UNDEFINED;
/**
* Virtual file system event tree is a data structure to store low level VFS
* events in a specific manner. Events are stored in a such way that an event
* tree corresponds to a file system items tree but includes only those tree
* branches that contain modified file system items.
* <p>
* By design event tree represents a time-based event segment. All events
* close enough (in terms of time line) to each other are included to the
* tree.
* </p>
* <p>
* This class uses {@link LinkedHashMap} to store an event chain, not to
* lose data when we have several sequential modifications of a single
* tree item.
* </p>
* <p>
* Note: for convenience there is a predefined event tree root node - '/',
* which is defined by {@link EventTreeNode#ROOT_NODE_NAME} constant.
* All trees must be started from that node using corresponding factory
* method {@link EventTreeNode#newRootInstance()}
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
public class EventTreeNode {
/**
* Tree root node name. All properly constructed trees must be started from this node.
*/
private static final String ROOT_NODE_NAME = "/";
private List<EventTreeNode> children;
private String name;
private String path;
private ItemType type;
/**
* Event chain to store all events occurred within a single time
* segment with this event tree node instance.
* Key - timestamp in millis, value - event type
*/
private Map<Long, FileWatcherEventType> events;
private EventTreeNode() {
this.events = new LinkedHashMap<>();
this.children = new LinkedList<>();
this.type = UNDEFINED;
}
public static EventTreeNode newRootInstance() {
return EventTreeNode.newInstance().withName(ROOT_NODE_NAME);
}
public static EventTreeNode newInstance() {
return new EventTreeNode();
}
public EventTreeNode withName(String name) {
this.name = name;
return this;
}
public EventTreeNode withParent(EventTreeNode parent) {
parent.withChild(this);
return this;
}
public EventTreeNode withChild(EventTreeNode child) {
this.children.add(child);
return this;
}
public EventTreeNode withEvent(LoEvent loEvent) {
this.events.put(loEvent.getTime(), loEvent.getEventType());
return this;
}
public EventTreeNode withPath(String path) {
this.path = path;
return this;
}
public EventTreeNode withType(ItemType type) {
this.type = type;
return this;
}
public String getName() {
return name;
}
public String getPath() {
return path;
}
public ItemType getType() {
return type;
}
public List<EventTreeNode> getChildren() {
return children;
}
public Optional<EventTreeNode> getChild(String name) {
for (EventTreeNode node : children) {
if (node.getName().equals(name)) {
return Optional.of(node);
}
}
return empty();
}
public Optional<EventTreeNode> getFirstChild() {
if (children.isEmpty()) {
return empty();
}
return Optional.of(children.get(0));
}
public Map<Long, FileWatcherEventType> getEvents() {
return events;
}
public FileWatcherEventType getLastEventType() {
final List<Map.Entry<Long, FileWatcherEventType>> entryList = new ArrayList<>(events.entrySet());
final Map.Entry<Long, FileWatcherEventType> lastEntry = entryList.get(entryList.size() - 1);
return lastEntry.getValue();
}
public boolean modificationOccurred() {
return !events.isEmpty();
}
public boolean isFile() {
return type.equals(FILE);
}
public boolean isDir() {
return type.equals(DIR);
}
public boolean isRoot() {
return ROOT_NODE_NAME.equals(name);
}
public Stream<EventTreeNode> stream() {
return concat(Stream.of(this), this.children.stream().flatMap(EventTreeNode::stream));
}
}

View File

@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.slf4j.Logger;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static java.util.Optional.empty;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Simple holder to benefit from Guice DI routines.
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
class EventTreeQueueHolder {
private static final Logger LOG = getLogger(EventTreeQueueHolder.class);
private final BlockingQueue<EventTreeNode> loVfsEventQueue;
public EventTreeQueueHolder() {
this.loVfsEventQueue = new LinkedBlockingQueue<>();
}
public void put(EventTreeNode loVfsEventTreeRoot) {
try {
loVfsEventQueue.put(loVfsEventTreeRoot);
} catch (InterruptedException e) {
LOG.error("Error trying to put an event tree to an event tree queue: {}", loVfsEventTreeRoot, e);
}
}
public Optional<EventTreeNode> take() {
try {
return Optional.of(loVfsEventQueue.take());
} catch (InterruptedException e) {
LOG.error("Error trying to take an event tree out of an event tree queue", e);
}
return empty();
}
}

View File

@ -0,0 +1,114 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.project.shared.dto.event.GitBranchCheckoutEventDto;
import org.eclipse.che.api.vfs.Path;
import org.eclipse.che.api.vfs.VirtualFileSystemProvider;
import org.slf4j.Logger;
import javax.inject.Inject;
import java.util.Optional;
import java.util.regex.Pattern;
import static java.io.File.separator;
import static java.util.Optional.empty;
import static java.util.regex.Pattern.compile;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.MODIFIED;
import static org.eclipse.che.api.vfs.impl.file.event.HiEvent.Category.UNDEFINED;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Detects if there was a .git/HEAD file modification, which is a sign of git branch
* checkout operation, though in some rare cases it simply shows that the head of
* current branch is changed.
* <p>
* By the moment of this class creation those situations are considered rare
* enough to ignore false detections.
* </p>
* <p>
* It is designed to detect only HEAD file MODIFICATION, which means that it will
* not trigger if those files are CREATED, DELETED, etc.
* </p>
* <p>
* This very implementation works only with git repositories initialized in
* the project root folder.
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
public class GitCheckoutHiEventDetector implements HiEventDetector<GitBranchCheckoutEventDto> {
private static final Logger LOG = getLogger(GitCheckoutHiEventDetector.class);
private static final String GIT_DIR = ".git";
private static final String HEAD_FILE = "HEAD";
private static final String GIT_OPERATION_WS_CHANNEL = "git-operations-channel";
private static final int PRIORITY = 50;
private static final Pattern PATTERN = compile("ref: refs" + separator + "heads" + separator);
private final VirtualFileSystemProvider virtualFileSystemProvider;
private final HiEventBroadcaster broadcaster;
@Inject
public GitCheckoutHiEventDetector(VirtualFileSystemProvider virtualFileSystemProvider,
HiEventClientBroadcaster highLevelVfsEventClientBroadcaster) {
this.virtualFileSystemProvider = virtualFileSystemProvider;
this.broadcaster = highLevelVfsEventClientBroadcaster;
}
@Override
public Optional<HiEvent<GitBranchCheckoutEventDto>> detect(EventTreeNode eventTreeNode) {
if (!eventTreeNode.isRoot() || eventTreeNode.getChildren().isEmpty()) {
return empty();
}
final Optional<EventTreeNode> headFile = eventTreeNode.getFirstChild()
.flatMap(o -> o.getChild(GIT_DIR))
.flatMap(o -> o.getChild(HEAD_FILE));
if (headFile.isPresent()
&& headFile.get().modificationOccurred()
&& MODIFIED.equals(headFile.get().getLastEventType())) {
final GitBranchCheckoutEventDto dto = newDto(GitBranchCheckoutEventDto.class).withBranchName(getBranchName(headFile.get()));
return Optional.of(HiEvent.newInstance(GitBranchCheckoutEventDto.class)
.withCategory(UNDEFINED.withPriority(PRIORITY))
.withBroadcaster(broadcaster)
.withChannel(GIT_OPERATION_WS_CHANNEL)
.withDto(dto));
} else {
return empty();
}
}
private String getBranchName(EventTreeNode file) {
try {
final String result = virtualFileSystemProvider.getVirtualFileSystem()
.getRoot()
.getChild(Path.of(file.getPath()))
.getContentAsString();
return PATTERN.split(result)[1];
} catch (ServerException | ForbiddenException e) {
LOG.error("Error trying to read {} file and broadcast it", file.getPath(), e);
}
return "";
}
}

View File

@ -0,0 +1,145 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import java.util.LinkedList;
import java.util.List;
/**
* High level virtual file system event implementation. Unlike {@link LoEvent}
* it is to represent not physical but logical events. In terms of file system
* logical events are those that may be caused by one or more physical events.
* In other words sets of low level VFS events result in high level VFS events.
* <p>
* Small example: creation of project's root folder ({@link LoEvent})
* along with populating it with content (creating new files and folders -
* also {@link LoEvent}) indicates that most likely a project is imported.
* And project import is a {@link HiEvent}.
* </p>
* <p>
* Note: We consider that the user of this class is responsible for setting
* proper broadcasters and (if it is required) all additional information
* (e.g. web socket channel).
* </p>
* <p>
* Each high level VFS event instance can have arbitrary number (but not less
* than one) of {@link HiEventBroadcaster} defined. By design an instance
* of {@link HiEventDetector} which is responsible for creating of event
* is also responsible for definition of a list of corresponding broadcasters.
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
public class HiEvent<T> {
/**
* Should be a plain DTO object representing this event. Is designed to be
* further used both for client-side and server-side notifications.
* <p>
* Note: The implementation of DTO should fulfill all restrictions and
* be located in {@code org.eclipse.che.api.project.shared.dto.event}
* package
* </p>
*/
private T dto;
/**
* Field holds category property of an event. Please see {@link Category}
*/
private Category category;
private List<HiEventBroadcaster> broadcasters;
/**
* Web socket channel name.
* <p>
* It is ignored if event is not expected to be sent over web socket
* (e.g. server side event)
* </p>
*/
private String channel;
private HiEvent() {
this.broadcasters = new LinkedList<>();
}
public static <T> HiEvent<T> newInstance(Class<T> type) {
return new HiEvent<>();
}
public T getDto() {
return dto;
}
public HiEvent<T> withDto(T dto) {
this.dto = dto;
return this;
}
public String getChannel() {
return channel;
}
public HiEvent<T> withChannel(String channel) {
this.channel = channel;
return this;
}
public Category getCategory() {
return category;
}
public HiEvent<T> withCategory(Category category) {
this.category = category;
return this;
}
public HiEvent<T> withBroadcaster(HiEventBroadcaster hiEventBroadcaster) {
this.broadcasters.add(hiEventBroadcaster);
return this;
}
public void broadcast() {
broadcasters.stream().forEach(o -> o.broadcast(this));
}
/**
* Simple enumeration to represent event categorizing mechanics.
* <p>
* The idea is to allow only one high level event per category for
* a single low level events set (tree snapshot). If we have several
* high level events with the same category we are to choose the most
* appropriate event according to its priority.
* </p>
* <p>
* Note: UNDEFINED is used for all events that have no category.
* </p>
*/
@Beta
public enum Category {
UNDEFINED,
PROJECT_INFRASTRUCTURE;
private long priority;
public long getPriority() {
return priority;
}
public Category withPriority(long priority) {
this.priority = priority;
return this;
}
}
}

View File

@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
/**
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@FunctionalInterface
public interface HiEventBroadcaster {
void broadcast(HiEvent event);
}

View File

@ -0,0 +1,62 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.api.vfs.impl.file.event.HiEvent.Category;
import javax.inject.Singleton;
import java.util.List;
import static java.lang.Long.compare;
import static java.util.stream.Collectors.toList;
import static org.eclipse.che.api.vfs.impl.file.event.HiEvent.Category.UNDEFINED;
import static org.eclipse.che.api.vfs.impl.file.event.HiEvent.Category.values;
/**
* Splits high level event list according to their categories (see {@link Category})
* and then broadcast categorized events with the highest priorities.
* <p>
* Note: broadcasters are secured for each {@link HiEvent} instance during
* creation, so it is out of scope of this class to define broadcasters, but simply
* to call them.
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
class HiEventBroadcasterManager {
void manageEvents(List<HiEvent> hiEvents) {
if (hiEvents.isEmpty()) {
return;
}
for (Category category : values()) {
final List<HiEvent> events = hiEvents.stream()
.filter(o -> o.getCategory().equals(category))
.sorted((o1, o2) -> compare(o1.getCategory().getPriority(),
o2.getCategory().getPriority()))
.collect(toList());
if (UNDEFINED.equals(category)) {
events.stream().forEach(HiEvent::broadcast);
} else if (!events.isEmpty()) {
// getting the event with the highest priority
events.get(0).broadcast();
}
}
}
}

View File

@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.dto.server.DtoFactory;
import org.everrest.websockets.WSConnectionContext;
import org.everrest.websockets.message.ChannelBroadcastMessage;
import org.slf4j.Logger;
import javax.inject.Singleton;
import javax.websocket.EncodeException;
import java.io.IOException;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Designed to broadcast high level events over web sockets to a client-side listener.
* WS listener must beforehand know corresponding WE channel name and event DTO class,
* because basically all this class does is broadcasting a DTO to a specific channel.
* Nothing more.
* <p>
* Note: Proper DTO object and web socket channel name are assumed to be stored in
* {@link HiEvent} instance passed to this broadcaster. Relevance of DTO and ws
* chanel data is the responsibility of {@link HiEvent} instance creator.
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
public class HiEventClientBroadcaster implements HiEventBroadcaster {
private static final Logger LOG = getLogger(HiEventClientBroadcaster.class);
@Override
public void broadcast(HiEvent event) {
final ChannelBroadcastMessage bm = new ChannelBroadcastMessage();
bm.setChannel(event.getChannel());
bm.setBody(DtoFactory.getInstance().toJson(event.getDto()));
try {
WSConnectionContext.sendMessage(bm);
LOG.debug("Sending message over websocket connection: {}", bm);
} catch (EncodeException | IOException e) {
LOG.error("Can't send a VFS notification over web socket. Event: {}", event, e);
}
}
}

View File

@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import java.util.Optional;
/**
* Event detectors are the key components of the whole VFS event handling subsystem.
* <p>
* The main purpose of each event detector is to analyze instances of
* {@link EventTreeNode} and to find out if a specific conditions are met.
* If yes - corresponding high level event should be generated. Besides the
* obvious (event detection), event detectors are also responsible for high
* level event generation. Those events should correspond to a specific event
* tree state and populated with relevant data.
* </p>
* <p>
* Perhaps those functions will be shared between detectors and event builders
* in future implementations.
* </p>
* <p>
* To omit details, it defines a way a set of low level events transforms into a
* specific high level event or a list of events.
* </p>
* <p>
* Note: Though it is not restricted to generate different high level events in
* correspondence to different event trees by a single detector, it is not
* recommended to do so to avoid complications in understanding and handling of
* events. Adhere to a rule: one detector - one event. Unless you know what you
* are doing.
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@FunctionalInterface
public interface HiEventDetector<T> {
Optional<HiEvent<T>> detect(EventTreeNode eventTreeNode);
}

View File

@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static java.util.stream.Collectors.toList;
/**
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
class HiEventDetectorManager {
private final Set<HiEventDetector<?>> hiEventDetectors;
@Inject
public HiEventDetectorManager(Set<HiEventDetector<?>> hiEventDetectors) {
this.hiEventDetectors = hiEventDetectors;
}
List<HiEvent> getDetectedEvents(EventTreeNode root) {
return hiEventDetectors.stream()
.map(o -> o.detect(root))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toList());
}
}

View File

@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.api.core.notification.EventService;
import org.slf4j.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Event server-side publisher with a very basic set of functionality.
* It simply publishes the events DTO to an instance of {@link EventService}
* <p>
* Note: DTO object is assumed to be stored in {@link HiEvent} instance
* passed to this broadcaster.
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
public class HiEventServerPublisher implements HiEventBroadcaster {
private static final Logger LOG = getLogger(HiEventClientBroadcaster.class);
private final EventService eventService;
@Inject
public HiEventServerPublisher(EventService eventService) {
this.eventService = eventService;
}
@Override
public void broadcast(HiEvent event) {
eventService.publish(event.getDto());
LOG.debug("Publishing event to event service: {}.", event.getDto());
}
}

View File

@ -0,0 +1,54 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;
import static org.slf4j.LoggerFactory.getLogger;
/**
* The main mission of this service is to manage event trees produced by {@link LoEventService}.
* It is done with the help of {@link HiEventDetectorManager} and {@link HiEventBroadcasterManager}.
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
public class HiEventService extends VfsEventService {
private final HiEventDetectorManager hiEventDetectorManager;
private final HiEventBroadcasterManager hiEventBroadcasterManager;
private final EventTreeQueueHolder eventTreeQueueHolder;
@Inject
public HiEventService(HiEventDetectorManager hiEventDetectorManager,
EventTreeQueueHolder eventTreeQueueHolder,
HiEventBroadcasterManager hiEventBroadcasterManager) {
this.hiEventDetectorManager = hiEventDetectorManager;
this.eventTreeQueueHolder = eventTreeQueueHolder;
this.hiEventBroadcasterManager = hiEventBroadcasterManager;
}
protected void run() {
final Optional<EventTreeNode> optional = eventTreeQueueHolder.take();
if (optional.isPresent()) {
final EventTreeNode treeRoot = optional.get();
final List<HiEvent> events = hiEventDetectorManager.getDetectedEvents(treeRoot);
hiEventBroadcasterManager.manageEvents(events);
}
}
}

View File

@ -0,0 +1,117 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType;
import org.eclipse.che.api.vfs.VirtualFileSystem;
import org.eclipse.che.api.vfs.impl.file.FileTreeWatcher;
import org.eclipse.che.api.vfs.impl.file.FileWatcherNotificationListener;
/**
* Low level virtual file system event implementation designed to represent
* trivial file system items manipulations ({@link FileWatcherEventType}).
* Roughly speaking it stands for physical file system operations (create file,
* edit file, create folder, etc.).
*
* * <p>
* It is used for both file and folder related events ({@link ItemType}) and
* contains all relevant data (time, path, event and item types).
* </p>
* <p>
* Note: {@link LoEvent#getTime()} and {@link LoEvent#withTime(long)} methods
* are designed to deal with the time that corresponds to the moment we caught
* the event inside the application, not the real item modification time that
* comes from underlying file system. This is due to the fact that most file
* systems support timestamps accuracy within one second which is not enough
* for our purposes.
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
public class LoEvent {
/**
* Time in milliseconds to represent the moment VFS event is fired by
* {@link FileTreeWatcher} and caught by one of {@link FileWatcherNotificationListener}
* implementations
*/
private long time;
/**
* Absolute item path in terms of {@link VirtualFileSystem}. It is expected
* that this path starts from root position of file system. In common sense
* it is the folder obtained by {@link VirtualFileSystem#getRoot()} method.
*/
private String path;
private String name;
private FileWatcherEventType eventType;
private ItemType itemType;
private LoEvent() {
}
public static LoEvent newInstance() {
return new LoEvent();
}
public ItemType getItemType() {
return itemType;
}
public LoEvent withItemType(ItemType itemType) {
this.itemType = itemType;
return this;
}
public long getTime() {
return time;
}
public LoEvent withTime(long time) {
this.time = time;
return this;
}
public String getPath() {
return path;
}
public LoEvent withPath(String path) {
this.path = path;
return this;
}
public String getName() {
return name;
}
public LoEvent withName(String name) {
this.name = name;
return this;
}
public FileWatcherEventType getEventType() {
return eventType;
}
public LoEvent withEventType(FileWatcherEventType eventType) {
this.eventType = eventType;
return this;
}
public enum ItemType {
FILE, DIR, UNDEFINED
}
}

View File

@ -0,0 +1,62 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.slf4j.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
import static org.slf4j.LoggerFactory.getLogger;
/**
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
public class LoEventListener implements EventSubscriber<LoEvent> {
private static final Logger LOG = getLogger(LoEventListener.class);
private final EventService eventService;
private final LoEventQueueHolder loEventQueueHolder;
@Inject
public LoEventListener(EventService eventService, LoEventQueueHolder loEventQueueHolder) {
this.eventService = eventService;
this.loEventQueueHolder = loEventQueueHolder;
}
@PostConstruct
void postConstruct() {
eventService.subscribe(this);
LOG.info("Subscribing to event service: {}", this.getClass());
}
@PreDestroy
void preDestroy() {
eventService.unsubscribe(this);
LOG.info("Unsubscribing to event service: {}", this.getClass());
}
@Override
public void onEvent(LoEvent event) {
loEventQueueHolder.put(event);
LOG.trace("Putting event {} to {}", event, LoEventQueueHolder.class);
}
}

View File

@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.slf4j.Logger;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static java.util.Optional.empty;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.slf4j.LoggerFactory.getLogger;
/**
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
class LoEventQueueHolder {
private static final Logger LOG = getLogger(LoEventQueueHolder.class);
private final BlockingQueue<LoEvent> loEventQueue;
public LoEventQueueHolder() {
this.loEventQueue = new LinkedBlockingQueue<>();
}
void put(LoEvent loEvent) {
try {
loEventQueue.put(loEvent);
} catch (InterruptedException e) {
LOG.error("Error trying to put an event to an event queue: {}", loEvent, e);
}
}
Optional<LoEvent> poll(long timeout) {
try {
return Optional.ofNullable(loEventQueue.poll(timeout, MILLISECONDS));
} catch (InterruptedException e) {
LOG.error("Error trying to poll an event out of an event queue", e);
}
return empty();
}
}

View File

@ -0,0 +1,113 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import org.slf4j.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import static org.eclipse.che.api.vfs.impl.file.event.EventTreeHelper.addEventAndCreatePrecedingNodes;
import static org.eclipse.che.api.vfs.impl.file.event.EventTreeNode.newRootInstance;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Main task of event low event service is to take low level events and process
* them into event tree according to their locations in a file system. The event
* tree is passed further to a event detectors and broadcasters managed by upper
* {@link HiEventService}.
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
@Singleton
public class LoEventService extends VfsEventService {
private static final Logger LOG = getLogger(LoEventService.class);
/**
* Maximal time interval between events that we consider them
* to belong a common event segment.
*/
static final long MAX_EVENT_INTERVAL_MILLIS = 300L;
/**
* Maximal time interval that an event segment can last. This is used
* to avoid long going events sequences. We are splitting them into several
* lesser.
*/
static final long MAX_TIME_SEGMENT_SIZE_MILLIS = 5 * MAX_EVENT_INTERVAL_MILLIS;
/**
* This constant is used to set undefined timestamp in case if a new event
* segment is not started but the event interval is big enough to finish
* previous event segment and to enqueue an old tree.
*/
private static final long UNDEFINED = -1L;
private final LoEventQueueHolder loEventQueueHolder;
private final EventTreeQueueHolder eventTreeQueueHolder;
private EventTreeNode vfsEventTreeRoot;
private long eventSegmentStartTime;
@Inject
public LoEventService(LoEventQueueHolder loEventQueueHolder,
EventTreeQueueHolder eventTreeQueueHolder) {
this.loEventQueueHolder = loEventQueueHolder;
this.eventTreeQueueHolder = eventTreeQueueHolder;
this.vfsEventTreeRoot = newRootInstance();
this.eventSegmentStartTime = UNDEFINED;
}
@Override
protected void run() {
Optional<LoEvent> optional = loEventQueueHolder.poll(MAX_EVENT_INTERVAL_MILLIS);
if (optional.isPresent()) {
final LoEvent loEvent = optional.get();
final long eventTime = loEvent.getTime();
if (eventSegmentStartTime == UNDEFINED || eventTime - eventSegmentStartTime >= MAX_TIME_SEGMENT_SIZE_MILLIS) {
LOG.trace("Starting new event segment.");
LOG.trace("Old event segment start time: {} ", eventSegmentStartTime);
LOG.trace("New event segment start time: {} ", eventTime);
flushOldTreeAndStartNew();
eventSegmentStartTime = eventTime;
}
addEventAndCreatePrecedingNodes(vfsEventTreeRoot, loEvent);
} else {
flushOldTreeAndStartNew();
eventSegmentStartTime = UNDEFINED;
}
}
private void flushOldTreeAndStartNew() {
if (vfsEventTreeRoot.getChildren().isEmpty()) {
return;
}
eventTreeQueueHolder.put(vfsEventTreeRoot);
LOG.trace("Flushing old event tree {}.", vfsEventTreeRoot);
vfsEventTreeRoot = newRootInstance();
LOG.trace("Starting new event tree {}.", vfsEventTreeRoot);
}
}

View File

@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import com.google.inject.Inject;
import org.eclipse.che.api.project.shared.dto.event.PomModifiedEventDto;
import java.util.Optional;
import static java.util.Optional.empty;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.MODIFIED;
import static org.eclipse.che.api.vfs.impl.file.event.HiEvent.Category.PROJECT_INFRASTRUCTURE;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
/**
* Detects if any of maven project object model files is MODIFIED during previous
* event segment. If there are more than one modified file, generate event for the
* highest (in context of file system location hierarchy) spotted.
*
* <p>
* Note: this implementation deals only with standard project object model file
* names - {@code pom.xml}. So if it is necessary to support custom naming you
* can extend this class.
* </p>
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
public class PomModifiedHiEventDetector implements HiEventDetector<PomModifiedEventDto> {
private static final String POM_XML = "pom.xml";
private HiEventBroadcaster broadcaster;
@Inject
public PomModifiedHiEventDetector(HiEventServerPublisher broadcaster) {
this.broadcaster = broadcaster;
}
@Override
public Optional<HiEvent<PomModifiedEventDto>> detect(EventTreeNode eventTreeNode) {
if (!eventTreeNode.isRoot() || eventTreeNode.getChildren().isEmpty()) {
return empty();
}
final Optional<EventTreeNode> highestPom = eventTreeNode.stream()
.filter(EventTreeNode::modificationOccurred)
.filter(EventTreeNode::isFile)
.filter(event -> POM_XML.equals(event.getName()))
.filter(event -> MODIFIED.equals(event.getLastEventType()))
// note the revers order of o1 and o2
.sorted((o1, o2) -> o2.getPath().compareTo(o1.getPath()))
.findFirst();
if (!highestPom.isPresent()) {
return empty();
}
PomModifiedEventDto dto = newDto(PomModifiedEventDto.class).withPath(highestPom.get().getPath());
return Optional.of((HiEvent.newInstance(PomModifiedEventDto.class)
.withCategory(PROJECT_INFRASTRUCTURE.withPriority(50))
.withBroadcaster(broadcaster)
.withDto(dto)));
}
}

View File

@ -0,0 +1,72 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import com.google.common.annotations.Beta;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.slf4j.LoggerFactory.getLogger;
/**
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@Beta
abstract class VfsEventService {
private static final Logger LOG = getLogger(VfsEventService.class);
private final ExecutorService executor;
private final AtomicBoolean running;
VfsEventService() {
final String threadName = getClass().getSimpleName().concat("Thread-%d");
final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(threadName)
.setDaemon(TRUE)
.build();
this.executor = newSingleThreadExecutor(threadFactory);
this.running = new AtomicBoolean(FALSE);
}
@PostConstruct
protected void postConstruct() {
running.compareAndSet(FALSE, TRUE);
executor.execute(this::runAsASingleThread);
LOG.info("Starting virtual file system event service: {}", this.getClass().getSimpleName());
}
@PreDestroy
protected void preDestroy() {
running.compareAndSet(TRUE, FALSE);
executor.shutdown();
LOG.info("Stopping virtual file system event service: {}", this.getClass().getSimpleName());
}
private void runAsASingleThread() {
while (running.get()) {
run();
}
}
protected abstract void run();
}

View File

@ -0,0 +1,99 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType;
import org.junit.Before;
import org.junit.Test;
import java.util.Optional;
import static java.io.File.separator;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.CREATED;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.MODIFIED;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType.FILE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link EventTreeNode}
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
public class EventTreeNodeTest {
private static final String TEST_PATH = separator + "che" + separator + "file";
private static final String CHILD_NAME_1 = "Child 1";
private static final String CHILD_NAME_2 = "Child 2";
private EventTreeNode root;
private EventTreeNode child;
private EventTreeNode anotherChild;
@Before
public void setUp() {
root = EventTreeNode.newRootInstance();
child = EventTreeNode.newInstance();
anotherChild = EventTreeNode.newInstance();
}
@Test
public void shouldProperlyAddChild() {
root.withChild(child);
final Optional<EventTreeNode> optional = root.getFirstChild();
assertTrue(optional.isPresent());
assertEquals(optional.get(), child);
assertEquals(1, root.getChildren().size());
}
@Test
public void shouldProperlyAddEvent() {
long time = System.currentTimeMillis();
assertTrue(child.getEvents().isEmpty());
child.withEvent(getLoVfsEvent(time, CREATED));
assertEquals(1, child.getEvents().size());
assertNotNull(child.getEvents().get(time));
}
@Test
public void shouldProperlyGetFirstChild() {
root.withChild(child.withName(CHILD_NAME_1));
root.withChild(anotherChild.withName(CHILD_NAME_2));
assertTrue(root.getFirstChild().isPresent());
assertEquals(CHILD_NAME_1, root.getFirstChild().get().getName());
}
@Test
public void shouldProperlyGetLastEventType() {
child.withEvent(getLoVfsEvent(System.currentTimeMillis(), CREATED));
child.withEvent(getLoVfsEvent(System.currentTimeMillis(), MODIFIED));
assertEquals(MODIFIED, child.getLastEventType());
}
private LoEvent getLoVfsEvent(long time, FileWatcherEventType type) {
return LoEvent.newInstance()
.withPath(TEST_PATH)
.withTime(time)
.withItemType(FILE)
.withEventType(type);
}
}

View File

@ -0,0 +1,97 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import org.eclipse.che.api.project.shared.dto.event.GitBranchCheckoutEventDto;
import org.eclipse.che.api.vfs.VirtualFile;
import org.eclipse.che.api.vfs.VirtualFileSystem;
import org.eclipse.che.api.vfs.VirtualFileSystemProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.File;
import java.util.Optional;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.CREATED;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.MODIFIED;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType.FILE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link GitCheckoutHiEventDetector}
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@RunWith(MockitoJUnitRunner.class)
public class GitCheckoutHiEventDetectorTest extends HiVfsEventDetectorTestHelper {
private static final String PROJECT_NAME = "project";
private static final String HEAD_FILE_PATH = ".git/HEAD";
private static final String TEST_VALUE = "TEST VALUE";
private static final String HEAD_FILE_CONTENT = "ref: refs" + File.separator + "heads" + File.separator + TEST_VALUE;
private static final String HEAD_FILE_NAME = "HEAD";
private static final String GIT_OPERATIONS_CHANNEL = "git-operations-channel";
@Mock
private HiEventClientBroadcaster hiVfsEventClientBroadcaster;
@Mock
private VirtualFileSystemProvider virtualFileSystemProvider;
private GitCheckoutHiEventDetector gitCheckoutHiVfsEventDetector;
@Before
public void setUp() throws Exception {
super.setUp();
gitCheckoutHiVfsEventDetector = new GitCheckoutHiEventDetector(virtualFileSystemProvider, hiVfsEventClientBroadcaster);
}
@Test
public void shouldReturnEmptyListBecauseNoHeadFileModified() {
assertFalse(gitCheckoutHiVfsEventDetector.detect(root).isPresent());
}
@Test
public void shouldReturnEmptyListBecauseOfWrongHeadFileModification() {
addEvent(HEAD_FILE_NAME, File.separator + PROJECT_NAME + File.separator + HEAD_FILE_PATH, CREATED, FILE);
assertFalse(gitCheckoutHiVfsEventDetector.detect(root).isPresent());
}
@Test
public void shouldReturnListWithCorrectEventBecauseHeadFileIsProperlyModified() throws Exception {
VirtualFileSystem vfs = mock(VirtualFileSystem.class);
VirtualFile file = mock(VirtualFile.class);
when(virtualFileSystemProvider.getVirtualFileSystem()).thenReturn(vfs);
when(vfs.getRoot()).thenReturn(file);
when(file.getChild(any())).thenReturn(file);
when(file.getContentAsString()).thenReturn(HEAD_FILE_CONTENT);
addEvent(HEAD_FILE_NAME, File.separator + PROJECT_NAME + File.separator + HEAD_FILE_PATH, MODIFIED, FILE);
Optional<HiEvent<GitBranchCheckoutEventDto>> optional = gitCheckoutHiVfsEventDetector.detect(root);
assertTrue(optional.isPresent());
HiEvent<GitBranchCheckoutEventDto> hiEvent = optional.get();
assertEquals(GIT_OPERATIONS_CHANNEL, hiEvent.getChannel());
assertEquals(TEST_VALUE, hiEvent.getDto().getBranchName());
}
}

View File

@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import static java.lang.Thread.sleep;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Test for {@link HiEventService}
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@RunWith(MockitoJUnitRunner.class)
public class HiEventServiceTest {
@Mock
private HiEventDetectorManager hiEventDetectorManager;
@Mock
private HiEventBroadcasterManager hiEventBroadcasterManager;
@Spy
private EventTreeQueueHolder eventTreeQueueHolder;
@InjectMocks
private HiEventService hiEventService;
@Before
public void setUp() throws Exception {
hiEventService.postConstruct();
}
@After
public void tearDown() throws Exception {
hiEventService.preDestroy();
}
@Test
public void shouldInvokeAllComponents() throws Exception {
final EventTreeNode eventTreeNode = mock(EventTreeNode.class);
eventTreeQueueHolder.put(eventTreeNode);
sleep(1000);
verify(eventTreeQueueHolder, atLeastOnce()).take();
verify(hiEventDetectorManager).getDetectedEvents(eq(eventTreeNode));
verify(hiEventBroadcasterManager).manageEvents(any());
}
}

View File

@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType;
import org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType;
import static org.eclipse.che.api.vfs.impl.file.event.EventTreeHelper.addEventAndCreatePrecedingNodes;
import static org.eclipse.che.api.vfs.impl.file.event.EventTreeNode.newRootInstance;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.newInstance;
/**
* @author Dmitry Kuleshov
*
* @since 4.5
*/
abstract class HiVfsEventDetectorTestHelper {
protected EventTreeNode root;
public void setUp() throws Exception {
root = newRootInstance();
}
void addEvent(String name,
String path,
FileWatcherEventType eventType,
ItemType itemType) {
LoEvent loEvent = newInstance()
.withName(name)
.withPath(path)
.withEventType(eventType)
.withItemType(itemType)
.withTime(System.currentTimeMillis());
addEventAndCreatePrecedingNodes(root, loEvent);
}
}

View File

@ -0,0 +1,74 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import org.eclipse.che.api.core.notification.EventService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Test for {@link LoEventListener}
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@RunWith(MockitoJUnitRunner.class)
public class LoEventListenerTest {
@Spy
private EventService eventService;
@Mock
private LoEventQueueHolder loEventQueueHolder;
@InjectMocks
private LoEventListener loEventListener;
@Before
public void setUp() throws Exception {
loEventListener.postConstruct();
verify(eventService).subscribe(any(LoEventListener.class));
}
@After
public void tearDown() throws Exception {
loEventListener.preDestroy();
verify(eventService).unsubscribe(isA(LoEventListener.class));
}
@Test
public void shouldEnqueuePublishedVfsEvent() throws Exception {
final LoEvent loEvent = mock(LoEvent.class);
eventService.publish(loEvent);
verify(loEventQueueHolder).put(eq(loEvent));
}
@Test
public void shouldNotEnqueuePublishedNonVfsEvent() throws Exception {
eventService.publish(new Object());
verifyZeroInteractions(loEventQueueHolder);
}
}

View File

@ -0,0 +1,191 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Optional;
import static java.io.File.separator;
import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.sleep;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.MODIFIED;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType.FILE;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.newInstance;
import static org.eclipse.che.api.vfs.impl.file.event.LoEventService.MAX_EVENT_INTERVAL_MILLIS;
import static org.eclipse.che.api.vfs.impl.file.event.LoEventService.MAX_TIME_SEGMENT_SIZE_MILLIS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link LoEventService}
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@RunWith(MockitoJUnitRunner.class)
public class LoEventServiceTest {
private static final long TIMEOUT = 2 * MAX_EVENT_INTERVAL_MILLIS;
private static final String FOLDER_NAME = "folder";
private static final String FILE_NAME = "file";
private static final String PATH = separator + FOLDER_NAME + separator + FILE_NAME;
@Spy
private LoEventQueueHolder loEventQueueHolder;
@Spy
private EventTreeQueueHolder eventTreeQueueHolder;
@InjectMocks
private LoEventService loEventService;
@Before
public void setUp() throws Exception {
loEventService.postConstruct();
}
@After
public void tearDown() throws Exception {
loEventService.preDestroy();
}
@Test
public void shouldConsumeSimpleEventSegmentAndProduceEventTree() throws Exception {
final LoEvent loEvent = getLoEvent(PATH, FILE_NAME);
loEventQueueHolder.put(loEvent);
final Optional<EventTreeNode> rootOptional = eventTreeQueueHolder.take();
assertNotNull(rootOptional);
assertTrue(rootOptional.isPresent());
final EventTreeNode root = rootOptional.get();
assertTrue(root.isRoot());
assertFalse(root.modificationOccurred());
final Optional<EventTreeNode> dirOptional = root.getChild(FOLDER_NAME);
assertNotNull(dirOptional);
assertTrue(dirOptional.isPresent());
final EventTreeNode dir = dirOptional.get();
assertEquals(1, dir.getChildren().size());
assertFalse(dir.modificationOccurred());
final Optional<EventTreeNode> fileOptional = dir.getChild(FILE_NAME);
assertNotNull(fileOptional);
assertTrue(fileOptional.isPresent());
final EventTreeNode file = fileOptional.get();
assertTrue(file.modificationOccurred());
assertEquals(MODIFIED, file.getLastEventType());
assertTrue(file.isFile());
assertEquals(PATH, file.getPath());
assertEquals(FILE_NAME, file.getName());
}
@Test
public void shouldConsumeTwoEventSegmentsAndProduceTwoEventTries() throws Exception {
final LoEvent loEventI = getLoEvent(separator + FOLDER_NAME + 1 + separator + FILE_NAME + 1, FILE_NAME + 1);
loEventQueueHolder.put(loEventI);
final Optional<EventTreeNode> rootOptionalI = eventTreeQueueHolder.take();
sleep(TIMEOUT); // this is required to properly simulate delay between events, should exceed max event interval
final LoEvent loEventII = getLoEvent(separator + FOLDER_NAME + 2 + separator + FILE_NAME + 2, FILE_NAME + 2);
loEventQueueHolder.put(loEventII);
final Optional<EventTreeNode> rootOptionalII = eventTreeQueueHolder.take();
assertNotNull(rootOptionalI);
assertTrue(rootOptionalI.isPresent());
assertNotNull(rootOptionalII);
assertTrue(rootOptionalII.isPresent());
final EventTreeNode rootI = rootOptionalI.get();
final EventTreeNode rootII = rootOptionalII.get();
assertEquals(1, rootI.getChildren().size());
assertEquals(1, rootII.getChildren().size());
final Optional<EventTreeNode> folderOptionalI = rootI.getChild(FOLDER_NAME + 1);
final Optional<EventTreeNode> folderOptionalII = rootII.getChild(FOLDER_NAME + 2);
assertNotNull(folderOptionalI);
assertTrue(folderOptionalI.isPresent());
assertNotNull(folderOptionalI);
assertTrue(folderOptionalII.isPresent());
final EventTreeNode folderI = folderOptionalI.get();
final EventTreeNode folderII = folderOptionalII.get();
assertEquals(1, folderI.getChildren().size());
assertEquals(1, folderII.getChildren().size());
final Optional<EventTreeNode> fileOptionalI = folderI.getChild(FILE_NAME + 1);
final Optional<EventTreeNode> fileOptionalII = folderII.getChild(FILE_NAME + 2);
assertNotNull(fileOptionalI);
assertTrue(fileOptionalI.isPresent());
assertNotNull(fileOptionalII);
assertTrue(fileOptionalII.isPresent());
final EventTreeNode fileI = fileOptionalI.get();
final EventTreeNode fileII = fileOptionalII.get();
assertEquals(FILE_NAME + 1, fileI.getName());
assertEquals(FILE_NAME + 2, fileII.getName());
}
@Test
public void shouldCreateEventTreeBecauseOfExceedingEventSegmentTimeFrame() throws Exception {
final long sleepTime = 150;
final long start = currentTimeMillis();
long totalCounter = 0;
long firstSegmentCounter = 0;
while ((currentTimeMillis() - start) < MAX_TIME_SEGMENT_SIZE_MILLIS + MAX_EVENT_INTERVAL_MILLIS) {
if ((firstSegmentCounter == 0) && ((currentTimeMillis() - start) > MAX_TIME_SEGMENT_SIZE_MILLIS)) {
firstSegmentCounter = totalCounter;
}
totalCounter++;
loEventQueueHolder.put(getLoEvent(PATH + totalCounter, FILE_NAME + totalCounter));
sleep(sleepTime);
}
final Optional<EventTreeNode> rootOptionalI = eventTreeQueueHolder.take();
final Optional<EventTreeNode> rootOptionalII = eventTreeQueueHolder.take();
assertTrue(rootOptionalI.isPresent());
assertTrue(rootOptionalII.isPresent());
final Optional<EventTreeNode> folderI = rootOptionalI.get().getFirstChild();
final Optional<EventTreeNode> folderII = rootOptionalII.get().getFirstChild();
assertTrue(folderI.isPresent());
assertTrue(folderII.isPresent());
assertEquals(firstSegmentCounter, folderI.get().getChildren().size());
assertEquals(totalCounter - firstSegmentCounter, folderII.get().getChildren().size());
}
private LoEvent getLoEvent(String path, String name) {
return newInstance().withName(name)
.withPath(path)
.withEventType(MODIFIED)
.withItemType(FILE)
.withTime(currentTimeMillis());
}
}

View File

@ -0,0 +1,86 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import org.junit.Before;
import org.junit.Test;
import java.util.Optional;
import static java.io.File.separator;
import static org.eclipse.che.api.vfs.impl.file.event.EventTreeHelper.addEventAndCreatePrecedingNodes;
import static org.eclipse.che.api.vfs.impl.file.event.EventTreeHelper.getTreeNode;
import static org.eclipse.che.api.vfs.impl.file.event.EventTreeNode.newRootInstance;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.newInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link EventTreeHelper}
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
public class LoEventTreeHelperTest {
private static final String CHE = "che";
private static final String CHU = "chu";
private static final String CHA = "cha";
private static final String CHI = "chi";
private static final String PATH = separator + CHE + separator + CHU + separator + CHA + separator + CHI;
private EventTreeNode root;
@Before
public void setUp() throws Exception {
root = newRootInstance();
}
@Test
public void shouldImplicitlyCreateAllAncestors() {
final LoEvent loEvent = newInstance().withPath(PATH);
addEventAndCreatePrecedingNodes(root, loEvent);
testNode(testNode(testNode(testNode(root, CHE), CHU), CHA), CHI);
}
@Test
public void shouldGetExistingTreeNode() {
final LoEvent loEvent = newInstance().withName(CHI).withPath(PATH);
addEventAndCreatePrecedingNodes(root, loEvent);
final Optional<EventTreeNode> nodeOptional = getTreeNode(root, PATH);
assertTrue(nodeOptional.isPresent());
final EventTreeNode node = nodeOptional.get();
assertEquals(loEvent.getPath(), node.getPath());
assertEquals(loEvent.getName(), node.getName());
}
@Test
public void shouldNotGetNonExistingTreeNode() {
assertFalse(getTreeNode(root, CHE).isPresent());
}
private EventTreeNode testNode(EventTreeNode parent, String name) {
final Optional<EventTreeNode> nodeOptional = parent.getFirstChild();
assertTrue(nodeOptional.isPresent());
final EventTreeNode node = nodeOptional.get();
assertNotNull(node);
assertEquals(name, node.getName());
return node;
}
}

View File

@ -0,0 +1,98 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.impl.file.event;
import org.eclipse.che.api.project.shared.dto.event.PomModifiedEventDto;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Optional;
import static java.io.File.separator;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.CREATED;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.MODIFIED;
import static org.eclipse.che.api.vfs.impl.file.event.HiEvent.Category.PROJECT_INFRASTRUCTURE;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType.DIR;
import static org.eclipse.che.api.vfs.impl.file.event.LoEvent.ItemType.FILE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link PomModifiedHiEventDetector}
*
* @author Dmitry Kuleshov
*
* @since 4.5
*/
@RunWith(MockitoJUnitRunner.class)
public class PomModifiedHiEventDetectorTest extends HiVfsEventDetectorTestHelper {
private static final String CHE_PATH = separator + "che" + separator;
private static final String FOLDER_1 = CHE_PATH + "folder1" + separator;
private static final String PATH_2 = CHE_PATH + "folder2" + separator;
private static final String TEST = "test";
private static final String POM_XML = "pom.xml";
@Mock
private HiEventServerPublisher hiVfsEventServerPublisher;
private PomModifiedHiEventDetector pomModifiedHiVfsEventDetector;
@Before
public void setUp() throws Exception {
super.setUp();
pomModifiedHiVfsEventDetector = new PomModifiedHiEventDetector(hiVfsEventServerPublisher);
}
@Test
public void shouldReturnEmptyListBecauseNoPomFileFound() {
assertFalse(pomModifiedHiVfsEventDetector.detect(root).isPresent());
}
@Test
public void shouldReturnEmptyListBecauseNoRelevantPomFileFound() {
addEvent(POM_XML, FOLDER_1 + POM_XML, CREATED, FILE);
addEvent(POM_XML, PATH_2 + POM_XML, MODIFIED, DIR);
addEvent(TEST, CHE_PATH + POM_XML + separator + TEST, MODIFIED, FILE);
assertFalse(pomModifiedHiVfsEventDetector.detect(root).isPresent());
}
@Test
public void shouldReturnListWithPomEvent() {
addEvent(POM_XML, FOLDER_1 + POM_XML, CREATED, FILE);
addEvent(POM_XML, FOLDER_1 + POM_XML, MODIFIED, FILE);
addEvent(POM_XML, PATH_2 + POM_XML, MODIFIED, DIR);
addEvent(TEST, CHE_PATH + POM_XML + separator + TEST, MODIFIED, FILE);
final Optional<HiEvent<PomModifiedEventDto>> eventOptional = pomModifiedHiVfsEventDetector.detect(root);
assertTrue(eventOptional.isPresent());
final HiEvent<PomModifiedEventDto> hiEvent = eventOptional.get();
assertEquals(PROJECT_INFRASTRUCTURE, hiEvent.getCategory());
assertEquals(FOLDER_1 + POM_XML, hiEvent.getDto().getPath());
}
@Test
public void shouldReturnListWithPomWithHighestFsHierarchyPosition() {
addEvent(POM_XML, FOLDER_1 + POM_XML, MODIFIED, FILE);
addEvent(POM_XML, CHE_PATH + POM_XML, MODIFIED, FILE);
final Optional<HiEvent<PomModifiedEventDto>> eventOptional = pomModifiedHiVfsEventDetector.detect(root);
assertTrue(eventOptional.isPresent());
final HiEvent<PomModifiedEventDto> hiEvent = eventOptional.get();
assertEquals(CHE_PATH + POM_XML, hiEvent.getDto().getPath());
}
}