Package org.apache.sling.ide.eclipse.core.internal

Source Code of org.apache.sling.ide.eclipse.core.internal.ResourceChangeCommandFactory

/*
* 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;

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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;
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.transport.Command;
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;

/**
* The <tt>ResourceChangeCommandFactory</tt> creates new {@link #Command commands} correspoding to resource addition,
* change, or removal
*
*/
public class ResourceChangeCommandFactory {

    private final Set<String> ignoredFileNames = new HashSet<String>();
    {
        ignoredFileNames.add(".vlt");
        ignoredFileNames.add(".vltignore");
    }

    private final SerializationManager serializationManager;

    public ResourceChangeCommandFactory(SerializationManager serializationManager) {
        this.serializationManager = serializationManager;
    }

    public Command<?> newCommandForAddedOrUpdated(Repository repository, IResource addedOrUpdated) throws CoreException {
        try {
            return addFileCommand(repository, addedOrUpdated);
        } catch (SerializationException e) {
            throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID, "Failed updating " + addedOrUpdated,
                    e));
        } 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 SerializationException,
            CoreException, IOException {

        ResourceAndInfo rai = buildResourceAndInfo(resource, repository);
       
        if (rai == null) {
            return null;
        }

        return repository.newAddOrUpdateNodeCommand(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,
            SerializationException, 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())) {
            InputStream contents = null;
            try {
                IFile file = (IFile) resource;
                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) {
                Activator.getDefault().getPluginLogger().warn(e.getMessage(), e);
                return null;
            } finally {
                IOUtils.closeQuietly(contents);
            }
        } 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);
        }

        if (isFiltered(filter, resourceProxy, resource)) {
            return null;
        }

        return new ResourceAndInfo(resourceProxy, info);
    }

    private FileInfo createFileInfo(IResource resource) throws SerializationException,
            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;
    }

    private FilterResult getFilterResult(IResource resource, Filter filter) throws SerializationException {

        File contentSyncRoot = ProjectUtil.getSyncDirectoryFile(resource.getProject());

        String repositoryPath = getRepositoryPathForDeletedResource(resource, contentSyncRoot);

        Activator.getDefault().getPluginLogger().trace("Filtering by {0} for {1}", repositoryPath, resource);

        return filter.filter(contentSyncRoot, repositoryPath);
    }

    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 boolean isFiltered(Filter filter, ResourceProxy resourceProxy, IResource resource) {

        if (filter == null) {
            return false;
        }

        FilterResult filterResult = filter.filter(ProjectUtil.getSyncDirectoryFile(resource.getProject()),
                resourceProxy.getPath());
        Activator.getDefault().getPluginLogger().trace("FilterResult for {0} is {1}", resource, filterResult);

        return filterResult == FilterResult.DENY || filterResult == FilterResult.PREREQUISITE;
    }

    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 = (IFolder) syncDirectory.findMember(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;
                }

                InputStream contents = possibleSerializationFile.getContents();
                ResourceProxy serializationData;
                try {
                    serializationData = serializationManager.readSerializationData(
                            parentSerializationFilePath.toPortableString(), contents);
                } finally {
                    IOUtils.closeQuietly(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;
            InputStream contents = null;
            try {
                contents = serializationFile.getContents();
                String serializationFilePath = serializationResource.getFullPath()
                        .makeRelativeTo(syncDirectory.getFullPath()).toPortableString();
                ResourceProxy resourceProxy = serializationManager.readSerializationData(serializationFilePath, contents);
                normaliseResourceChildren(serializationFile, resourceProxy, syncDirectory, repository);

                return resourceProxy;
            } finally {
                IOUtils.closeQuietly(contents);
            }
        }

        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<String, IResource>();
        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);
        } catch (SerializationException e) {
            throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID, "Failed removing" + removed, e));
        } 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,
            SerializationException, 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());

        if (filter != null) {
            FilterResult filterResult = getFilterResult(resource, filter);
            if (filterResult == FilterResult.DENY || filterResult == FilterResult.PREREQUISITE) {
                return null;
            }
        }
       
        String resourceLocation = getRepositoryPathForDeletedResource(resource,
                ProjectUtil.getSyncDirectoryFile(resource.getProject()));
       
        // make sure that a 'plain' folder being deleted does not signal that the content structure
        // was rearranged under a covering parent aggregate
        if (resource.getType() == IResource.FOLDER) {
            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 for resource at {0},  skipping deletion and performing an update instead",
                                resource.getFullPath());
                FileInfo info = createFileInfo(resource);
                return repository.newAddOrUpdateNodeCommand(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) {
                return null;
            }

            return repository.newReorderChildNodesCommand(rai.getResource());
        } catch (SerializationException e) {
            throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID, "Failed reordering child nodes for "
                    + res, e));
        } catch (IOException e) {
            throw new CoreException(new Status(Status.ERROR, Activator.PLUGIN_ID, "Failed reordering child nodes for "
                    + res, e));
        }
    }
}
TOP

Related Classes of org.apache.sling.ide.eclipse.core.internal.ResourceChangeCommandFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.