diff --git a/node.server/repository-message-api.js b/node.server/repository-message-api.js index 5d8222b..940bd29 100644 --- a/node.server/repository-message-api.js +++ b/node.server/repository-message-api.js @@ -235,8 +235,11 @@ MessagesRepository.prototype.resourceCreated = function(data) { var hash = data.hash; var type = data.type; + var repoProject = this.repository._getProjectStorage(username, projectName); + var repoResource = repoProject.resources[resource]; + this.repository.hasResource(username, projectName, resource, function(err, resourceExists) { - if (err === null && !resourceExists) { + if (err === null && (!resourceExists || repoResource.hash !== hash)) { this.socket.emit('getResourceRequest', { 'callback_id' : 0, 'username' : username, diff --git a/org.eclipse.flux.client.java/src/main/java/org/eclipse/flux/client/IMessageHandler.java b/org.eclipse.flux.client.java/src/main/java/org/eclipse/flux/client/IMessageHandler.java index 470159f..fdcc157 100644 --- a/org.eclipse.flux.client.java/src/main/java/org/eclipse/flux/client/IMessageHandler.java +++ b/org.eclipse.flux.client.java/src/main/java/org/eclipse/flux/client/IMessageHandler.java @@ -19,6 +19,25 @@ * */ public interface IMessageHandler { + public static final String GET_PROJECT_REQUEST = "getProjectRequest"; + public static final String GET_PROJECT_RESPONSE = "getProjectResponse"; + public static final String GET_PROJECTS_REQUEST = "getProjectsRequest"; + public static final String GET_PROJECTS_RESPONSE = "getProjectsResponse"; + public static final String GET_LIVE_RESOURCE_REQUEST = "getLiveResourcesRequest"; + public static final String GET_METADATA_REQUEST = "getMetadataRequest"; + public static final String GET_METADATA_RESPONSE = "getMetadataResponse"; + public static final String GET_RESOURCE_REQUEST = "getResourceRequest"; + public static final String GET_RESOURCE_RESPONSE = "getResourceResponse"; + public static final String LIVE_RESOURCE_STARTED = "liveResourceStarted"; + public static final String LIVE_RESOURCE_STARTED_RESPONSE = "liveResourceStartedResponse"; + public static final String LIVE_RESOURCE_CHANGED = "liveResourceChanged"; + public static final String METADATA_CHANGED = "metadataChanged"; + public static final String PROJECT_CONNECTED = "projectConnected"; + public static final String PROJECT_DISCONNECTED = "projectDisconnected"; + public static final String RESOURCE_CHANGED = "resourceChanged"; + public static final String RESOURCE_CREATED = "resourceCreated"; + public static final String RESOURCE_DELETED = "resourceDeleted"; + public static final String RESOURCE_STORED = "resourceStored"; boolean canHandle(String type, JSONObject message); diff --git a/org.eclipse.flux.client.java/src/main/java/org/eclipse/flux/client/MessageConstants.java b/org.eclipse.flux.client.java/src/main/java/org/eclipse/flux/client/MessageConstants.java index b522b55..ab1cea9 100644 --- a/org.eclipse.flux.client.java/src/main/java/org/eclipse/flux/client/MessageConstants.java +++ b/org.eclipse.flux.client.java/src/main/java/org/eclipse/flux/client/MessageConstants.java @@ -56,5 +56,28 @@ public abstract class MessageConstants { * Name of the Flux super user. */ public static final String SUPER_USER = "$super$"; - + + public static final String CHANNEL = "channel"; + public static final String CONNECTED_TO_CHANNEL = "connectedToChannel"; + public static final String CONTENT = "content"; + public static final String DELETED = "deleted"; + public static final String FILES = "files"; + public static final String HASH = "hash"; + public static final String INCLUDE_DELETED = "includeDeleted"; + public static final String PATH = "path"; + public static final String RESOURCE = "resource"; + public static final String TIMESTAMP = "timestamp"; + public static final String TYPE = "type"; + public static final String SAVE_POINT_HASH = "savePointHash"; + public static final String SAVE_POINT_TIMESTAMP = "savePointTimestamp"; + public static final String LIVE_CONTENT = "liveContent"; + public static final String OFFSET = "offset"; + public static final String REMOVED_CHAR_COUNT = "removedCharCount"; + public static final String ADDED_CHARACTERS = "addedCharacters"; + public static final String PROJECT_REG_EX = "projectRegEx"; + public static final String RESOURCE_REG_EX = "resourceRegEx"; + public static final String METADATA = "metadata"; + public static final String PROJECTS = "projects"; + public static final String NAME = "name"; + public static final String MARKER = "marker"; } diff --git a/org.eclipse.flux.core/META-INF/MANIFEST.MF b/org.eclipse.flux.core/META-INF/MANIFEST.MF index 8aa5454..88a7e08 100644 --- a/org.eclipse.flux.core/META-INF/MANIFEST.MF +++ b/org.eclipse.flux.core/META-INF/MANIFEST.MF @@ -14,7 +14,9 @@ Require-Bundle: org.eclipse.flux.client.java.osgi, org.eclipse.core.runtime, org.apache.commons.io, org.eclipse.jdt.core, - org.eclipse.m2e.core;resolution:=optional + org.eclipse.m2e.core;resolution:=optional, + org.eclipse.flux.watcher;bundle-version="1.0.0", + com.google.inject;bundle-version="3.0.0" Export-Package: org.eclipse.flux.core, org.eclipse.flux.core.util Bundle-ActivationPolicy: lazy; exclude:="io.socket, org.java_websocket, org.java_websocket.client, org.java_websocket.drafts, org.java_websocket.exceptions, org.java_websocket.framing, org.java_websocket.handshake, org.java_websocket.server, org.java_websocket.util, org.json" diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/Activator.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/Activator.java index 661e380..51dbe6e 100644 --- a/org.eclipse.flux.core/src/org/eclipse/flux/core/Activator.java +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/Activator.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.flux.core; +import java.net.URL; import java.util.Collection; import java.util.HashSet; import java.util.concurrent.Executors; @@ -35,11 +36,15 @@ import org.eclipse.flux.client.MessageConstants; import org.eclipse.flux.client.config.SocketIOFluxConfig; import org.eclipse.flux.core.internal.CloudSyncMetadataListener; -import org.eclipse.flux.core.internal.CloudSyncResourceListener; import org.eclipse.flux.core.util.ExceptionUtil; +import org.eclipse.flux.watcher.core.RepositoryModule; +import org.eclipse.flux.watcher.fs.JDKProjectModule; import org.osgi.framework.BundleContext; import org.osgi.service.prefs.BackingStoreException; +import com.google.inject.Guice; +import com.google.inject.Injector; + /** * @author Martin Lippert * @author Miles Parker @@ -57,11 +62,10 @@ public class Activator extends Plugin { private MessageConnector messageConnector; private ChannelSwitcher channelSwitcher; - private Repository repository; + private RepositoryAdapter repository; private LiveEditCoordinator liveEditCoordinator; private boolean lazyStart = false; - private CloudSyncResourceListener resourceListener; private CloudSyncMetadataListener metadataListener; private IRepositoryListener repositoryListener; private IResourceChangeListener workspaceListener; @@ -109,6 +113,7 @@ public void start(BundleContext context) throws Exception { final String userChannel = lazyStart ? MessageConstants.SUPER_USER : channel; + Injector injector = Guice.createInjector(new RepositoryModule(), new JDKProjectModule()); //Connecting to channel done asynchronously. To avoid blocking plugin state initialization. FluxClient.DEFAULT_INSTANCE.getExecutor().execute(new Runnable() { @Override @@ -165,14 +170,11 @@ public void stop(BundleContext context) throws Exception { } private void initCoreService(String userChannel) throws CoreException { - repository = new Repository(messageConnector, userChannel); + repository = new RepositoryAdapter(messageConnector, userChannel); liveEditCoordinator = new LiveEditCoordinator(messageConnector); IWorkspace workspace = ResourcesPlugin.getWorkspace(); - resourceListener = new CloudSyncResourceListener(repository); - workspace.addResourceChangeListener(resourceListener, IResourceChangeEvent.POST_CHANGE); - metadataListener = new CloudSyncMetadataListener(repository); workspace.addResourceChangeListener(metadataListener, IResourceChangeEvent.POST_BUILD); @@ -225,7 +227,6 @@ private void disposeCoreServices(String userChannel) { if (userChannel.equals(repository.getUsername())) { IWorkspace workspace = ResourcesPlugin.getWorkspace(); workspace.removeResourceChangeListener(workspaceListener); - workspace.removeResourceChangeListener(resourceListener); workspace.removeResourceChangeListener(metadataListener); repository.removeRepositoryListener(repositoryListener); liveEditCoordinator.dispose(); @@ -242,7 +243,7 @@ private void updateProjectConnections() throws CoreException { if (!project.isOpen()) { project.open(null); } - Repository repository = org.eclipse.flux.core.Activator.getDefault() + RepositoryAdapter repository = org.eclipse.flux.core.Activator.getDefault() .getRepository(); repository.addProject(project); } @@ -310,7 +311,7 @@ public MessageConnector getMessageConnector() { return messageConnector; } - public Repository getRepository() { + public RepositoryAdapter getRepository() { return repository; } diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/ConnectedProject.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/ConnectedProject.java index 996616f..e6c7079 100644 --- a/org.eclipse.flux.core/src/org/eclipse/flux/core/ConnectedProject.java +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/ConnectedProject.java @@ -10,93 +10,78 @@ *******************************************************************************/ package org.eclipse.flux.core; -import java.io.IOException; import java.io.InputStream; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.commons.codec.digest.DigestUtils; -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceVisitor; -import org.eclipse.core.runtime.CoreException; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; /** * @author Martin Lippert */ public class ConnectedProject { - - private IProject project; - private Map resourceHash; - private Map resourceTimestamp; - - public ConnectedProject(IProject project) { - this.project = project; - this.resourceHash = new ConcurrentHashMap(); - this.resourceTimestamp = new ConcurrentHashMap(); - - try { - project.refreshLocal(IResource.DEPTH_INFINITE, null); - project.accept(new IResourceVisitor() { - @Override - public boolean visit(IResource resource) throws CoreException { - String path = resource.getProjectRelativePath().toString(); - ConnectedProject.this.setTimestamp(path, resource.getLocalTimeStamp()); - - if (resource instanceof IFile) { - try { - IFile file = (IFile) resource; - ConnectedProject.this.setHash(path, DigestUtils.shaHex(file.getContents())); - } catch (IOException e) { - e.printStackTrace(); - } - } - else if (resource instanceof IFolder) { - ConnectedProject.this.setHash(path, "0"); - } - - return true; - } - }, IResource.DEPTH_INFINITE, IContainer.EXCLUDE_DERIVED); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public IProject getProject() { - return project; - } - - public String getName() { - return this.project.getName(); - } + private String name; + private Project project; + + public ConnectedProject(Project project) { + this.project = project; + this.name = project.id(); + } + + public IProject getProject() { + return null; + } + + public String getName() { + return this.project.id(); + } + + public static ConnectedProject readFromJSON(InputStream inputStream, IProject project) { + return null; + } + + public long getTimestamp(String resourcePath) { + Resource resource = this.project.getResource(resourcePath); + return resource.timestamp(); + } + + public String getHash(String resourcePath) { + Resource resource = this.project.getResource(resourcePath); + return resource.hash(); + } + + public boolean containsResource(String resourcePath) { + return this.project.hasResource(resourcePath); + } - public static ConnectedProject readFromJSON(InputStream inputStream, IProject project) { - return new ConnectedProject(project); - } - - public void setTimestamp(String resourcePath, long newTimestamp) { - this.resourceTimestamp.put(resourcePath, newTimestamp); - } - - public long getTimestamp(String resourcePath) { - return this.resourceTimestamp.get(resourcePath); - } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } - public void setHash(String resourcePath, String hash) { - this.resourceHash.put(resourcePath, hash); - } - - public String getHash(String resourcePath) { - return this.resourceHash.get(resourcePath); - } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ConnectedProject)) { + return false; + } + ConnectedProject other = (ConnectedProject)obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } - public boolean containsResource(String resourcePath) { - return this.resourceTimestamp.containsKey(resourcePath); - } - } diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/LiveEditCoordinator.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/LiveEditCoordinator.java index 6b06ba6..8af49d0 100644 --- a/org.eclipse.flux.core/src/org/eclipse/flux/core/LiveEditCoordinator.java +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/LiveEditCoordinator.java @@ -18,7 +18,10 @@ import org.eclipse.flux.client.IMessageHandler; import org.eclipse.flux.client.MessageConnector; -import org.eclipse.flux.client.MessageHandler; +import org.eclipse.flux.core.handlers.LiveResourceChangedHandler; +import org.eclipse.flux.core.handlers.LiveResourceRequestHandler; +import org.eclipse.flux.core.handlers.LiveResourceStartedHandler; +import org.eclipse.flux.core.handlers.LiveResourceStartedResponseHandler; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -28,129 +31,21 @@ */ public class LiveEditCoordinator { - private MessageConnector messagingConnector; + private MessageConnector messagingConnector; private Collection liveEditConnectors; - private Collection messageHandlers; + private Collection messageHandlers; - public LiveEditCoordinator(MessageConnector messagingConnector) { - this.messagingConnector = messagingConnector; + public LiveEditCoordinator(MessageConnector messageConnector) { + this.messagingConnector = messageConnector; this.liveEditConnectors = new CopyOnWriteArrayList<>(); - this.messageHandlers = new ArrayList(4); + this.messageHandlers = new ArrayList(4); - IMessageHandler startLiveUnit = new MessageHandler("liveResourceStarted") { - @Override - public void handle(String messageType, JSONObject message) { - startLiveUnit(message); - } - }; - messagingConnector.addMessageHandler(startLiveUnit); - messageHandlers.add(startLiveUnit); - - IMessageHandler startLiveUnitResponse = new MessageHandler("liveResourceStartedResponse") { - @Override - public void handle(String messageType, JSONObject message) { - startLiveUnitResponse(message); - } - }; - messagingConnector.addMessageHandler(startLiveUnitResponse); - messageHandlers.add(startLiveUnitResponse); - - IMessageHandler modelChangedHandler = new MessageHandler("liveResourceChanged") { - @Override - public void handle(String messageType, JSONObject message) { - modelChanged(message); - } - }; - messagingConnector.addMessageHandler(modelChangedHandler); - messageHandlers.add(modelChangedHandler); - - // Listen to the internal broadcast channel to send out info about current live edit units - IMessageHandler liveUnits = new MessageHandler("getLiveResourcesRequest") { - @Override - public void handle(String messageType, JSONObject message) { - sendLiveUnits(message); - } - }; - messagingConnector.addMessageHandler(liveUnits); - messageHandlers.add(liveUnits); + addMessageHandler(new LiveResourceStartedHandler(liveEditConnectors)); + addMessageHandler(new LiveResourceStartedResponseHandler(liveEditConnectors)); + addMessageHandler(new LiveResourceChangedHandler(liveEditConnectors)); + addMessageHandler(new LiveResourceRequestHandler(liveEditConnectors)); } - protected void startLiveUnit(JSONObject message) { - try { - String requestSenderID = message.getString("requestSenderID"); - int callbackID = message.getInt("callback_id"); - String username = message.getString("username"); - String projectName = message.getString("project"); - String resourcePath = message.getString("resource"); - String hash = message.getString("hash"); - long timestamp = message.getLong("timestamp"); - - String liveEditID = projectName + "/" + resourcePath; - for (ILiveEditConnector connector : liveEditConnectors) { - connector.liveEditingStarted(requestSenderID, callbackID, username, liveEditID, hash, timestamp); - } - } - catch (Exception e) { - e.printStackTrace(); - } - } - - protected void startLiveUnitResponse(JSONObject message) { - try { - String requestSenderID = message.getString("requestSenderID"); - int callbackID = message.getInt("callback_id"); - String username = message.getString("username"); - String projectName = message.getString("project"); - String resourcePath = message.getString("resource"); - String savePointHash = message.getString("savePointHash"); - long savePointTimestamp = message.getLong("savePointTimestamp"); - String content = message.getString("liveContent"); - - for (ILiveEditConnector connector : liveEditConnectors) { - connector.liveEditingStartedResponse(requestSenderID, callbackID, username, projectName, resourcePath, savePointHash, savePointTimestamp, content); - } - } - catch (Exception e) { - e.printStackTrace(); - } - } - - protected void modelChanged(JSONObject message) { - try { - String username = message.getString("username"); - String projectName = message.getString("project"); - String resourcePath = message.getString("resource"); - - int offset = message.getInt("offset"); - int removedCharCount = message.getInt("removedCharCount"); - String addedChars = message.has("addedCharacters") ? message.getString("addedCharacters") : ""; - - String liveEditID = projectName + "/" + resourcePath; - - for (ILiveEditConnector connector : liveEditConnectors) { - connector.liveEditingEvent(username, liveEditID, offset, removedCharCount, addedChars); - } - } - catch (Exception e) { - e.printStackTrace(); - } - } - - protected void sendLiveUnits(JSONObject message) { - try { - String username = message.has("username") ? message.getString("username") : null; - String requestSenderID = message.getString("requestSenderID"); - String projectRegEx = message.has("projectRegEx") ? message.getString("projectRegEx") : null; - String resourceRegEx = message.has("resourceRegEx") ? message.getString("resourceRegEx") : null; - int callbackID = message.getInt("callback_id"); - for (ILiveEditConnector connector : liveEditConnectors) { - connector.liveEditors(requestSenderID, callbackID, username, projectRegEx, resourceRegEx); - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - public void addLiveEditConnector(ILiveEditConnector connector) { liveEditConnectors.add(connector); } @@ -166,11 +61,9 @@ public void sendModelChangedMessage(String changeOriginID, String username, Stri message.put("project", projectName); message.put("resource", resourcePath); message.put("offset", offset); - message.put("offset", offset); message.put("removedCharCount", removedCharactersCount); message.put("addedCharacters", newText != null ? newText : ""); - - this.messagingConnector.send("liveResourceChanged", message); + this.messagingConnector.send(IMessageHandler.LIVE_RESOURCE_CHANGED, message); } catch (Exception e) { e.printStackTrace(); @@ -194,8 +87,7 @@ public void sendLiveEditStartedMessage(String changeOriginID, String username, S message.put("resource", resourcePath); message.put("hash", hash); message.put("timestamp", timestamp); - - this.messagingConnector.send("liveResourceStarted", message); + this.messagingConnector.send(IMessageHandler.LIVE_RESOURCE_STARTED, message); } catch (Exception e) { e.printStackTrace(); @@ -220,8 +112,7 @@ public void sendLiveEditStartedResponse(String responseOriginID, String requestS message.put("savePointTimestamp", savePointTimestamp); message.put("savePointHash", savePointHash); message.put("liveContent", content); - - this.messagingConnector.send("liveResourceStartedResponse", message); + this.messagingConnector.send(IMessageHandler.LIVE_RESOURCE_STARTED_RESPONSE, message); } catch (Exception e) { e.printStackTrace(); @@ -251,8 +142,7 @@ public void sendLiveResourcesResponse(String requestSenderID, liveEditUnits.put(entry.getKey(), new JSONArray(entry.getValue())); } message.put("liveEditUnits", liveEditUnits); - - this.messagingConnector.send("getLiveResourcesResponse", message); + this.messagingConnector.send(IMessageHandler.GET_LIVE_RESOURCE_REQUEST, message); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -274,9 +164,14 @@ public ResourceData(String path, String hash, long timestamp) { } public void dispose() { - for (IMessageHandler messageHanlder : messageHandlers) { - messagingConnector.removeMessageHandler(messageHanlder); - } + for (IMessageHandler messageHanlder : messageHandlers) { + messagingConnector.removeMessageHandler(messageHanlder); + } } + + private void addMessageHandler(IMessageHandler messageHandler){ + this.messagingConnector.addMessageHandler(messageHandler); + this.messageHandlers.add(messageHandler); + } } diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/Repository.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/Repository.java deleted file mode 100644 index 8d2beef..0000000 --- a/org.eclipse.flux.core/src/org/eclipse/flux/core/Repository.java +++ /dev/null @@ -1,973 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2013, 2014 Pivotal Software, Inc. and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * Pivotal Software, Inc. - initial API and implementation -*******************************************************************************/ -package org.eclipse.flux.core; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.io.IOUtils; -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.IResourceVisitor; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.flux.client.CallbackIDAwareMessageHandler; -import org.eclipse.flux.client.IMessageHandler; -import org.eclipse.flux.client.MessageConnector; -import org.eclipse.flux.client.MessageHandler; -import org.eclipse.jdt.core.IClassFile; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaCore; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * @author Martin Lippert - */ -public class Repository { - - private String username; - private MessageConnector messagingConnector; - private Collection messageHandlers; - - private ConcurrentMap syncedProjects; - private Collection repositoryListeners; - - private static int GET_PROJECT_CALLBACK = "Repository - getProjectCallback".hashCode(); - private static int GET_RESOURCE_CALLBACK = "Repository - getResourceCallback".hashCode(); - - private AtomicBoolean connected; - - public Repository(MessageConnector messagingConnector, String user) { - this.username = user; - this.connected = new AtomicBoolean(true); - this.messagingConnector = messagingConnector; - - this.syncedProjects = new ConcurrentHashMap(); - this.repositoryListeners = new ConcurrentLinkedDeque<>(); - - this.messageHandlers = new ArrayList(9); - - IMessageHandler resourceChangedHandler = new MessageHandler("resourceChanged") { - @Override - public void handle(String messageType, JSONObject message) { - updateResource(message); - } - }; - this.messagingConnector.addMessageHandler(resourceChangedHandler); - messageHandlers.add(resourceChangedHandler); - - IMessageHandler resourceCreatedHandler = new MessageHandler("resourceCreated") { - @Override - public void handle(String messageType, JSONObject message) { - createResource(message); - } - }; - this.messagingConnector.addMessageHandler(resourceCreatedHandler); - this.messageHandlers.add(resourceCreatedHandler); - - IMessageHandler resourceDeletedHandler = new MessageHandler("resourceDeleted") { - @Override - public void handle(String messageType, JSONObject message) { - deleteResource(message); - } - }; - this.messagingConnector.addMessageHandler(resourceDeletedHandler); - this.messageHandlers.add(resourceDeletedHandler); - - IMessageHandler getProjectsRequestHandler = new MessageHandler("getProjectsRequest") { - @Override - public void handle(String messageType, JSONObject message) { - getProjects(message); - } - }; - this.messagingConnector.addMessageHandler(getProjectsRequestHandler); - this.messageHandlers.add(getProjectsRequestHandler); - - IMessageHandler getProjectRequestHandler = new MessageHandler("getProjectRequest") { - @Override - public void handle(String messageType, JSONObject message) { - getProject(message); - } - }; - this.messagingConnector.addMessageHandler(getProjectRequestHandler); - this.messageHandlers.add(getProjectRequestHandler); - - IMessageHandler getProjectResponseHandler = new CallbackIDAwareMessageHandler("getProjectResponse", Repository.GET_PROJECT_CALLBACK) { - @Override - public void handle(String messageType, JSONObject message) { - getProjectResponse(message); - } - }; - this.messagingConnector.addMessageHandler(getProjectResponseHandler); - this.messageHandlers.add(getProjectResponseHandler); - - IMessageHandler getResourceRequestHandler = new MessageHandler("getResourceRequest") { - @Override - public void handle(String messageType, JSONObject message) { - try { - final String resourcePath = message.getString("resource"); - - if (resourcePath.startsWith("classpath:")) { - getClasspathResource(message); - } - else { - getResource(message); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }; - this.messagingConnector.addMessageHandler(getResourceRequestHandler); - this.messageHandlers.add(getResourceRequestHandler); - - IMessageHandler getResourceResponseHandler = new CallbackIDAwareMessageHandler("getResourceResponse", Repository.GET_RESOURCE_CALLBACK) { - @Override - public void handle(String messageType, JSONObject message) { - getResourceResponse(message); - } - }; - this.messagingConnector.addMessageHandler(getResourceResponseHandler); - this.messageHandlers.add(getResourceResponseHandler); - - IMessageHandler getMetadataRequestHandler = new MessageHandler("getMetadataRequest") { - @Override - public void handle(String messageType, JSONObject message) { - getMetadata(message); - } - }; - this.messagingConnector.addMessageHandler(getMetadataRequestHandler); - this.messageHandlers.add(getMetadataRequestHandler); - } - - public String getUsername() { - return this.username; - } - - protected void connect() { - for (String projectName : syncedProjects.keySet()) { - sendProjectConnectedMessage(projectName); - syncConnectedProject(projectName); - } - } - - public ConnectedProject getProject(IProject project) { - return getProject(project.getName()); - } - - public ConnectedProject getProject(String projectName) { - return this.syncedProjects.get(projectName); - } - - public boolean isConnected(IProject project) { - return this.syncedProjects.containsKey(project.getName()); - } - - public boolean isConnected(String project) { - return this.syncedProjects.containsKey(project); - } - - public void addProject(IProject project) { - String projectName = project.getName(); - if (!this.syncedProjects.containsKey(projectName)) { - this.syncedProjects.put(projectName, new ConnectedProject(project)); - notifyProjectConnected(project); - sendProjectConnectedMessage(projectName); - syncConnectedProject(projectName); - } - } - - public void removeProject(IProject project) { - String projectName = project.getName(); - if (this.syncedProjects.containsKey(projectName)) { - this.syncedProjects.remove(projectName); - notifyProjectDisonnected(project); - try { - JSONObject message = new JSONObject(); - message.put("username", this.username); - message.put("project", projectName); - messagingConnector.send("projectDisconnected", message); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - protected void syncConnectedProject(String projectName) { - try { - JSONObject message = new JSONObject(); - message.put("username", this.username); - message.put("project", projectName); - message.put("includeDeleted", true); - message.put("callback_id", GET_PROJECT_CALLBACK); - messagingConnector.send("getProjectRequest", message); - } catch (Exception e) { - e.printStackTrace(); - } - } - - protected void sendProjectConnectedMessage(String projectName) { - try { - JSONObject message = new JSONObject(); - message.put("username", this.username); - message.put("project", projectName); - messagingConnector.send("projectConnected", message); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public ConnectedProject[] getConnectedProjects() { - return syncedProjects.values().toArray( - new ConnectedProject[syncedProjects.size()]); - } - - public void getProjects(JSONObject request) { - try { - int callbackID = getIntMaybe(request, "callback_id"); - String sender = request.getString("requestSenderID"); - String username = request.getString("username"); - - if (this.username.equals(username)) { - JSONArray projects = new JSONArray(); - for (String projectName : this.syncedProjects.keySet()) { - JSONObject proj = new JSONObject(); - proj.put("name", projectName); - projects.put(proj); - } - - JSONObject message = new JSONObject(); - message.put("callback_id", callbackID); - message.put("requestSenderID", sender); - message.put("username", this.username); - message.put("projects", projects); - - messagingConnector.send("getProjectsResponse", message); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - private int getIntMaybe(JSONObject request, String prop) throws JSONException { - if (request.has(prop)) { - return request.getInt(prop); - } - return 0; - } - - public void getProject(JSONObject request) { - try { - final int callbackID = request.getInt("callback_id"); - final String sender = request.getString("requestSenderID"); - final String projectName = request.getString("project"); - final String username = request.getString("username"); - - final ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null) { - - final JSONArray files = new JSONArray(); - - IProject project = connectedProject.getProject(); - - try { - project.accept(new IResourceVisitor() { - @Override - public boolean visit(IResource resource) throws CoreException { - JSONObject projectResource = new JSONObject(); - String path = resource.getProjectRelativePath().toString(); - try { - projectResource.put("path", path); - projectResource.put("timestamp", connectedProject.getTimestamp(path)); - projectResource.put("hash", connectedProject.getHash(path)); - - if (resource instanceof IFile) { - projectResource.put("type", "file"); - } else if (resource instanceof IFolder) { - projectResource.put("type", "folder"); - } - - files.put(projectResource); - } catch (JSONException e) { - e.printStackTrace(); - } - return true; - } - }, IResource.DEPTH_INFINITE, IContainer.EXCLUDE_DERIVED); - } catch (Exception e) { - e.printStackTrace(); - } - - JSONObject message = new JSONObject(); - message.put("callback_id", callbackID); - message.put("requestSenderID", sender); - message.put("username", this.username); - message.put("project", projectName); - message.put("username", this.username); - message.put("files", files); - - messagingConnector.send("getProjectResponse", message); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void getProjectResponse(JSONObject response) { - try { - final String username = response.getString("username"); - final String projectName = response.getString("project"); - final JSONArray files = response.getJSONArray("files"); - final JSONArray deleted = response.optJSONArray("deleted"); - - ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null) { - - for (int i = 0; i < files.length(); i++) { - JSONObject resource = files.getJSONObject(i); - - String resourcePath = resource.getString("path"); - long timestamp = resource.getLong("timestamp"); - - String type = resource.optString("type"); - String hash = resource.optString("hash"); - - boolean newFile = type != null && type.equals("file") && !connectedProject.containsResource(resourcePath); - boolean updatedFileTimestamp = type != null && type.equals("file") && connectedProject.containsResource(resourcePath) - && connectedProject.getHash(resourcePath).equals(hash) && connectedProject.getTimestamp(resourcePath) < timestamp; - boolean updatedFile = type != null && type.equals("file") && connectedProject.containsResource(resourcePath) - && !connectedProject.getHash(resourcePath).equals(hash) && connectedProject.getTimestamp(resourcePath) < timestamp; - - if (newFile || updatedFile) { - JSONObject message = new JSONObject(); - message.put("callback_id", GET_RESOURCE_CALLBACK); - message.put("project", projectName); - message.put("username", this.username); - message.put("resource", resourcePath); - message.put("timestamp", timestamp); - message.put("hash", hash); - - messagingConnector.send("getResourceRequest", message); - } - - if (updatedFileTimestamp) { - connectedProject.setTimestamp(resourcePath, timestamp); - IResource file = connectedProject.getProject().findMember(resourcePath); - file.setLocalTimeStamp(timestamp); - } - - boolean newFolder = type != null && type.equals("folder") && !connectedProject.containsResource(resourcePath); - boolean updatedFolder = type != null && type.equals("folder") && connectedProject.containsResource(resourcePath) - && !(connectedProject.getHash(resourcePath) == null || connectedProject.getHash(resourcePath).equals(hash)) && connectedProject.getTimestamp(resourcePath) < timestamp; - - if (newFolder) { - IProject project = connectedProject.getProject(); - IFolder folder = project.getFolder(resourcePath); - - connectedProject.setHash(resourcePath, hash); - connectedProject.setTimestamp(resourcePath, timestamp); - - folder.create(true, true, null); - folder.setLocalTimeStamp(timestamp); - } - else if (updatedFolder) { - } - } - - if (deleted != null) { - for (int i = 0; i < deleted.length(); i++) { - JSONObject deletedResource = deleted.getJSONObject(i); - - String resourcePath = deletedResource.getString("path"); - long deletedTimestamp = deletedResource.getLong("timestamp"); - - IProject project = connectedProject.getProject(); - IResource resource = project.findMember(resourcePath); - - if (resource != null && resource.exists() && (resource instanceof IFile || resource instanceof IFolder)) { - long localTimestamp = connectedProject.getTimestamp(resourcePath); - - if (localTimestamp < deletedTimestamp) { - resource.delete(true, null); - } - } - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void getResource(JSONObject request) { - try { - final String username = request.getString("username"); - final int callbackID = request.getInt("callback_id"); - final String sender = request.getString("requestSenderID"); - final String projectName = request.getString("project"); - final String resourcePath = request.getString("resource"); - - ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null && connectedProject.containsResource(resourcePath)) { - IProject project = connectedProject.getProject(); - - if (request.has("timestamp") && request.getLong("timestamp") != connectedProject.getTimestamp(resourcePath)) { - return; - } - - IResource resource = project.findMember(resourcePath); - - JSONObject message = new JSONObject(); - message.put("callback_id", callbackID); - message.put("requestSenderID", sender); - message.put("username", this.username); - message.put("project", projectName); - message.put("resource", resourcePath); - message.put("timestamp", connectedProject.getTimestamp(resourcePath)); - message.put("hash", connectedProject.getHash(resourcePath)); - - if (resource instanceof IFile) { - if (request.has("hash") && !request.getString("hash").equals(connectedProject.getHash(resourcePath))) { - return; - } - - IFile file = (IFile) resource; - - ByteArrayOutputStream array = new ByteArrayOutputStream(); - if (!file.isSynchronized(IResource.DEPTH_ZERO)) { - file.refreshLocal(IResource.DEPTH_ZERO, null); - } - - IOUtils.copy(file.getContents(), array); - - String content = new String(array.toByteArray(), file.getCharset()); - - message.put("content", content); - message.put("type", "file"); - } else if (resource instanceof IFolder) { - message.put("type", "folder"); - } - - messagingConnector.send("getResourceResponse", message); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void getClasspathResource(JSONObject request) { - try { - final int callbackID = request.getInt("callback_id"); - final String sender = request.getString("requestSenderID"); - final String projectName = request.getString("project"); - final String resourcePath = request.getString("resource"); - final String username = request.getString("username"); - - ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null) { - String typeName = resourcePath.substring("classpath:/".length()); - if (typeName.endsWith(".class")) { - typeName = typeName.substring(0, typeName.length() - ".class".length()); - } - typeName = typeName.replace('/', '.'); - - IJavaProject javaProject = JavaCore.create(connectedProject.getProject()); - if (javaProject != null) { - IType type = javaProject.findType(typeName); - IClassFile classFile = type.getClassFile(); - if (classFile != null && classFile.getSourceRange() != null) { - - JSONObject message = new JSONObject(); - message.put("callback_id", callbackID); - message.put("requestSenderID", sender); - message.put("username", this.username); - message.put("project", projectName); - message.put("resource", resourcePath); - message.put("readonly", true); - - String content = classFile.getSource(); - - message.put("content", content); - message.put("type", "file"); - - messagingConnector.send("getResourceResponse", message); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void updateResource(JSONObject request) { - try { - final String username = request.getString("username"); - final String projectName = request.getString("project"); - final String resourcePath = request.getString("resource"); - final long updateTimestamp = request.getLong("timestamp"); - final String updateHash = request.optString("hash"); - - ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null) { - IProject project = connectedProject.getProject(); - IResource resource = project.findMember(resourcePath); - - if (resource != null && resource instanceof IFile) { - String localHash = connectedProject.getHash(resourcePath); - long localTimestamp = connectedProject.getTimestamp(resourcePath); - - if (localHash != null && !localHash.equals(updateHash) && localTimestamp < updateTimestamp) { - JSONObject message = new JSONObject(); - message.put("callback_id", GET_RESOURCE_CALLBACK); - message.put("username", this.username); - message.put("project", projectName); - message.put("resource", resourcePath); - message.put("timestamp", updateTimestamp); - message.put("hash", updateHash); - - messagingConnector.send("getResourceRequest", message); - notifyResourceChanged(resource); - } - } - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void createResource(JSONObject request) { - try { - final String username = request.getString("username"); - final String projectName = request.getString("project"); - final String resourcePath = request.getString("resource"); - final long updateTimestamp = request.getLong("timestamp"); - final String updateHash = request.optString("hash"); - final String type = request.optString("type"); - - ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null) { - IProject project = connectedProject.getProject(); - IResource resource = project.findMember(resourcePath); - - if (resource == null) { - if ("folder".equals(type)) { - IFolder newFolder = project.getFolder(resourcePath); - - connectedProject.setHash(resourcePath, updateHash); - connectedProject.setTimestamp(resourcePath, updateTimestamp); - - newFolder.create(true, true, null); - newFolder.setLocalTimeStamp(updateTimestamp); - - JSONObject message = new JSONObject(); - message.put("username", this.username); - message.put("project", projectName); - message.put("resource", resourcePath); - message.put("timestamp", updateTimestamp); - message.put("hash", updateHash); - message.put("type", type); - - messagingConnector.send("resourceStored", message); - } - else if ("file".equals(type)) { - JSONObject message = new JSONObject(); - message.put("callback_id", GET_RESOURCE_CALLBACK); - message.put("username", this.username); - message.put("project", projectName); - message.put("resource", resourcePath); - message.put("timestamp", updateTimestamp); - message.put("hash", updateHash); - message.put("type", type); - - messagingConnector.send("getResourceRequest", message); - } - } - else { - // TODO - } - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void deleteResource(JSONObject request) { - try { - final String username = request.getString("username"); - final String projectName = request.getString("project"); - final String resourcePath = request.getString("resource"); - final long deletedTimestamp = request.getLong("timestamp"); - - ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null) { - IProject project = connectedProject.getProject(); - IResource resource = project.findMember(resourcePath); - - if (resource != null && resource.exists() && (resource instanceof IFile || resource instanceof IFolder)) { - long localTimestamp = connectedProject.getTimestamp(resourcePath); - - if (localTimestamp < deletedTimestamp) { - resource.delete(true, null); - } - } - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void getResourceResponse(JSONObject response) { - try { - final String username = response.getString("username"); - final String projectName = response.getString("project"); - final String resourcePath = response.getString("resource"); - final long updateTimestamp = response.getLong("timestamp"); - final String updateHash = response.getString("hash"); - - ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null) { - boolean stored = false; - - IProject project = connectedProject.getProject(); - IResource resource = project.findMember(resourcePath); - - if (resource != null) { - if (resource instanceof IFile) { - String localHash = connectedProject.getHash(resourcePath); - long localTimestamp = connectedProject.getTimestamp(resourcePath); - - if (localHash != null && !localHash.equals(updateHash) && localTimestamp < updateTimestamp) { - IFile file = (IFile) resource; - String newResourceContent = response.getString("content"); - - connectedProject.setTimestamp(resourcePath, updateTimestamp); - connectedProject.setHash(resourcePath, updateHash); - - file.setContents(new ByteArrayInputStream(newResourceContent.getBytes()), true, true, null); - file.setLocalTimeStamp(updateTimestamp); - stored = true; - } - } - } - else { - IFile newFile = project.getFile(resourcePath); - String newResourceContent = response.getString("content"); - - connectedProject.setHash(resourcePath, updateHash); - connectedProject.setTimestamp(resourcePath, updateTimestamp); - - newFile.create(new ByteArrayInputStream(newResourceContent.getBytes()), true, null); - newFile.setLocalTimeStamp(updateTimestamp); - stored = true; - } - - if (stored) { - JSONObject message = new JSONObject(); - message.put("username", this.username); - message.put("project", connectedProject.getName()); - message.put("resource", resourcePath); - message.put("timestamp", updateTimestamp); - message.put("hash", updateHash); - message.put("type", "file"); - messagingConnector.send("resourceStored", message); - if (resource != null) { - notifyResourceChanged(resource); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void getMetadata(JSONObject request) { - try { - final String username = request.getString("username"); - final int callbackID = request.getInt("callback_id"); - final String sender = request.getString("requestSenderID"); - final String projectName = request.getString("project"); - final String resourcePath = request.getString("resource"); - - ConnectedProject connectedProject = this.syncedProjects.get(projectName); - if (this.username.equals(username) && connectedProject != null) { - IProject project = connectedProject.getProject(); - IResource resource = project.findMember(resourcePath); - - JSONObject message = new JSONObject(); - message.put("callback_id", callbackID); - message.put("requestSenderID", sender); - message.put("username", this.username); - message.put("project", projectName); - message.put("resource", resourcePath); - message.put("type", "marker"); - - IMarker[] markers = resource.findMarkers(null, true, IResource.DEPTH_INFINITE); - String markerJSON = toJSON(markers); - JSONArray content = new JSONArray(markerJSON); - message.put("metadata", content); - - messagingConnector.send("getMetadataResponse", message); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void resourceChanged(IResourceDelta delta) { - IProject project = delta.getResource().getProject(); - if (project != null) { - if (isConnected(project)) { - reactToResourceChange(delta); - } - } - } - - public void metadataChanged(IResourceDelta delta) { - IProject project = delta.getResource().getProject(); - IMarkerDelta[] markerDeltas = delta.getMarkerDeltas(); - if (project != null && isConnected(project) && markerDeltas != null && markerDeltas.length > 0) { - sendMetadataUpdate(delta.getResource()); - } - } - - public void reactToResourceChange(IResourceDelta delta) { - IResource resource = delta.getResource(); - - if (resource != null && resource.isDerived(IResource.CHECK_ANCESTORS)) { - return; - } - - switch (delta.getKind()) { - case IResourceDelta.ADDED: - reactOnResourceAdded(resource); - break; - case IResourceDelta.REMOVED: - reactOnResourceRemoved(resource); - break; - case IResourceDelta.CHANGED: - reactOnResourceChange(resource); - break; - } - } - - protected void reactOnResourceAdded(IResource resource) { - try { - ConnectedProject connectedProject = this.syncedProjects.get(resource.getProject().getName()); - - String resourcePath = resource.getProjectRelativePath().toString(); - long timestamp = resource.getLocalTimeStamp(); - String hash = "0"; - String type = null; - - connectedProject.setTimestamp(resourcePath, timestamp); - - if (resource instanceof IFile) { - try { - IFile file = (IFile) resource; - hash = DigestUtils.shaHex(file.getContents()); - type = "file"; - } catch (IOException e) { - e.printStackTrace(); - } - } else if (resource instanceof IFolder) { - type = "folder"; - } - - connectedProject.setHash(resourcePath, hash); - - JSONObject createdMessage = new JSONObject(); - createdMessage.put("username", this.username); - createdMessage.put("project", connectedProject.getName()); - createdMessage.put("resource", resourcePath); - createdMessage.put("timestamp", timestamp); - createdMessage.put("hash", hash); - createdMessage.put("type", type); - messagingConnector.send("resourceCreated", createdMessage); - - JSONObject storedMessage = new JSONObject(); - storedMessage.put("username", this.username); - storedMessage.put("project", connectedProject.getName()); - storedMessage.put("resource", resourcePath); - storedMessage.put("timestamp", timestamp); - storedMessage.put("hash", hash); - storedMessage.put("type", type); - messagingConnector.send("resourceStored", storedMessage); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - protected void reactOnResourceRemoved(IResource resource) { - if (resource instanceof IProject) { - this.removeProject((IProject) resource); - } - else if (!resource.isDerived() && (resource instanceof IFile || resource instanceof IFolder)) { - ConnectedProject connectedProject = this.syncedProjects.get(resource.getProject().getName()); - String resourcePath = resource.getProjectRelativePath().toString(); - long deletedTimestamp = System.currentTimeMillis(); - - try { - JSONObject message = new JSONObject(); - message.put("username", this.username); - message.put("project", connectedProject.getName()); - message.put("resource", resourcePath); - message.put("timestamp", deletedTimestamp); - - messagingConnector.send("resourceDeleted", message); - } - catch (Exception e) { - e.printStackTrace(); - } - } - } - - protected void reactOnResourceChange(IResource resource) { - if (resource != null && resource instanceof IFile) { - IFile file = (IFile) resource; - - ConnectedProject connectedProject = this.syncedProjects.get(file.getProject().getName()); - String resourcePath = resource.getProjectRelativePath().toString(); - - try { - - long changeTimestamp = file.getLocalTimeStamp(); - if (changeTimestamp > connectedProject.getTimestamp(resourcePath)) { - String changeHash = DigestUtils.shaHex(file.getContents()); - if (!changeHash.equals(connectedProject.getHash(resourcePath))) { - - connectedProject.setTimestamp(resourcePath, changeTimestamp); - connectedProject.setHash(resourcePath, changeHash); - - JSONObject changedMessage = new JSONObject(); - changedMessage.put("username", this.username); - changedMessage.put("project", connectedProject.getName()); - changedMessage.put("resource", resourcePath); - changedMessage.put("timestamp", changeTimestamp); - changedMessage.put("hash", changeHash); - messagingConnector.send("resourceChanged", changedMessage); - - JSONObject storedMessage = new JSONObject(); - storedMessage.put("username", this.username); - storedMessage.put("project", connectedProject.getName()); - storedMessage.put("resource", resourcePath); - storedMessage.put("timestamp", changeTimestamp); - storedMessage.put("hash", changeHash); - messagingConnector.send("resourceStored", storedMessage); - - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public void sendMetadataUpdate(IResource resource) { - try { - String project = resource.getProject().getName(); - String resourcePath = resource.getProjectRelativePath().toString(); - - JSONObject message = new JSONObject(); - message.put("username", this.username); - message.put("project", project); - message.put("resource", resourcePath); - message.put("type", "marker"); - - IMarker[] markers = resource.findMarkers(null, true, IResource.DEPTH_INFINITE); - String markerJSON = toJSON(markers); - JSONArray content = new JSONArray(markerJSON); - message.put("metadata", content); - - messagingConnector.send("metadataChanged", message); - } catch (Exception e) { - - } - } - - public String toJSON(IMarker[] markers) { - StringBuilder result = new StringBuilder(); - boolean flag = false; - result.append("["); - for (IMarker m : markers) { - if (flag) { - result.append(","); - } - - result.append("{"); - result.append("\"description\":" + JSONObject.quote(m.getAttribute("message", ""))); - result.append(",\"line\":" + m.getAttribute("lineNumber", 0)); - result.append(",\"severity\":\"" + (m.getAttribute("severity", IMarker.SEVERITY_WARNING) == IMarker.SEVERITY_ERROR ? "error" : "warning") - + "\""); - result.append(",\"start\":" + m.getAttribute("charStart", 0)); - result.append(",\"end\":" + m.getAttribute("charEnd", 0)); - result.append("}"); - - flag = true; - } - result.append("]"); - return result.toString(); - } - - public void addRepositoryListener(IRepositoryListener listener) { - this.repositoryListeners.add(listener); - } - - public void removeRepositoryListener(IRepositoryListener listener) { - this.repositoryListeners.remove(listener); - } - - protected void notifyProjectConnected(IProject project) { - for (IRepositoryListener listener : this.repositoryListeners) { - listener.projectConnected(project); - } - } - - protected void notifyProjectDisonnected(IProject project) { - for (IRepositoryListener listener : this.repositoryListeners) { - listener.projectDisconnected(project); - } - } - - protected void notifyResourceChanged(IResource resource) { - for (IRepositoryListener listener : this.repositoryListeners) { - listener.resourceChanged(resource); - } - } - - public void dispose() { - connected.set(false); - for (IMessageHandler messageHandler : messageHandlers) { - messagingConnector.removeMessageHandler(messageHandler); - } - syncedProjects.clear(); - } - -} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/RepositoryAdapter.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/RepositoryAdapter.java new file mode 100644 index 0000000..70fdc06 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/RepositoryAdapter.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 Pivotal Software, Inc. and others. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License v1.0 + * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution + * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.flux.core; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedDeque; + +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.flux.client.MessageConnector; +import org.eclipse.flux.core.sync.FluxSystemSync; +import org.eclipse.flux.watcher.core.spi.Project; + +/** + * @author Martin Lippert + */ +public class RepositoryAdapter{ + private FluxSystemSync systemSync; + + private Collection repositoryListeners; + + public RepositoryAdapter(MessageConnector messageConnector, String user) { + this.systemSync = new FluxSystemSync(messageConnector, user); + this.repositoryListeners = new ConcurrentLinkedDeque<>(); + } + + public String getUsername() { + return systemSync.getUsername(); + } + + public ConnectedProject getProject(IProject project) { + return getProject(project.getName()); + } + + public ConnectedProject getProject(String projectName) { + return new ConnectedProject(systemSync.getWatcherProject(projectName)); + } + + public boolean isConnected(IProject project) { + return isConnected(project.getName()); + } + + public boolean isConnected(String projectName) { + return systemSync.isProjectConnected(projectName); + } + + public void addProject(IProject project) { + this.systemSync.addProject(project.getName(), project.getLocationURI().getPath()); + notifyProjectConnected(project); + } + + public void removeProject(IProject project) { + this.systemSync.removeProject(project.getName()); + notifyProjectDisonnected(project); + } + + public ConnectedProject[] getConnectedProjects() { + Set projects = systemSync.getSynchronizedProjects(); + Set connectedProjects = new HashSet<>(); + for(Project project : projects){ + connectedProjects.add(new ConnectedProject(project)); + } + return connectedProjects.toArray(new ConnectedProject[connectedProjects.size()]); + } + + public void metadataChanged(IResourceDelta delta) { + IProject project = delta.getResource().getProject(); + IMarkerDelta[] markerDeltas = delta.getMarkerDeltas(); + if (project != null && isConnected(project) && markerDeltas != null && markerDeltas.length > 0) { + systemSync.sendMetadataUpdate(delta.getResource()); + } + } + + public void addRepositoryListener(IRepositoryListener listener) { + this.repositoryListeners.add(listener); + } + + public void removeRepositoryListener(IRepositoryListener listener) { + this.repositoryListeners.remove(listener); + } + + protected void notifyProjectConnected(IProject project) { + for (IRepositoryListener listener : this.repositoryListeners) { + listener.projectConnected(project); + } + } + + protected void notifyProjectDisonnected(IProject project) { + for (IRepositoryListener listener : this.repositoryListeners) { + listener.projectDisconnected(project); + } + } + + protected void notifyResourceChanged(IResource resource) { + for (IRepositoryListener listener : this.repositoryListeners) { + listener.resourceChanged(resource); + } + } + + public void dispose() { + systemSync.dispose(); + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/AbstractMsgHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/AbstractMsgHandler.java new file mode 100644 index 0000000..8be2abb --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/AbstractMsgHandler.java @@ -0,0 +1,40 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.client.MessageHandler; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.Resource.ResourceType; +import org.json.JSONObject; + +public abstract class AbstractMsgHandler extends MessageHandler { + protected ISystemSync repositoryCallback; + + public AbstractMsgHandler(ISystemSync repositoryCallback, String type) { + super(type); + this.repositoryCallback = repositoryCallback; + } + + @Override + public void handle(String type, JSONObject message) { + try{ + onMessage(type, message); + } + catch(Exception e){ + e.printStackTrace(); + } + } + + protected abstract void onMessage(String type, JSONObject message) throws Exception; + + protected ResourceType getResourceType(JSONObject jsonObject){ + String type = jsonObject.optString(MessageConstants.TYPE, ResourceType.UNKNOWN.name()); + return ResourceType.valueOf(type.toUpperCase()); + } + + protected boolean IsResourcesNotEquals(Resource resource, String hash, long timestamp){ + boolean isLocalResourceOutdated = resource.timestamp() < timestamp; + boolean isEquals = resource.hash().equals(hash); + return isLocalResourceOutdated && !isEquals; + } +} \ No newline at end of file diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceChangedHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceChangedHandler.java new file mode 100644 index 0000000..634d1e0 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceChangedHandler.java @@ -0,0 +1,33 @@ +package org.eclipse.flux.core.handlers; + +import java.util.Collection; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.ILiveEditConnector; +import org.json.JSONObject; + +public class LiveResourceChangedHandler extends AbstractMsgHandler { + private Collection liveEditConnectors; + + public LiveResourceChangedHandler(Collection liveEditConnectors) { + super(null, LIVE_RESOURCE_CHANGED); + this.liveEditConnectors = liveEditConnectors; + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String username = message.getString(MessageConstants.USERNAME); + String projectName = message.getString(MessageConstants.PROJECT_NAME); + String resourcePath = message.getString(MessageConstants.RESOURCE); + int offset = message.getInt(MessageConstants.OFFSET); + int removedCharCount = message.getInt(MessageConstants.REMOVED_CHAR_COUNT); + String addedChars = message.optString(MessageConstants.ADDED_CHARACTERS); + String liveEditID = projectName + "/" + resourcePath; + + for (ILiveEditConnector connector : liveEditConnectors) { + connector.liveEditingEvent(username, liveEditID, offset, removedCharCount, addedChars); + } + } + + +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceRequestHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceRequestHandler.java new file mode 100644 index 0000000..0c1f84a --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceRequestHandler.java @@ -0,0 +1,30 @@ +package org.eclipse.flux.core.handlers; + +import java.util.Collection; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.ILiveEditConnector; +import org.json.JSONObject; + +public class LiveResourceRequestHandler extends AbstractMsgHandler { + private Collection liveEditConnectors; + + public LiveResourceRequestHandler(Collection liveEditConnectors) { + super(null, GET_LIVE_RESOURCE_REQUEST); + this.liveEditConnectors = liveEditConnectors; + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String username = message.optString(MessageConstants.USERNAME, null); + String requestSenderID = message.getString(MessageConstants.REQUEST_SENDER_ID); + String projectRegEx = message.optString(MessageConstants.PROJECT_REG_EX, null); + String resourceRegEx = message.optString(MessageConstants.RESOURCE_REG_EX, null); + int callbackID = message.getInt(MessageConstants.CALLBACK_ID); + + for (ILiveEditConnector connector : liveEditConnectors) { + connector.liveEditors(requestSenderID, callbackID, username, projectRegEx, resourceRegEx); + } + } + +} \ No newline at end of file diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceStartedHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceStartedHandler.java new file mode 100644 index 0000000..88defb5 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceStartedHandler.java @@ -0,0 +1,32 @@ +package org.eclipse.flux.core.handlers; + +import java.util.Collection; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.ILiveEditConnector; +import org.json.JSONObject; + +public class LiveResourceStartedHandler extends AbstractMsgHandler { + private Collection liveEditConnectors; + + public LiveResourceStartedHandler(Collection liveEditConnectors) { + super(null, LIVE_RESOURCE_STARTED); + this.liveEditConnectors = liveEditConnectors; + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String requestSenderID = message.getString(MessageConstants.REQUEST_SENDER_ID); + int callbackID = message.getInt(MessageConstants.CALLBACK_ID); + String username = message.getString(MessageConstants.USERNAME); + String projectName = message.getString(MessageConstants.PROJECT_NAME); + String resourcePath = message.getString(MessageConstants.RESOURCE); + String hash = message.getString(MessageConstants.HASH); + long timestamp = message.getLong(MessageConstants.TIMESTAMP); + String liveEditID = projectName + "/" + resourcePath; + + for (ILiveEditConnector connector : liveEditConnectors) { + connector.liveEditingStarted(requestSenderID, callbackID, username, liveEditID, hash, timestamp); + } + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceStartedResponseHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceStartedResponseHandler.java new file mode 100644 index 0000000..66305e8 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/LiveResourceStartedResponseHandler.java @@ -0,0 +1,32 @@ +package org.eclipse.flux.core.handlers; + +import java.util.Collection; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.ILiveEditConnector; +import org.json.JSONObject; + +public class LiveResourceStartedResponseHandler extends AbstractMsgHandler { + private Collection liveEditConnectors; + + public LiveResourceStartedResponseHandler(Collection liveEditConnectors) { + super(null, LIVE_RESOURCE_STARTED_RESPONSE); + this.liveEditConnectors = liveEditConnectors; + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String requestSenderID = message.getString(MessageConstants.REQUEST_SENDER_ID); + int callbackID = message.getInt(MessageConstants.CALLBACK_ID); + String username = message.getString(MessageConstants.USERNAME); + String projectName = message.getString(MessageConstants.PROJECT_NAME); + String resourcePath = message.getString(MessageConstants.RESOURCE); + String savePointHash = message.getString(MessageConstants.SAVE_POINT_HASH); + long savePointTimestamp = message.getLong(MessageConstants.SAVE_POINT_TIMESTAMP); + String liveContent = message.getString(MessageConstants.LIVE_CONTENT); + + for (ILiveEditConnector connector : liveEditConnectors) { + connector.liveEditingStartedResponse(requestSenderID, callbackID, username, projectName, resourcePath, savePointHash, savePointTimestamp, liveContent); + } + } +} \ No newline at end of file diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/MetadataRequestHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/MetadataRequestHandler.java new file mode 100644 index 0000000..a74f318 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/MetadataRequestHandler.java @@ -0,0 +1,33 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.core.resources.IResource; +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.core.util.JSONUtils; +import org.eclipse.flux.core.util.Utils; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONObject; + +public class MetadataRequestHandler extends AbstractMsgHandler { + + public MetadataRequestHandler(ISystemSync repositoryCallback) { + super(repositoryCallback, GET_METADATA_REQUEST); + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String projectName = message.getString(MessageConstants.PROJECT_NAME); + String resourcePath = message.getString(MessageConstants.RESOURCE); + Project project = repositoryCallback.getWatcherProject(projectName); + if(project != null){ + Resource resource = project.getResource(resourcePath); + if(resource != null){ + IResource file = Utils.getResourceByPath(projectName, resourcePath); + message.put(MessageConstants.TYPE, "marker"); + message.put(MessageConstants.METADATA, JSONUtils.toJSON(file.findMarkers(null, true, IResource.DEPTH_INFINITE))); + repositoryCallback.sendMessage(GET_METADATA_RESPONSE, message); + } + } + } +} \ No newline at end of file diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectRequestHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectRequestHandler.java new file mode 100644 index 0000000..e6f9965 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectRequestHandler.java @@ -0,0 +1,34 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONArray; +import org.json.JSONObject; + +public class ProjectRequestHandler extends AbstractMsgHandler { + + public ProjectRequestHandler(ISystemSync repositoryCallback) { + super(repositoryCallback, GET_PROJECT_REQUEST); + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String projectName = message.getString(MessageConstants.PROJECT_NAME); + Project project = repositoryCallback.getWatcherProject(projectName); + if(project == null) + return; + JSONArray files = new JSONArray(); + for(Resource resource : project.getResources()){ + JSONObject file = new JSONObject(); + file.put(MessageConstants.PATH, resource.path()); + file.put(MessageConstants.TIMESTAMP, resource.timestamp()); + file.put(MessageConstants.HASH, resource.hash()); + file.put(MessageConstants.TYPE, resource.type().name().toLowerCase()); + files.put(file); + } + message.put(MessageConstants.FILES, files); + repositoryCallback.sendMessage(GET_PROJECT_RESPONSE, message); + } +} \ No newline at end of file diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectResponseHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectResponseHandler.java new file mode 100644 index 0000000..50eb900 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectResponseHandler.java @@ -0,0 +1,70 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONArray; +import org.json.JSONObject; + +public class ProjectResponseHandler extends AbstractMsgHandler { + private int callbackID; + + public ProjectResponseHandler(ISystemSync repositoryCallback, int callbackID) { + super(repositoryCallback, GET_PROJECT_RESPONSE); + this.callbackID = callbackID; + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + JSONArray files = message.getJSONArray(MessageConstants.FILES); + JSONArray deleted = message.getJSONArray(MessageConstants.DELETED); + Project project = repositoryCallback.getWatcherProject(message.getString(MessageConstants.PROJECT_NAME)); + if(project == null) + return; + for(int i = 0; i < files.length(); i++){ + JSONObject resource = files.getJSONObject(i); + String path = resource.getString(MessageConstants.PATH); + long timestamp = resource.getLong(MessageConstants.TIMESTAMP); + String hash = resource.optString(MessageConstants.HASH); + Resource localResource = project.getResource(path); + switch(getResourceType(resource)){ + case FILE: + if(localResource == null || IsResourcesNotEquals(localResource, hash, timestamp)){ + JSONObject content = new JSONObject(); + content.put(MessageConstants.CALLBACK_ID, callbackID); + content.put(MessageConstants.PROJECT_NAME, project.id()); + content.put(MessageConstants.RESOURCE, path); + content.put(MessageConstants.TIMESTAMP, timestamp); + content.put(MessageConstants.HASH, hash); + repositoryCallback.sendMessage(GET_RESOURCE_REQUEST, content); + } + break; + case FOLDER: + if(localResource == null) + project.createResource(Resource.newFolder(path, timestamp)); + break; + default: + break; + } + } + if(deleted != null){ + for(int i = 0; i < deleted.length(); i++){ + JSONObject resource = deleted.getJSONObject(i); + String path = resource.getString(MessageConstants.PATH); + long timestamp = resource.getLong(MessageConstants.TIMESTAMP); + Resource localResource = project.getResource(path); + if(localResource != null && localResource.timestamp() < timestamp) + project.deleteResource(localResource); + } + } + + } + + @Override + public boolean canHandle(String messageType, JSONObject message) { + return super.canHandle(messageType, message) && message.has("callback_id") + && message.optInt("callback_id") == this.callbackID; + } + +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectsResponseHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectsResponseHandler.java new file mode 100644 index 0000000..0b66c63 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ProjectsResponseHandler.java @@ -0,0 +1,36 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONArray; +import org.json.JSONObject; + +public class ProjectsResponseHandler extends AbstractMsgHandler { + + public ProjectsResponseHandler(ISystemSync repositoryCallback) { + super(repositoryCallback, GET_PROJECTS_REQUEST); + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + int callbackID = message.optInt(MessageConstants.CALLBACK_ID); + String requestSenderId = message.getString(MessageConstants.REQUEST_SENDER_ID); + String username = message.getString(MessageConstants.USERNAME); + + JSONArray projects = new JSONArray(); + for (Project fluxProject : repositoryCallback.getSynchronizedProjects()) { + JSONObject project = new JSONObject(); + project.put(MessageConstants.NAME, fluxProject.id()); + projects.put(project); + } + + JSONObject content = new JSONObject(); + content.put(MessageConstants.CALLBACK_ID, callbackID); + content.put(MessageConstants.REQUEST_SENDER_ID, requestSenderId); + content.put(MessageConstants.USERNAME, username); + content.put(MessageConstants.PROJECTS, projects); + + repositoryCallback.sendMessage(GET_PROJECTS_RESPONSE, content); + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceChangedHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceChangedHandler.java new file mode 100644 index 0000000..450d4b2 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceChangedHandler.java @@ -0,0 +1,46 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.RepositoryEvent; +import org.eclipse.flux.watcher.core.RepositoryEventType; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.Resource.ResourceType; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONObject; + +public class ResourceChangedHandler extends AbstractMsgHandler { + private int callbackId; + + public ResourceChangedHandler(ISystemSync repositoryCallback, int callbackID) { + super(repositoryCallback, RESOURCE_CHANGED); + this.callbackId = callbackID; + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String username = message.getString(MessageConstants.USERNAME); + String projectName = message.getString(MessageConstants.PROJECT_NAME); + String resourcePath = message.getString(MessageConstants.RESOURCE); + long resourceTimestamp = message.getLong(MessageConstants.TIMESTAMP); + String resourceHash = message.getString(MessageConstants.HASH); + Project project = repositoryCallback.getWatcherProject(projectName); + if(project == null) + return; + Resource localResource = project.getResource(resourcePath); + if(localResource == null || localResource.type() != ResourceType.FILE) + return; + if (localResource != null && IsResourcesNotEquals(localResource, resourceHash, resourceTimestamp)) { + JSONObject content = new JSONObject(); + content.put(MessageConstants.CALLBACK_ID, this.callbackId); + content.put(MessageConstants.USERNAME, username); + content.put(MessageConstants.PROJECT_NAME, projectName); + content.put(MessageConstants.RESOURCE, resourcePath); + content.put(MessageConstants.TIMESTAMP, resourceTimestamp); + content.put(MessageConstants.HASH, resourceHash); + repositoryCallback.sendMessage(GET_RESOURCE_REQUEST, content); + RepositoryEvent event = new RepositoryEvent(RepositoryEventType.PROJECT_RESOURCE_MODIFIED, localResource, project); + repositoryCallback.onEvent(event); + } + } +} \ No newline at end of file diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceCreatedHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceCreatedHandler.java new file mode 100644 index 0000000..3b5c8c3 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceCreatedHandler.java @@ -0,0 +1,47 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONObject; + +public class ResourceCreatedHandler extends AbstractMsgHandler { + private int callbackID; + + public ResourceCreatedHandler(ISystemSync repositoryCallback, int callbackID) { + super(repositoryCallback, RESOURCE_CREATED); + this.callbackID = callbackID; + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String projectName = message.getString(MessageConstants.PROJECT_NAME); + String resourcePath = message.getString(MessageConstants.RESOURCE); + long resourceTimestamp = message.getLong(MessageConstants.TIMESTAMP); + String resourceHash = message.getString(MessageConstants.HASH); + String username = message.getString(MessageConstants.USERNAME); + Project project = repositoryCallback.getWatcherProject(projectName); + if(project == null || project.hasResource(resourcePath)) + return; + JSONObject content = new JSONObject(); + content.put(MessageConstants.USERNAME, username); + content.put(MessageConstants.PROJECT_NAME, projectName); + content.put(MessageConstants.RESOURCE, resourcePath); + content.put(MessageConstants.TIMESTAMP, resourceTimestamp); + content.put(MessageConstants.HASH, resourceHash); + content.put(MessageConstants.TYPE, getResourceType(message).name().toLowerCase()); + switch(getResourceType(message)){ + case FILE: + content.put(MessageConstants.CALLBACK_ID, callbackID); + repositoryCallback.sendMessage(GET_RESOURCE_REQUEST, content); + break; + case FOLDER: + project.createResource(Resource.newFolder(resourcePath, resourceTimestamp)); + repositoryCallback.sendMessage(RESOURCE_STORED, content); + break; + default: + break; + } + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceDeletedHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceDeletedHandler.java new file mode 100644 index 0000000..d6a150e --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceDeletedHandler.java @@ -0,0 +1,32 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONObject; + +public class ResourceDeletedHandler extends AbstractMsgHandler { + + public ResourceDeletedHandler(ISystemSync repositoryCallback) { + super(repositoryCallback, RESOURCE_DELETED); + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String projectName = message.getString(MessageConstants.PROJECT_NAME); + String resourcePath = message.getString(MessageConstants.RESOURCE); + long resourceTimestamp = message.getLong(MessageConstants.TIMESTAMP); + Project project = repositoryCallback.getWatcherProject(projectName); + if(project == null) + return; + Resource localResource = project.getResource(resourcePath); + if(localResource == null) + return; + boolean isLocalResourceOutdated = localResource.timestamp() < resourceTimestamp; + if (isLocalResourceOutdated) { + project.deleteResource(Resource.newUnknown(resourcePath, resourceTimestamp)); + } + } + +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceRequestHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceRequestHandler.java new file mode 100644 index 0000000..ac866d9 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceRequestHandler.java @@ -0,0 +1,40 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.Resource.ResourceType; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONException; +import org.json.JSONObject; + +public class ResourceRequestHandler extends AbstractMsgHandler { + public ResourceRequestHandler(ISystemSync repositoryCallback) { + super(repositoryCallback, GET_RESOURCE_REQUEST); + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + Project project = repositoryCallback.getWatcherProject(message.getString(MessageConstants.PROJECT_NAME)); + if (project == null) + return; + Resource resource = project.getResource(message.getString(MessageConstants.RESOURCE)); + if (resource == null || compareTimestamp(message, resource)) + return; + message.put(MessageConstants.TIMESTAMP, resource.timestamp()); + message.put(MessageConstants.HASH, resource.hash()); + message.put(MessageConstants.TYPE, resource.type().name().toLowerCase()); + if (resource.type() == ResourceType.FILE && !compareHash(message, resource)) { + message.put(MessageConstants.CONTENT, new String(resource.content())); + } + repositoryCallback.sendMessage(GET_RESOURCE_RESPONSE, message); + } + + private boolean compareHash(JSONObject message, Resource resource) throws JSONException { + return message.has(MessageConstants.HASH) && !message.getString(MessageConstants.HASH).equals(resource.hash()); + } + + private boolean compareTimestamp(JSONObject message, Resource resource) throws JSONException { + return message.has(MessageConstants.TIMESTAMP) && message.getLong(MessageConstants.TIMESTAMP) != resource.timestamp(); + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceResponseHandler.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceResponseHandler.java new file mode 100644 index 0000000..d5bc7c6 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/handlers/ResourceResponseHandler.java @@ -0,0 +1,72 @@ +package org.eclipse.flux.core.handlers; + +import org.eclipse.core.resources.IResource; +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.core.util.Utils; +import org.eclipse.flux.watcher.core.RepositoryEvent; +import org.eclipse.flux.watcher.core.RepositoryEventType; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.Resource.ResourceType; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONObject; + +public class ResourceResponseHandler extends AbstractMsgHandler { + private int callbackID; + + public ResourceResponseHandler(ISystemSync repositoryCallback, int callbackID) { + super(repositoryCallback, GET_RESOURCE_RESPONSE); + this.callbackID = callbackID; + } + + @Override + protected void onMessage(String type, JSONObject message) throws Exception { + String projectName = message.getString(MessageConstants.PROJECT_NAME); + String resourcePath = message.getString(MessageConstants.RESOURCE); + long resourceTimestamp = message.getLong(MessageConstants.TIMESTAMP); + String resourceHash = message.getString(MessageConstants.HASH); + String resourceContent = message.getString(MessageConstants.CONTENT); + String username = message.getString(MessageConstants.USERNAME); + + Project project = repositoryCallback.getWatcherProject(projectName); + if(project == null) + return; + boolean isResourceStore = false; + Resource localResource = project.getResource(resourcePath); + Resource newResource = Resource.newFile(resourcePath, resourceTimestamp, resourceContent.getBytes()); + if(localResource != null && localResource.type() == ResourceType.FILE) + { + if(IsResourcesNotEquals(localResource, resourceHash, resourceTimestamp)){ + project.updateResource(newResource); + isResourceStore = true; + } + } + else { + project.createResource(newResource); + isResourceStore = true; + } + + if(isResourceStore){ + Utils.getResourceByPath(projectName, resourcePath).refreshLocal(IResource.DEPTH_ZERO, null); + JSONObject content = new JSONObject(); + content.put(MessageConstants.USERNAME, username); + content.put(MessageConstants.PROJECT_NAME, projectName); + content.put(MessageConstants.RESOURCE, resourcePath); + content.put(MessageConstants.TIMESTAMP, resourceTimestamp); + content.put(MessageConstants.HASH, resourceHash); + content.put(MessageConstants.TYPE, "file"); + repositoryCallback.sendMessage(RESOURCE_STORED, content); + if(localResource != null){ + RepositoryEvent event = new RepositoryEvent(RepositoryEventType.PROJECT_RESOURCE_MODIFIED, localResource, project); + repositoryCallback.onEvent(event); + } + } + } + + @Override + public boolean canHandle(String messageType, JSONObject message) { + return super.canHandle(messageType, message) + && message.has("callback_id") + && message.optInt("callback_id") == callbackID; + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/internal/CloudSyncMetadataListener.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/internal/CloudSyncMetadataListener.java index 9e395ae..caeff39 100644 --- a/org.eclipse.flux.core/src/org/eclipse/flux/core/internal/CloudSyncMetadataListener.java +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/internal/CloudSyncMetadataListener.java @@ -15,16 +15,16 @@ import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.runtime.CoreException; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; /** * @author Martin Lippert */ public class CloudSyncMetadataListener implements IResourceChangeListener{ - private Repository repository; + private RepositoryAdapter repository; - public CloudSyncMetadataListener(Repository repository) { + public CloudSyncMetadataListener(RepositoryAdapter repository) { this.repository = repository; } diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/internal/CloudSyncResourceListener.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/internal/CloudSyncResourceListener.java deleted file mode 100644 index 3b0bd88..0000000 --- a/org.eclipse.flux.core/src/org/eclipse/flux/core/internal/CloudSyncResourceListener.java +++ /dev/null @@ -1,46 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2013, 2014 Pivotal Software, Inc. and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * Pivotal Software, Inc. - initial API and implementation -*******************************************************************************/ -package org.eclipse.flux.core.internal; - -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.IResourceDeltaVisitor; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.flux.core.Repository; - -/** - * @author Martin Lippert - */ -public class CloudSyncResourceListener implements IResourceChangeListener { - - private Repository repository; - - public CloudSyncResourceListener(Repository repository) { - this.repository = repository; - } - - @Override - public void resourceChanged(IResourceChangeEvent event) { - try { - event.getDelta().accept(new IResourceDeltaVisitor() { - @Override - public boolean visit(IResourceDelta delta) throws CoreException { - repository.resourceChanged(delta); - return true; - } - }); - } catch (CoreException e) { - e.printStackTrace(); - } - } - -} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceCreatedListener.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceCreatedListener.java new file mode 100644 index 0000000..1fbf5ab --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceCreatedListener.java @@ -0,0 +1,31 @@ +package org.eclipse.flux.core.listeners; + +import org.eclipse.flux.client.IMessageHandler; +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.RepositoryEvent; +import org.eclipse.flux.watcher.core.RepositoryEventType; +import org.eclipse.flux.watcher.core.RepositoryEventTypes; +import org.eclipse.flux.watcher.core.RepositoryListener; +import org.json.JSONObject; + +@RepositoryEventTypes(RepositoryEventType.PROJECT_RESOURCE_CREATED) +public class ResourceCreatedListener implements RepositoryListener { + private ISystemSync repositoryCallback; + + public ResourceCreatedListener(ISystemSync repositoryCallback){ + this.repositoryCallback = repositoryCallback; + } + @Override + public void onEvent(RepositoryEvent event) throws Exception { + JSONObject message = new JSONObject(); + message.put(MessageConstants.USERNAME, repositoryCallback.getUsername()); + message.put(MessageConstants.PROJECT_NAME, event.project().id()); + message.put(MessageConstants.RESOURCE, event.resource().path()); + message.put(MessageConstants.TIMESTAMP, event.resource().timestamp()); + message.put(MessageConstants.HASH, event.resource().hash()); + message.put(MessageConstants.TYPE, event.resource().type().name().toLowerCase()); + repositoryCallback.sendMessage(IMessageHandler.RESOURCE_CREATED, message); + repositoryCallback.sendMessage(IMessageHandler.RESOURCE_STORED, message); + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceDeletedListener.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceDeletedListener.java new file mode 100644 index 0000000..d479220 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceDeletedListener.java @@ -0,0 +1,30 @@ +package org.eclipse.flux.core.listeners; + +import org.eclipse.flux.client.IMessageHandler; +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.RepositoryEvent; +import org.eclipse.flux.watcher.core.RepositoryEventType; +import org.eclipse.flux.watcher.core.RepositoryEventTypes; +import org.eclipse.flux.watcher.core.RepositoryListener; +import org.json.JSONObject; + +@RepositoryEventTypes(RepositoryEventType.PROJECT_RESOURCE_DELETED) +public class ResourceDeletedListener implements RepositoryListener { + private ISystemSync repositoryCallback; + + public ResourceDeletedListener(ISystemSync repositoryCallback){ + this.repositoryCallback = repositoryCallback; + } + + @Override + public void onEvent(RepositoryEvent event) throws Exception { + JSONObject message = new JSONObject(); + message.put(MessageConstants.USERNAME, repositoryCallback.getUsername()); + message.put(MessageConstants.PROJECT_NAME, event.project().id()); + message.put(MessageConstants.RESOURCE, event.resource().path()); + message.put(MessageConstants.TIMESTAMP, event.resource().timestamp()); + repositoryCallback.sendMessage(IMessageHandler.RESOURCE_DELETED, message); + } + +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceModifiedListener.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceModifiedListener.java new file mode 100644 index 0000000..b29bbbd --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/listeners/ResourceModifiedListener.java @@ -0,0 +1,31 @@ +package org.eclipse.flux.core.listeners; + +import org.eclipse.flux.client.IMessageHandler; +import org.eclipse.flux.client.MessageConstants; +import org.eclipse.flux.core.sync.ISystemSync; +import org.eclipse.flux.watcher.core.RepositoryEvent; +import org.eclipse.flux.watcher.core.RepositoryEventType; +import org.eclipse.flux.watcher.core.RepositoryEventTypes; +import org.eclipse.flux.watcher.core.RepositoryListener; +import org.json.JSONObject; + +@RepositoryEventTypes(RepositoryEventType.PROJECT_RESOURCE_MODIFIED) +public class ResourceModifiedListener implements RepositoryListener { + private ISystemSync repositoryCallback; + + public ResourceModifiedListener(ISystemSync repositoryCallback){ + this.repositoryCallback = repositoryCallback; + } + + @Override + public void onEvent(RepositoryEvent event) throws Exception { + JSONObject message = new JSONObject(); + message.put(MessageConstants.USERNAME, repositoryCallback.getUsername()); + message.put(MessageConstants.PROJECT_NAME, event.project().id()); + message.put(MessageConstants.RESOURCE, event.resource().path()); + message.put(MessageConstants.TIMESTAMP, event.resource().timestamp()); + message.put(MessageConstants.HASH, event.resource().hash()); + repositoryCallback.sendMessage(IMessageHandler.RESOURCE_CHANGED, message); + repositoryCallback.sendMessage(IMessageHandler.RESOURCE_STORED, message); + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/sync/FluxSystemSync.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/sync/FluxSystemSync.java new file mode 100644 index 0000000..0f0cab0 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/sync/FluxSystemSync.java @@ -0,0 +1,173 @@ +package org.eclipse.flux.core.sync; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.flux.client.IMessageHandler; +import org.eclipse.flux.client.MessageConnector; +import org.eclipse.flux.core.handlers.MetadataRequestHandler; +import org.eclipse.flux.core.handlers.ProjectRequestHandler; +import org.eclipse.flux.core.handlers.ProjectResponseHandler; +import org.eclipse.flux.core.handlers.ProjectsResponseHandler; +import org.eclipse.flux.core.handlers.ResourceChangedHandler; +import org.eclipse.flux.core.handlers.ResourceCreatedHandler; +import org.eclipse.flux.core.handlers.ResourceDeletedHandler; +import org.eclipse.flux.core.handlers.ResourceRequestHandler; +import org.eclipse.flux.core.handlers.ResourceResponseHandler; +import org.eclipse.flux.core.listeners.ResourceCreatedListener; +import org.eclipse.flux.core.listeners.ResourceDeletedListener; +import org.eclipse.flux.core.listeners.ResourceModifiedListener; +import org.eclipse.flux.core.util.JSONUtils; +import org.eclipse.flux.watcher.core.Repository; +import org.eclipse.flux.watcher.core.RepositoryEvent; +import org.eclipse.flux.watcher.core.RepositoryEventBus; +import org.eclipse.flux.watcher.core.RepositoryModule; +import org.eclipse.flux.watcher.core.spi.Project; +import org.eclipse.flux.watcher.fs.JDKProjectModule; +import org.json.JSONArray; +import org.json.JSONObject; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +public class FluxSystemSync implements ISystemSync { + private static int GET_PROJECT_CALLBACK = "Repository - getProjectCallback".hashCode(); + private static int GET_RESOURCE_CALLBACK = "Repository - getResourceCallback".hashCode(); + + private String username; + private Repository repository; + private RepositoryEventBus repositoryEventBus; + private MessageConnector messageConnector; + private Collection messageHandlers; + + public FluxSystemSync(MessageConnector messageConnector, String user) { + this.messageConnector = messageConnector; + this.username = user; + this.messageHandlers = new ArrayList<>(); + Injector injector = Guice.createInjector(new RepositoryModule(), new JDKProjectModule()); + this.repository = injector.getInstance(Repository.class); + this.repositoryEventBus = this.repository.repositoryEventBus(); + + this.repositoryEventBus.addRepositoryListener(new ResourceCreatedListener(this)); + this.repositoryEventBus.addRepositoryListener(new ResourceModifiedListener(this)); + this.repositoryEventBus.addRepositoryListener(new ResourceDeletedListener(this)); + + addMessageHandler(new MetadataRequestHandler(this)); + addMessageHandler(new ProjectsResponseHandler(this)); + addMessageHandler(new ProjectRequestHandler(this)); + addMessageHandler(new ProjectResponseHandler(this, GET_PROJECT_CALLBACK)); + addMessageHandler(new ResourceRequestHandler(this)); + addMessageHandler(new ResourceResponseHandler(this, GET_RESOURCE_CALLBACK)); + addMessageHandler(new ResourceCreatedHandler(this, GET_RESOURCE_CALLBACK)); + addMessageHandler(new ResourceChangedHandler(this, GET_RESOURCE_CALLBACK)); + addMessageHandler(new ResourceDeletedHandler(this)); + } + + + public Set getSynchronizedProjects() { + return this.repository.getSynchronizedProjects(); + } + + public void addProject(String name, String path) { + this.repository.addProject(name, path); + sendProjectConnectedMessage(name); + syncConnectedProject(name); + } + + public void removeProject(String name) { + this.repository.removeProject(name); + try { + JSONObject message = new JSONObject(); + message.put("username", this.username); + message.put("project", name); + messageConnector.send("projectDisconnected", message); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public void sendMetadataUpdate(IResource resource) { + try { + String project = resource.getProject().getName(); + String resourcePath = resource.getProjectRelativePath().toString(); + + JSONObject message = new JSONObject(); + message.put("username", this.username); + message.put("project", project); + message.put("resource", resourcePath); + message.put("type", "marker"); + + IMarker[] markers = resource.findMarkers(null, true, IResource.DEPTH_INFINITE); + JSONArray content = JSONUtils.toJSON(markers); + message.put("metadata", content); + messageConnector.send(IMessageHandler.METADATA_CHANGED, message); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + + public boolean isProjectConnected(String projectName) { + return Objects.nonNull(this.getWatcherProject(projectName)); + } + + public String getUsername() { + return this.username; + } + + private void syncConnectedProject(String projectName){ + try{ + JSONObject message=new JSONObject(); + message.put("username",this.username); + message.put("project",projectName); + message.put("includeDeleted",true); + message.put("callback_id", GET_PROJECT_CALLBACK); + messageConnector.send("getProjectRequest",message); + }catch(Exception e){ + e.printStackTrace(); + } + } + + private void sendProjectConnectedMessage(String projectName){ + try{ + JSONObject message=new JSONObject(); + message.put("username",this.username); + message.put("project",projectName); + messageConnector.send("projectConnected",message); + }catch(Exception e){ + e.printStackTrace(); + } + } + + public void dispose() { + for(IMessageHandler messageHandler : messageHandlers){ + messageConnector.removeMessageHandler(messageHandler); + } + } + + private void addMessageHandler(IMessageHandler messageHandler){ + this.messageConnector.addMessageHandler(messageHandler); + this.messageHandlers.add(messageHandler); + } + + @Override + public void onEvent(RepositoryEvent event) throws Exception { + + } + + @Override + public void sendMessage(String messageType, JSONObject content) throws Exception { + this.messageConnector.send(messageType, content); + } + + @Override + public Project getWatcherProject(String projectName) { + return this.repository.getProject(projectName); + } +} \ No newline at end of file diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/sync/ISystemSync.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/sync/ISystemSync.java new file mode 100644 index 0000000..f32bc58 --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/sync/ISystemSync.java @@ -0,0 +1,14 @@ +package org.eclipse.flux.core.sync; + +import java.util.Set; + +import org.eclipse.flux.watcher.core.RepositoryListener; +import org.eclipse.flux.watcher.core.spi.Project; +import org.json.JSONObject; + +public interface ISystemSync extends RepositoryListener { + void sendMessage(String messageType, JSONObject content) throws Exception; + Project getWatcherProject(String projectName); + Set getSynchronizedProjects(); + String getUsername(); +} \ No newline at end of file diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/util/JSONUtils.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/util/JSONUtils.java new file mode 100644 index 0000000..9c15f9f --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/util/JSONUtils.java @@ -0,0 +1,38 @@ +package org.eclipse.flux.core.util; + +import org.eclipse.core.resources.IMarker; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class JSONUtils { + public static String getString(JSONObject object, String key, String defaultValue){ + try { + return object.getString(key); + } + catch (Exception e) { + return defaultValue; + } + } + + public static JSONArray toJSON(IMarker[] markers) throws JSONException{ + JSONArray objects = new JSONArray(); + for(IMarker marker : markers){ + JSONObject object = new JSONObject(); + object.put("description", marker.getAttribute("message", "")); + object.put("line", marker.getAttribute("lineNumber", 0)); + switch(marker.getAttribute("severity", IMarker.SEVERITY_WARNING)){ + case IMarker.SEVERITY_WARNING: + object.put("severity", marker.getAttribute("severity", "warning")); + break; + case IMarker.SEVERITY_ERROR: + object.put("severity", marker.getAttribute("severity", "error")); + break; + } + object.put("start", marker.getAttribute("charStart", 0)); + object.put("end", marker.getAttribute("charEnd", 0)); + objects.put(object); + } + return objects; + } +} diff --git a/org.eclipse.flux.core/src/org/eclipse/flux/core/util/Utils.java b/org.eclipse.flux.core/src/org/eclipse/flux/core/util/Utils.java new file mode 100644 index 0000000..e969a6b --- /dev/null +++ b/org.eclipse.flux.core/src/org/eclipse/flux/core/util/Utils.java @@ -0,0 +1,14 @@ +package org.eclipse.flux.core.util; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Path; + +public class Utils { + public static IResource getResourceByPath(String projectName, String resourcePath){ + Path path = new Path(projectName + "/" + resourcePath); + IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path); + return file; + } +} diff --git a/org.eclipse.flux.headless.releng/pom.xml b/org.eclipse.flux.headless.releng/pom.xml index 979c0ea..5434275 100644 --- a/org.eclipse.flux.headless.releng/pom.xml +++ b/org.eclipse.flux.headless.releng/pom.xml @@ -30,6 +30,8 @@ ../org.eclipse.flux.parent.java ../org.eclipse.flux.client.java + + ../org.eclipse.flux.watcher diff --git a/org.eclipse.flux.jdt.service/jdt ui/org/eclipse/jdt/internal/compiler/lookup/TypeBindingVisitor.java b/org.eclipse.flux.jdt.service/jdt ui/org/eclipse/jdt/internal/compiler/lookup/TypeBindingVisitor.java index 610f6fd..d6b4003 100644 --- a/org.eclipse.flux.jdt.service/jdt ui/org/eclipse/jdt/internal/compiler/lookup/TypeBindingVisitor.java +++ b/org.eclipse.flux.jdt.service/jdt ui/org/eclipse/jdt/internal/compiler/lookup/TypeBindingVisitor.java @@ -42,9 +42,9 @@ public boolean visit(ParameterizedTypeBinding parameterizedTypeBinding) { return true; // continue traversal. } - public boolean visit(IntersectionCastTypeBinding intersectionCastTypeBinding) { + /*public boolean visit(IntersectionCastTypeBinding intersectionCastTypeBinding) { return true; // continue traversal. - } + }*/ public boolean visit(RawTypeBinding rawTypeBinding) { return true; // continue traversal. @@ -128,11 +128,11 @@ public static void visit(TypeBindingVisitor visitor, TypeBinding type) { } break; - case Binding.INTERSECTION_CAST_TYPE: + /*case Binding.INTERSECTION_CAST_TYPE: IntersectionCastTypeBinding intersectionCastTypeBinding = (IntersectionCastTypeBinding) type; if (visitor.visit(intersectionCastTypeBinding)) visit(visitor, intersectionCastTypeBinding.intersectingTypes); - break; + break;*/ case Binding.POLY_TYPE: visitor.visit((PolyTypeBinding) type); diff --git a/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/InitializeServiceEnvironment.java b/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/InitializeServiceEnvironment.java index 764693c..a4ea5e0 100644 --- a/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/InitializeServiceEnvironment.java +++ b/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/InitializeServiceEnvironment.java @@ -19,7 +19,7 @@ import org.eclipse.flux.client.MessageHandler; import org.eclipse.flux.core.DownloadProject; import org.eclipse.flux.core.DownloadProject.CompletionCallback; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -34,12 +34,12 @@ public class InitializeServiceEnvironment { private static int GET_PROJECTS_CALLBACK = "InitializeServiceEnvironment - getProjectsCallback".hashCode(); private final MessageConnector messagingConnector; - final Repository repository; + final RepositoryAdapter repository; private IMessageHandler getProjectsResponseHandler; private IMessageHandler projectConnectedHandler; - public InitializeServiceEnvironment(MessageConnector messagingConnector, Repository repository) { + public InitializeServiceEnvironment(MessageConnector messagingConnector, RepositoryAdapter repository) { this.messagingConnector = messagingConnector; this.repository = repository; } diff --git a/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/JDTComponent.java b/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/JDTComponent.java index 5bed097..5b1bb14 100644 --- a/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/JDTComponent.java +++ b/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/JDTComponent.java @@ -17,7 +17,7 @@ import org.eclipse.flux.core.ChannelSwitcher; import org.eclipse.flux.core.KeepAliveConnector; import org.eclipse.flux.core.LiveEditCoordinator; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; import org.eclipse.flux.core.ServiceDiscoveryConnector; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -109,7 +109,7 @@ public void connected(String userChannel) { } MessageConnector messagingConnector = org.eclipse.flux.core.Activator .getDefault().getMessageConnector(); - Repository repository = org.eclipse.flux.core.Activator.getDefault() + RepositoryAdapter repository = org.eclipse.flux.core.Activator.getDefault() .getRepository(); LiveEditCoordinator liveEditCoordinator = org.eclipse.flux.core.Activator .getDefault().getLiveEditCoordinator(); diff --git a/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/LiveEditUnits.java b/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/LiveEditUnits.java index bbb8518..6333f23 100644 --- a/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/LiveEditUnits.java +++ b/org.eclipse.flux.jdt.service/src/org/eclipse/flux/jdt/services/LiveEditUnits.java @@ -32,7 +32,7 @@ import org.eclipse.flux.core.ILiveEditConnector; import org.eclipse.flux.core.IRepositoryListener; import org.eclipse.flux.core.LiveEditCoordinator; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; import org.eclipse.jdt.core.IBuffer; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IProblemRequestor; @@ -53,7 +53,7 @@ public class LiveEditUnits { private static int GET_LIVE_RESOURCES_CALLBACK = "LiveEditUnits - getLiveResourcesCallback".hashCode(); private ConcurrentMap liveEditUnits; - private Repository repository; + private RepositoryAdapter repository; private MessageConnector messagingConnector; private LiveEditCoordinator liveEditCoordinator; @@ -62,7 +62,7 @@ public class LiveEditUnits { private IResourceChangeListener metadataChangeListener; private IMessageHandler liveResourcesResponseHandler; - public LiveEditUnits(MessageConnector messagingConnector, LiveEditCoordinator liveEditCoordinator, Repository repository) { + public LiveEditUnits(MessageConnector messagingConnector, LiveEditCoordinator liveEditCoordinator, RepositoryAdapter repository) { this.messagingConnector = messagingConnector; this.liveEditCoordinator = liveEditCoordinator; this.repository = repository; diff --git a/org.eclipse.flux.releng/flux-eclipse-target.target b/org.eclipse.flux.releng/flux-eclipse-target.target index 6fa2720..e227f03 100644 --- a/org.eclipse.flux.releng/flux-eclipse-target.target +++ b/org.eclipse.flux.releng/flux-eclipse-target.target @@ -1,12 +1,26 @@ - - + + + + + + + + + + + + + + + + diff --git a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/UiStartup.java b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/UiStartup.java index d31a00f..03b779b 100644 --- a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/UiStartup.java +++ b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/UiStartup.java @@ -17,7 +17,7 @@ import org.eclipse.flux.core.ChannelSwitcher; import org.eclipse.flux.core.IRepositoryListener; import org.eclipse.flux.core.LiveEditCoordinator; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; import org.eclipse.flux.ui.integration.handlers.LiveEditConnector; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.swt.widgets.Display; @@ -84,7 +84,7 @@ public void resourceChanged(IResource resource) { @Override public void connected(String userChannel) { - Repository repository = org.eclipse.flux.core.Activator + RepositoryAdapter repository = org.eclipse.flux.core.Activator .getDefault().getRepository(); repository.addRepositoryListener(repositoryListener); diff --git a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/LiveEditConnector.java b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/LiveEditConnector.java index 95879f9..58f611b 100644 --- a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/LiveEditConnector.java +++ b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/LiveEditConnector.java @@ -44,7 +44,7 @@ import org.eclipse.flux.core.IRepositoryListener; import org.eclipse.flux.core.LiveEditCoordinator; import org.eclipse.flux.core.LiveEditCoordinator.ResourceData; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; @@ -73,7 +73,7 @@ public class LiveEditConnector { private IResourceChangeListener workspaceListener; private IPartListener2 partListener; private ILiveEditConnector liveEditConnector; - private Repository repository; + private RepositoryAdapter repository; private ConcurrentMap resourceMappings; private ConcurrentMap documentMappings; @@ -81,7 +81,7 @@ public class LiveEditConnector { private ConcurrentHashMap pendingLiveEditStartedResponses; - public LiveEditConnector(LiveEditCoordinator liveEditCoordinator, Repository repository) { + public LiveEditConnector(LiveEditCoordinator liveEditCoordinator, RepositoryAdapter repository) { this.liveEditCoordinator = liveEditCoordinator; this.repository = repository; @@ -151,7 +151,7 @@ public void bufferContentReplaced(IFileBuffer buffer) { } // Send a message to get the latest edits. - Repository repository = LiveEditConnector.this.repository; + RepositoryAdapter repository = LiveEditConnector.this.repository; if (repository.isConnected(project)) { ConnectedProject connectedProject = repository.getProject(project); String hash = connectedProject.getHash(resourcePath); diff --git a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncConnectHandler.java b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncConnectHandler.java index dbe091e..4619225 100644 --- a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncConnectHandler.java +++ b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncConnectHandler.java @@ -19,7 +19,7 @@ import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.ISources; @@ -37,7 +37,7 @@ public Object execute(ExecutionEvent event) throws ExecutionException { ISelection selection = HandlerUtil.getCurrentSelection(event); IProject[] selectedProjects = getSelectedProjects(selection); - Repository repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); + RepositoryAdapter repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); if (repository != null) { for (IProject project : selectedProjects) { @@ -51,7 +51,7 @@ public Object execute(ExecutionEvent event) throws ExecutionException { @Override public void setEnabled(Object evaluationContext) { - Repository repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); + RepositoryAdapter repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); if (repository != null && evaluationContext instanceof IEvaluationContext) { IEvaluationContext evalContext = (IEvaluationContext) evaluationContext; Object selection = evalContext.getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME); diff --git a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncDisconnectHandler.java b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncDisconnectHandler.java index 8cd465b..0dd1422 100644 --- a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncDisconnectHandler.java +++ b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncDisconnectHandler.java @@ -19,7 +19,7 @@ import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.ISources; @@ -38,7 +38,7 @@ public Object execute(ExecutionEvent event) throws ExecutionException { // selectedProjects[0].getLocalTimeStamp(); // selectedProjects[0].getFile(".project").getLocalTimeStamp(); - Repository repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); + RepositoryAdapter repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); if (repository != null) { for (IProject project : selectedProjects) { @@ -53,7 +53,7 @@ public Object execute(ExecutionEvent event) throws ExecutionException { @Override public void setEnabled(Object evaluationContext) { - Repository repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); + RepositoryAdapter repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); if (repository != null && evaluationContext instanceof IEvaluationContext) { IEvaluationContext evalContext = (IEvaluationContext) evaluationContext; Object selection = evalContext.getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME); diff --git a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncDownloadHandler.java b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncDownloadHandler.java index 756a3bb..35f7bb7 100644 --- a/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncDownloadHandler.java +++ b/org.eclipse.flux.ui.integration/src/org/eclipse/flux/ui/integration/handlers/SyncDownloadHandler.java @@ -17,7 +17,7 @@ import org.eclipse.flux.client.MessageConnector; import org.eclipse.flux.core.DownloadProject; import org.eclipse.flux.core.DownloadProject.CompletionCallback; -import org.eclipse.flux.core.Repository; +import org.eclipse.flux.core.RepositoryAdapter; import org.eclipse.flux.ui.integration.FluxUiPlugin; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.viewers.LabelProvider; @@ -32,7 +32,7 @@ public class SyncDownloadHandler extends AbstractHandler { @Override public Object execute(final ExecutionEvent event) throws ExecutionException { - final Repository repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); + final RepositoryAdapter repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); final MessageConnector messagingConnector = org.eclipse.flux.core.Activator.getDefault().getMessageConnector(); if (repository == null || messagingConnector == null) { @@ -68,7 +68,7 @@ public void downloadComplete(IProject project) { @Override public boolean isEnabled() { - final Repository repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); + final RepositoryAdapter repository = org.eclipse.flux.core.Activator.getDefault().getRepository(); final MessageConnector messagingConnector = org.eclipse.flux.core.Activator.getDefault().getMessageConnector(); return repository != null && messagingConnector != null; } diff --git a/org.eclipse.flux.watcher/META-INF/MANIFEST.MF b/org.eclipse.flux.watcher/META-INF/MANIFEST.MF new file mode 100644 index 0000000..ad48fa7 --- /dev/null +++ b/org.eclipse.flux.watcher/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Watch +Bundle-SymbolicName: org.eclipse.flux.watcher +Bundle-Version: 1.0.0.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Require-Bundle: org.eclipse.flux.client.java.osgi;bundle-version="1.0.0", + org.mockito, + org.junit +Import-Package: com.google.common.base, + com.google.common.collect;version="15.0.0", + com.google.inject;version="1.3.0", + com.google.inject.binder;version="1.3.0", + com.google.inject.multibindings;version="1.3.0", + javax.inject;version="1.0.0" +Export-Package: org.eclipse.flux.watcher.core, + org.eclipse.flux.watcher.core.spi, + org.eclipse.flux.watcher.core.utils, + org.eclipse.flux.watcher.fs diff --git a/org.eclipse.flux.watcher/build.properties b/org.eclipse.flux.watcher/build.properties new file mode 100644 index 0000000..3c54799 --- /dev/null +++ b/org.eclipse.flux.watcher/build.properties @@ -0,0 +1,4 @@ +source.. = src/main/java/, src/test/java +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.eclipse.flux.watcher/pom.xml b/org.eclipse.flux.watcher/pom.xml new file mode 100644 index 0000000..599fa3a --- /dev/null +++ b/org.eclipse.flux.watcher/pom.xml @@ -0,0 +1,26 @@ + + 4.0.0 + org.eclipse.flux.watcher + eclipse-plugin + + 0.1.0-SNAPSHOT + org.eclipse.flux.group + org.eclipse.flux.parent + ../org.eclipse.flux.parent/pom.xml + + + src + + + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + + + + 1.0.0.qualifier + \ No newline at end of file diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/Repository.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/Repository.java new file mode 100644 index 0000000..ab311cc --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/Repository.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import org.eclipse.flux.watcher.core.spi.Project; +import org.eclipse.flux.watcher.core.spi.ProjectFactory; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableSet; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.notNull; + +/** + * Represents a repository with Flux connectivity capabilities. + * + * @author Kevin Pollet + */ +@Singleton +public class Repository { + private final Set projects; + private final RepositoryEventBus repositoryEventBus; + private final ProjectFactory projectFactory; + + /** + * Constructs an instance of {@link Repository}. + * + * @param messageBus + * the {@link com.codenvy.flux.watcher.core.FluxMessageBus} instance. + * @param projectFactory + * the {@link com.codenvy.flux.watcher.core.spi.ProjectFactory} instance. + * @param repositoryEventBus + * the {@link com.codenvy.flux.watcher.core.RepositoryEventBus} instance. + * @throws java.lang.NullPointerException + * if {@code messageBus}, {@code projectFactory} or {@code repositoryEventBus} parameter is {@code null}. + */ + @Inject + Repository(ProjectFactory projectFactory, RepositoryEventBus repositoryEventBus) { + this.repositoryEventBus = checkNotNull(repositoryEventBus); + this.projectFactory = checkNotNull(projectFactory); + this.projects = new CopyOnWriteArraySet<>(); + } + + /** + * Add a project to the repository. + * + * @param projectId + * the project id. + * @param projectPath + * the absolute project path. + * @return the added {@link com.codenvy.flux.watcher.core.spi.Project} instance, never {@code null}. + * @throws java.lang.NullPointerException + * if {@code projectId} or {@code projectPath} parameter is {@code null}. + * @throws java.lang.IllegalArgumentException + * if the given {@code projectPath} is not a directory, not absolute or doesn't exist. + */ + public Project addProject(String projectId, String projectPath) { + checkNotNull(projectId); + checkNotNull(projectPath); + + Project project = getProject(projectId); + if (project == null) { + project = projectFactory.newProject(projectId, projectPath); + project.setSynchronized(true); + projects.add(project); + } + return project; + } + + /** + * Remove a project from the repository. + * + * @param projectId + * the project id. + * @return the removed {@link com.codenvy.flux.watcher.core.spi.Project} instance or {@code null} if none. + */ + public Project removeProject(String projectId) { + final Project project = getProject(projectId); + if (project != null) { + projects.remove(project); + project.setSynchronized(false); + } + return project; + } + + /** + * Returns the {@link com.codenvy.flux.watcher.core.spi.Project} with the given id. + * + * @return the {@link com.codenvy.flux.watcher.core.spi.Project} or {@code null} if none. + */ + public Project getProject(final String projectId) { + return FluentIterable.from(projects) + .firstMatch(new Predicate() { + @Override + public boolean apply(Project project) { + return Objects.equals(projectId, project.id()); + } + }) + .orNull(); + } + + /** + * Returns all synchronized {@link com.codenvy.flux.watcher.core.spi.Project}. + * + * @return a {@link java.util.Set} of all synchronized {@link com.codenvy.flux.watcher.core.spi.Project}. + */ + public Set getSynchronizedProjects() { + return ImmutableSet.copyOf(FluentIterable.from(projects) + .filter(notNull()) + .filter(new Predicate() { + @Override + public boolean apply(Project project) { + return project.getSynchronized(); + } + })); + } + + /** + * Returns the {@link com.codenvy.flux.watcher.core.RepositoryEventBus}. + * + * @return the {@link com.codenvy.flux.watcher.core.RepositoryEventBus}, never {@code null}. + */ + public RepositoryEventBus repositoryEventBus() { + return repositoryEventBus; + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEvent.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEvent.java new file mode 100644 index 0000000..c58bebf --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEvent.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import org.eclipse.flux.watcher.core.spi.Project; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Event sent when a modification is done in the repository. + * + * @author Kevin Pollet + * @see com.codenvy.flux.watcher.core.RepositoryEventType + * @see com.codenvy.flux.watcher.core.Resource + * @see com.codenvy.flux.watcher.core.spi.Project + */ +public class RepositoryEvent { + private final RepositoryEventType type; + private final Project project; + private final Resource resource; + + /** + * Constructs an instance of {@link com.codenvy.flux.watcher.core.RepositoryEvent}. + * + * @param type + * the {@link com.codenvy.flux.watcher.core.RepositoryEventType}. + * @param resource + * the {@link com.codenvy.flux.watcher.core.Resource} source of the event. + * @param project + * the {@link com.codenvy.flux.watcher.core.spi.Project} source of the event. + * @throws java.lang.NullPointerException + * if {@code type}, {@code resource} or {@code project} parameter is {@code null}. + */ + public RepositoryEvent(RepositoryEventType type, Resource resource, Project project) { + this.project = checkNotNull(project); + this.type = checkNotNull(type); + this.resource = checkNotNull(resource); + } + + /** + * Returns the {@link com.codenvy.flux.watcher.core.RepositoryEventType} of this event. + * + * @return the {@link com.codenvy.flux.watcher.core.RepositoryEventType}, never {@code null}. + */ + public RepositoryEventType type() { + return type; + } + + /** + * Returns the {@link com.codenvy.flux.watcher.core.Resource} source of this event. + * + * @return the {@link com.codenvy.flux.watcher.core.Resource}, never {@code null}. + */ + public Resource resource() { + return resource; + } + + /** + * Returns the {@link com.codenvy.flux.watcher.core.spi.Project} source of this event. + * + * @return the {@link com.codenvy.flux.watcher.core.spi.Project} source of this event, never {@code null}. + */ + public Project project() { + return project; + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventBus.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventBus.java new file mode 100644 index 0000000..40e4e5d --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventBus.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.notNull; +import static java.util.Arrays.asList; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableSet; + +/** + * Event bus to listen and fire {@link com.codenvy.flux.watcher.core.Repository} events. + * + * @author Kevin Pollet + * @see com.codenvy.flux.watcher.core.RepositoryEvent + */ +@Singleton +public class RepositoryEventBus { + private final Provider repository; + private final Set repositoryListeners; + + /** + * Constructs an instance of {@link com.codenvy.flux.watcher.core.RepositoryEventBus}. + * + * @param repositoryListeners + * the repository listeners to register. + * @param repository + * the {@link com.codenvy.flux.watcher.core.Repository}. + * @throws java.lang.NullPointerException + * if {@code repositoryListeners} or {@code repository} parameter is {@code null}. + */ + @Inject + public RepositoryEventBus(Set repositoryListeners, Provider repository) { + this.repository = checkNotNull(repository); + this.repositoryListeners = new CopyOnWriteArraySet<>(checkNotNull(repositoryListeners)); + } + + /** + * Adds a {@link com.codenvy.flux.watcher.core.RepositoryListener}. + * + * @param listener + * the {@link com.codenvy.flux.watcher.core.RepositoryListener} to add. + * @return {@code true} if the listener was not already added, {@code false} otherwise. + * @throws java.lang.NullPointerException + * if {@code listener} parameter is {@code null}. + */ + public boolean addRepositoryListener(RepositoryListener listener) { + return repositoryListeners.add(checkNotNull(listener)); + } + + /** + * Removes a {@link com.codenvy.flux.watcher.core.RepositoryListener}. + * + * @param listener + * the {@link com.codenvy.flux.watcher.core.RepositoryListener} to remove. + * @return {@code true} if the listener has been removed, {@code false} otherwise. + * @throws java.lang.NullPointerException + * if {@code listener} parameter is {@code null}. + */ + public boolean removeRepositoryListener(RepositoryListener listener) { + return repositoryListeners.remove(checkNotNull(listener)); + } + + /** + * Fires a {@link com.codenvy.flux.watcher.core.RepositoryEvent} to all {@link com.codenvy.flux.watcher.core.RepositoryListener} + * registered. + * + * @param event + * the {@link com.codenvy.flux.watcher.core.RepositoryEvent} to fire. + * @throws java.lang.NullPointerException + * if {@code event} parameter is {@code null}. + * @throws java.lang.IllegalStateException + * if the {@link com.codenvy.flux.watcher.core.spi.Project} concerned by the event is not in the {@link + * com.codenvy.flux.watcher.core.Repository}. + */ + public void fireRepositoryEvent(final RepositoryEvent event) { + checkNotNull(event); + checkState(repository.get().getProject(event.project().id()) != null); + + final Set filteredRepositoryListeners = ImmutableSet.copyOf(FluentIterable + .from(repositoryListeners) + .filter(notNull()) + .filter(new Predicate() { + @Override + public boolean apply(RepositoryListener listener) { + final RepositoryEventTypes repositoryEventTypes = listener.getClass().getAnnotation(RepositoryEventTypes.class); + if(repositoryEventTypes == null) + return true; + return asList(repositoryEventTypes.value()).contains(event.type()); + } + }));; + + for (RepositoryListener oneRepositoryListener : filteredRepositoryListeners) { + try { + + oneRepositoryListener.onEvent(event); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventType.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventType.java new file mode 100644 index 0000000..59a1246 --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventType.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +/** + * Type of events sent by a {@link com.codenvy.flux.watcher.core.Repository}. + * + * @author Kevin Pollet + */ +public enum RepositoryEventType { + PROJECT_RESOURCE_CREATED, + PROJECT_RESOURCE_MODIFIED, + PROJECT_RESOURCE_DELETED +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventTypes.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventTypes.java new file mode 100644 index 0000000..a53fb4c --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryEventTypes.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation used on a {@link com.codenvy.flux.watcher.core.RepositoryListener} to specify the {@link + * com.codenvy.flux.watcher.core.RepositoryEventType} it listens. + * + * @author Kevin Pollet + */ +@Target(TYPE) +@Retention(RUNTIME) +public @interface RepositoryEventTypes { + RepositoryEventType[] value(); +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryListener.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryListener.java new file mode 100644 index 0000000..605c45b --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryListener.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +/** + * Listener used to be notified for {@link com.codenvy.flux.watcher.core.Repository} events. + * + * @author Kevin Pollet + */ +public interface RepositoryListener { + /** + * Called when an events is fired by a {@link com.codenvy.flux.watcher.core.Repository}. + * + * @param event + * the {@link com.codenvy.flux.watcher.core.RepositoryEvent} instance, never {@code null}. + * @throws java.lang.Exception + * if something goes wrong. + */ + void onEvent(RepositoryEvent event) throws Exception; +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryModule.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryModule.java new file mode 100644 index 0000000..0991aae --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/RepositoryModule.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; + +/** + * Guice bindings for {@link RepositoryModule}. + * + * @author Kevin Pollet + */ +public class RepositoryModule extends AbstractModule { + @Override + protected void configure() { + bind(Repository.class); + bind(RepositoryEventBus.class); + + // repository listener bindings + final Multibinder repositoryListeners = Multibinder.newSetBinder(binder(), RepositoryListener.class); + //repositoryListeners.addBinding().to(ProjectResourceCreatedListener.class); + //repositoryListeners.addBinding().to(ProjectResourceDeletedListener.class); + //repositoryListeners.addBinding().to(ProjectResourceModifiedListener.class); + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/Resource.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/Resource.java new file mode 100644 index 0000000..72d147c --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/Resource.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import org.eclipse.flux.watcher.core.utils.ResourceHelper; + +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FILE; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FOLDER; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.UNKNOWN; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Represents a project resource in a repository. + * + * @author Kevin Pollet + */ +public class Resource { + private static final String NULL_CONTENT_HASH = "0"; + + private final String path; + private final long timestamp; + private final String hash; + private final ResourceType type; + private final byte[] content; + + /** + * Constructs an instance of {@link com.codenvy.flux.watcher.core.Resource} representing a {@link + * Resource.ResourceType#UNKNOWN}. + * + * @param path + * the {@link com.codenvy.flux.watcher.core.Resource} relative path. + * @param timestamp + * the {@link com.codenvy.flux.watcher.core.Resource} timestamp. + * @return the new {@link com.codenvy.flux.watcher.core.Resource} instance. + * @throws java.lang.NullPointerException + * if {@code path} parameter is {@code null}. + */ + public static Resource newUnknown(String path, long timestamp) { + return new Resource(path, timestamp, UNKNOWN, null); + } + + /** + * Constructs an instance of {@link com.codenvy.flux.watcher.core.Resource} representing a {@link + * Resource.ResourceType#FOLDER}. + * + * @param path + * the {@link com.codenvy.flux.watcher.core.Resource} relative path. + * @param timestamp + * the {@link com.codenvy.flux.watcher.core.Resource} timestamp. + * @return the new {@link com.codenvy.flux.watcher.core.Resource} instance. + * @throws java.lang.NullPointerException + * if {@code path} parameter is {@code null}. + */ + public static Resource newFolder(String path, long timestamp) { + return new Resource(path, timestamp, FOLDER, null); + } + + /** + * Constructs an instance of {@link com.codenvy.flux.watcher.core.Resource} representing a {@link + * Resource.ResourceType#FILE}. + * + * @param path + * the {@link com.codenvy.flux.watcher.core.Resource} relative path. + * @param timestamp + * the {@link com.codenvy.flux.watcher.core.Resource} timestamp. + * @param content + * the {@link com.codenvy.flux.watcher.core.Resource} content. + * @return the new {@link com.codenvy.flux.watcher.core.Resource} instance. + * @throws java.lang.NullPointerException + * if {@code path} parameter is {@code null}. + */ + public static Resource newFile(String path, long timestamp, byte[] content) { + return new Resource(path, timestamp, FILE, content); + } + + /** + * Constructs an instance of {@link com.codenvy.flux.watcher.core.Resource}. + * + * @param path + * the {@link com.codenvy.flux.watcher.core.Resource} relative path. + * @param timestamp + * the {@link com.codenvy.flux.watcher.core.Resource} timestamp. + * @param type + * the {@link com.codenvy.flux.watcher.core.Resource} {@link Resource.ResourceType}. + * @param content + * the {@link com.codenvy.flux.watcher.core.Resource} content. + * @throws java.lang.NullPointerException + * if {@code path} or {@code type} parameter is {@code null}. + */ + private Resource(String path, long timestamp, ResourceType type, byte[] content) { + this.path = checkNotNull(path); + this.timestamp = timestamp; + this.type = checkNotNull(type); + this.content = content; + hash = content != null ? ResourceHelper.sha1(content) : NULL_CONTENT_HASH; + } + + /** + * Returns the relative path of this {@link com.codenvy.flux.watcher.core.Resource}. + * + * @return this {@link com.codenvy.flux.watcher.core.Resource} relative path, never {@code null}. + */ + public String path() { + return path; + } + + /** + * Returns the timestamp of this {@link com.codenvy.flux.watcher.core.Resource}. + * + * @return this {@link com.codenvy.flux.watcher.core.Resource} timestamp. + */ + public long timestamp() { + return timestamp; + } + + /** + * Returns the type hash this {@link com.codenvy.flux.watcher.core.Resource}. + * + * @return this {@link com.codenvy.flux.watcher.core.Resource} hash, never {@code null}. + */ + public String hash() { + return hash; + } + + /** + * Returns the type of this {@link com.codenvy.flux.watcher.core.Resource}. + * + * @return this {@link com.codenvy.flux.watcher.core.Resource} type, never {@code null}. + */ + public ResourceType type() { + return type; + } + + /** + * Returns the content of this {@link com.codenvy.flux.watcher.core.Resource}. + * + * @return this {@link com.codenvy.flux.watcher.core.Resource} content, {@code null} if none. + */ + public byte[] content() { + return content; + } + + /** + * The {@link com.codenvy.flux.watcher.core.Resource} type. + */ + public enum ResourceType { + FILE, + FOLDER, + UNKNOWN + } + + @Override + public String toString() { + return "Resource:" + path + ", " + type + ", " + hash + ", " + timestamp; + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/spi/Project.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/spi/Project.java new file mode 100644 index 0000000..bc9def6 --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/spi/Project.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core.spi; + +import org.eclipse.flux.watcher.core.Resource; + +import java.util.Set; + +/** + * Project contract implemented by a provider. + * + * @author Kevin Pollet + * @see com.codenvy.flux.watcher.core.spi.ProjectFactory + */ +public interface Project { + /** + * Returns the project unique id. + * + * @return the project unique id, never {@code null}. + */ + String id(); + + /** + * Returns the project absolute path. + * + * @return the project absolute path, never {@code null}. + */ + String path(); + + /** + * Defines if this project is synchronized. The implementer must send the project events to the {@link + * com.codenvy.flux.watcher.core.RepositoryEventBus}. + * + * @param synchronize + * {@code true} if the project have to be synchronized, {@code false} otherwise. + */ + void setSynchronized(boolean synchronize); + + /** + * Returns whether or not the project is currently synchronized. + * + * @return true or false, never {@code null}. + */ + boolean getSynchronized(); + + /** + * Returns all project {@link com.codenvy.flux.watcher.core.Resource}. + * + * @return the {@link com.codenvy.flux.watcher.core.Resource} {@link java.util.Set}, never {@code null}. + */ + Set getResources(); + + boolean hasResource(String resourcePath); + + /** + * Returns the {@link com.codenvy.flux.watcher.core.Resource} with the given relative resource path. + * + * @param resourcePath + * the {@link com.codenvy.flux.watcher.core.Resource} relative path. + * @return the {@link com.codenvy.flux.watcher.core.Resource} or {@code null} if not found. + * @throws java.lang.NullPointerException + * if {@code resourcePath} parameter is {@code null}. + */ + Resource getResource(String resourcePath); + + /** + * Creates the given {@link com.codenvy.flux.watcher.core.Resource}. + * + * @param resource + * the {@link com.codenvy.flux.watcher.core.Resource} to be created. + * @throws java.lang.NullPointerException + * if {@code resource} parameter is {@code null}. + */ + void createResource(Resource resource); + + /** + * Updates the given {@link com.codenvy.flux.watcher.core.Resource}. + * + * @param resource + * the {@link com.codenvy.flux.watcher.core.Resource} to be updated. + * @throws java.lang.NullPointerException + * if {@code resource} parameter is {@code null}. + * @throws java.lang.IllegalArgumentException + * if {@code resource} parameter is not a file. + */ + void updateResource(Resource resource); + + /** + * Deletes the given {@link com.codenvy.flux.watcher.core.Resource}. + * + * @param resource + * the {@link com.codenvy.flux.watcher.core.Resource} to be deleted. + * @throws java.lang.NullPointerException + * if {@code resource} parameter is {@code null}. + */ + void deleteResource(Resource resource); +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/spi/ProjectFactory.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/spi/ProjectFactory.java new file mode 100644 index 0000000..0e9236f --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/spi/ProjectFactory.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core.spi; + +/** + * Project factory contract implemented by a provider. + * + * @author Kevin Pollet + */ +public interface ProjectFactory { + /** + * Returns a new {@link com.codenvy.flux.watcher.core.spi.Project} instance for the given project id and path. + * + * @param projectId + * the project id. + * @param projectPath + * the project path. + * @return the new {@link com.codenvy.flux.watcher.core.spi.Project} instance, never {@code null}. + * @throws java.lang.NullPointerException + * if {@code projectId} or {@code projectPath} parameter is {@code null}. + * @throws java.lang.IllegalArgumentException + * if {@code projectPath} parameter is not absolute, doesn't exist or is not a folder. + */ + Project newProject(String projectId, String projectPath); +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/utils/ResourceHelper.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/utils/ResourceHelper.java new file mode 100644 index 0000000..da9d1cd --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/core/utils/ResourceHelper.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core.utils; + +import javax.xml.bind.DatatypeConverter; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Helper providing methods to work with {@link com.codenvy.flux.watcher.core.Resource}. + * + * @author Kevin Pollet + */ +public final class ResourceHelper { + private static final MessageDigest messageDigest; + + static { + try { + + messageDigest = MessageDigest.getInstance("SHA-1"); + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Calculates the sha1 for the given {@link java.lang.Byte} array. + * + * @param bytes + * the {@link java.lang.Byte} array. + * @return the sha1 as an hexadecimal {@link String}, never {@code null}. + * @throws java.lang.NullPointerException + * if {@code bytes} parameter is {@code null}. + */ + public static String sha1(byte[] bytes) { + final byte[] digest = messageDigest.digest(checkNotNull(bytes)); + return DatatypeConverter.printHexBinary(digest).toLowerCase(); + } + + /** + * Disable instantiation. + */ + private ResourceHelper() { + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProject.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProject.java new file mode 100644 index 0000000..3ff6491 --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProject.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; + +import javax.inject.Singleton; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.HashSet; +import java.util.Set; + +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FILE; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FOLDER; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.file.FileVisitResult.CONTINUE; +import static java.nio.file.Files.createDirectory; +import static java.nio.file.Files.delete; +import static java.nio.file.Files.exists; +import static java.nio.file.Files.getLastModifiedTime; +import static java.nio.file.Files.isDirectory; +import static java.nio.file.Files.readAllBytes; +import static java.nio.file.Files.setLastModifiedTime; +import static java.nio.file.Files.walkFileTree; +import static java.nio.file.Files.write; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +/** + * {@link com.codenvy.flux.watcher.core.spi.Project} implementation backed by Java {@code FileSystem}. + * + * @author Kevin Pollet + */ +@Singleton +public class JDKProject implements Project { + private final String id; + private final Path path; + private final JDKProjectWatchService watchService; + private boolean sync; + + /** + * Constructs an instance of {@link com.codenvy.flux.watcher.fs.JDKProject}. + * + * @param fileSystem + * the {@link java.nio.file.FileSystem} + * @param watchService + * the {@link JDKProjectWatchService}. + * @param id + * the project id. + * @param path + * the project absolute path. + * @throws java.lang.NullPointerException + * if {@code fileSystem}, {@code watchService}, {@code id} or {@code path} parameter is {@code null}. + * @throws java.lang.IllegalArgumentException + * if {@code path} parameter is not absolute, doesn't exist or is not a folder. + */ + JDKProject(FileSystem fileSystem, JDKProjectWatchService watchService, String id, String path) { + this.id = checkNotNull(id); + this.watchService = checkNotNull(watchService); + + this.path = checkNotNull(fileSystem).getPath(checkNotNull(path)); + checkArgument(exists(this.path) && isDirectory(this.path) && this.path.isAbsolute()); + + // start the watch service + if(!this.watchService.isAlive()) this.watchService.start(); + } + + @Override + public String id() { + return id; + } + + @Override + public String path() { + return path.toString(); + } + + @Override + public void setSynchronized(boolean synchronize) { + if (synchronize) { + watchService.watch(this); + } else { + watchService.unwatch(this); + } + sync = synchronize; + } + + @Override + public boolean getSynchronized() { + return sync; + } + + @Override + public Set getResources() { + final Set resources = new HashSet<>(); + try { + + walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (!dir.equals(path)) { + final long timestamp = getLastModifiedTime(dir).toMillis(); + final String relativeResourcePath = path.relativize(dir).toString(); + + resources.add(Resource.newFolder(relativeResourcePath, timestamp)); + } + + return CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + final long timestamp = getLastModifiedTime(file).toMillis(); + final String relativeResourcePath = path.relativize(file).toString(); + final byte[] content = readAllBytes(file); + + resources.add(Resource.newFile(relativeResourcePath, timestamp, content)); + + return CONTINUE; + } + }); + + } catch (IOException e) { + throw new RuntimeException(e); + } + + return resources; + } + + @Override + public Resource getResource(String resourcePath) { + checkNotNull(resourcePath); + + final Path resource = path.resolve(resourcePath); + if (exists(resource)) { + try { + final boolean isDirectory = isDirectory(resource); + final long timestamp = getLastModifiedTime(resource).toMillis(); + + return isDirectory ? Resource.newFolder(resourcePath, timestamp) + : Resource.newFile(resourcePath, timestamp, readAllBytes(resource)); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return null; + } + + @Override + public void createResource(Resource resource) { + checkNotNull(resource); + + final Path resourcePath = path.resolve(resource.path()); + if (!exists(resourcePath)) { + try { + + if (resource.type() == FOLDER) { + createDirectory(resourcePath); + setLastModifiedTime(resourcePath, FileTime.from(resource.timestamp(), MILLISECONDS)); + + } else if (resource.type() == FILE) { + write(resourcePath, resource.content()); + setLastModifiedTime(resourcePath, FileTime.from(resource.timestamp(), MILLISECONDS)); + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void updateResource(Resource resource) { + checkNotNull(resource); + checkArgument(resource.type() == FILE); + + final Path resourcePath = path.resolve(resource.path()); + if (exists(resourcePath)) { + try { + + write(resourcePath, resource.content()); + setLastModifiedTime(resourcePath, FileTime.from(resource.timestamp(), MILLISECONDS)); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void deleteResource(Resource resource) { + checkNotNull(resource); + + final Path resourcePath = path.resolve(resource.path()); + if (exists(resourcePath)) { + try { + + Files.walkFileTree(resourcePath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + delete(file); + return CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path folder, IOException exc) throws IOException { + delete(folder); + return CONTINUE; + } + }); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JDKProject that = (JDKProject)o; + + if (!id.equals(that.id)) return false; + if (!path.equals(that.path)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + path.hashCode(); + return result; + } + + @Override + public boolean hasResource(String resourcePath) { + return exists(path.resolve(resourcePath)); + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectFactory.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectFactory.java new file mode 100644 index 0000000..acd9476 --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectFactory.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + +import org.eclipse.flux.watcher.core.RepositoryEventBus; +import org.eclipse.flux.watcher.core.spi.Project; +import org.eclipse.flux.watcher.core.spi.ProjectFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.nio.file.FileSystem; +import java.nio.file.Path; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.file.Files.exists; +import static java.nio.file.Files.isDirectory; + +/** + * {@link com.codenvy.flux.watcher.core.spi.ProjectFactory} implementation. + * + * @author Kevin Pollet + */ +@Singleton +public class JDKProjectFactory implements ProjectFactory { + private final FileSystem fileSystem; + private final JDKProjectWatchService watchService; + + /** + * Constructs an instance of {@link com.codenvy.flux.watcher.fs.JDKProjectFactory}. + * + * @param fileSystem + * the {@link java.nio.file.FileSystem} instance. + * @param repositoryEventBus + * the {@link com.codenvy.flux.watcher.core.RepositoryEventBus}. + * @throws java.lang.NullPointerException + * if {@code fileSystem} or {@code repositoryEventBus} is {@code null}. + */ + @Inject + public JDKProjectFactory(FileSystem fileSystem, RepositoryEventBus repositoryEventBus) { + this.fileSystem = checkNotNull(fileSystem); + this.watchService = new JDKProjectWatchService(fileSystem, checkNotNull(repositoryEventBus)); + } + + @Override + public Project newProject(String projectId, String projectPath) { + checkNotNull(projectId); + checkNotNull(projectPath); + + final Path path = fileSystem.getPath(projectPath); + checkArgument(exists(path) && isDirectory(path) && path.isAbsolute()); + + return new JDKProject(fileSystem, watchService, projectId, projectPath); + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectModule.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectModule.java new file mode 100644 index 0000000..12a0d02 --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectModule.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + +import org.eclipse.flux.watcher.core.spi.ProjectFactory; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; + +import javax.inject.Singleton; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; + +/** + * Guice bindings. + * + * @author Kevin Pollet + */ +public class JDKProjectModule extends AbstractModule { + @Override + protected void configure() { + bind(ProjectFactory.class).to(JDKProjectFactory.class); + } + + @Singleton + @Provides + protected FileSystem provideFileSystem() { + return FileSystems.getDefault(); + } +} diff --git a/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectWatchService.java b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectWatchService.java new file mode 100644 index 0000000..b7f745c --- /dev/null +++ b/org.eclipse.flux.watcher/src/main/java/org/eclipse/flux/watcher/fs/JDKProjectWatchService.java @@ -0,0 +1,299 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + +import org.eclipse.flux.watcher.core.RepositoryEvent; +import org.eclipse.flux.watcher.core.RepositoryEventBus; +import org.eclipse.flux.watcher.core.RepositoryEventType; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.Resource.ResourceType; +import org.eclipse.flux.watcher.core.spi.Project; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +import java.io.IOException; +import java.nio.file.ClosedWatchServiceException; +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; + +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_CREATED; +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_DELETED; +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_MODIFIED; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.file.FileVisitResult.CONTINUE; +import static java.nio.file.Files.exists; +import static java.nio.file.Files.isDirectory; +import static java.nio.file.Files.readAllBytes; +import static java.nio.file.Files.walkFileTree; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import static java.nio.file.StandardWatchEventKinds.OVERFLOW; +import static java.nio.file.WatchEvent.Kind; + +/** + * Thread watching the file system to notify clients about modifications. + * + * @author Kevin Pollet + */ +public class JDKProjectWatchService extends Thread { + private final WatchService watchService; + private final BiMap watchKeyToPath; + private final Map pathToProject; + private final Object mutex; + private final RepositoryEventBus repositoryEventBus; + private final FileSystem fileSystem; + + /** + * Constructs an instance of {@link JDKProjectWatchService}. + * + * @param fileSystem + * the {@link java.nio.file.FileSystem} to watch. + * @param repositoryEventBus + * the {@link com.codenvy.flux.watcher.core.RepositoryEvent} bus. + * @throws java.lang.NullPointerException + * if {@code repositoryEventBus} or {@code fileSystem} parameter is {@code null}. + */ + JDKProjectWatchService(FileSystem fileSystem, RepositoryEventBus repositoryEventBus) { + this.watchKeyToPath = HashBiMap.create(); + this.pathToProject = new HashMap<>(); + this.mutex = new Object(); + this.repositoryEventBus = checkNotNull(repositoryEventBus); + this.fileSystem = checkNotNull(fileSystem); + + try { + + this.watchService = fileSystem.newWatchService(); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void watch(Project project) { + checkNotNull(project); + watch(project, fileSystem.getPath(project.path())); + } + + private void watch(final Project project, Path path) { + checkNotNull(path); + checkArgument(exists(path) && isDirectory(path) && path.isAbsolute()); + + synchronized (mutex) { + try { + + walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (!watchKeyToPath.containsValue(dir)) { + final WatchKey watchKey = dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + + watchKeyToPath.put(watchKey, dir); + pathToProject.put(dir, project); + } + return CONTINUE; + } + }); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public void unwatch(Project project) { + checkNotNull(project); + unwatch(fileSystem.getPath(project.path())); + } + + private void unwatch(Path path) { + checkNotNull(path); + checkArgument(exists(path) && isDirectory(path) && path.isAbsolute()); + + synchronized (mutex) { + try { + + walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + final WatchKey watchKey = watchKeyToPath.inverse().get(dir); + if (watchKey != null) { + watchKeyToPath.inverse().remove(dir); + pathToProject.remove(dir); + watchKey.cancel(); + } + return CONTINUE; + } + }); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Process all events for keys queued to the watcher. + */ + @Override + public void run() { + while (!isInterrupted()) { + try { + + final WatchKey watchKey = watchService.take(); + synchronized (mutex) { + if (!watchKeyToPath.containsKey(watchKey)) { + continue; + } + } + + for (WatchEvent oneEvent : watchKey.pollEvents()) { + final Path watchablePath = (Path)watchKey.watchable(); + final WatchEvent pathEvent = cast(oneEvent); + final Path resourcePath = watchablePath.resolve(pathEvent.context()); + + if (oneEvent.kind() == OVERFLOW) { + continue; + } + + if (oneEvent.kind() == ENTRY_CREATE && isDirectory(resourcePath)) { + watch(pathToProject.get(watchablePath), resourcePath); + } + + final Project project = pathToProject.get(watchablePath); + final RepositoryEventType repositoryEventType = kindToRepositoryEventType(pathEvent.kind()); + final Resource resource = pathToResource(pathEvent.kind(), project, resourcePath); + repositoryEventBus.fireRepositoryEvent(new RepositoryEvent(repositoryEventType, resource, project)); + } + + final boolean isValid = watchKey.reset(); + if (!isValid) { + synchronized (mutex) { + final Path path = watchKeyToPath.remove(watchKey); + if (path != null) { + pathToProject.remove(path); + } + } + } + + } catch (ClosedWatchServiceException | InterruptedException e) { + return; + } + } + } + + /** + * Converts a {@link java.nio.file.WatchEvent} {@link java.nio.file.WatchEvent.Kind} to a {@link + * com.codenvy.flux.watcher.core.RepositoryEventType}. + * + * @param kind + * the {@link java.nio.file.WatchEvent.Kind} to convert. + * @return the corresponding {@link com.codenvy.flux.watcher.core.RepositoryEventType} or {@code null} if none. + */ + private RepositoryEventType kindToRepositoryEventType(Kind kind) { + if (kind == ENTRY_CREATE) { + return PROJECT_RESOURCE_CREATED; + } + if (kind == ENTRY_MODIFY) { + return PROJECT_RESOURCE_MODIFIED; + } + if (kind == ENTRY_DELETE) { + return PROJECT_RESOURCE_DELETED; + } + return null; + } + + /** + * Converts the given resource {@link java.nio.file.Path} to a {@link com.codenvy.flux.watcher.core.Resource}. + * + * @param kind + * the watch event {@link java.nio.file.WatchEvent.Kind}. + * @param project + * the {@link com.codenvy.flux.watcher.core.spi.Project} containing the resource. + * @param resourcePath + * the absolute resource {@link java.nio.file.Path}. + * @return the {@link com.codenvy.flux.watcher.core.Resource} instance, never {@code null}. + * @throws java.lang.NullPointerException + * if {@code kind}, {@code project} or {@code resourcePath} is {@code null}. + * @throws java.lang.IllegalArgumentException + * if the resource does not exist and the {@link java.nio.file.WatchEvent.Kind} is not {@link + * java.nio.file.StandardWatchEventKinds#ENTRY_DELETE}. + */ + private Resource pathToResource(Kind kind, Project project, Path resourcePath) { + checkNotNull(kind); + checkNotNull(project); + checkNotNull(resourcePath); + checkArgument(resourcePath.isAbsolute()); + + try { + + Path projectPath = fileSystem.getPath(project.path()); + String relativeResourcePath = projectPath.relativize(resourcePath).toString(); + long timestamp = getLastModifiedTime(resourcePath); + switch (getResourceType(resourcePath)) { + case FILE: + return Resource.newFile(relativeResourcePath, timestamp, readAllBytes(resourcePath)); + case FOLDER: + return Resource.newFolder(relativeResourcePath, timestamp); + default: + return Resource.newUnknown(relativeResourcePath, timestamp); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private long getLastModifiedTime(Path resourcePath) { + try { + return Files.getLastModifiedTime(resourcePath).toMillis(); + } catch (IOException | NullPointerException e) { + return System.currentTimeMillis(); + } + } + + private ResourceType getResourceType(Path path) { + boolean isExists = Files.exists(path); + boolean isDirectory = Files.isDirectory(path); + if (isExists) { + if (isDirectory) { + return ResourceType.FOLDER; + } else { + return ResourceType.FILE; + } + + } + return ResourceType.UNKNOWN; + } + + /** + * Cast the given {@link java.nio.file.WatchEvent} to a {@link java.nio.file.Path} {@link java.nio.file.WatchEvent}. + * + * @param event + * the {@link java.nio.file.WatchEvent} to cast. + * @return the casted {@link java.nio.file.WatchEvent}. + * @throws java.lang.NullPointerException + * if {@code event} parameter is {@code null}. + */ + @SuppressWarnings("unchecked") + private WatchEvent cast(WatchEvent event) { + return (WatchEvent)checkNotNull(event); + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryEventBusTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryEventBusTest.java new file mode 100644 index 0000000..71927c2 --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryEventBusTest.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import org.eclipse.flux.watcher.core.spi.Project; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import javax.inject.Provider; +import java.util.Collections; + +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_CREATED; +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_DELETED; +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_MODIFIED; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link com.codenvy.flux.watcher.core.RepositoryEventBus} tests + * + * @author Kevin Pollet + */ +public final class RepositoryEventBusTest { + private static final String PROJECT_ID = "project-id"; + + private RepositoryEventBus repositoryEventBus; + + @SuppressWarnings("unchecked") + @Before + public void beforeTest() { + final Provider repositoryProviderMock = mock(Provider.class); + when(repositoryProviderMock.get()).thenAnswer(new Answer() { + @Override + public Repository answer(InvocationOnMock invocationOnMock) throws Throwable { + final Repository repositoryMock = mock(Repository.class); + final Project projectMock = mock(Project.class); + when(repositoryMock.getProject(PROJECT_ID)).thenReturn(projectMock); + return repositoryMock; + } + }); + + this.repositoryEventBus = new RepositoryEventBus(Collections.emptySet(), repositoryProviderMock); + } + + @SuppressWarnings("unchecked") + @Test(expected = NullPointerException.class) + public void testNewWithNullRepositoryListeners() { + new RepositoryEventBus(null, mock(Provider.class)); + } + + @Test(expected = NullPointerException.class) + public void testNewWithNullRepository() { + new RepositoryEventBus(Collections.emptySet(), null); + } + + @Test(expected = NullPointerException.class) + public void testAddRepositoryListenerWithNullListener() { + repositoryEventBus.addRepositoryListener(null); + } + + @Test(expected = NullPointerException.class) + public void testRemoveRepositoryListenerWithNullListener() { + repositoryEventBus.removeRepositoryListener(null); + } + + @Test(expected = NullPointerException.class) + public void testFireRepositoryEventWithNullEvent() { + repositoryEventBus.fireRepositoryEvent(null); + } + + @Test(expected = IllegalStateException.class) + public void testFireRepositoryEventWithNonAddedProject() { + final RepositoryEvent entryCreatedEvent = new RepositoryEvent(PROJECT_RESOURCE_CREATED, mock(Resource.class), mock(Project.class)); + + repositoryEventBus.fireRepositoryEvent(entryCreatedEvent); + } + + @Test + public void testFireRepositoryEvent() throws Exception { + final EntryCreatedListener entryCreatedListener = new EntryCreatedListener(); + repositoryEventBus.addRepositoryListener(entryCreatedListener); + + final EntryModifiedListener entryModifiedListener = new EntryModifiedListener(); + repositoryEventBus.addRepositoryListener(entryModifiedListener); + + final EntryDeletedListener entryDeletedListener = new EntryDeletedListener(); + repositoryEventBus.addRepositoryListener(entryDeletedListener); + + final EntryCreatedAndModifiedListener entryCreatedAndModifiedListener = new EntryCreatedAndModifiedListener(); + repositoryEventBus.addRepositoryListener(entryCreatedAndModifiedListener); + + fireAllEventTypes(); + + verify(entryCreatedListener.mock, times(1)).onEvent(any(RepositoryEvent.class)); + verify(entryModifiedListener.mock, times(1)).onEvent(any(RepositoryEvent.class)); + verify(entryDeletedListener.mock, times(1)).onEvent(any(RepositoryEvent.class)); + verify(entryCreatedAndModifiedListener.mock, times(2)).onEvent(any(RepositoryEvent.class)); + } + + private void fireAllEventTypes() { + final Project projectMock = mock(Project.class); + when(projectMock.id()).thenReturn(PROJECT_ID); + + final RepositoryEvent entryCreatedEvent = new RepositoryEvent(PROJECT_RESOURCE_CREATED, mock(Resource.class), projectMock); + repositoryEventBus.fireRepositoryEvent(entryCreatedEvent); + + final RepositoryEvent entryDeletedEvent = new RepositoryEvent(PROJECT_RESOURCE_DELETED, mock(Resource.class), projectMock); + repositoryEventBus.fireRepositoryEvent(entryDeletedEvent); + + final RepositoryEvent entryModifiedEvent = new RepositoryEvent(PROJECT_RESOURCE_MODIFIED, mock(Resource.class), projectMock); + repositoryEventBus.fireRepositoryEvent(entryModifiedEvent); + } + + public static class AbstractRepositoryListener implements RepositoryListener { + public final RepositoryListener mock; + + public AbstractRepositoryListener() { + this.mock = mock(RepositoryListener.class); + } + + @Override + public void onEvent(RepositoryEvent event) throws Exception { + mock.onEvent(event); + } + } + + @RepositoryEventTypes(PROJECT_RESOURCE_CREATED) + public static class EntryCreatedListener extends AbstractRepositoryListener { + } + + @RepositoryEventTypes(PROJECT_RESOURCE_DELETED) + public static class EntryDeletedListener extends AbstractRepositoryListener { + } + + @RepositoryEventTypes(PROJECT_RESOURCE_MODIFIED) + public static class EntryModifiedListener extends AbstractRepositoryListener { + } + + @RepositoryEventTypes({PROJECT_RESOURCE_CREATED, PROJECT_RESOURCE_MODIFIED}) + public static class EntryCreatedAndModifiedListener extends AbstractRepositoryListener { + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryEventTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryEventTest.java new file mode 100644 index 0000000..85a38d7 --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryEventTest.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import org.eclipse.flux.watcher.core.spi.Project; + +import org.junit.Test; + +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_CREATED; +import static org.mockito.Mockito.mock; + +/** + * {@link com.codenvy.flux.watcher.core.RepositoryEvent} tests. + * + * @author Kevin Pollet + */ +public final class RepositoryEventTest { + @Test(expected = NullPointerException.class) + public void testNewRepositoryEventWithNullRepositoryEventType() { + new RepositoryEvent(null, mock(Resource.class), mock(Project.class)); + } + + @Test(expected = NullPointerException.class) + public void testNewRepositoryEventWithNullResource() { + new RepositoryEvent(PROJECT_RESOURCE_CREATED, null, mock(Project.class)); + } + + @Test(expected = NullPointerException.class) + public void testNewRepositoryEventWithNullProject() { + new RepositoryEvent(PROJECT_RESOURCE_CREATED, mock(Resource.class), null); + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryModuleTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryModuleTest.java new file mode 100644 index 0000000..3cded30 --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryModuleTest.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import org.eclipse.flux.watcher.core.spi.ProjectFactory; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; + +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.mock; + +/** + * Tests Google Guice bootstrap. + * + * @author Kevin Pollet + */ +public final class RepositoryModuleTest { + @Test + public void testBootstrap() { + final Injector injector = Guice.createInjector(new RepositoryModule(), new TestModule()); + + Assert.assertNotNull(injector.getInstance(Repository.class)); + } + + public static class TestModule extends AbstractModule { + @Override + protected void configure() { + bind(ProjectFactory.class).toInstance(mock(ProjectFactory.class)); + } + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryTest.java new file mode 100644 index 0000000..770edf2 --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/RepositoryTest.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + +import org.eclipse.flux.watcher.core.spi.Project; +import org.eclipse.flux.watcher.core.spi.ProjectFactory; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * {@link com.codenvy.flux.watcher.core.Repository} tests. + * + * @author Kevin Pollet + */ +public final class RepositoryTest { + private final static String PROJECT_ID = "project-id"; + private final static String PROJECT_PATH = "/project-id"; + + private Repository repository; + + @Before + public void beforeTest() { + final Project projectMock = mock(Project.class); + when(projectMock.id()).thenReturn(PROJECT_ID); + when(projectMock.path()).thenReturn(PROJECT_PATH); + + final ProjectFactory projectFactoryMock = mock(ProjectFactory.class); + when(projectFactoryMock.newProject(anyString(), anyString())).thenReturn(projectMock); + } + + @Test(expected = NullPointerException.class) + public void testAddProjectWithNullProjectId() { + repository.addProject(null, PROJECT_PATH); + } + + @Test(expected = NullPointerException.class) + public void testAddProjectWithNullProjectPath() { + repository.addProject(PROJECT_ID, null); + } + + @Test + public void testRemoveProjectWithNonExistentProjectId() { + final Project project = repository.removeProject(PROJECT_ID); + + Assert.assertNull(project); + } + + @Test + public void testRemoveProject() { + final Project project = repository.addProject(PROJECT_ID, PROJECT_PATH); + final Project removedProject = repository.removeProject(PROJECT_ID); + + Assert.assertNotNull(project); + Assert.assertNotNull(removedProject); + Assert.assertSame(project, removedProject); + } + + @Test + public void testAddProjectWithAlreadyAddedProject() { + final Project project = repository.addProject(PROJECT_ID, PROJECT_PATH); + + Assert.assertNotNull(project); + + final Project newProject = repository.addProject(PROJECT_ID, PROJECT_PATH); + + Assert.assertNotNull(newProject); + Assert.assertSame(project, newProject); + } + + @Test + public void testGetProjectWithNullProjectId() { + final Project project = repository.getProject(null); + + Assert.assertNull(project); + } + + @Test + public void testGetProjectWithNonExistentProjectId() { + final Project project = repository.getProject("foo"); + + Assert.assertNull(project); + } + + @Test + public void testGetProject() { + final Project project = repository.addProject(PROJECT_ID, PROJECT_PATH); + + Assert.assertNotNull(project); + + final Project currentProject = repository.getProject(PROJECT_ID); + + Assert.assertNotNull(currentProject); + Assert.assertSame(project, currentProject); + Assert.assertEquals(PROJECT_ID, currentProject.id()); + Assert.assertEquals(PROJECT_PATH, currentProject.path()); + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/ResourceTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/ResourceTest.java new file mode 100644 index 0000000..ff0628b --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/core/ResourceTest.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.core; + + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FILE; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FOLDER; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.UNKNOWN; + +/** + * {@link com.codenvy.flux.watcher.core.Resource} tests. + * + * @author Kevin Pollet + */ +public final class ResourceTest { + private final static String RESOURCE_PATH = "/foo"; + + @Test(expected = NullPointerException.class) + public void testNewUnknownWithNullPath() { + Resource.newUnknown(null, System.currentTimeMillis()); + } + + @Test + public void testNewUnknown() { + final long timestamp = System.currentTimeMillis(); + final Resource resource = Resource.newUnknown(RESOURCE_PATH, timestamp); + + Assert.assertNotNull(resource); + Assert.assertEquals(timestamp, resource.timestamp()); + Assert.assertEquals(RESOURCE_PATH, resource.path()); + Assert.assertEquals(null, resource.content()); + Assert.assertEquals(UNKNOWN, resource.type()); + Assert.assertEquals("0", resource.hash()); + } + + @Test(expected = NullPointerException.class) + public void testNewFolderWithNullPath() { + Resource.newFolder(null, System.currentTimeMillis()); + } + + @Test + public void testNewFolder() { + final long timestamp = System.currentTimeMillis(); + final Resource resource = Resource.newFolder(RESOURCE_PATH, timestamp); + + Assert.assertNotNull(resource); + Assert.assertEquals(timestamp, resource.timestamp()); + Assert.assertEquals(RESOURCE_PATH, resource.path()); + Assert.assertEquals(null, resource.content()); + Assert.assertEquals(FOLDER, resource.type()); + Assert.assertEquals("0", resource.hash()); + } + + @Test(expected = NullPointerException.class) + public void testNewFileWithNullPath() { + Resource.newFile(null, System.currentTimeMillis(), new byte[0]); + } + + @Test + public void testNewFile() { + final byte[] content = "content".getBytes(); + final long timestamp = System.currentTimeMillis(); + final Resource resource = Resource.newFile(RESOURCE_PATH, timestamp, content); + + Assert.assertNotNull(resource); + Assert.assertEquals(timestamp, resource.timestamp()); + Assert.assertEquals(RESOURCE_PATH, resource.path()); + Assert.assertTrue(Arrays.equals(content, resource.content())); + Assert.assertEquals(FILE, resource.type()); + Assert.assertNotNull(resource.hash()); + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/AbstractTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/AbstractTest.java new file mode 100644 index 0000000..90d0043 --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/AbstractTest.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + +import org.junit.After; +import org.junit.Before; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import static java.nio.file.FileVisitResult.CONTINUE; +import static java.nio.file.Files.createDirectory; +import static java.nio.file.Files.createFile; +import static java.nio.file.Files.delete; +import static java.nio.file.Files.walkFileTree; + +/** + * Abstract test class. + * + * @author Kevin Pollet + */ +public class AbstractTest { + public static final String PROJECT_ID = "codenvy-project-id"; + public static final String PROJECT_PATH = Paths.get("codenvy-project").toAbsolutePath().toString(); + public static final String RELATIVE_PROJECT_SRC_FOLDER_PATH = "src"; + public static final String RELATIVE_PROJECT_MAIN_FOLDER_PATH = "src/main"; + public static final String RELATIVE_PROJECT_HELLO_FILE_PATH = "src/hello"; + public static final String RELATIVE_PROJECT_README_FILE_PATH = "readme"; + + private FileSystem fileSystem; + + public FileSystem fileSystem() { + return fileSystem; + } + + @Before + public void initFileSystem() throws IOException { + fileSystem = FileSystems.getDefault(); + + createDirectory(fileSystem.getPath(PROJECT_PATH)); + createDirectory(fileSystem.getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_SRC_FOLDER_PATH)); + createFile(fileSystem.getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_README_FILE_PATH)); + } + + @After + public void destroyFileSystem() throws IOException { + walkFileTree(fileSystem.getPath(PROJECT_PATH), new SimpleFileVisitor() { + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + delete(dir); + return CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + delete(file); + return CONTINUE; + } + }); + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectFactoryTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectFactoryTest.java new file mode 100644 index 0000000..583a28e --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectFactoryTest.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + + +import org.eclipse.flux.watcher.core.RepositoryEventBus; +import org.eclipse.flux.watcher.core.spi.Project; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +import static org.mockito.Mockito.mock; + +/** + * {@link com.codenvy.flux.watcher.fs.JDKProjectFactory} tests. + * + * @author Kevin Pollet + */ +public final class JDKProjectFactoryTest extends AbstractTest { + private JDKProjectFactory jdkProjectFactory; + + @Before + public void beforeTest() { + jdkProjectFactory = new JDKProjectFactory(fileSystem(), mock(RepositoryEventBus.class)); + } + + @Test(expected = NullPointerException.class) + public void testNewJDKProjectFactoryWithNullFileSystem() { + new JDKProjectFactory(null, mock(RepositoryEventBus.class)); + } + + @Test(expected = NullPointerException.class) + public void testNewJDKProjectFactoryWithNullRepositoryEventBus() { + new JDKProjectFactory(fileSystem(), null); + } + + @Test(expected = NullPointerException.class) + public void testNewProjectWithNullProjectId() { + jdkProjectFactory.newProject(null, PROJECT_PATH); + } + + @Test(expected = NullPointerException.class) + public void testNewProjectWithNullProjectPath() { + jdkProjectFactory.newProject(PROJECT_ID, null); + } + + @Test(expected = IllegalArgumentException.class) + public void testNewProjectWithNonExistentProjectPath() { + jdkProjectFactory.newProject(PROJECT_ID, "foo"); + } + + @Test(expected = IllegalArgumentException.class) + public void testNewProjectWithFileProjectPath() { + jdkProjectFactory.newProject(PROJECT_ID, PROJECT_PATH + File.separator + RELATIVE_PROJECT_README_FILE_PATH); + } + + @Test(expected = IllegalArgumentException.class) + public void testNewProjectWithNonAbsoluteProjectPath() { + jdkProjectFactory.newProject(PROJECT_ID, RELATIVE_PROJECT_README_FILE_PATH); + } + + @Test + public void testNewProject() { + final Project project = jdkProjectFactory.newProject(PROJECT_ID, PROJECT_PATH); + + Assert.assertNotNull(project); + Assert.assertEquals(PROJECT_ID, project.id()); + Assert.assertEquals(PROJECT_PATH, project.path()); + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectModuleTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectModuleTest.java new file mode 100644 index 0000000..c37acfd --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectModuleTest.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + +import org.eclipse.flux.watcher.core.Repository; +import org.eclipse.flux.watcher.core.RepositoryModule; +import com.google.inject.Guice; +import com.google.inject.Injector; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests Google Guice bootstrap. + * + * @author Kevin Pollet + */ +public final class JDKProjectModuleTest { + @Test + public void testBootstrap() { + final Injector injector = Guice.createInjector(new RepositoryModule(), new JDKProjectModule()); + + Assert.assertNotNull(injector.getInstance(Repository.class)); + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectTest.java new file mode 100644 index 0000000..40b7c3b --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectTest.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + + +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; +import com.google.common.collect.Sets; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Calendar; +import java.util.Set; + +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FILE; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FOLDER; +import static java.nio.file.Files.createFile; +import static java.nio.file.Files.exists; +import static java.nio.file.Files.getLastModifiedTime; +import static java.nio.file.Files.isDirectory; +import static java.nio.file.Files.readAllBytes; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * {@link com.codenvy.flux.watcher.fs.JDKProject} tests. + * + * @author Kevin Pollet + */ +public final class JDKProjectTest extends AbstractTest { + private JDKProject project; + private JDKProjectWatchService jdkProjectWatchServiceMock; + + @Before + public void beforeTest() throws IOException { + jdkProjectWatchServiceMock = mock(JDKProjectWatchService.class); + project = new JDKProject(fileSystem(), jdkProjectWatchServiceMock, PROJECT_ID, PROJECT_PATH); + } + + @Test + public void testSetSynchronizedWithTrue() { + project.setSynchronized(true); + verify(jdkProjectWatchServiceMock, times(1)).watch(any(Project.class)); + verify(jdkProjectWatchServiceMock, times(0)).unwatch(any(Project.class)); + } + + @Test + public void testSetSynchronizedWithFalse() { + project.setSynchronized(false); + verify(jdkProjectWatchServiceMock, times(0)).watch(any(Project.class)); + verify(jdkProjectWatchServiceMock, times(1)).unwatch(any(Project.class)); + } + + @Test + public void testGetResources() { + final Set resources = project.getResources(); + final Set paths = Sets.newHashSet(RELATIVE_PROJECT_SRC_FOLDER_PATH, RELATIVE_PROJECT_README_FILE_PATH); + + Assert.assertNotNull(resources); + Assert.assertEquals(2, resources.size()); + + for (Resource oneResource : resources) { + if (paths.contains(oneResource.path())) { + paths.remove(oneResource.path()); + } + } + + Assert.assertTrue(paths.isEmpty()); + } + + @Test(expected = NullPointerException.class) + public void testGetResourceWithNullResourcePath() { + project.getResource(null); + } + + @Test + public void testGetResourceWithNonExistentResourcePath() { + final Resource resource = project.getResource("foo"); + + Assert.assertNull(resource); + } + + @Test + public void testGetResourceWithFilePath() throws IOException { + final Path absoluteResourcePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_README_FILE_PATH); + final Resource resource = project.getResource(RELATIVE_PROJECT_README_FILE_PATH); + + Assert.assertNotNull(resource); + Assert.assertEquals(RELATIVE_PROJECT_README_FILE_PATH, resource.path()); + Assert.assertEquals(FILE, resource.type()); + Assert.assertEquals(getLastModifiedTime(absoluteResourcePath).toMillis(), resource.timestamp()); + Assert.assertArrayEquals(readAllBytes(absoluteResourcePath), resource.content()); + Assert.assertNotNull(resource.hash()); + } + + @Test + public void testGetResourceWithFolderPath() throws IOException { + final Path absoluteFolderPath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_SRC_FOLDER_PATH); + final Resource resource = project.getResource(RELATIVE_PROJECT_SRC_FOLDER_PATH); + + Assert.assertNotNull(resource); + Assert.assertEquals(RELATIVE_PROJECT_SRC_FOLDER_PATH, resource.path()); + Assert.assertEquals(FOLDER, resource.type()); + Assert.assertEquals(getLastModifiedTime(absoluteFolderPath).toMillis(), resource.timestamp()); + Assert.assertEquals(null, resource.content()); + Assert.assertNotNull(resource.hash()); + } + + @Test(expected = NullPointerException.class) + public void testCreateResourceWithNullResource() { + project.createResource(null); + } + + @Test + public void testCreateResourceWithFolderResource() throws IOException { + final Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.DAY_OF_MONTH, 26); + calendar.set(Calendar.MONTH, 8); + calendar.set(Calendar.YEAR, 1984); + + final Path absoluteFolderPath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_MAIN_FOLDER_PATH); + project.createResource(Resource.newFolder(RELATIVE_PROJECT_MAIN_FOLDER_PATH, calendar.getTimeInMillis())); + + Assert.assertTrue(exists(absoluteFolderPath)); + Assert.assertTrue(isDirectory(absoluteFolderPath)); + Assert.assertEquals(calendar.getTimeInMillis(), getLastModifiedTime(absoluteFolderPath).toMillis()); + } + + @Test + public void testCreateResourceWithFileResource() throws IOException { + final byte[] helloFileContent = "helloWorld".getBytes(); + final Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.DAY_OF_MONTH, 26); + calendar.set(Calendar.MONTH, 8); + calendar.set(Calendar.YEAR, 1984); + + final Path absoluteFilePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_HELLO_FILE_PATH); + project.createResource(Resource.newFile(RELATIVE_PROJECT_HELLO_FILE_PATH, calendar.getTimeInMillis(), helloFileContent)); + + Assert.assertTrue(exists(absoluteFilePath)); + Assert.assertFalse(isDirectory(absoluteFilePath)); + Assert.assertArrayEquals(readAllBytes(absoluteFilePath), helloFileContent); + Assert.assertEquals(calendar.getTimeInMillis(), getLastModifiedTime(absoluteFilePath).toMillis()); + } + + @Test(expected = NullPointerException.class) + public void testUpdateResourceWithNullResource() { + project.updateResource(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testUpdateResourceWithFolderResource() { + project.updateResource(Resource.newFolder(RELATIVE_PROJECT_MAIN_FOLDER_PATH, System.currentTimeMillis())); + } + + @Test + public void testUpdateResource() throws IOException { + final byte[] readmeContent = "readme".getBytes(); + final long timestamp = System.currentTimeMillis(); + final Path resourcePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_README_FILE_PATH); + final Resource resource = Resource.newFile(RELATIVE_PROJECT_README_FILE_PATH, timestamp, readmeContent); + + Assert.assertArrayEquals(new byte[0], Files.readAllBytes(resourcePath)); + + project.updateResource(resource); + + Assert.assertEquals(timestamp, getLastModifiedTime(resourcePath).toMillis()); + Assert.assertArrayEquals(readmeContent, readAllBytes(resourcePath)); + } + + @Test(expected = NullPointerException.class) + public void testDeleteResourceWithNullResource() { + project.deleteResource(null); + } + + @Test + public void testDeleteResourceWithEmptyFolderResource() throws IOException { + final Path absoluteFolderPath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_SRC_FOLDER_PATH); + project.deleteResource(Resource.newFolder(RELATIVE_PROJECT_SRC_FOLDER_PATH, System.currentTimeMillis())); + + Assert.assertFalse(exists(absoluteFolderPath)); + } + + @Test + public void testDeleteResourceWithNonEmptyFolderResource() throws IOException { + final Path absoluteFilePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_HELLO_FILE_PATH); + createFile(absoluteFilePath); + + final Path absoluteFolderPath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_SRC_FOLDER_PATH); + project.deleteResource(Resource.newFolder(RELATIVE_PROJECT_SRC_FOLDER_PATH, System.currentTimeMillis())); + + Assert.assertFalse(exists(absoluteFolderPath)); + } + + @Test + public void testDeleteResourceWithFileResource() throws IOException { + final Path absoluteFilePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_README_FILE_PATH); + project + .deleteResource(Resource.newFile(RELATIVE_PROJECT_README_FILE_PATH, System.currentTimeMillis(), new byte[0])); + + Assert.assertFalse(exists(absoluteFilePath)); + } +} \ No newline at end of file diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectWatchServiceTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectWatchServiceTest.java new file mode 100644 index 0000000..0f9d27b --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/JDKProjectWatchServiceTest.java @@ -0,0 +1,408 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs; + +import org.eclipse.flux.watcher.core.Repository; +import org.eclipse.flux.watcher.core.RepositoryEvent; +import org.eclipse.flux.watcher.core.RepositoryEventBus; +import org.eclipse.flux.watcher.core.RepositoryEventType; +import org.eclipse.flux.watcher.core.RepositoryEventTypes; +import org.eclipse.flux.watcher.core.RepositoryListener; +import org.eclipse.flux.watcher.core.Resource; +import org.eclipse.flux.watcher.core.spi.Project; +import com.google.common.base.Throwables; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import javax.inject.Provider; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; + +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_CREATED; +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_DELETED; +import static org.eclipse.flux.watcher.core.RepositoryEventType.PROJECT_RESOURCE_MODIFIED; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FILE; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.FOLDER; +import static org.eclipse.flux.watcher.core.Resource.ResourceType.UNKNOWN; +import static java.nio.file.Files.createDirectory; +import static java.nio.file.Files.createFile; +import static java.nio.file.Files.delete; +import static java.nio.file.Files.getLastModifiedTime; +import static java.nio.file.Files.readAllBytes; +import static java.nio.file.Files.write; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import static java.nio.file.WatchEvent.Kind; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * {@link com.codenvy.flux.watcher.fs.JDKProjectWatchService} tests. + * + * @author Kevin Pollet + */ +public final class JDKProjectWatchServiceTest extends AbstractTest { + private JDKProject jdkProject; + private JDKProjectWatchService jdkProjectWatchService; + private RepositoryEventBus repositoryEventBus; + + @SuppressWarnings("unchecked") + @Before + public void beforeTest() throws NoSuchMethodException { + final Provider repositoryProviderMock = mock(Provider.class); + when(repositoryProviderMock.get()).thenAnswer(new Answer() { + @Override + public Repository answer(InvocationOnMock invocationOnMock) throws Throwable { + final Repository repositoryMock = mock(Repository.class); + final Project projectMock = mock(Project.class); + when(repositoryMock.getProject(PROJECT_ID)).thenReturn(projectMock); + + return repositoryMock; + } + }); + + repositoryEventBus = new RepositoryEventBus(Collections.emptySet(), repositoryProviderMock); + jdkProjectWatchService = new JDKProjectWatchService(fileSystem(), repositoryEventBus); + jdkProject = new JDKProject(fileSystem(), jdkProjectWatchService, PROJECT_ID, PROJECT_PATH); + } + + @Test(expected = NullPointerException.class) + public void testWatchWithNullProject() { + jdkProjectWatchService.watch(null); + } + + @Test(expected = NullPointerException.class) + public void testUnwatchWithNullProject() { + jdkProjectWatchService.unwatch(null); + } + + @Test + public void testKindToRepositoryEventTypeWithNullKind() throws Exception { + final RepositoryEventType repositoryEventType = kindToRepositoryEventType(null); + + Assert.assertNull(repositoryEventType); + } + + @Test + public void testKindToRepositoryEventTypeWithEntryCreateKind() throws Exception { + final RepositoryEventType repositoryEventType = kindToRepositoryEventType(ENTRY_CREATE); + + Assert.assertNotNull(repositoryEventType); + Assert.assertEquals(PROJECT_RESOURCE_CREATED, repositoryEventType); + } + + @Test + public void testKindToRepositoryEventTypeWithEntryDeleteKind() throws Exception { + final RepositoryEventType repositoryEventType = kindToRepositoryEventType(ENTRY_DELETE); + + Assert.assertNotNull(repositoryEventType); + Assert.assertEquals(PROJECT_RESOURCE_DELETED, repositoryEventType); + } + + @Test + public void testKindToRepositoryEventTypeWithEntryModifyKind() throws Exception { + final RepositoryEventType repositoryEventType = kindToRepositoryEventType(ENTRY_MODIFY); + + Assert.assertNotNull(repositoryEventType); + Assert.assertEquals(PROJECT_RESOURCE_MODIFIED, repositoryEventType); + } + + @Test(expected = NullPointerException.class) + public void testCastWithNullEvent() throws Throwable { + final Method castMethod = JDKProjectWatchService.class.getDeclaredMethod("cast", WatchEvent.class); + castMethod.setAccessible(true); + + try { + + castMethod.invoke(jdkProjectWatchService, (WatchEvent)null); + + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + @Test(expected = NullPointerException.class) + public void testPathToResourceWithNullKind() throws Exception { + pathToResource(null, jdkProject, fileSystem().getPath(PROJECT_PATH)); + } + + @Test(expected = NullPointerException.class) + public void testPathToResourceWithNullProject() throws Exception { + pathToResource(ENTRY_CREATE, null, fileSystem().getPath(PROJECT_PATH)); + } + + @Test(expected = NullPointerException.class) + public void testPathToResourceWithNullResourcePath() throws Exception { + pathToResource(ENTRY_CREATE, jdkProject, null); + } + + @Test(expected = IllegalArgumentException.class) + public void testPathToResourceWithCreateKindAndNonExistentResourcePath() throws Exception { + pathToResource(ENTRY_CREATE, jdkProject, fileSystem().getPath("foo")); + } + + @Test(expected = IllegalArgumentException.class) + public void testPathToResourceWithRelativeResourcePath() throws Exception { + pathToResource(ENTRY_CREATE, jdkProject, fileSystem().getPath(RELATIVE_PROJECT_README_FILE_PATH)); + } + + @Test + public void testPathToResourceWithFolderPath() throws Exception { + final Path absoluteFolderPath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_SRC_FOLDER_PATH); + final Resource resource = pathToResource(ENTRY_CREATE, jdkProject, absoluteFolderPath); + + Assert.assertNotNull(resource); + Assert.assertEquals(RELATIVE_PROJECT_SRC_FOLDER_PATH, resource.path()); + Assert.assertEquals(FOLDER, resource.type()); + Assert.assertEquals(getLastModifiedTime(absoluteFolderPath).toMillis(), resource.timestamp()); + Assert.assertArrayEquals(null, resource.content()); + Assert.assertNotNull(resource.hash()); + } + + @Test + public void testPathToResourceWithFilePath() throws Exception { + final Path absoluteFilePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_README_FILE_PATH); + final Resource resource = pathToResource(ENTRY_CREATE, jdkProject, absoluteFilePath); + + Assert.assertNotNull(resource); + Assert.assertEquals(RELATIVE_PROJECT_README_FILE_PATH, resource.path()); + Assert.assertEquals(FILE, resource.type()); + Assert.assertEquals(getLastModifiedTime(absoluteFilePath).toMillis(), resource.timestamp()); + Assert.assertArrayEquals(readAllBytes(absoluteFilePath), resource.content()); + Assert.assertNotNull(resource.hash()); + } + + @Test + public void testWatchEntryCreateFile() throws InterruptedException, IOException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final ProjectResourceCreatedListener projectResourceCreatedListener = new ProjectResourceCreatedListener(countDownLatch); + + jdkProjectWatchService.watch(jdkProject); + repositoryEventBus.addRepositoryListener(projectResourceCreatedListener); + + final Path absoluteFilePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_HELLO_FILE_PATH); + createFile(absoluteFilePath); + + countDownLatch.await(1, MINUTES); + + final RepositoryEvent repositoryEvent = projectResourceCreatedListener.repositoryEvent; + + Assert.assertNotNull(repositoryEvent); + Assert.assertEquals(PROJECT_ID, repositoryEvent.project().id()); + Assert.assertEquals(PROJECT_RESOURCE_CREATED, repositoryEvent.type()); + Assert.assertEquals(RELATIVE_PROJECT_HELLO_FILE_PATH, repositoryEvent.resource().path()); + Assert.assertEquals(FILE, repositoryEvent.resource().type()); + Assert.assertEquals(getLastModifiedTime(absoluteFilePath).toMillis(), repositoryEvent.resource().timestamp()); + Assert.assertArrayEquals(readAllBytes(absoluteFilePath), repositoryEvent.resource().content()); + Assert.assertNotNull(repositoryEvent.resource().hash()); + } + + @Test + public void testWatchEntryCreateFolder() throws InterruptedException, IOException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final ProjectResourceCreatedListener projectResourceCreatedListener = new ProjectResourceCreatedListener(countDownLatch); + + jdkProjectWatchService.watch(jdkProject); + repositoryEventBus.addRepositoryListener(projectResourceCreatedListener); + + final Path absoluteFilePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_MAIN_FOLDER_PATH); + createDirectory(absoluteFilePath); + + countDownLatch.await(1, MINUTES); + + final RepositoryEvent repositoryEvent = projectResourceCreatedListener.repositoryEvent; + + Assert.assertNotNull(repositoryEvent); + Assert.assertEquals(PROJECT_RESOURCE_CREATED, repositoryEvent.type()); + Assert.assertEquals(PROJECT_ID, repositoryEvent.project().id()); + Assert.assertEquals(RELATIVE_PROJECT_MAIN_FOLDER_PATH, projectResourceCreatedListener.repositoryEvent.resource().path()); + Assert.assertEquals(FOLDER, projectResourceCreatedListener.repositoryEvent.resource().type()); + Assert.assertEquals(getLastModifiedTime(absoluteFilePath).toMillis(), repositoryEvent.resource().timestamp()); + Assert.assertArrayEquals(null, repositoryEvent.resource().content()); + Assert.assertNotNull(repositoryEvent.resource().hash()); + } + + @Test + public void testWatchEntryModifyFile() throws InterruptedException, IOException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final ProjectResourceModifiedListener projectResourceModifiedListener = new ProjectResourceModifiedListener(countDownLatch); + + jdkProjectWatchService.watch(jdkProject); + repositoryEventBus.addRepositoryListener(projectResourceModifiedListener); + + final String content = "README"; + final Path absoluteFilePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_README_FILE_PATH); + write(absoluteFilePath, content.getBytes()); + + countDownLatch.await(1, MINUTES); + + final RepositoryEvent repositoryEvent = projectResourceModifiedListener.repositoryEvent; + + Assert.assertNotNull(repositoryEvent); + Assert.assertEquals(PROJECT_RESOURCE_MODIFIED, repositoryEvent.type()); + Assert.assertEquals(PROJECT_ID, repositoryEvent.project().id()); + Assert.assertEquals(RELATIVE_PROJECT_README_FILE_PATH, repositoryEvent.resource().path()); + Assert.assertEquals(FILE, repositoryEvent.resource().type()); + Assert.assertEquals(getLastModifiedTime(absoluteFilePath).toMillis(), repositoryEvent.resource().timestamp()); + Assert.assertArrayEquals(content.getBytes(), repositoryEvent.resource().content()); + Assert.assertNotNull(repositoryEvent.resource().hash()); + } + + @Test + public void testWatchEntryDeleteFile() throws InterruptedException, IOException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final ProjectResourceDeletedListener projectResourceDeletedListener = new ProjectResourceDeletedListener(countDownLatch); + + jdkProjectWatchService.watch(jdkProject); + repositoryEventBus.addRepositoryListener(projectResourceDeletedListener); + + final Path absoluteFilePath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_README_FILE_PATH); + delete(absoluteFilePath); + + countDownLatch.await(1, MINUTES); + + final RepositoryEvent repositoryEvent = projectResourceDeletedListener.repositoryEvent; + + Assert.assertNotNull(repositoryEvent); + Assert.assertEquals(PROJECT_RESOURCE_DELETED, repositoryEvent.type()); + Assert.assertEquals(PROJECT_ID, repositoryEvent.project().id()); + Assert.assertEquals(RELATIVE_PROJECT_README_FILE_PATH, repositoryEvent.resource().path()); + Assert.assertEquals(UNKNOWN, projectResourceDeletedListener.repositoryEvent.resource().type()); + Assert.assertArrayEquals(null, repositoryEvent.resource().content()); + Assert.assertNotNull(repositoryEvent.resource().hash()); + } + + @Test + public void testWatchEntryDeleteFolder() throws InterruptedException, IOException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final ProjectResourceDeletedListener projectResourceDeletedListener = new ProjectResourceDeletedListener(countDownLatch); + + jdkProjectWatchService.watch(jdkProject); + repositoryEventBus.addRepositoryListener(projectResourceDeletedListener); + + final Path absoluteFolderPath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_SRC_FOLDER_PATH); + delete(absoluteFolderPath); + + countDownLatch.await(1, MINUTES); + + final RepositoryEvent repositoryEvent = projectResourceDeletedListener.repositoryEvent; + + Assert.assertNotNull(repositoryEvent); + Assert.assertEquals(PROJECT_RESOURCE_DELETED, repositoryEvent.type()); + Assert.assertEquals(PROJECT_ID, repositoryEvent.project().id()); + Assert.assertEquals(RELATIVE_PROJECT_SRC_FOLDER_PATH, repositoryEvent.resource().path()); + Assert.assertEquals(UNKNOWN, repositoryEvent.resource().type()); + Assert.assertArrayEquals(null, repositoryEvent.resource().content()); + Assert.assertNotNull(repositoryEvent.resource().hash()); + } + + @Test + public void testUnwatch() throws IOException, InterruptedException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final ProjectResourceDeletedListener projectResourceDeletedListener = new ProjectResourceDeletedListener(countDownLatch); + + jdkProjectWatchService.watch(jdkProject); + repositoryEventBus.addRepositoryListener(projectResourceDeletedListener); + jdkProjectWatchService.unwatch(jdkProject); + + final Path absoluteFolderPath = fileSystem().getPath(PROJECT_PATH).resolve(RELATIVE_PROJECT_SRC_FOLDER_PATH); + delete(absoluteFolderPath); + + countDownLatch.await(30, SECONDS); + + Assert.assertNull(projectResourceDeletedListener.repositoryEvent); + } + + private RepositoryEventType kindToRepositoryEventType(Kind kind) throws Exception { + final Method kindToRepositoryEventTypeMethod = + JDKProjectWatchService.class.getDeclaredMethod("kindToRepositoryEventType", Kind.class); + kindToRepositoryEventTypeMethod.setAccessible(true); + + try { + + return (RepositoryEventType)kindToRepositoryEventTypeMethod.invoke(jdkProjectWatchService, kind); + + } catch (InvocationTargetException e) { + throw Throwables.propagate(e.getCause()); + } + } + + private Resource pathToResource(Kind kind, Project project, Path resourcePath) throws Exception { + final Method pathToResourceMethod = + JDKProjectWatchService.class.getDeclaredMethod("pathToResource", Kind.class, Project.class, Path.class); + pathToResourceMethod.setAccessible(true); + + try { + + return (Resource)pathToResourceMethod.invoke(jdkProjectWatchService, kind, project, resourcePath); + + } catch (InvocationTargetException e) { + throw Throwables.propagate(e.getCause()); + } + } + + private static abstract class AbstractRepositoryListener implements RepositoryListener { + private final CountDownLatch countDownLatch; + public RepositoryEvent repositoryEvent; + + public AbstractRepositoryListener(CountDownLatch countDownLatch) { + this.countDownLatch = countDownLatch; + this.repositoryEvent = null; + } + + @Override + public void onEvent(RepositoryEvent event) { + try { + + if (repositoryEvent != null) { + throw new IllegalStateException(); + } + repositoryEvent = event; + + } finally { + countDownLatch.countDown(); + } + } + } + + @RepositoryEventTypes(PROJECT_RESOURCE_CREATED) + private static class ProjectResourceCreatedListener extends AbstractRepositoryListener { + public ProjectResourceCreatedListener(CountDownLatch countDownLatch) { + super(countDownLatch); + } + } + + @RepositoryEventTypes(PROJECT_RESOURCE_MODIFIED) + private static class ProjectResourceModifiedListener extends AbstractRepositoryListener { + public ProjectResourceModifiedListener(CountDownLatch countDownLatch) { + super(countDownLatch); + } + } + + @RepositoryEventTypes(PROJECT_RESOURCE_DELETED) + private static class ProjectResourceDeletedListener extends AbstractRepositoryListener { + public ProjectResourceDeletedListener(CountDownLatch countDownLatch) { + super(countDownLatch); + } + } +} diff --git a/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/utils/ResourceHelperTest.java b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/utils/ResourceHelperTest.java new file mode 100644 index 0000000..ffcb0d4 --- /dev/null +++ b/org.eclipse.flux.watcher/src/test/java/org/eclipse/flux/watcher/fs/utils/ResourceHelperTest.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2014 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.flux.watcher.fs.utils; + + +import org.eclipse.flux.watcher.core.utils.ResourceHelper; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Kevin Pollet + */ +public final class ResourceHelperTest { + @Test(expected = NullPointerException.class) + public void testSHA1WithNullBytes() { + ResourceHelper.sha1(null); + } + + @Test + public void testSHA1() { + final byte[] hello = "hello".getBytes(); + final byte[] helloWorld = "helloWorld".getBytes(); + + final String helloHash = ResourceHelper.sha1(hello); + final String helloWorldHash = ResourceHelper.sha1(helloWorld); + + Assert.assertNotNull(helloHash); + Assert.assertNotNull(helloWorldHash); + Assert.assertNotEquals(helloHash, helloWorldHash); + } +} diff --git a/pom.xml b/pom.xml index 6ea30d4..27f9f11 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 @@ -43,7 +42,8 @@ org.eclipse.flux.parent.java org.eclipse.flux.client.java - - + + org.eclipse.flux.watcher + - + \ No newline at end of file