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/16 12:07:14 UTC
[sling-ide-tooling] 01/01: SLING-5618 - Make the
ResourceChangeCommandFactory independent from Eclipse
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to branch feature/SLING-5618
in repository https://gitbox.apache.org/repos/asf/sling-ide-tooling.git
commit bfb9237e07cffb20e05c0e6c9ae32d4e4a2a4ec9
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Apr 13 18:04:38 2018 +0300
SLING-5618 - Make the ResourceChangeCommandFactory independent from Eclipse
- Introduce abstractions in the API module which support
working with the ResourceChangeCommandFactory outside of Eclipse.
- Implement needed abstractions in the eclipse-core module and stop using
the ResourceChangeCommandFactory internally
- The ResourceChangeCommandFactory is now left only as a deprecated stub
until the tests are ported to use the DefaultCommandFactoryImpl.
---
eclipse/eclipse-core/META-INF/MANIFEST.MF | 1 +
.../sling/ide/eclipse/core/EclipseResources.java | 52 ++
.../sling/ide/eclipse/core/ResourceUtil.java | 3 +-
.../sling/ide/eclipse/core/internal/Activator.java | 10 +
.../internal/ResourceChangeCommandFactory.java | 522 +--------------------
.../core/internal/SlingLaunchpadBehaviour.java | 2 +-
.../sync/content/EclipseWorkspaceDirectory.java | 75 +++
.../sync/content/EclipseWorkspaceFile.java | 62 +++
.../sync/content/EclipseWorkspaceProject.java | 63 +++
.../sync/content/EclipseWorkspaceResource.java | 116 +++++
.../impl/ResourceChangeCommandFactoryTest.java | 2 +-
eclipse/eclipse-ui/META-INF/MANIFEST.MF | 1 +
.../sling/ide/eclipse/ui/internal/Activator.java | 12 +-
.../ide/eclipse/ui/internal/ExportWizard.java | 27 +-
.../ui/internal/ImportRepositoryContentAction.java | 8 +-
.../ide/serialization/SerializationManager.java | 13 +
.../sling/ide/sync/content/SyncCommandFactory.java | 71 +++
.../sling/ide/sync/content/WorkspaceDirectory.java | 53 +++
.../content/WorkspaceFile.java} | 41 +-
.../sling/ide/sync/content/WorkspacePath.java | 126 +++++
.../sling/ide/sync/content/WorkspacePaths.java | 37 ++
.../sling/ide/sync/content/WorkspaceProject.java | 35 ++
.../sling/ide/sync/content/WorkspaceResource.java | 93 ++++
.../content/impl/DefaultSyncCommandFactory.java | 447 ++++++++++++++++++
.../sling/ide/sync/content/package-info.java | 19 +
.../sling/ide/transport}/ResourceAndInfo.java | 5 +-
.../java/org/apache/sling/ide/util/PathUtil.java | 3 +
.../sling/ide/sync/content/WorkspacePathTest.java | 175 +++++++
.../sling/ide/sync/content/WorkspacePathsTest.java | 33 ++
29 files changed, 1548 insertions(+), 559 deletions(-)
diff --git a/eclipse/eclipse-core/META-INF/MANIFEST.MF b/eclipse/eclipse-core/META-INF/MANIFEST.MF
index bd419a5..0d667cd 100644
--- a/eclipse/eclipse-core/META-INF/MANIFEST.MF
+++ b/eclipse/eclipse-core/META-INF/MANIFEST.MF
@@ -24,6 +24,7 @@ Import-Package: org.apache.commons.httpclient;version="3.1.0",
org.apache.sling.ide.log,
org.apache.sling.ide.osgi,
org.apache.sling.ide.serialization,
+ org.apache.sling.ide.sync.content,
org.apache.sling.ide.transport,
org.apache.sling.ide.util,
org.eclipse.core.commands,
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/EclipseResources.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/EclipseResources.java
new file mode 100644
index 0000000..e839076
--- /dev/null
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/EclipseResources.java
@@ -0,0 +1,52 @@
+/*
+ * 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.eclipse.core;
+
+import java.util.Set;
+
+import org.apache.sling.ide.eclipse.core.internal.Activator;
+import org.apache.sling.ide.eclipse.core.internal.sync.content.EclipseWorkspaceDirectory;
+import org.apache.sling.ide.eclipse.core.internal.sync.content.EclipseWorkspaceFile;
+import org.apache.sling.ide.eclipse.core.internal.sync.content.EclipseWorkspaceProject;
+import org.apache.sling.ide.sync.content.WorkspaceResource;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+
+public abstract class EclipseResources {
+
+ public static WorkspaceResource create(IResource resource) {
+
+ Set<String> ignoredFileNames = Activator.getDefault().getPreferences().getIgnoredFileNamesForSync();
+
+ switch ( resource.getType() ) {
+ case IResource.FILE:
+ return new EclipseWorkspaceFile((IFile) resource, ignoredFileNames);
+ case IResource.FOLDER:
+ return new EclipseWorkspaceDirectory((IFolder) resource, ignoredFileNames);
+ case IResource.PROJECT:
+ return new EclipseWorkspaceProject((IProject) resource, ignoredFileNames);
+ default:
+ throw new IllegalArgumentException("Unable to create a local resource for Eclipse IResource.getType() = " + resource.getType() );
+ }
+ }
+
+ private EclipseResources() {
+
+ }
+}
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/ResourceUtil.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/ResourceUtil.java
index 641a399..53e35c9 100644
--- a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/ResourceUtil.java
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/ResourceUtil.java
@@ -17,6 +17,7 @@
package org.apache.sling.ide.eclipse.core;
import org.apache.sling.ide.eclipse.core.internal.Activator;
+import org.apache.sling.ide.sync.content.SyncCommandFactory;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.QualifiedName;
@@ -36,7 +37,7 @@ public abstract class ResourceUtil {
* </p>
*/
public static final QualifiedName QN_IMPORT_MODIFICATION_TIMESTAMP = new QualifiedName(Activator.PLUGIN_ID,
- "importModificationTimestamp");
+ SyncCommandFactory.PN_IMPORT_MODIFICATION_TIMESTAMP);
private ResourceUtil() {
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/Activator.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/Activator.java
index c6d58c3..48e8ca8 100644
--- a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/Activator.java
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/Activator.java
@@ -28,6 +28,7 @@ import org.apache.sling.ide.filter.FilterLocator;
import org.apache.sling.ide.log.Logger;
import org.apache.sling.ide.osgi.OsgiClientFactory;
import org.apache.sling.ide.serialization.SerializationManager;
+import org.apache.sling.ide.sync.content.SyncCommandFactory;
import org.apache.sling.ide.transport.BatcherFactory;
import org.apache.sling.ide.transport.CommandExecutionProperties;
import org.apache.sling.ide.transport.RepositoryFactory;
@@ -63,6 +64,7 @@ public class Activator extends Plugin {
private ServiceTracker<Logger, Logger> tracer;
private ServiceTracker<BatcherFactory, BatcherFactory> batcherFactoryLocator;
private ServiceTracker<SourceReferenceResolver, Object> sourceReferenceLocator;
+ private ServiceTracker<SyncCommandFactory, SyncCommandFactory> commandFactory;
private ServiceRegistration<Logger> tracerRegistration;
@@ -107,6 +109,9 @@ public class Activator extends Plugin {
sourceReferenceLocator = new ServiceTracker<>(context, SourceReferenceResolver.class, null);
sourceReferenceLocator.open();
+
+ commandFactory = new ServiceTracker<>(context, SyncCommandFactory.class, null);
+ commandFactory.open();
}
/*
@@ -128,6 +133,7 @@ public class Activator extends Plugin {
tracer.close();
batcherFactoryLocator.close();
sourceReferenceLocator.close();
+ commandFactory.close();
plugin = null;
super.stop(context);
@@ -172,6 +178,10 @@ public class Activator extends Plugin {
return (BatcherFactory) ServiceUtil.getNotNull(batcherFactoryLocator);
}
+ public SyncCommandFactory getCommandFactory() {
+ return ServiceUtil.getNotNull(commandFactory);
+ }
+
/**
* @deprecated This should not be used directly to communicate with the client . There is no direct replacement
*/
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/ResourceChangeCommandFactory.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/ResourceChangeCommandFactory.java
index 2b3aaa8..4e6a9f8 100644
--- a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/ResourceChangeCommandFactory.java
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/ResourceChangeCommandFactory.java
@@ -16,555 +16,49 @@
*/
package org.apache.sling.ide.eclipse.core.internal;
-import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.sling.ide.eclipse.core.ProjectUtil;
-import org.apache.sling.ide.eclipse.core.ResourceUtil;
-import org.apache.sling.ide.filter.Filter;
-import org.apache.sling.ide.filter.FilterResult;
-import org.apache.sling.ide.log.Logger;
-import org.apache.sling.ide.serialization.SerializationDataBuilder;
-import org.apache.sling.ide.serialization.SerializationException;
-import org.apache.sling.ide.serialization.SerializationKind;
-import org.apache.sling.ide.serialization.SerializationKindManager;
-import org.apache.sling.ide.serialization.SerializationManager;
+import org.apache.sling.ide.eclipse.core.EclipseResources;
import org.apache.sling.ide.transport.Command;
-import org.apache.sling.ide.transport.CommandContext;
-import org.apache.sling.ide.transport.FileInfo;
import org.apache.sling.ide.transport.Repository;
-import org.apache.sling.ide.transport.RepositoryException;
-import org.apache.sling.ide.transport.ResourceProxy;
-import org.apache.sling.ide.util.PathUtil;
-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.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
-import org.eclipse.ui.statushandlers.StatusManager;
/**
* The <tt>ResourceChangeCommandFactory</tt> creates new {@link #Command commands} correspoding to resource addition,
* change, or removal
*
+ * @deprecated - Use the {@link DefaultCommandFactory} instead. This class is present until the tests are migrated off it
*/
+@Deprecated
public class ResourceChangeCommandFactory {
- private final Set<String> ignoredFileNames;
-
- private final SerializationManager serializationManager;
-
- public ResourceChangeCommandFactory(SerializationManager serializationManager, Set<String> ignoredFileNames) {
- this.serializationManager = serializationManager;
- this.ignoredFileNames = ignoredFileNames;
- }
-
public Command<?> newCommandForAddedOrUpdated(Repository repository, IResource addedOrUpdated) throws CoreException {
+
try {
- return addFileCommand(repository, addedOrUpdated);
+ return Activator.getDefault().getCommandFactory().newCommandForAddedOrUpdatedResource(repository, EclipseResources.create(addedOrUpdated));
} catch (IOException e) {
throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID, "Failed updating " + addedOrUpdated,
e));
}
}
- private Command<?> addFileCommand(Repository repository, IResource resource) throws CoreException, IOException {
-
- ResourceAndInfo rai = buildResourceAndInfo(resource, repository);
-
- if (rai == null) {
- return null;
- }
-
- CommandContext context = new CommandContext(ProjectUtil.loadFilter(resource.getProject()));
-
- if (rai.isOnlyWhenMissing()) {
- return repository.newAddOrUpdateNodeCommand(context, rai.getInfo(), rai.getResource(),
- Repository.CommandExecutionFlag.CREATE_ONLY_WHEN_MISSING);
- }
-
- return repository.newAddOrUpdateNodeCommand(context, rai.getInfo(), rai.getResource());
- }
-
- /**
- * Convenience method which builds a <tt>ResourceAndInfo</tt> info for a specific <tt>IResource</tt>
- *
- * @param resource the resource to process
- * @param repository the repository, used to extract serialization information for different resource types
- * @return the build object, or null if one could not be built
- * @throws CoreException
- * @throws SerializationException
- * @throws IOException
- */
- public ResourceAndInfo buildResourceAndInfo(IResource resource, Repository repository) throws CoreException,
- IOException {
- if (ignoredFileNames.contains(resource.getName())) {
- return null;
- }
-
- Long modificationTimestamp = (Long) resource.getSessionProperty(ResourceUtil.QN_IMPORT_MODIFICATION_TIMESTAMP);
-
- if (modificationTimestamp != null && modificationTimestamp >= resource.getModificationStamp()) {
- Activator.getDefault().getPluginLogger()
- .trace("Change for resource {0} ignored as the import timestamp {1} >= modification timestamp {2}",
- resource, modificationTimestamp, resource.getModificationStamp());
- return null;
- }
-
- if (resource.isTeamPrivateMember(IResource.CHECK_ANCESTORS)) {
- Activator.getDefault().getPluginLogger().trace("Skipping team-private resource {0}", resource);
- return null;
- }
-
- FileInfo info = createFileInfo(resource);
- Activator.getDefault().getPluginLogger().trace("For {0} built fileInfo {1}", resource, info);
-
- File syncDirectoryAsFile = ProjectUtil.getSyncDirectoryFullPath(resource.getProject()).toFile();
- IFolder syncDirectory = ProjectUtil.getSyncDirectory(resource.getProject());
-
- Filter filter = ProjectUtil.loadFilter(resource.getProject());
-
- ResourceProxy resourceProxy = null;
-
- if (serializationManager.isSerializationFile(resource.getLocation().toOSString())) {
- IFile file = (IFile) resource;
- try (InputStream contents = file.getContents()) {
- String resourceLocation = file.getFullPath().makeRelativeTo(syncDirectory.getFullPath())
- .toPortableString();
- resourceProxy = serializationManager.readSerializationData(resourceLocation, contents);
- normaliseResourceChildren(file, resourceProxy, syncDirectory, repository);
-
-
- // TODO - not sure if this 100% correct, but we definitely should not refer to the FileInfo as the
- // .serialization file, since for nt:file/nt:resource nodes this will overwrite the file contents
- String primaryType = (String) resourceProxy.getProperties().get(Repository.JCR_PRIMARY_TYPE);
- if (Repository.NT_FILE.equals(primaryType)) {
- // TODO move logic to serializationManager
- File locationFile = new File(info.getLocation());
- String locationFileParent = locationFile.getParent();
- int endIndex = locationFileParent.length() - ".dir".length();
- File actualFile = new File(locationFileParent.substring(0, endIndex));
- String newLocation = actualFile.getAbsolutePath();
- String newName = actualFile.getName();
- String newRelativeLocation = actualFile.getAbsolutePath().substring(
- syncDirectoryAsFile.getAbsolutePath().length());
- info = new FileInfo(newLocation, newRelativeLocation, newName);
-
- Activator.getDefault().getPluginLogger()
- .trace("Adjusted original location from {0} to {1}", resourceLocation, newLocation);
-
- }
-
- } catch (IOException e) {
- Status s = new Status(Status.WARNING, Activator.PLUGIN_ID, "Failed reading file at "
- + resource.getFullPath(), e);
- StatusManager.getManager().handle(s, StatusManager.LOG | StatusManager.SHOW);
- return null;
- }
- } else {
-
- // TODO - move logic to serializationManager
- // possible .dir serialization holder
- if (resource.getType() == IResource.FOLDER && resource.getName().endsWith(".dir")) {
- IFolder folder = (IFolder) resource;
- IResource contentXml = folder.findMember(".content.xml");
- // .dir serialization holder ; nothing to process here, the .content.xml will trigger the actual work
- if (contentXml != null && contentXml.exists()
- && serializationManager.isSerializationFile(contentXml.getLocation().toOSString())) {
- return null;
- }
- }
-
- resourceProxy = buildResourceProxyForPlainFileOrFolder(resource, syncDirectory, repository);
- }
-
- FilterResult filterResult = getFilterResult(resource, resourceProxy, filter);
-
- switch (filterResult) {
-
- case ALLOW:
- return new ResourceAndInfo(resourceProxy, info);
- case PREREQUISITE:
- // never try to 'create' the root node, we assume it exists
- if (!resourceProxy.getPath().equals("/")) {
- // we don't explicitly set the primary type, which will allow the the repository to choose the best
- // suited one ( typically nt:unstructured )
- return new ResourceAndInfo(new ResourceProxy(resourceProxy.getPath()), null, true);
- }
- case DENY: // falls through
- default:
- return null;
- }
- }
-
- private FileInfo createFileInfo(IResource resource) throws CoreException {
-
- if (resource.getType() != IResource.FILE) {
- return null;
- }
-
- IProject project = resource.getProject();
-
- IFolder syncFolder = project.getFolder(ProjectUtil.getSyncDirectoryValue(project));
-
- IPath relativePath = resource.getFullPath().makeRelativeTo(syncFolder.getFullPath());
-
- FileInfo info = new FileInfo(resource.getLocation().toOSString(), relativePath.toOSString(), resource.getName());
-
- Activator.getDefault().getPluginLogger().trace("For {0} built fileInfo {1}", resource, info);
-
- return info;
- }
-
- /**
- * Gets the filter result for a resource/resource proxy combination
- *
- * <p>
- * The resourceProxy may be null, typically when a resource is already deleted.
- *
- * <p>
- * In case the filter is {@code null} no resource should be added, i.e. {@link FilterResult#DENY} is returned
- *
- * @param resource the resource to filter for, must not be <code>null</code>
- * @param resourceProxy the resource proxy to filter for, possibly <code>null</code>
- * @param filter the filter to use, possibly <tt>null</tt>
- * @return the filtering result, never <code>null</code>
- */
- private FilterResult getFilterResult(IResource resource, ResourceProxy resourceProxy, Filter filter) {
-
- if (filter == null) {
- return FilterResult.DENY;
- }
-
- File contentSyncRoot = ProjectUtil.getSyncDirectoryFile(resource.getProject());
-
- String repositoryPath = resourceProxy != null ? resourceProxy.getPath() : getRepositoryPathForDeletedResource(
- resource, contentSyncRoot);
-
- FilterResult filterResult = filter.filter(repositoryPath);
-
- Activator.getDefault().getPluginLogger().trace("Filter result for {0} for {1}", repositoryPath, filterResult);
-
- return filterResult;
- }
-
- private String getRepositoryPathForDeletedResource(IResource resource, File contentSyncRoot) {
- IFolder syncFolder = ProjectUtil.getSyncDirectory(resource.getProject());
- IPath relativePath = resource.getFullPath().makeRelativeTo(syncFolder.getFullPath());
-
- String absFilePath = new File(contentSyncRoot, relativePath.toOSString()).getAbsolutePath();
- String filePath = serializationManager.getBaseResourcePath(absFilePath);
-
- IPath osPath = Path.fromOSString(filePath);
- String repositoryPath = serializationManager.getRepositoryPath(osPath.makeRelativeTo(syncFolder.getLocation())
- .makeAbsolute().toPortableString());
-
- Activator.getDefault().getPluginLogger()
- .trace("Repository path for deleted resource {0} is {1}", resource, repositoryPath);
-
- return repositoryPath;
- }
-
- private ResourceProxy buildResourceProxyForPlainFileOrFolder(IResource changedResource, IFolder syncDirectory,
- Repository repository)
- throws CoreException, IOException {
-
- SerializationKind serializationKind;
- String fallbackNodeType;
- if (changedResource.getType() == IResource.FILE) {
- serializationKind = SerializationKind.FILE;
- fallbackNodeType = Repository.NT_FILE;
- } else { // i.e. IResource.FOLDER
- serializationKind = SerializationKind.FOLDER;
- fallbackNodeType = Repository.NT_FOLDER;
- }
-
- String resourceLocation = '/' + changedResource.getFullPath().makeRelativeTo(syncDirectory.getFullPath())
- .toPortableString();
- IPath serializationFilePath = Path.fromOSString(serializationManager.getSerializationFilePath(
- resourceLocation, serializationKind));
- IResource serializationResource = syncDirectory.findMember(serializationFilePath);
-
- if (serializationResource == null && changedResource.getType() == IResource.FOLDER) {
- ResourceProxy dataFromCoveringParent = findSerializationDataFromCoveringParent(changedResource,
- syncDirectory, resourceLocation, serializationFilePath);
-
- if (dataFromCoveringParent != null) {
- return dataFromCoveringParent;
- }
- }
- return buildResourceProxy(resourceLocation, serializationResource, syncDirectory, fallbackNodeType, repository);
- }
-
- /**
- * Tries to find serialization data from a resource in a covering parent
- *
- * <p>
- * If the serialization resource is null, it's valid to look for a serialization resource higher in the filesystem,
- * given that the found serialization resource covers this resource
- *
- * @param changedResource the resource which has changed
- * @param syncDirectory the content sync directory for the resource's project
- * @param resourceLocation the resource location relative to the sync directory
- * @param serializationFilePath the location
- * @return a <tt>ResourceProxy</tt> if there is a covering parent, or null is there is not
- * @throws CoreException
- * @throws IOException
- */
- private ResourceProxy findSerializationDataFromCoveringParent(IResource changedResource, IFolder syncDirectory,
- String resourceLocation, IPath serializationFilePath) throws CoreException, IOException {
-
- // TODO - this too should be abstracted in the service layer, rather than in the Eclipse-specific code
-
- Logger logger = Activator.getDefault().getPluginLogger();
- logger.trace("Found plain nt:folder candidate at {0}, trying to find a covering resource for it",
- changedResource.getProjectRelativePath());
- // don't use isRoot() to prevent infinite loop when the final path is '//'
- while (serializationFilePath.segmentCount() != 0) {
- serializationFilePath = serializationFilePath.removeLastSegments(1);
- IFolder folderWithPossibleSerializationFile = syncDirectory.getFolder(serializationFilePath);
- if (folderWithPossibleSerializationFile == null) {
- logger.trace("No folder found at {0}, moving up to the next level", serializationFilePath);
- continue;
- }
-
- // it's safe to use a specific SerializationKind since this scenario is only valid for METADATA_PARTIAL
- // coverage
- String possibleSerializationFilePath = serializationManager.getSerializationFilePath(
- ((IFolder) folderWithPossibleSerializationFile).getLocation().toOSString(),
- SerializationKind.METADATA_PARTIAL);
-
- logger.trace("Looking for serialization data in {0}", possibleSerializationFilePath);
-
- if (serializationManager.isSerializationFile(possibleSerializationFilePath)) {
-
- IPath parentSerializationFilePath = Path.fromOSString(possibleSerializationFilePath).makeRelativeTo(
- syncDirectory.getLocation());
- IFile possibleSerializationFile = syncDirectory.getFile(parentSerializationFilePath);
- if (!possibleSerializationFile.exists()) {
- logger.trace("Potential serialization data file {0} does not exist, moving up to the next level",
- possibleSerializationFile.getFullPath());
- continue;
- }
-
-
- ResourceProxy serializationData;
- try (InputStream contents = possibleSerializationFile.getContents()) {
- serializationData = serializationManager.readSerializationData(
- parentSerializationFilePath.toPortableString(), contents);
- }
-
- String repositoryPath = serializationManager.getRepositoryPath(resourceLocation);
- String potentialPath = serializationData.getPath();
- boolean covered = serializationData.covers(repositoryPath);
-
- logger.trace(
- "Found possible serialization data at {0}. Resource :{1} ; our resource: {2}. Covered: {3}",
- parentSerializationFilePath, potentialPath, repositoryPath, covered);
- // note what we don't need to normalize the children here since this resource's data is covered by
- // another resource
- if (covered) {
- return serializationData.getChild(repositoryPath);
- }
-
- break;
- }
- }
-
- return null;
- }
-
- private ResourceProxy buildResourceProxy(String resourceLocation, IResource serializationResource,
- IFolder syncDirectory, String fallbackPrimaryType, Repository repository) throws CoreException, IOException {
- if (serializationResource instanceof IFile) {
- IFile serializationFile = (IFile) serializationResource;
- try (InputStream contents = serializationFile.getContents() ) {
-
- String serializationFilePath = serializationResource.getFullPath()
- .makeRelativeTo(syncDirectory.getFullPath()).toPortableString();
- ResourceProxy resourceProxy = serializationManager.readSerializationData(serializationFilePath, contents);
- normaliseResourceChildren(serializationFile, resourceProxy, syncDirectory, repository);
-
- return resourceProxy;
- }
- }
-
- return new ResourceProxy(serializationManager.getRepositoryPath(resourceLocation), Collections.singletonMap(
- Repository.JCR_PRIMARY_TYPE, (Object) fallbackPrimaryType));
- }
-
- /**
- * Normalises the of the specified <tt>resourceProxy</tt> by comparing the serialization data and the filesystem
- * data
- *
- * @param serializationFile the file which contains the serialization data
- * @param resourceProxy the resource proxy
- * @param syncDirectory the sync directory
- * @param repository TODO
- * @throws CoreException
- */
- private void normaliseResourceChildren(IFile serializationFile, ResourceProxy resourceProxy, IFolder syncDirectory,
- Repository repository) throws CoreException {
-
- // TODO - this logic should be moved to the serializationManager
- try {
- SerializationKindManager skm = new SerializationKindManager();
- skm.init(repository);
-
- String primaryType = (String) resourceProxy.getProperties().get(Repository.JCR_PRIMARY_TYPE);
- List<String> mixinTypesList = getMixinTypes(resourceProxy);
- SerializationKind serializationKind = skm.getSerializationKind(primaryType, mixinTypesList);
-
- if (serializationKind == SerializationKind.METADATA_FULL) {
- return;
- }
- } catch (RepositoryException e) {
- throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed creating a "
- + SerializationDataBuilder.class.getName(), e));
- }
-
- IPath serializationDirectoryPath = serializationFile.getFullPath().removeLastSegments(1);
-
- Iterator<ResourceProxy> childIterator = resourceProxy.getChildren().iterator();
- Map<String, IResource> extraChildResources = new HashMap<>();
- for (IResource member : serializationFile.getParent().members()) {
- if (member.equals(serializationFile)) {
- continue;
- }
- extraChildResources.put(member.getName(), member);
- }
-
- while (childIterator.hasNext()) {
- ResourceProxy child = childIterator.next();
- String childName = PathUtil.getName(child.getPath());
- String osPath = serializationManager.getOsPath(childName);
-
- // covered children might have a FS representation, depending on their child nodes, so
- // accept a directory which maps to their name
- extraChildResources.remove(osPath);
-
- // covered children do not need a filesystem representation
- if (resourceProxy.covers(child.getPath())) {
- continue;
- }
-
- IPath childPath = serializationDirectoryPath.append(osPath);
-
- IResource childResource = ResourcesPlugin.getWorkspace().getRoot().findMember(childPath);
- if (childResource == null) {
-
- Activator.getDefault().getPluginLogger()
- .trace("For resource at with serialization data {0} the serialized child resource at {1} does not exist in the filesystem and will be ignored",
- serializationFile, childPath);
- childIterator.remove();
- }
- }
-
- for ( IResource extraChildResource : extraChildResources.values()) {
- IPath extraChildResourcePath = extraChildResource.getFullPath()
- .makeRelativeTo(syncDirectory.getFullPath()).makeAbsolute();
- resourceProxy.addChild(new ResourceProxy(serializationManager
- .getRepositoryPath(extraChildResourcePath.toPortableString())));
-
- Activator.getDefault().getPluginLogger()
- .trace("For resource at with serialization data {0} the found a child resource at {1} which is not listed in the serialized child resources and will be added",
- serializationFile, extraChildResource);
- }
- }
-
- private List<String> getMixinTypes(ResourceProxy resourceProxy) {
-
- Object mixinTypesProp = resourceProxy.getProperties().get(Repository.JCR_MIXIN_TYPES);
-
- if (mixinTypesProp == null) {
- return Collections.emptyList();
- }
-
- if (mixinTypesProp instanceof String) {
- return Collections.singletonList((String) mixinTypesProp);
- }
-
- return Arrays.asList((String[]) mixinTypesProp);
- }
-
public Command<?> newCommandForRemovedResources(Repository repository, IResource removed) throws CoreException {
-
try {
- return removeFileCommand(repository, removed);
+ return Activator.getDefault().getCommandFactory().newCommandForRemovedResource(repository, EclipseResources.create(removed));
} catch (IOException e) {
throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID, "Failed removing" + removed, e));
}
}
- private Command<?> removeFileCommand(Repository repository, IResource resource) throws CoreException, IOException {
-
- if (resource.isTeamPrivateMember(IResource.CHECK_ANCESTORS)) {
- Activator.getDefault().getPluginLogger().trace("Skipping team-private resource {0}", resource);
- return null;
- }
-
- if (ignoredFileNames.contains(resource.getName())) {
- return null;
- }
-
- IFolder syncDirectory = ProjectUtil.getSyncDirectory(resource.getProject());
-
- Filter filter = ProjectUtil.loadFilter(syncDirectory.getProject());
-
- FilterResult filterResult = getFilterResult(resource, null, filter);
- if (filterResult == FilterResult.DENY || filterResult == FilterResult.PREREQUISITE) {
- return null;
- }
-
- String resourceLocation = getRepositoryPathForDeletedResource(resource,
- ProjectUtil.getSyncDirectoryFile(resource.getProject()));
-
- // verify whether a resource being deleted does not signal that the content structure
- // was rearranged under a covering parent aggregate
- IPath serializationFilePath = Path.fromOSString(serializationManager.getSerializationFilePath(resourceLocation,
- SerializationKind.FOLDER));
-
- ResourceProxy coveringParentData = findSerializationDataFromCoveringParent(resource, syncDirectory,
- resourceLocation, serializationFilePath);
- if (coveringParentData != null) {
- Activator
- .getDefault()
- .getPluginLogger()
- .trace("Found covering resource data ( repository path = {0} ) for resource at {1}, skipping deletion and performing an update instead",
- coveringParentData.getPath(), resource.getFullPath());
- FileInfo info = createFileInfo(resource);
- return repository.newAddOrUpdateNodeCommand(new CommandContext(filter), info, coveringParentData);
- }
-
- return repository.newDeleteNodeCommand(serializationManager.getRepositoryPath(resourceLocation));
- }
-
public Command<Void> newReorderChildNodesCommand(Repository repository, IResource res) throws CoreException {
-
try {
- ResourceAndInfo rai = buildResourceAndInfo(res, repository);
-
- if (rai == null || rai.isOnlyWhenMissing()) {
- return null;
- }
-
- return repository.newReorderChildNodesCommand(rai.getResource());
+ return Activator.getDefault().getCommandFactory().newReorderChildNodesCommand(repository, EclipseResources.create(res));
} catch (IOException e) {
throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID, "Failed reordering child nodes for "
+ res, e));
}
}
+
}
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/SlingLaunchpadBehaviour.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/SlingLaunchpadBehaviour.java
index c15560d..47aa0fe 100644
--- a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/SlingLaunchpadBehaviour.java
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/SlingLaunchpadBehaviour.java
@@ -196,7 +196,7 @@ public class SlingLaunchpadBehaviour extends ServerBehaviourDelegateWithModulePu
Logger logger = Activator.getDefault().getPluginLogger();
if (commandFactory == null) {
- commandFactory = new ResourceChangeCommandFactory(Activator.getDefault().getSerializationManager(), Activator.getDefault().getPreferences().getIgnoredFileNamesForSync());
+ commandFactory = new ResourceChangeCommandFactory();
}
logger.trace(traceOperation(kind, deltaKind, module));
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceDirectory.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceDirectory.java
new file mode 100644
index 0000000..bac29c3
--- /dev/null
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceDirectory.java
@@ -0,0 +1,75 @@
+/*
+ * 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.eclipse.core.internal.sync.content;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.sling.ide.eclipse.core.EclipseResources;
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspaceFile;
+import org.apache.sling.ide.sync.content.WorkspaceProject;
+import org.apache.sling.ide.sync.content.WorkspaceResource;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.runtime.CoreException;
+
+public class EclipseWorkspaceDirectory extends EclipseWorkspaceResource implements WorkspaceDirectory {
+
+ public EclipseWorkspaceDirectory(IFolder folder, Set<String> ignoredFileNames) {
+ super(folder, ignoredFileNames);
+ }
+
+ @Override
+ protected IFolder getResource() {
+ return (IFolder) super.getResource();
+ }
+
+ @Override
+ public WorkspaceProject getProject() {
+ return new EclipseWorkspaceProject(getResource().getProject(), getIgnoredFileNames());
+ }
+
+ @Override
+ public WorkspacePath getLocalPath() {
+ return new WorkspacePath(getResource().getFullPath().toPortableString());
+ }
+
+ @Override
+ public WorkspaceFile getFile(WorkspacePath relativePath) {
+ return new EclipseWorkspaceFile(getResource().getFile(relativePath.asPortableString()), getIgnoredFileNames());
+ }
+
+ @Override
+ public WorkspaceDirectory getDirectory(WorkspacePath relativePath) {
+ return new EclipseWorkspaceDirectory(getResource().getFolder(relativePath.asPortableString()), getIgnoredFileNames());
+ }
+
+ @Override
+ public List<WorkspaceResource> getChildren() {
+ try {
+ return Arrays.stream(getResource().members())
+ .map(EclipseResources::create)
+ .collect(Collectors.toList());
+ } catch ( CoreException e ) {
+ // TODO - proper exception handling
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceFile.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceFile.java
new file mode 100644
index 0000000..06b735e
--- /dev/null
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceFile.java
@@ -0,0 +1,62 @@
+/*
+ * 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.eclipse.core.internal.sync.content;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspaceFile;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * A {@link WorkspaceFile} implemenation based on Eclipse APIs.
+ *
+ */
+public class EclipseWorkspaceFile extends EclipseWorkspaceResource implements WorkspaceFile {
+
+ public EclipseWorkspaceFile(IFile resource, Set<String> ignoredFileNames) {
+ super(resource, ignoredFileNames);
+ }
+
+ @Override
+ public InputStream getContents() {
+ try {
+ return getResource().getContents();
+ } catch (CoreException e) {
+ // TODO Auto-generated catch block
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public WorkspaceDirectory getParent() {
+ IContainer parent = getResource().getParent();
+
+ if ( parent instanceof IFolder )
+ return new EclipseWorkspaceDirectory((IFolder) parent, getIgnoredFileNames());
+
+ return null;
+ }
+ @Override
+ protected IFile getResource() {
+ return (IFile) super.getResource();
+ }
+}
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceProject.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceProject.java
new file mode 100644
index 0000000..393c565
--- /dev/null
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceProject.java
@@ -0,0 +1,63 @@
+/*
+ * 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.eclipse.core.internal.sync.content;
+
+import java.util.Set;
+
+import org.apache.sling.ide.eclipse.core.ProjectUtil;
+import org.apache.sling.ide.filter.Filter;
+import org.apache.sling.ide.sync.content.WorkspaceDirectory;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.apache.sling.ide.sync.content.WorkspaceProject;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+
+public class EclipseWorkspaceProject extends EclipseWorkspaceResource implements WorkspaceProject {
+
+ public EclipseWorkspaceProject(IProject project, Set<String> ignoredFileNames) {
+ super(project, ignoredFileNames);
+ }
+
+ @Override
+ public WorkspaceDirectory getSyncDirectory() {
+ return new EclipseWorkspaceDirectory(ProjectUtil.getSyncDirectory(getResource()), getIgnoredFileNames());
+ }
+
+ @Override
+ public Filter getFilter() {
+ try {
+ final Filter filter = ProjectUtil.loadFilter(getResource());
+ if ( filter == null )
+ throw new IllegalStateException("No filter for " + this);
+ return filter;
+ } catch (CoreException e) {
+ // TODO Auto-generated catch block
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public WorkspaceDirectory getDirectory(WorkspacePath path) {
+ return new EclipseWorkspaceDirectory(getResource().getFolder(path.asPortableString()), getIgnoredFileNames());
+ }
+
+ @Override
+ protected IProject getResource() {
+ return (IProject) super.getResource();
+ }
+
+}
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceResource.java b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceResource.java
new file mode 100644
index 0000000..a307f3a
--- /dev/null
+++ b/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/sync/content/EclipseWorkspaceResource.java
@@ -0,0 +1,116 @@
+/*
+ * 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.eclipse.core.internal.sync.content;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.sling.ide.eclipse.core.internal.Activator;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.apache.sling.ide.sync.content.WorkspaceProject;
+import org.apache.sling.ide.sync.content.WorkspaceResource;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+
+public abstract class EclipseWorkspaceResource implements WorkspaceResource {
+
+ private final IResource resource;
+ private final Set<String> ignoredFileNames;
+
+ protected EclipseWorkspaceResource(IResource resource, Set<String> ignoredFileNames) {
+ this.resource = resource;
+ this.ignoredFileNames = ignoredFileNames;
+ }
+
+ @Override
+ public boolean exists() {
+ return resource.exists();
+ }
+
+ @Override
+ public boolean isIgnored() {
+ return ignoredFileNames.contains(getName()) ||
+ resource.isTeamPrivateMember(IResource.CHECK_ANCESTORS);
+ }
+
+ @Override
+ public WorkspacePath getLocalPath() {
+ return new WorkspacePath(resource.getFullPath().toPortableString());
+ }
+
+ @Override
+ public Path getOSPath() {
+ return Paths.get(resource.getLocation().toOSString());
+ }
+
+ @Override
+ public WorkspaceProject getProject() {
+ return new EclipseWorkspaceProject(resource.getProject(), ignoredFileNames);
+ }
+
+ @Override
+ public long getLastModified() {
+ return resource.getModificationStamp();
+ }
+
+ @Override
+ public Object getTransientProperty(String propertyName) {
+ try {
+ return resource.getSessionProperty(new QualifiedName(Activator.PLUGIN_ID, propertyName));
+ } catch (CoreException e) {
+ // TODO Auto-generated catch block
+ throw new RuntimeException();
+ }
+ }
+
+ protected IResource getResource() {
+ return resource;
+ }
+
+ protected Set<String> getIgnoredFileNames() {
+ return ignoredFileNames;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((resource == null) ? 0 : resource.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if ( !(obj instanceof EclipseWorkspaceResource) ) {
+ return false;
+ }
+
+ EclipseWorkspaceResource other = (EclipseWorkspaceResource) obj;
+
+ return Objects.equals(this.resource, other.resource);
+
+ }
+
+ @Override
+ public String toString() {
+ return resource.toString();
+ }
+
+}
diff --git a/eclipse/eclipse-test/src/org/apache/sling/ide/test/impl/ResourceChangeCommandFactoryTest.java b/eclipse/eclipse-test/src/org/apache/sling/ide/test/impl/ResourceChangeCommandFactoryTest.java
index 998aa40..c5a328d 100644
--- a/eclipse/eclipse-test/src/org/apache/sling/ide/test/impl/ResourceChangeCommandFactoryTest.java
+++ b/eclipse/eclipse-test/src/org/apache/sling/ide/test/impl/ResourceChangeCommandFactoryTest.java
@@ -72,7 +72,7 @@ public class ResourceChangeCommandFactoryTest {
Set<String> ignoredFileNames = new HashSet<>();
ignoredFileNames.add(".gitignore");
- factory = new ResourceChangeCommandFactory(Activator.getDefault().getSerializationManager(), ignoredFileNames);
+ factory = new ResourceChangeCommandFactory();
spyRepo = new SpyRepository();
}
diff --git a/eclipse/eclipse-ui/META-INF/MANIFEST.MF b/eclipse/eclipse-ui/META-INF/MANIFEST.MF
index ac70d18..b96203a 100644
--- a/eclipse/eclipse-ui/META-INF/MANIFEST.MF
+++ b/eclipse/eclipse-ui/META-INF/MANIFEST.MF
@@ -23,6 +23,7 @@ Import-Package: javax.jcr,
org.apache.sling.ide.log,
org.apache.sling.ide.osgi,
org.apache.sling.ide.serialization,
+ org.apache.sling.ide.sync.content;version="1.0.0",
org.apache.sling.ide.transport,
org.apache.sling.ide.util,
org.eclipse.core.commands,
diff --git a/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/Activator.java b/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/Activator.java
index 28a3810..0e56bdd 100644
--- a/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/Activator.java
+++ b/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/Activator.java
@@ -24,6 +24,7 @@ import org.apache.sling.ide.filter.FilterLocator;
import org.apache.sling.ide.log.Logger;
import org.apache.sling.ide.osgi.OsgiClientFactory;
import org.apache.sling.ide.serialization.SerializationManager;
+import org.apache.sling.ide.sync.content.SyncCommandFactory;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.plugin.AbstractUIPlugin;
@@ -44,7 +45,8 @@ public class Activator extends AbstractUIPlugin {
private ServiceTracker<EmbeddedArtifactLocator, EmbeddedArtifactLocator> artifactLocator;
private ServiceTracker<OsgiClientFactory, OsgiClientFactory> osgiClientFactory;
private ServiceTracker<Logger, Logger> tracer;
-
+ private ServiceTracker<SyncCommandFactory, SyncCommandFactory> commandFactory;
+
private ServiceRegistration<Logger> tracerRegistration;
private ScopedPreferenceStore preferenceStore;
@@ -80,6 +82,9 @@ public class Activator extends AbstractUIPlugin {
tracer = new ServiceTracker<>(context, tracerRegistration.getReference(), null);
tracer.open();
+
+ commandFactory = new ServiceTracker<>(context, SyncCommandFactory.class, null);
+ commandFactory.open();
INSTANCE = this;
}
@@ -92,6 +97,7 @@ public class Activator extends AbstractUIPlugin {
eventAdmin.close();
artifactLocator.close();
osgiClientFactory.close();
+ commandFactory.close();
super.stop(context);
}
@@ -120,6 +126,10 @@ public class Activator extends AbstractUIPlugin {
public Logger getPluginLogger() {
return (Logger) ServiceUtil.getNotNull(tracer);
}
+
+ public SyncCommandFactory getCommandFactory() {
+ return ServiceUtil.getNotNull(commandFactory);
+ }
@Override
public IPreferenceStore getPreferenceStore() {
diff --git a/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ExportWizard.java b/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ExportWizard.java
index 5b9e1fe..e647e34 100644
--- a/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ExportWizard.java
+++ b/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ExportWizard.java
@@ -16,11 +16,14 @@
*/
package org.apache.sling.ide.eclipse.ui.internal;
+import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
+import org.apache.sling.ide.eclipse.core.EclipseResources;
import org.apache.sling.ide.eclipse.core.ServerUtil;
import org.apache.sling.ide.eclipse.core.internal.ResourceChangeCommandFactory;
import org.apache.sling.ide.eclipse.ui.WhitelabelSupport;
+import org.apache.sling.ide.sync.content.SyncCommandFactory;
import org.apache.sling.ide.transport.Command;
import org.apache.sling.ide.transport.Repository;
import org.apache.sling.ide.transport.Result;
@@ -61,8 +64,7 @@ public class ExportWizard extends Wizard {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
- final ResourceChangeCommandFactory factory = new ResourceChangeCommandFactory(Activator
- .getDefault().getSerializationManager(), Activator.getDefault().getPreferences().getIgnoredFileNamesForSync());
+ final SyncCommandFactory factory = Activator.getDefault().getCommandFactory();
final Repository[] selectedServer = new Repository[1];
Display.getDefault().syncExec(new Runnable() {
@@ -82,16 +84,21 @@ public class ExportWizard extends Wizard {
@Override
public boolean visit(IResource resource) throws CoreException {
- Command<?> command = factory.newCommandForAddedOrUpdated(selectedServer[0], resource);
- if (command == null) {
- return true;
- }
- Result<?> result = command.execute();
- if (!result.isSuccess()) {
+ try {
+ Command<?> command = factory.newCommandForAddedOrUpdatedResource(selectedServer[0],
+ EclipseResources.create(resource));
+ if (command == null) {
+ return true;
+ }
+ Result<?> result = command.execute();
+ if (!result.isSuccess()) {
+ throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID,
+ "Failed exporting: " + result.toString()));
+ }
+ } catch (IOException e) {
throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID,
- "Failed exporting: " + result.toString()));
+ "Failed exporting: " + e.getMessage()));
}
-
return true;
}
});
diff --git a/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java b/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java
index 1df4b11..e5e9196 100644
--- a/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java
+++ b/eclipse/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java
@@ -29,10 +29,10 @@ import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.util.Text;
+import org.apache.sling.ide.eclipse.core.EclipseResources;
import org.apache.sling.ide.eclipse.core.ProjectUtil;
import org.apache.sling.ide.eclipse.core.ResourceUtil;
import org.apache.sling.ide.eclipse.core.ServerUtil;
-import org.apache.sling.ide.eclipse.core.internal.ResourceAndInfo;
import org.apache.sling.ide.eclipse.core.internal.ResourceChangeCommandFactory;
import org.apache.sling.ide.eclipse.core.progress.ProgressUtils;
import org.apache.sling.ide.filter.Filter;
@@ -45,9 +45,11 @@ import org.apache.sling.ide.serialization.SerializationException;
import org.apache.sling.ide.serialization.SerializationKind;
import org.apache.sling.ide.serialization.SerializationKindManager;
import org.apache.sling.ide.serialization.SerializationManager;
+import org.apache.sling.ide.sync.content.SyncCommandFactory;
import org.apache.sling.ide.transport.Command;
import org.apache.sling.ide.transport.Repository;
import org.apache.sling.ide.transport.RepositoryException;
+import org.apache.sling.ide.transport.ResourceAndInfo;
import org.apache.sling.ide.transport.ResourceProxy;
import org.apache.sling.ide.transport.Result;
import org.eclipse.core.resources.IContainer;
@@ -185,7 +187,7 @@ public class ImportRepositoryContentAction {
private void recordNotIgnoredResources() throws CoreException {
- final ResourceChangeCommandFactory rccf = new ResourceChangeCommandFactory(serializationManager, Activator.getDefault().getPreferences().getIgnoredFileNamesForSync());
+ final SyncCommandFactory commandFactory = Activator.getDefault().getCommandFactory();
IResource importStartingPoint = contentSyncRootDir.findMember(repositoryImportRoot);
if (importStartingPoint == null) {
@@ -197,7 +199,7 @@ public class ImportRepositoryContentAction {
public boolean visit(IResource resource) throws CoreException {
try {
- ResourceAndInfo rai = rccf.buildResourceAndInfo(resource, repository);
+ ResourceAndInfo rai = commandFactory.buildResourceAndInfo(EclipseResources.create(resource), repository);
if (rai == null) {
// can be a prerequisite
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/serialization/SerializationManager.java b/shared/modules/api/src/main/java/org/apache/sling/ide/serialization/SerializationManager.java
index 86639a0..3f56aec 100644
--- a/shared/modules/api/src/main/java/org/apache/sling/ide/serialization/SerializationManager.java
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/serialization/SerializationManager.java
@@ -27,10 +27,23 @@ public interface SerializationManager {
void destroy();
+ /**
+ * @param filePath the filesystem path
+ * @return
+ */
boolean isSerializationFile(String filePath);
+ /**
+ * @param serializationFilePath the full OS path to the serialization file
+ * @return
+ */
String getBaseResourcePath(String serializationFilePath);
+ /**
+ * @param baseFilePath the filesystem path of the resource
+ * @param serializationKind
+ * @return
+ */
String getSerializationFilePath(String baseFilePath, SerializationKind serializationKind);
String getRepositoryPath(String osPath);
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/SyncCommandFactory.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/SyncCommandFactory.java
new file mode 100644
index 0000000..e0047b2
--- /dev/null
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/SyncCommandFactory.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.sync.content;
+
+import java.io.IOException;
+
+import org.apache.sling.ide.transport.Command;
+import org.apache.sling.ide.transport.Repository;
+import org.apache.sling.ide.transport.ResourceAndInfo;
+
+/**
+ * Creates commands in response to local resource changes
+ */
+public interface SyncCommandFactory {
+
+ String PN_IMPORT_MODIFICATION_TIMESTAMP = "importModificationTimestamp";
+
+ /**
+ * Creates a command in response to a local resource being deleted
+ *
+ * @param repository the repository that will create the command
+ * @param resource the resource that was deleted
+ * @return the commmand to execute
+ * @throws IOException I/O related problems
+ */
+ Command<?> newCommandForRemovedResource(Repository repository, WorkspaceResource resource) throws IOException;
+
+ /**
+ * Creates a command in response to a local resource being added or updated
+ *
+ * @param repository the repository that will create the command
+ * @param resource the resource that was added or updated
+ * @return the commmand to execute
+ * @throws IOException I/O related problems
+ */
+ Command<?> newCommandForAddedOrUpdatedResource(Repository repository, WorkspaceResource resource) throws IOException;
+
+ /**
+ * Creates a command which reorders the child nodes of the node corresponding to the specified local resource
+ *
+ * @param repository the repository that will create the command
+ * @param resource the resource whose children should be reorderer
+ * @return the command to execut
+ * @throws IOException I/O related problems
+ */
+ Command<Void> newReorderChildNodesCommand(Repository repository, WorkspaceResource resource) throws IOException;
+
+ /**
+ * Convenience method which builds a <tt>ResourceAndInfo</tt> info for a specific <tt>IResource</tt>
+ *
+ * @param resource the resource to process
+ * @param repository the repository, used to extract serialization information for different resource types
+ * @return the built object, or null if one could not be built
+ * @throws IOException I/O related problems
+ */
+ ResourceAndInfo buildResourceAndInfo(WorkspaceResource resource, Repository repository) throws IOException;
+}
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceDirectory.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceDirectory.java
new file mode 100644
index 0000000..596af73
--- /dev/null
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceDirectory.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.sync.content;
+
+import java.util.List;
+
+/**
+ * Represents a local directory, <tt>i.e.</tt> present in the local workspace
+ *
+ */
+public interface WorkspaceDirectory extends WorkspaceResource {
+
+ /**
+ * Returns a child file for this directory.
+ *
+ * <p>The file may not exist, so make sure to check {@link WorkspaceResource#exists()}</p>
+ *
+ * @param relativePath the relative path to the file
+ * @return the file ( which may not exist )
+ */
+ WorkspaceFile getFile(WorkspacePath relativePath);
+
+ /**
+ * Returns a child directory for this directory.
+ *
+ * <p>The directory may not exist, so make sure to check {@link WorkspaceResource#exists()}</p>
+ *
+ * @param relativePath the relative path to the directory
+ * @return the directory ( which may not exist )
+ */
+ WorkspaceDirectory getDirectory(WorkspacePath relativePath);
+
+ /**
+ * Returns a list of children, guaranteed to exist at the time of the call
+ *
+ * @return a list of children
+ */
+ List<WorkspaceResource> getChildren();
+}
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/serialization/SerializationManager.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceFile.java
similarity index 50%
copy from shared/modules/api/src/main/java/org/apache/sling/ide/serialization/SerializationManager.java
copy to shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceFile.java
index 86639a0..e2f59db 100644
--- a/shared/modules/api/src/main/java/org/apache/sling/ide/serialization/SerializationManager.java
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceFile.java
@@ -14,36 +14,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sling.ide.serialization;
+package org.apache.sling.ide.sync.content;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import org.apache.sling.ide.transport.Repository;
-import org.apache.sling.ide.transport.ResourceProxy;
-
-public interface SerializationManager {
-
- void destroy();
-
- boolean isSerializationFile(String filePath);
-
- String getBaseResourcePath(String serializationFilePath);
-
- String getSerializationFilePath(String baseFilePath, SerializationKind serializationKind);
-
- String getRepositoryPath(String osPath);
-
- String getOsPath(String repositoryPath);
+/**
+ * Represents a local file, <tt>i.e.</tt> present in the local workspace
+ */
+public interface WorkspaceFile extends WorkspaceResource {
- SerializationDataBuilder newBuilder(Repository repository, File contentSyncRoot) throws SerializationException;
+ /**
+ * Returns the contents of the file
+ *
+ * @throws IOException I/O errors
+ * @return the contents of the file
+ */
+ InputStream getContents() throws IOException;
/**
- * @param filePath The filePath, in repository format
- * @param source
- * @return
- * @throws IOException
+ * Returns the parent of this file
+ *
+ * @return the parent of this file
*/
- ResourceProxy readSerializationData(String filePath, InputStream source) throws IOException;
+ WorkspaceDirectory getParent();
+
}
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspacePath.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspacePath.java
new file mode 100644
index 0000000..eeb450d
--- /dev/null
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspacePath.java
@@ -0,0 +1,126 @@
+/*
+ * 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.sync.content;
+
+import java.nio.file.Path;
+import java.util.Objects;
+
+import org.apache.sling.ide.util.PathUtil;
+
+/**
+ * A path in the local workspace
+ *
+ * <p>The workspace path always uses the forward slash ( <tt>/</tt> ) for separating segments.</p>
+ *
+ */
+public class WorkspacePath {
+
+ private final String portablePath;
+
+ public WorkspacePath(String portablePath) {
+ if ( portablePath == null )
+ throw new IllegalArgumentException("Invalid path :'" + portablePath+"'");
+
+ if ( portablePath.length() > 1 && portablePath.endsWith("/") )
+ portablePath = portablePath.substring(0, portablePath.length() - 1);
+
+ this.portablePath = portablePath;
+ }
+
+ public String getName() {
+ return PathUtil.getName(portablePath);
+ }
+
+ public String asPortableString() {
+ return portablePath;
+ }
+
+ /**
+ * Creates relative bath between this path and the one passed as an argument.
+ *
+ * <p>This method uses the same name uses the same meaning for the parameters` as the {@link Path#relativize(Path)} method.</p>
+ *
+ * <p>For this comparison the paths are made absolute, but the returned path is relative.</p>
+ *
+ * @param other the potential parent path
+ * @return a relative path, or <code>null</code> is the paths are unrelated
+ */
+ public WorkspacePath relativize(WorkspacePath other) {
+
+ String ours = absolute().portablePath;
+ String theirs = other.absolute().portablePath;
+
+ if ( ours.equals(theirs) )
+ return new WorkspacePath("");
+
+ if ( theirs.startsWith(ours) )
+ return new WorkspacePath(theirs.substring(ours.length() + 1));
+
+ return null;
+ }
+
+ public boolean isRoot() {
+ return portablePath.equals("/");
+ }
+
+ public WorkspacePath getParent() {
+ String path = PathUtil.getParent(portablePath);
+ if ( path == null )
+ return null;
+ return new WorkspacePath(path);
+ }
+
+ public WorkspacePath absolute() {
+ if ( isAbsolute() )
+ return this;
+
+ return new WorkspacePath('/' + portablePath);
+ }
+
+ private boolean isAbsolute() {
+ return portablePath.charAt(0) == '/';
+ }
+
+ public WorkspacePath append(String name) {
+ if ( name == null || name.isEmpty() || name.indexOf('/') != -1)
+ throw new IllegalArgumentException("Invalid name: '" + name + "'");
+
+ return new WorkspacePath(portablePath + '/' + name);
+
+ }
+
+ public WorkspacePath append(WorkspacePath other) {
+
+ if ( other == null )
+ throw new IllegalArgumentException("Unable to append null path");
+
+ return new WorkspacePath(PathUtil.join(portablePath, other.portablePath));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if ( !(obj instanceof WorkspacePath) )
+ return false;
+
+ return Objects.equals(portablePath, ((WorkspacePath) obj).portablePath);
+ }
+
+ @Override
+ public String toString() {
+ return asPortableString();
+ }
+}
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspacePaths.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspacePaths.java
new file mode 100644
index 0000000..1f745f4
--- /dev/null
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspacePaths.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sync.content;
+
+import java.io.File;
+import java.nio.file.Path;
+
+/**
+ *
+ * Holds utility methods related to path instances
+ *
+ */
+public abstract class WorkspacePaths {
+
+ public static WorkspacePath fromOsPath(Path path) {
+
+ return new WorkspacePath(path.toString().replace(File.separatorChar, '/'));
+ }
+
+ private WorkspacePaths() {
+
+ }
+}
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
new file mode 100644
index 0000000..4be9cae
--- /dev/null
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceProject.java
@@ -0,0 +1,35 @@
+/*
+ * 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.sync.content;
+
+import org.apache.sling.ide.filter.Filter;
+
+/**
+ * Represents a local project, <tt>i.e.</tt> present in the local workspace
+ *
+ * <p>It also provides easy access to various content sync objects related
+ * to the projects.</p>
+ */
+public interface WorkspaceProject extends WorkspaceResource {
+
+ WorkspaceDirectory getSyncDirectory();
+
+ Filter getFilter();
+
+ WorkspaceDirectory getDirectory(WorkspacePath path);
+
+}
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceResource.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceResource.java
new file mode 100644
index 0000000..2b29237
--- /dev/null
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/WorkspaceResource.java
@@ -0,0 +1,93 @@
+/*
+ * 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.sync.content;
+
+import java.nio.file.Path;
+
+/**
+ * Groups common operations on workspace resources.
+ */
+public interface WorkspaceResource {
+
+ /**
+ * Returns the name of this resource
+ *
+ * @return the name of the resource
+ */
+ default String getName() {
+ return getLocalPath().getName();
+ }
+
+ /**
+ * Returns true if the resource exists, false otherwise
+ *
+ * <p>A resource object not exist if it is constructed for instance to
+ * send a notification for a deleted resource.</p>
+ *
+ * @return true if the resource exists, false otherwise
+ */
+ boolean exists();
+
+ /**
+ * Returns true if this resource is ignored for content sync purposes
+ *
+ * @return true if the resource is ignored, false otherwise
+ */
+ boolean isIgnored();
+
+ /**
+ * Returns the absolute local path, rooted at the workspace level
+ *
+ * @return the local path
+ */
+ WorkspacePath getLocalPath();
+
+ /**
+ * Returns the absolute OS path to this resource
+ *
+ * @return the OS path
+ */
+ Path getOSPath();
+
+ /**
+ * @return the project which holds this resource
+ */
+ WorkspaceProject getProject();
+
+ /**
+ * @return the last modified timestamp
+ */
+ long getLastModified();
+
+ /**
+ * Returns a value for the specified transient property
+ *
+ * <p>The properties are not persisted and the lifetime of the duration
+ * is governed by the specific implementation.</p>
+ *
+ * @param propertyName property name
+ * @return the value for the transient property, or <code>null</code>
+ */
+ Object getTransientProperty(String propertyName);
+
+ default WorkspacePath getPathRelativeToSyncDir() {
+ final WorkspacePath relativePath = getProject().getSyncDirectory().getLocalPath().relativize(getLocalPath());
+ if ( relativePath == null )
+ throw new RuntimeException("Unable to get relative path between sync dir " + getProject().getSyncDirectory().getLocalPath() + " and " + getLocalPath());
+ return relativePath;
+ }
+}
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/impl/DefaultSyncCommandFactory.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/impl/DefaultSyncCommandFactory.java
new file mode 100644
index 0000000..ec222ad
--- /dev/null
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/impl/DefaultSyncCommandFactory.java
@@ -0,0 +1,447 @@
+/*
+ * 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.sync.content.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.ide.filter.FilterResult;
+import org.apache.sling.ide.log.Logger;
+import org.apache.sling.ide.serialization.SerializationKind;
+import org.apache.sling.ide.serialization.SerializationKindManager;
+import org.apache.sling.ide.serialization.SerializationManager;
+import org.apache.sling.ide.sync.content.SyncCommandFactory;
+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.WorkspacePaths;
+import org.apache.sling.ide.sync.content.WorkspaceResource;
+import org.apache.sling.ide.transport.Command;
+import org.apache.sling.ide.transport.CommandContext;
+import org.apache.sling.ide.transport.FileInfo;
+import org.apache.sling.ide.transport.Repository;
+import org.apache.sling.ide.transport.RepositoryException;
+import org.apache.sling.ide.transport.ResourceAndInfo;
+import org.apache.sling.ide.transport.ResourceProxy;
+import org.apache.sling.ide.util.PathUtil;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(service = SyncCommandFactory.class)
+public class DefaultSyncCommandFactory implements SyncCommandFactory {
+
+ @Reference
+ private SerializationManager serializationManager;
+
+ @Reference
+ private Logger logger;
+
+
+ @Override
+ public Command<?> newCommandForRemovedResource(Repository repository, WorkspaceResource resource) throws IOException {
+
+ if (resource.isIgnored()) {
+ logger.trace("Skipping ignored resource {0}", resource);
+ return null;
+ }
+
+ String repositoryPath = resource.getPathRelativeToSyncDir().absolute().asPortableString();
+
+ FilterResult filterResult = resource.getProject().getFilter().filter(repositoryPath);
+
+ logger.trace("Filter result for {0} is {1}", repositoryPath, filterResult);
+
+ if (filterResult == null || filterResult == FilterResult.DENY || filterResult == FilterResult.PREREQUISITE) {
+ return null;
+ }
+
+ // TODO - we send different parametrs to findSerializationDataFromCoveringParent
+ // for added vs removed resources
+
+ // verify whether a resource being deleted does not signal that the content structure
+ // was rearranged under a covering parent aggregate
+ Path serializationFilePath = Paths.get(serializationManager.getSerializationFilePath(repositoryPath,
+ SerializationKind.FOLDER));
+
+ WorkspacePath serializationFileLocalPath = resource.getProject().getSyncDirectory().getLocalPath().append(WorkspacePaths.fromOsPath(serializationFilePath));
+
+ ResourceProxy coveringParentData = findSerializationDataFromCoveringParent(resource, repositoryPath, serializationFileLocalPath);
+ if (coveringParentData != null) {
+ logger.trace("Found covering resource data ( repository path = {0} ) for resource at {1}, skipping deletion and performing an update instead",
+ coveringParentData.getPath(), resource.getLocalPath());
+ FileInfo info = createFileInfo(resource);
+ return repository.newAddOrUpdateNodeCommand(new CommandContext(resource.getProject().getFilter()), info, coveringParentData);
+ }
+
+ return repository.newDeleteNodeCommand(serializationManager.getRepositoryPath(repositoryPath));
+ }
+
+ @Override
+ public Command<?> newCommandForAddedOrUpdatedResource(Repository repository, WorkspaceResource resource)
+ throws IOException {
+ ResourceAndInfo rai = buildResourceAndInfo(resource, repository);
+
+ if (rai == null) {
+ return null;
+ }
+
+ CommandContext context = new CommandContext(resource.getProject().getFilter());
+
+ if (rai.isOnlyWhenMissing()) {
+ return repository.newAddOrUpdateNodeCommand(context, rai.getInfo(), rai.getResource(),
+ Repository.CommandExecutionFlag.CREATE_ONLY_WHEN_MISSING);
+ }
+
+ return repository.newAddOrUpdateNodeCommand(context, rai.getInfo(), rai.getResource());
+ }
+
+ private ResourceProxy findSerializationDataFromCoveringParent(WorkspaceResource localFile,
+ String resourceLocation, WorkspacePath serializationFilePath) throws IOException {
+
+ logger.trace("Found plain nt:folder candidate at {0}, trying to find a covering resource for it",
+localFile);
+
+ WorkspacePath orig = serializationFilePath;
+
+ WorkspacePath syncDirPath = localFile.getProject().getSyncDirectory().getLocalPath();
+ serializationFilePath = localFile.getProject().getLocalPath().relativize(serializationFilePath);
+ if ( serializationFilePath == null )
+ throw new RuntimeException("Unable to get relative path from " + localFile.getProject().getLocalPath() + " to " + orig);
+ serializationFilePath = serializationFilePath.absolute();
+
+ while (!syncDirPath.equals(serializationFilePath)) {
+ serializationFilePath = serializationFilePath.getParent();
+ // TODO - better check for path going outside the sync directory
+ if ( serializationFilePath.asPortableString().lastIndexOf('/') == 0 ) {
+ break;
+ }
+ WorkspaceDirectory folderWithPossibleSerializationFile = localFile.getProject().getDirectory(serializationFilePath);
+
+
+ if (!folderWithPossibleSerializationFile.exists()) {
+ logger.trace("No folder found at {0}, moving up to the next level", serializationFilePath);
+ continue;
+ }
+
+ // it's safe to use a specific SerializationKind since this scenario is only valid for METADATA_PARTIAL
+ // coverage
+ String possibleSerializationFilePath = serializationManager.getSerializationFilePath(
+ folderWithPossibleSerializationFile.getOSPath().toString(),
+ SerializationKind.METADATA_PARTIAL);
+
+ logger.trace("Looking for serialization data in {0}", possibleSerializationFilePath);
+ if (serializationManager.isSerializationFile(possibleSerializationFilePath)) {
+
+ Path parentFileSerializationOSPath = localFile.getProject().getSyncDirectory().getOSPath().
+ relativize(Paths.get(possibleSerializationFilePath));
+
+ WorkspacePath parentSerializationFilePath = new WorkspacePath(parentFileSerializationOSPath.toString())
+ .absolute();
+
+ WorkspaceFile possibleSerializationFile = localFile.getProject().getSyncDirectory().getFile(parentSerializationFilePath);
+ if (!possibleSerializationFile.exists()) {
+ logger.trace("Potential serialization data file {0} does not exist, moving up to the next level",
+ possibleSerializationFile.getLocalPath());
+ continue;
+ }
+
+ ResourceProxy serializationData;
+ try (InputStream contents = possibleSerializationFile.getContents()) {
+ serializationData = serializationManager.readSerializationData(
+ parentSerializationFilePath.asPortableString(), contents);
+ }
+
+ String repositoryPath = serializationManager.getRepositoryPath(resourceLocation);
+ String potentialPath = serializationData.getPath();
+ boolean covered = serializationData.covers(repositoryPath);
+
+ logger.trace(
+ "Found possible serialization data at {0}. Resource :{1} ; our resource: {2}. Covered: {3}",
+ parentSerializationFilePath, potentialPath, repositoryPath, covered);
+ // note what we don't need to normalize the children here since this resource's data is covered by
+ // another resource
+ if (covered) {
+ return serializationData.getChild(repositoryPath);
+ }
+
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ private FileInfo createFileInfo(WorkspaceResource resource) {
+
+ if (!(resource instanceof WorkspaceFile)) {
+ return null;
+ }
+
+ FileInfo info = new FileInfo(resource.getOSPath().toString(), resource.getPathRelativeToSyncDir().asPortableString(), resource.getName());
+
+ logger.trace("For {0} built fileInfo {1}", resource, info);
+
+ return info;
+ }
+
+ public ResourceAndInfo buildResourceAndInfo(WorkspaceResource resource, Repository repository) throws IOException {
+
+ Long modificationTimestamp = (Long) resource.getTransientProperty(PN_IMPORT_MODIFICATION_TIMESTAMP);
+
+ if (modificationTimestamp != null && modificationTimestamp >= resource.getLastModified()) {
+ logger.trace("Change for resource {0} ignored as the import timestamp {1} >= modification timestamp {2}",
+ resource, modificationTimestamp, resource.getLastModified());
+ return null;
+ }
+
+ if (resource.isIgnored()) {
+ logger.trace("Skipping team-private resource {0}", resource);
+ return null;
+ }
+
+ FileInfo info = createFileInfo(resource);
+ logger.trace("For {0} built fileInfo {1}", resource, info);
+
+ WorkspaceDirectory syncDirectory = resource.getProject().getSyncDirectory();
+ File syncDirectoryAsFile = syncDirectory.getOSPath().toFile();
+
+ ResourceProxy resourceProxy = null;
+
+ if (serializationManager.isSerializationFile(resource.getOSPath().toString())) {
+ WorkspaceFile file = (WorkspaceFile) resource;
+ try (InputStream contents = file.getContents()) {
+ String resourceLocation = file.getPathRelativeToSyncDir().asPortableString();
+ resourceProxy = serializationManager.readSerializationData(resourceLocation, contents);
+ normaliseResourceChildren(file, resourceProxy, repository);
+
+
+ // TODO - not sure if this 100% correct, but we definitely should not refer to the FileInfo as the
+ // .serialization file, since for nt:file/nt:resource nodes this will overwrite the file contents
+ String primaryType = (String) resourceProxy.getProperties().get(Repository.JCR_PRIMARY_TYPE);
+ if (Repository.NT_FILE.equals(primaryType)) {
+ // TODO move logic to serializationManager
+ File locationFile = new File(info.getLocation());
+ String locationFileParent = locationFile.getParent();
+ int endIndex = locationFileParent.length() - ".dir".length();
+ File actualFile = new File(locationFileParent.substring(0, endIndex));
+ String newLocation = actualFile.getAbsolutePath();
+ String newName = actualFile.getName();
+ String newRelativeLocation = actualFile.getAbsolutePath().substring(
+ syncDirectoryAsFile.getAbsolutePath().length());
+ info = new FileInfo(newLocation, newRelativeLocation, newName);
+
+ logger.trace("Adjusted original location from {0} to {1}", resourceLocation, newLocation);
+
+ }
+ }
+ } else {
+
+ // TODO - move logic to serializationManager
+ // possible .dir serialization holder
+ if (resource instanceof WorkspaceDirectory && resource.getName().endsWith(".dir")) {
+ WorkspaceDirectory folder = (WorkspaceDirectory) resource;
+ WorkspaceResource contentXml = folder.getFile(new WorkspacePath(".content.xml"));
+ // .dir serialization holder ; nothing to process here, the .content.xml will trigger the actual work
+ if (contentXml.exists()
+ && serializationManager.isSerializationFile(contentXml.getOSPath().toString())) {
+ return null;
+ }
+ }
+
+ resourceProxy = buildResourceProxyForPlainFileOrFolder(resource, repository);
+ }
+
+ if ( resourceProxy == null )
+ throw new RuntimeException("ResourceProxy is null for resource " + resource);
+
+ FilterResult filterResult = resource.getProject().getFilter().filter(resourceProxy.getPath());
+
+ switch (filterResult) {
+
+ case ALLOW:
+ return new ResourceAndInfo(resourceProxy, info);
+ case PREREQUISITE:
+ // never try to 'create' the root node, we assume it exists
+ if (!resourceProxy.getPath().equals("/")) {
+ // we don't explicitly set the primary type, which will allow the the repository to choose the best
+ // suited one ( typically nt:unstructured )
+ return new ResourceAndInfo(new ResourceProxy(resourceProxy.getPath()), null, true);
+ }
+ case DENY: // falls through
+ default:
+ return null;
+ }
+ }
+
+ private ResourceProxy buildResourceProxyForPlainFileOrFolder(WorkspaceResource changedResource, Repository repository)
+ throws IOException {
+
+ SerializationKind serializationKind;
+ String fallbackNodeType;
+ if (changedResource instanceof WorkspaceFile) {
+ serializationKind = SerializationKind.FILE;
+ fallbackNodeType = Repository.NT_FILE;
+ } else { // i.e. LocalFolder
+ serializationKind = SerializationKind.FOLDER;
+ fallbackNodeType = Repository.NT_FOLDER;
+ }
+
+ String resourceLocation = changedResource.getPathRelativeToSyncDir().asPortableString();
+ String serializationFilePath = serializationManager.getSerializationFilePath(
+ resourceLocation, serializationKind);
+ WorkspaceFile serializationResource = changedResource.getProject().getSyncDirectory().getFile(new WorkspacePath(serializationFilePath));
+
+ if (!serializationResource.exists() && changedResource instanceof WorkspaceDirectory) {
+ ResourceProxy dataFromCoveringParent = findSerializationDataFromCoveringParent(changedResource,
+ resourceLocation, serializationResource.getLocalPath());
+
+ if (dataFromCoveringParent != null) {
+ return dataFromCoveringParent;
+ }
+
+ }
+
+ return buildResourceProxy(resourceLocation, serializationResource, fallbackNodeType, repository);
+ }
+
+ private ResourceProxy buildResourceProxy(String resourceLocation, WorkspaceFile serializationFile,
+ String fallbackPrimaryType, Repository repository) throws IOException {
+ if (serializationFile.exists()) {
+ try (InputStream contents = serializationFile.getContents() ) {
+
+ String serializationFilePath = serializationFile.getPathRelativeToSyncDir().asPortableString();
+ ResourceProxy resourceProxy = serializationManager.readSerializationData(serializationFilePath, contents);
+ normaliseResourceChildren(serializationFile, resourceProxy, repository);
+
+ return resourceProxy;
+ }
+ }
+
+ return new ResourceProxy(serializationManager.getRepositoryPath(resourceLocation), Collections.singletonMap(
+ Repository.JCR_PRIMARY_TYPE, (Object) fallbackPrimaryType));
+ }
+
+ /**
+ * Normalises the of the specified <tt>resourceProxy</tt> by comparing the serialization data and the filesystem
+ * data
+ *
+ * @param serializationFile the file which contains the serialization data
+ * @param resourceProxy the resource proxy
+ * @param syncDirectory the sync directory
+ * @param repository TODO
+ * @throws CoreException
+ */
+ private void normaliseResourceChildren(WorkspaceFile serializationFile, ResourceProxy resourceProxy,
+ Repository repository) {
+ // TODO - this logic should be moved to the serializationManager
+ try {
+ SerializationKindManager skm = new SerializationKindManager();
+ skm.init(repository);
+
+ String primaryType = (String) resourceProxy.getProperties().get(Repository.JCR_PRIMARY_TYPE);
+ List<String> mixinTypesList = getMixinTypes(resourceProxy);
+ SerializationKind serializationKind = skm.getSerializationKind(primaryType, mixinTypesList);
+
+ if (serializationKind == SerializationKind.METADATA_FULL) {
+ return;
+ }
+ } catch (RepositoryException e) {
+ // TODO proper exception handling
+ throw new RuntimeException(e);
+ }
+
+ WorkspacePath serializationDirectoryPath = serializationFile.getLocalPath().getParent();
+
+ Iterator<ResourceProxy> childIterator = resourceProxy.getChildren().iterator();
+ Map<String, WorkspaceResource> extraChildResources = new HashMap<>();
+ for (WorkspaceResource member : serializationFile.getParent().getChildren()) {
+ if (member.equals(serializationFile)) {
+ continue;
+ }
+ extraChildResources.put(member.getName(), member);
+ }
+
+ while (childIterator.hasNext()) {
+ ResourceProxy child = childIterator.next();
+ String childName = PathUtil.getName(child.getPath());
+ String osChildName = serializationManager.getOsPath(childName);
+
+ // covered children might have a FS representation, depending on their child nodes, so
+ // accept a directory which maps to their name
+ extraChildResources.remove(osChildName);
+
+ // covered children do not need a filesystem representation
+ if (resourceProxy.covers(child.getPath())) {
+ continue;
+ }
+
+ WorkspacePath childPath = serializationDirectoryPath.append(osChildName);
+
+ WorkspaceResource childResource = serializationFile.getParent().getDirectory(new WorkspacePath(osChildName));
+ if (!childResource.exists()) {
+ logger.trace("For resource at with serialization data {0} the serialized child resource at {1} does not exist in the filesystem and will be ignored",
+ serializationFile, childPath);
+ childIterator.remove();
+ }
+ }
+
+ for ( WorkspaceResource extraChildResource : extraChildResources.values()) {
+ WorkspacePath extraChildResourcePath = extraChildResource.getPathRelativeToSyncDir();
+ resourceProxy.addChild(new ResourceProxy(serializationManager
+ .getRepositoryPath(extraChildResourcePath.asPortableString())));
+
+ logger.trace("For resource at with serialization data {0} the found a child resource at {1} which is not listed in the serialized child resources and will be added",
+ serializationFile, extraChildResource);
+ }
+ }
+
+ private List<String> getMixinTypes(ResourceProxy resourceProxy) {
+
+ Object mixinTypesProp = resourceProxy.getProperties().get(Repository.JCR_MIXIN_TYPES);
+
+ if (mixinTypesProp == null) {
+ return Collections.emptyList();
+ }
+
+ if (mixinTypesProp instanceof String) {
+ return Collections.singletonList((String) mixinTypesProp);
+ }
+
+ return Arrays.asList((String[]) mixinTypesProp);
+ }
+
+ @Override
+ public Command<Void> newReorderChildNodesCommand(Repository repository, WorkspaceResource resource) throws IOException {
+ ResourceAndInfo rai = buildResourceAndInfo(resource, repository);
+
+ if (rai == null || rai.isOnlyWhenMissing()) {
+ return null;
+ }
+
+ return repository.newReorderChildNodesCommand(rai.getResource());
+ }
+}
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/package-info.java b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/package-info.java
new file mode 100644
index 0000000..714e15f
--- /dev/null
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/sync/content/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.ide.sync.content;
\ No newline at end of file
diff --git a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/ResourceAndInfo.java b/shared/modules/api/src/main/java/org/apache/sling/ide/transport/ResourceAndInfo.java
similarity index 91%
rename from eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/ResourceAndInfo.java
rename to shared/modules/api/src/main/java/org/apache/sling/ide/transport/ResourceAndInfo.java
index 7a69614..56bba21 100644
--- a/eclipse/eclipse-core/src/org/apache/sling/ide/eclipse/core/internal/ResourceAndInfo.java
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/transport/ResourceAndInfo.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sling.ide.eclipse.core.internal;
-
-import org.apache.sling.ide.transport.FileInfo;
-import org.apache.sling.ide.transport.ResourceProxy;
+package org.apache.sling.ide.transport;
/**
* The <tt>ResourceAndInfo</tt> is a simple value class allowing both a ResourceProxy and a FileInfo to be passed
diff --git a/shared/modules/api/src/main/java/org/apache/sling/ide/util/PathUtil.java b/shared/modules/api/src/main/java/org/apache/sling/ide/util/PathUtil.java
index 8181d8f..6f146ab 100644
--- a/shared/modules/api/src/main/java/org/apache/sling/ide/util/PathUtil.java
+++ b/shared/modules/api/src/main/java/org/apache/sling/ide/util/PathUtil.java
@@ -34,6 +34,9 @@ public class PathUtil {
}
public static String getName(String path) {
+
+ if ( path.length() == 1 && path.charAt(0) == '/')
+ return path;
return path.substring(path.lastIndexOf('/') + 1);
}
diff --git a/shared/modules/api/src/test/java/org/apache/sling/ide/sync/content/WorkspacePathTest.java b/shared/modules/api/src/test/java/org/apache/sling/ide/sync/content/WorkspacePathTest.java
new file mode 100644
index 0000000..35fcd86
--- /dev/null
+++ b/shared/modules/api/src/test/java/org/apache/sling/ide/sync/content/WorkspacePathTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.sync.content;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+public class WorkspacePathTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void nullPath() {
+ new WorkspacePath(null);
+ }
+
+
+ public void emptyPath() {
+ assertThat(new WorkspacePath("").asPortableString(), equalTo(""));
+ }
+
+ @Test
+ public void removeTrailingSlash() {
+ assertThat(new WorkspacePath("/first/").asPortableString(), equalTo("/first"));
+ }
+
+ @Test
+ public void getName_root() {
+ assertThat(new WorkspacePath("/").getName(), equalTo("/"));
+ }
+
+ @Test
+ public void getName_firstLevelChild() {
+ assertThat(new WorkspacePath("/first").getName(), equalTo("first"));
+ }
+
+ @Test
+ public void getName_secondLevelChild() {
+ assertThat(new WorkspacePath("/first/second").getName(), equalTo("second"));
+ }
+
+ @Test
+ public void absolute_root() {
+ assertThat(new WorkspacePath("/").absolute().asPortableString(), equalTo("/"));
+ }
+
+ @Test
+ public void absolute_noop() {
+ assertThat(new WorkspacePath("/first").absolute().asPortableString(), equalTo("/first"));
+ }
+
+ @Test
+ public void absolute_changed() {
+ assertThat(new WorkspacePath("first").absolute().asPortableString(), equalTo("/first"));
+ }
+
+ @Test
+ public void makeRelative_parentAndChild() {
+
+ WorkspacePath parent = new WorkspacePath("first");
+ WorkspacePath child = new WorkspacePath("first/second/third");
+
+ assertThat(parent.relativize(child).asPortableString(), equalTo("second/third"));
+ }
+
+ @Test
+ public void makeRelative_same() {
+
+ WorkspacePath path = new WorkspacePath("first");
+
+ assertThat(path.relativize(path).asPortableString(), equalTo(""));
+ }
+
+ @Test
+ public void makeRelative_unrelated() {
+
+ assertThat(new WorkspacePath("/first").relativize(new WorkspacePath("/second")), nullValue());
+ }
+
+ @Test
+ public void isRoot_root() {
+
+ assertThat(new WorkspacePath("/").isRoot(), equalTo(true));
+ }
+
+ @Test
+ public void isRoot_notRoot() {
+ assertThat(new WorkspacePath("/first").isRoot(), equalTo(false));
+ }
+
+ @Test
+ public void getParent_root() {
+ assertThat(new WorkspacePath("/").getParent(), nullValue());
+ }
+
+ @Test
+ public void getParent_firstLevel() {
+ assertThat(new WorkspacePath("/first").getParent().asPortableString(), equalTo("/"));
+ }
+
+ @Test
+ public void getParent_secondLevel() {
+ assertThat(new WorkspacePath("/first/second").getParent().asPortableString(), equalTo("/first"));
+ }
+
+ @Test
+ public void equals_same() {
+ assertThat(new WorkspacePath("/first").equals(new WorkspacePath("/first")), equalTo(true));
+ }
+
+ @Test
+ public void equals_different() {
+ assertThat(new WorkspacePath("/first").equals(new WorkspacePath("/second")), equalTo(false));
+ }
+
+ @Test
+ public void equals_relativeVsAbsolute() {
+ assertThat(new WorkspacePath("/first").equals(new WorkspacePath("first")), equalTo(false));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void append_notRelative() {
+ new WorkspacePath("/first").append("/second");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void append_null() {
+ new WorkspacePath("/first").append((String) null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void append_empty() {
+ new WorkspacePath("/first").append("");
+ }
+
+ @Test
+ public void append_ok() {
+ assertThat(new WorkspacePath("/first").append("second").asPortableString(), equalTo("/first/second"));
+ }
+
+ @Test
+ public void appendPath_absolute() {
+ assertThat(new WorkspacePath("/first").append(new WorkspacePath("/second")), equalTo(new WorkspacePath("/first/second")));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void appendPath_null() {
+ new WorkspacePath("/first").append((WorkspacePath) null);
+ }
+
+ @Test
+ public void appendPath_relative() {
+ assertThat(new WorkspacePath("/first").append(new WorkspacePath("second/third")), equalTo(new WorkspacePath("/first/second/third")));
+ }
+
+ @Test
+ public void appendPath_bothRelative() {
+ assertThat(new WorkspacePath("first").append(new WorkspacePath("second/third")), equalTo(new WorkspacePath("first/second/third")));
+ }
+}
diff --git a/shared/modules/api/src/test/java/org/apache/sling/ide/sync/content/WorkspacePathsTest.java b/shared/modules/api/src/test/java/org/apache/sling/ide/sync/content/WorkspacePathsTest.java
new file mode 100644
index 0000000..9310e66
--- /dev/null
+++ b/shared/modules/api/src/test/java/org/apache/sling/ide/sync/content/WorkspacePathsTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.sync.content;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.nio.file.Paths;
+
+import org.junit.Test;
+
+public class WorkspacePathsTest {
+
+ @Test
+ public void fromLocalPath() {
+
+ assertThat(WorkspacePaths.fromOsPath(Paths.get("some", "path")), equalTo(new WorkspacePath("some/path")));
+ }
+}
--
To stop receiving notification emails like this one, please contact
rombert@apache.org.