You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2018/04/18 16:16:06 UTC

[sling-ide-tooling] 01/08: SLING-7587 - Create a CLI-only tool to sync content

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-ide-tooling.git

commit 76f1931945488669637663fd4f6e16d515b3b614
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 17 15:09:03 2018 +0300

    SLING-7587 - Create a CLI-only tool to sync content
    
    Add a new sync-fs module.
---
 .../sling/ide/sync/content/WorkspaceProject.java   |   4 +-
 shared/modules/pom.xml                             |   1 +
 shared/modules/sync-fs/pom.xml                     |  84 +++++++++++++
 .../content/sync/fs/impl/FSWorkspaceDirectory.java |  81 +++++++++++++
 .../ide/content/sync/fs/impl/FSWorkspaceFile.java  |  71 +++++++++++
 .../content/sync/fs/impl/FSWorkspaceProject.java   |  86 ++++++++++++++
 .../content/sync/fs/impl/FSWorkspaceResource.java  |  98 +++++++++++++++
 .../sync/fs/impl/FSWorkspaceDirectoryTest.java     | 132 +++++++++++++++++++++
 .../content/sync/fs/impl/FSWorkspaceFileTest.java  |  78 ++++++++++++
 .../sync/fs/impl/FSWorkspaceProjectTest.java       | 109 +++++++++++++++++
 .../content/sync/fs/impl/MockFilterLocator.java    |  53 +++++++++
 11 files changed, 796 insertions(+), 1 deletion(-)

diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceProject.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceProject.java
index 4be9cae..b233032 100644
--- a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceProject.java
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceProject.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sling.ide.sync.content;
 
+import java.io.IOException;
+
 import org.apache.sling.ide.filter.Filter;
 
 /**
@@ -28,7 +30,7 @@ public interface WorkspaceProject extends WorkspaceResource {
     
     WorkspaceDirectory getSyncDirectory();
 
-    Filter getFilter();
+    Filter getFilter() throws IOException;
 
     WorkspaceDirectory getDirectory(WorkspacePath path);
 
diff --git a/shared/modules/pom.xml b/shared/modules/pom.xml
index 3d11973..1be41e5 100644
--- a/shared/modules/pom.xml
+++ b/shared/modules/pom.xml
@@ -38,5 +38,6 @@
         <module>impl-resource</module>
         <module>impl-vlt</module>
         <module>artifacts</module>
+        <module>sync-fs</module>
     </modules>
 </project>
diff --git a/shared/modules/sync-fs/pom.xml b/shared/modules/sync-fs/pom.xml
new file mode 100644
index 0000000..43b7fe9
--- /dev/null
+++ b/shared/modules/sync-fs/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+    license agreements. See the NOTICE file distributed with this work for additional 
+    information regarding copyright ownership. The ASF licenses this file to 
+    you under the Apache License, Version 2.0 (the "License"); you may not use 
+    this file except in compliance with the License. You may obtain a copy of 
+    the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
+    by applicable law or agreed to in writing, software distributed under the 
+    License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
+    OF ANY KIND, either express or implied. See the License for the specific 
+    language governing permissions and limitations under the License. -->
+<project
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+    xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling.ide</groupId>
+        <artifactId>sling-ide-shared-parent</artifactId>
+        <version>1.2.3-SNAPSHOT</version>
+        <relativePath>../parent/</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.ide.sync-fs</artifactId>
+    <name>Apache Sling IDE Tools - Filesystem Sync implementation</name>
+
+    <scm>
+        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-ide-tooling.git</connection>
+        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-ide-tooling.git</developerConnection>
+        <url>https://gitbox.apache.org/repos/asf?p=sling-ide-tooling.git</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling.ide</groupId>
+            <artifactId>org.apache.sling.ide.api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
+         <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.6</version>
+            <scope>test</scope>
+         </dependency>
+        <!-- Note that OSGi annotations are OK since they are not retained at compile time -->        
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.versioning</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.metatype.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+    <properties>
+        <sling.java.version>8</sling.java.version>
+    </properties>
+</project>
diff --git a/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceDirectory.java b/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceDirectory.java
new file mode 100644
index 0000000..91af787
--- /dev/null
+++ b/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceDirectory.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.content.sync.fs.impl;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspaceFile;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.apache.sling.ide.sync.content.WorkspaceProject;
+import org.apache.sling.ide.sync.content.WorkspaceResource;
+
+public class FSWorkspaceDirectory extends FSWorkspaceResource implements WorkspaceDirectory {
+
+    private final FSWorkspaceProject project;
+    private final WorkspacePath path;
+
+    public FSWorkspaceDirectory(File dir, FSWorkspaceProject project) {
+        super(dir, true);
+        this.project = project;
+        this.path = getPath(project, dir);
+    }
+
+    @Override
+    public WorkspacePath getLocalPath() {
+        return path;
+    }
+
+    @Override
+    public WorkspaceProject getProject() {
+        return project;
+    }
+
+    @Override
+    public Object getTransientProperty(String propertyName) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public WorkspaceFile getFile(WorkspacePath relativePath) {
+        return new FSWorkspaceFile(new File(backingFile(), relativePath.asPortableString().replace('/', File.separatorChar)), project);
+    }
+
+    @Override
+    public WorkspaceDirectory getDirectory(WorkspacePath relativePath) {
+        return new FSWorkspaceDirectory(new File(backingFile(), relativePath.asPortableString().replace('/', File.separatorChar)), project);
+    }
+
+    @Override
+    public List<WorkspaceResource> getChildren() {
+        return Arrays.stream(backingFile().listFiles())
+                .map( f -> {
+                    if ( f.isFile() )
+                        return new FSWorkspaceFile(f, project);
+                    else if ( f.isDirectory() )
+                        return new FSWorkspaceDirectory(f, project);
+                    else return null;
+                })
+                .filter( r -> r != null)
+                .collect(Collectors.toList());
+    }
+
+}
diff --git a/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceFile.java b/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceFile.java
new file mode 100644
index 0000000..bbc7f49
--- /dev/null
+++ b/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceFile.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.content.sync.fs.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspaceFile;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.apache.sling.ide.sync.content.WorkspaceProject;
+
+public class FSWorkspaceFile extends FSWorkspaceResource implements WorkspaceFile {
+    
+    private final FSWorkspaceProject project;
+    private final WorkspacePath path;
+
+    public FSWorkspaceFile(File file, FSWorkspaceProject project) {
+        super(file, false);
+        this.project = project;
+        this.path = getPath(project, file);
+    }
+
+    @Override
+    public boolean isIgnored() {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public WorkspacePath getLocalPath() {
+        return path;
+    }
+
+    @Override
+    public WorkspaceProject getProject() {
+        return project;
+    }
+
+    @Override
+    public Object getTransientProperty(String propertyName) {
+        return null;
+    }
+
+    @Override
+    public InputStream getContents() throws IOException {
+        return new FileInputStream(backingFile());
+    }
+
+    @Override
+    public WorkspaceDirectory getParent() {
+        return new FSWorkspaceDirectory(backingFile().getParentFile(), project);
+    }
+
+}
diff --git a/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceProject.java b/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceProject.java
new file mode 100644
index 0000000..424b2ea
--- /dev/null
+++ b/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceProject.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.content.sync.fs.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.sling.ide.filter.Filter;
+import org.apache.sling.ide.filter.FilterLocator;
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.apache.sling.ide.sync.content.WorkspaceProject;
+
+public class FSWorkspaceProject extends FSWorkspaceResource implements WorkspaceProject {
+
+    private final File syncRoot;
+    private final FilterLocator filterLocator;
+    
+    public FSWorkspaceProject(File projectDir, File syncRoot, FilterLocator filterLocator) {
+        super(projectDir, true);
+        this.syncRoot = syncRoot;
+        this.filterLocator = filterLocator;
+    }
+
+    @Override
+    public boolean isIgnored() {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public WorkspacePath getLocalPath() {
+        // by definition the project path is '/' + the folder name
+        return new WorkspacePath('/' + backingFile().getName());
+    }
+
+    @Override
+    public WorkspaceProject getProject() {
+        return this;
+    }
+
+
+    @Override
+    public Object getTransientProperty(String propertyName) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public WorkspaceDirectory getSyncDirectory() {
+        return new FSWorkspaceDirectory(new File(syncRoot, "jcr_root"), this);
+    }
+
+    @Override
+    public Filter getFilter() throws IOException {
+        File filterFile = filterLocator.findFilterLocation(syncRoot);
+        if ( filterFile == null || !filterFile.exists() )
+            return null;
+        
+        try ( InputStream filter = new FileInputStream(filterFile) ) {
+            return filterLocator.loadFilter(filter);
+        }
+    }
+
+    @Override
+    public WorkspaceDirectory getDirectory(WorkspacePath path) {
+        return new FSWorkspaceDirectory(new File(backingFile(), path.asPortableString().replace('/', File.separatorChar)), this);
+    }
+
+}
diff --git a/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceResource.java b/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceResource.java
new file mode 100644
index 0000000..cb14004
--- /dev/null
+++ b/shared/modules/sync-fs/src/main/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceResource.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.content.sync.fs.impl;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Objects;
+
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.apache.sling.ide.sync.content.WorkspacePaths;
+import org.apache.sling.ide.sync.content.WorkspaceProject;
+import org.apache.sling.ide.sync.content.WorkspaceResource;
+
+public abstract class FSWorkspaceResource implements WorkspaceResource {
+    
+    static WorkspacePath getPath(WorkspaceProject project, File file){
+        return WorkspacePaths.fromOsPath(project.getOSPath().getParent().relativize(file.toPath())).absolute();
+    }
+
+    private final File backingFile;
+
+    protected FSWorkspaceResource(File backingFile, boolean shouldBeDirectory) {
+        if ( backingFile == null )
+            throw new IllegalArgumentException("dir is null");
+        if ( backingFile.exists() ) {
+            if ( shouldBeDirectory && !backingFile.isDirectory())
+                    throw new IllegalArgumentException("File '" + backingFile + "' is not a directory");
+            if ( !shouldBeDirectory && !backingFile.isFile())
+                throw new IllegalArgumentException("File '" + backingFile + "' is not a regular file");
+        }
+        
+        this.backingFile = backingFile;
+        
+    }
+    
+    protected File backingFile() {
+        return backingFile;
+    }
+
+    @Override
+    public boolean exists() {
+        return backingFile.exists();
+    }
+
+    @Override
+    public boolean isIgnored() {
+        return false;
+    }
+
+    @Override
+    public Path getOSPath() {
+        return backingFile.toPath();
+    }
+
+    @Override
+    public long getLastModified() {
+        return backingFile.lastModified();
+    }
+
+    @Override
+    public Object getTransientProperty(String propertyName) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(backingFile);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        FSWorkspaceResource other = (FSWorkspaceResource) obj;
+        
+        return Objects.equals(backingFile, other.backingFile);
+    }
+}
diff --git a/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceDirectoryTest.java b/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceDirectoryTest.java
new file mode 100644
index 0000000..f577400
--- /dev/null
+++ b/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceDirectoryTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.content.sync.fs.impl;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspaceFile;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.apache.sling.ide.sync.content.WorkspaceResource;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class FSWorkspaceDirectoryTest {
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+    private File projectDir;
+    private File syncRoot;
+    private FSWorkspaceProject project;
+    private File fileChild;
+    private File dirChild;
+    private WorkspaceDirectory dir; 
+    
+    @Before
+    public void prepareProject() throws IOException {
+        
+        projectDir = folder.newFolder("test-project");
+        syncRoot = new File(projectDir, "jcr_root");
+        syncRoot.mkdir();
+        
+        project = new FSWorkspaceProject(projectDir, syncRoot, new MockFilterLocator());
+        fileChild = new File(syncRoot, "file.txt");
+        fileChild.createNewFile();
+        
+        dirChild = new File(syncRoot, "dir");
+        dirChild.mkdir();
+        
+        dir = project.getDirectory(new WorkspacePath("jcr_root"));
+    }
+    
+    @Test
+    public void basicOperations() {
+        
+        assertThat("dir.exists", dir.exists(), equalTo(true));
+        assertThat("dir.osPath", dir.getOSPath(), equalTo(syncRoot.toPath()));
+        assertThat("dir.localPath", dir.getLocalPath(), equalTo(new WorkspacePath("/test-project/jcr_root")));
+        assertThat("dir.lastModified", dir.getLastModified(), equalTo(syncRoot.lastModified()));
+        assertThat("dir.name", dir.getName(), equalTo("jcr_root"));
+        assertThat("dir.project", dir.getProject(), sameInstance(project));
+    }
+    
+    @Test
+    public void getChildren() {
+        
+        List<WorkspaceResource> children = dir.getChildren();
+        assertThat("children.size", children, hasSize(2));
+        List<WorkspaceFile> files = children.stream()
+                .filter( r -> (r instanceof WorkspaceFile ) )
+                .map( r -> ( (WorkspaceFile) r))
+                .collect(Collectors.toList());
+        List<WorkspaceDirectory> dirs = children.stream()
+                .filter( r -> (r instanceof WorkspaceDirectory ) )
+                .map( r -> ( (WorkspaceDirectory) r))
+                .collect(Collectors.toList());
+        
+        assertThat("files.size", files, hasSize(1));
+        assertThat("dirs.size", dirs, hasSize(1));
+        
+        WorkspaceFile file = files.get(0);
+        assertThat("file.exists", file.exists(), equalTo(true));
+        assertThat("file.localPath", file.getLocalPath(), equalTo(new WorkspacePath("/test-project/jcr_root/file.txt")));
+        assertThat("file.osPath", file.getOSPath(), equalTo(fileChild.toPath()));
+        
+        WorkspaceDirectory dir = dirs.get(0);
+        assertThat("dir.exists", dir.exists(), equalTo(true));
+        assertThat("dir.localPath", dir.getLocalPath(), equalTo(new WorkspacePath("/test-project/jcr_root/dir")));
+        assertThat("dir.osPath", dir.getOSPath(), equalTo(dirChild.toPath()));
+    }
+    
+    @Test
+    public void getFile_exists() {
+        WorkspaceFile file = dir.getFile(new WorkspacePath("file.txt"));
+        assertThat("file.exists", file.exists(), equalTo(true));
+        assertThat("file.localPath", file.getLocalPath(), equalTo(new WorkspacePath("/test-project/jcr_root/file.txt")));
+    }
+
+    @Test
+    public void getFile_doesNotExist() {
+        WorkspaceFile file = dir.getFile(new WorkspacePath("does-not-exist.txt"));
+        assertThat("file.exists", file.exists(), equalTo(false));
+        assertThat("file.localPath", file.getLocalPath(), equalTo(new WorkspacePath("/test-project/jcr_root/does-not-exist.txt")));
+    }
+    
+    @Test
+    public void getDir_exists() {
+        WorkspaceDirectory directory = dir.getDirectory(new WorkspacePath("dir"));
+        assertThat("directory.exists", directory.exists(), equalTo(true));
+        assertThat("directory.localPath", directory.getLocalPath(), equalTo(new WorkspacePath("/test-project/jcr_root/dir")));
+    }
+    
+    @Test
+    public void getDir_doesNotExist() {
+        WorkspaceDirectory directory = dir.getDirectory(new WorkspacePath("does-not-exist"));
+        assertThat("directory.exists", directory.exists(), equalTo(false));
+        assertThat("file.localPath", directory.getLocalPath(), equalTo(new WorkspacePath("/test-project/jcr_root/does-not-exist")));
+    }
+}
diff --git a/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceFileTest.java b/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceFileTest.java
new file mode 100644
index 0000000..3db1c46
--- /dev/null
+++ b/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceFileTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.content.sync.fs.impl;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspaceFile;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class FSWorkspaceFileTest {
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+    private File projectDir;
+    private File syncRoot;
+    private FSWorkspaceProject project;
+    private File fileChild;
+    private File dirChild;
+    private WorkspaceDirectory dir;
+
+    @Before
+    public void prepareProject() throws IOException {
+
+        projectDir = folder.newFolder("test-project");
+        syncRoot = new File(projectDir, "jcr_root");
+        syncRoot.mkdir();
+
+        project = new FSWorkspaceProject(projectDir, syncRoot, new MockFilterLocator());
+        fileChild = new File(syncRoot, "file.txt");
+        fileChild.createNewFile();
+        try (FileWriter fw = new FileWriter(fileChild)) {
+            fw.write("hello, world");
+        }
+
+        dirChild = new File(syncRoot, "dir");
+        dirChild.mkdir();
+
+        dir = project.getDirectory(new WorkspacePath("jcr_root"));
+    }
+
+    @Test
+    public void basicOperations() throws IOException {
+     
+        WorkspaceFile file = project.getDirectory(new WorkspacePath("jcr_root")).getFile(new WorkspacePath("file.txt"));
+        
+        assertThat("file.lastModified", file.getLastModified(), equalTo(fileChild.lastModified()));
+        assertThat("file.localPath", file.getLocalPath(), equalTo(new WorkspacePath("/test-project/jcr_root/file.txt")));
+        assertThat("file.name", file.getName(), equalTo("file.txt"));
+        assertThat("file.contents", IOUtils.toString(file.getContents(), UTF_8), equalTo("hello, world"));
+        assertThat("file.parent", file.getParent(), equalTo(dir));
+    }
+}
diff --git a/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceProjectTest.java b/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceProjectTest.java
new file mode 100644
index 0000000..127a351
--- /dev/null
+++ b/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/FSWorkspaceProjectTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.content.sync.fs.impl;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class FSWorkspaceProjectTest {
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+    private File projectDir;
+    private File syncRoot;
+    private File metaInfDir;
+    private File vaultDir;
+    private File jcrRoot;
+    private FSWorkspaceProject project; 
+    
+    @Before
+    public void prepareProject() throws IOException {
+        projectDir = folder.newFolder("test-project");
+        syncRoot = new File(projectDir, "content");
+        syncRoot.mkdir();
+        
+        metaInfDir = new File(syncRoot, "META-INF");
+        metaInfDir.mkdir();
+        vaultDir = new File(metaInfDir, "vault");
+        vaultDir.mkdir();
+        File filterXml = new File(vaultDir, "filter.xml");
+        filterXml.createNewFile();
+        
+        jcrRoot = new File(syncRoot, "jcr_root");
+        jcrRoot.mkdir();
+        
+        project = new FSWorkspaceProject(projectDir, syncRoot, new MockFilterLocator());
+    }
+    
+    @Test
+    public void basicOperations() {
+
+        assertThat("project.exists", project.exists(), equalTo(true));
+        assertThat("project.name", project.getName(), equalTo("test-project"));
+        assertThat("project.localPath", project.getLocalPath(), equalTo(new WorkspacePath("/test-project")));
+        assertThat("project.osPath", project.getOSPath(), equalTo(projectDir.toPath()));
+        assertThat("project.project", project.getProject(), sameInstance(project));
+        assertThat("project.lastModified", project.getLastModified(), equalTo(projectDir.lastModified()));
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void invalidDir_null() {
+        new FSWorkspaceProject(null, null, null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void invalidDir_file() throws IOException {
+        new FSWorkspaceProject(folder.newFile(), null, null);
+    }
+
+    @Test
+    public void accessChildResources() throws IOException {
+        
+        WorkspaceDirectory directory = project.getDirectory(new WorkspacePath("content"));
+        assertThat("directory", directory, not(nullValue()));
+        assertThat("directory.exists", directory.exists(), equalTo(true));
+        assertThat("directory.osPath", directory.getOSPath(), equalTo(syncRoot.toPath()));
+        assertThat("directory.localPath", directory.getLocalPath(), equalTo(new WorkspacePath("/test-project/content")));
+    }
+    
+    @Test
+    public void syncDirLocation() throws IOException {
+        
+        WorkspaceDirectory syncDir = project.getSyncDirectory();
+        assertThat("syncDir.exists", syncDir.exists(), equalTo(true));
+        assertThat("syncDir.localPath", syncDir.getLocalPath(), equalTo(new WorkspacePath("/test-project/content/jcr_root")));
+    }
+    
+    @Test
+    public void filter() throws IOException {
+
+        assertThat("filter", project.getFilter(), equalTo(MockFilterLocator.MOCK_FILTER));
+    }
+}
diff --git a/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/MockFilterLocator.java b/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/MockFilterLocator.java
new file mode 100644
index 0000000..85a935c
--- /dev/null
+++ b/shared/modules/sync-fs/src/test/java/org/apache/sling/ide/content/sync/fs/impl/MockFilterLocator.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.content.sync.fs.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Paths;
+
+import org.apache.sling.ide.filter.Filter;
+import org.apache.sling.ide.filter.FilterLocator;
+import org.apache.sling.ide.filter.FilterResult;
+
+public class MockFilterLocator implements FilterLocator {
+
+    public static final Filter MOCK_FILTER = new Filter() {
+        
+        @Override
+        public FilterResult filter(String repositoryPath) {
+            return FilterResult.ALLOW;
+        }
+    };
+    
+    @Override
+    public File findFilterLocation(File syncDirectory) {
+        
+        return syncDirectory.toPath().
+                resolve(Paths.get("META-INF", "vault", "filter.xml")).toFile();
+        
+    }
+
+    @Override
+    public Filter loadFilter(InputStream filterFileContents) throws IOException {
+        if ( filterFileContents == null )
+            return null;
+        return MOCK_FILTER;
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
rombert@apache.org.