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();
+ }
+ }
+ }
+
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventClientBroadcaster.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventClientBroadcaster.java
new file mode 100644
index 0000000000..707d9b3058
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventClientBroadcaster.java
@@ -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.
+ *
+ * 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.
+ *
+ *
+ * @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);
+ }
+ }
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventDetector.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventDetector.java
new file mode 100644
index 0000000000..f383c3d11a
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventDetector.java
@@ -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.
+ *
+ * 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.
+ *
+ *
+ * Perhaps those functions will be shared between detectors and event builders
+ * in future implementations.
+ *
+ *
+ * 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.
+ *
+ *
+ * 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.
+ *
+ *
+ * @author Dmitry Kuleshov
+ *
+ * @since 4.5
+ */
+@Beta
+@FunctionalInterface
+public interface HiEventDetector {
+ Optional> detect(EventTreeNode eventTreeNode);
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventDetectorManager.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventDetectorManager.java
new file mode 100644
index 0000000000..a124f63693
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventDetectorManager.java
@@ -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> hiEventDetectors;
+
+ @Inject
+ public HiEventDetectorManager(Set> hiEventDetectors) {
+ this.hiEventDetectors = hiEventDetectors;
+ }
+
+ List getDetectedEvents(EventTreeNode root) {
+ return hiEventDetectors.stream()
+ .map(o -> o.detect(root))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toList());
+ }
+
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventServerPublisher.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventServerPublisher.java
new file mode 100644
index 0000000000..b451d00a05
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventServerPublisher.java
@@ -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}
+ *
+ * Note: DTO object is assumed to be stored in {@link HiEvent} instance
+ * passed to this broadcaster.
+ *
+ *
+ * @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());
+ }
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventService.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventService.java
new file mode 100644
index 0000000000..fdf1488021
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/HiEventService.java
@@ -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 optional = eventTreeQueueHolder.take();
+ if (optional.isPresent()) {
+ final EventTreeNode treeRoot = optional.get();
+ final List events = hiEventDetectorManager.getDetectedEvents(treeRoot);
+ hiEventBroadcasterManager.manageEvents(events);
+ }
+ }
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEvent.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEvent.java
new file mode 100644
index 0000000000..7fcbe3712c
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEvent.java
@@ -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.).
+ *
+ * *
+ * It is used for both file and folder related events ({@link ItemType}) and
+ * contains all relevant data (time, path, event and item types).
+ *
+ *
+ * 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.
+ *
+ *
+ * @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
+ }
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventListener.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventListener.java
new file mode 100644
index 0000000000..e7fc3fc979
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventListener.java
@@ -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 {
+ 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);
+ }
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventQueueHolder.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventQueueHolder.java
new file mode 100644
index 0000000000..cbc46f6a23
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventQueueHolder.java
@@ -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 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 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();
+ }
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventService.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventService.java
new file mode 100644
index 0000000000..bb2def5883
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/LoEventService.java
@@ -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 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);
+ }
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/PomModifiedHiEventDetector.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/PomModifiedHiEventDetector.java
new file mode 100644
index 0000000000..b7df57bfef
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/PomModifiedHiEventDetector.java
@@ -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.
+ *
+ *
+ * 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.
+ *
+ *
+ * @author Dmitry Kuleshov
+ *
+ * @since 4.5
+ */
+@Beta
+public class PomModifiedHiEventDetector implements HiEventDetector {
+
+ private static final String POM_XML = "pom.xml";
+
+ private HiEventBroadcaster broadcaster;
+
+ @Inject
+ public PomModifiedHiEventDetector(HiEventServerPublisher broadcaster) {
+ this.broadcaster = broadcaster;
+ }
+
+ @Override
+ public Optional> detect(EventTreeNode eventTreeNode) {
+ if (!eventTreeNode.isRoot() || eventTreeNode.getChildren().isEmpty()) {
+ return empty();
+ }
+
+ final Optional 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)));
+ }
+}
diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/VfsEventService.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/VfsEventService.java
new file mode 100644
index 0000000000..f0d66615b9
--- /dev/null
+++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/VfsEventService.java
@@ -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();
+}
diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/EventTreeNodeTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/EventTreeNodeTest.java
new file mode 100644
index 0000000000..c86c543d48
--- /dev/null
+++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/EventTreeNodeTest.java
@@ -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 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);
+ }
+
+}
diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/GitCheckoutHiEventDetectorTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/GitCheckoutHiEventDetectorTest.java
new file mode 100644
index 0000000000..876fb61282
--- /dev/null
+++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/GitCheckoutHiEventDetectorTest.java
@@ -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> optional = gitCheckoutHiVfsEventDetector.detect(root);
+ assertTrue(optional.isPresent());
+
+ HiEvent hiEvent = optional.get();
+ assertEquals(GIT_OPERATIONS_CHANNEL, hiEvent.getChannel());
+ assertEquals(TEST_VALUE, hiEvent.getDto().getBranchName());
+ }
+}
diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/HiEventServiceTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/HiEventServiceTest.java
new file mode 100644
index 0000000000..80d83191a3
--- /dev/null
+++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/HiEventServiceTest.java
@@ -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());
+ }
+
+}
diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/HiVfsEventDetectorTestHelper.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/HiVfsEventDetectorTestHelper.java
new file mode 100644
index 0000000000..e06fc5d6b9
--- /dev/null
+++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/HiVfsEventDetectorTestHelper.java
@@ -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);
+ }
+
+}
diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventListenerTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventListenerTest.java
new file mode 100644
index 0000000000..a06d95c5ac
--- /dev/null
+++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventListenerTest.java
@@ -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);
+ }
+
+}
diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventServiceTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventServiceTest.java
new file mode 100644
index 0000000000..91a35d3aee
--- /dev/null
+++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventServiceTest.java
@@ -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 rootOptional = eventTreeQueueHolder.take();
+
+ assertNotNull(rootOptional);
+ assertTrue(rootOptional.isPresent());
+
+ final EventTreeNode root = rootOptional.get();
+ assertTrue(root.isRoot());
+ assertFalse(root.modificationOccurred());
+
+ final Optional 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 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 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 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 folderOptionalI = rootI.getChild(FOLDER_NAME + 1);
+ final Optional 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 fileOptionalI = folderI.getChild(FILE_NAME + 1);
+ final Optional 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 rootOptionalI = eventTreeQueueHolder.take();
+ final Optional rootOptionalII = eventTreeQueueHolder.take();
+ assertTrue(rootOptionalI.isPresent());
+ assertTrue(rootOptionalII.isPresent());
+
+ final Optional folderI = rootOptionalI.get().getFirstChild();
+ final Optional 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());
+ }
+}
diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventTreeHelperTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventTreeHelperTest.java
new file mode 100644
index 0000000000..d0477ec0c9
--- /dev/null
+++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/LoEventTreeHelperTest.java
@@ -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 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 nodeOptional = parent.getFirstChild();
+ assertTrue(nodeOptional.isPresent());
+
+ final EventTreeNode node = nodeOptional.get();
+ assertNotNull(node);
+ assertEquals(name, node.getName());
+
+ return node;
+ }
+}
diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/PomModifiedHiEventDetectorTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/PomModifiedHiEventDetectorTest.java
new file mode 100644
index 0000000000..a9e9106c1c
--- /dev/null
+++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/vfs/impl/file/event/PomModifiedHiEventDetectorTest.java
@@ -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> eventOptional = pomModifiedHiVfsEventDetector.detect(root);
+ assertTrue(eventOptional.isPresent());
+
+ final HiEvent 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> eventOptional = pomModifiedHiVfsEventDetector.detect(root);
+ assertTrue(eventOptional.isPresent());
+
+ final HiEvent hiEvent = eventOptional.get();
+ assertEquals(CHE_PATH + POM_XML, hiEvent.getDto().getPath());
+ }
+}