| /* |
| * 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.ui.internal; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.jackrabbit.util.Text; |
| 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; |
| import org.apache.sling.ide.filter.FilterResult; |
| import org.apache.sling.ide.filter.IgnoredResources; |
| import org.apache.sling.ide.log.Logger; |
| import org.apache.sling.ide.serialization.SerializationData; |
| 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.Repository; |
| import org.apache.sling.ide.transport.RepositoryException; |
| import org.apache.sling.ide.transport.ResourceProxy; |
| import org.apache.sling.ide.transport.Result; |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceVisitor; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.wst.server.core.IServer; |
| |
| // intentionally does not implement IRunnableWithProgress to cut dependency on JFace |
| public class ImportRepositoryContentAction { |
| |
| private final IServer server; |
| private final IPath projectRelativePath; |
| private final IProject project; |
| private final Logger logger; |
| |
| private SerializationManager serializationManager; |
| private SerializationDataBuilder builder; |
| private IgnoredResources ignoredResources; |
| private IProgressMonitor monitor; |
| private Repository repository; |
| private Filter filter; |
| private File contentSyncRoot; |
| private IFolder contentSyncRootDir; |
| private Set<IResource> currentResources; |
| private IPath repositoryImportRoot; |
| |
| /** |
| * @param server |
| * @param projectRelativePath |
| * @param project |
| * @throws SerializationException |
| */ |
| public ImportRepositoryContentAction(IServer server, IPath projectRelativePath, IProject project, |
| SerializationManager serializationManager) throws SerializationException { |
| this.logger = Activator.getDefault().getPluginLogger(); |
| this.server = server; |
| this.projectRelativePath = projectRelativePath; |
| this.project = project; |
| this.serializationManager = serializationManager; |
| this.ignoredResources = new IgnoredResources(); |
| this.currentResources = new HashSet<>(); |
| } |
| |
| public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException, |
| SerializationException, CoreException { |
| |
| // TODO: We should try to make this give 'nice' progress feedback (aka here's what I'm processing) |
| monitor.beginTask("Repository import", IProgressMonitor.UNKNOWN); |
| |
| this.monitor = monitor; |
| |
| repository = ServerUtil.getConnectedRepository(server, monitor); |
| |
| this.builder = serializationManager.newBuilder( |
| repository, ProjectUtil.getSyncDirectoryFile(project)); |
| |
| SerializationKindManager skm; |
| |
| try { |
| skm = new SerializationKindManager(); |
| skm.init(repository); |
| } catch (RepositoryException e1) { |
| throw new InvocationTargetException(e1); |
| } |
| |
| filter = ProjectUtil.loadFilter(project); |
| |
| ProgressUtils.advance(monitor, 1); |
| |
| try { |
| |
| contentSyncRootDir = ProjectUtil.getSyncDirectory(project); |
| repositoryImportRoot = projectRelativePath |
| .makeRelativeTo(contentSyncRootDir.getProjectRelativePath()) |
| .makeAbsolute(); |
| |
| contentSyncRoot = ProjectUtil.getSyncDirectoryFullPath(project).toFile(); |
| |
| readVltIgnoresNotUnderImportRoot(contentSyncRootDir, repositoryImportRoot); |
| |
| ProgressUtils.advance(monitor, 1); |
| |
| Activator |
| .getDefault() |
| .getPluginLogger() |
| .trace("Starting import; repository start point is {0}, workspace start point is {1}", |
| repositoryImportRoot, projectRelativePath); |
| |
| recordNotIgnoredResources(); |
| |
| ProgressUtils.advance(monitor, 1); |
| |
| crawlChildrenAndImport(repositoryImportRoot.toPortableString()); |
| |
| removeNotIgnoredAndNotUpdatedResources(new NullProgressMonitor()); |
| |
| ProgressUtils.advance(monitor, 1); |
| |
| } catch (OperationCanceledException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new InvocationTargetException(e); |
| } finally { |
| if (builder!=null) { |
| builder.destroy(); |
| builder = null; |
| } |
| monitor.done(); |
| } |
| |
| } |
| |
| private void readVltIgnoresNotUnderImportRoot(IFolder syncDir, IPath repositoryImportRoot) throws IOException, |
| CoreException { |
| |
| IFolder current = syncDir; |
| for (int i = 0; i < repositoryImportRoot.segmentCount(); i++) { |
| IPath repoPath = current.getProjectRelativePath().makeRelativeTo(syncDir.getProjectRelativePath()) |
| .makeAbsolute(); |
| parseIgnoreFiles(current, repoPath.toPortableString()); |
| current = (IFolder) current.findMember(repositoryImportRoot.segment(i)); |
| } |
| |
| } |
| |
| private void recordNotIgnoredResources() throws CoreException { |
| |
| final ResourceChangeCommandFactory rccf = new ResourceChangeCommandFactory(serializationManager, Activator.getDefault().getPreferences().getIgnoredFileNamesForSync()); |
| |
| IResource importStartingPoint = contentSyncRootDir.findMember(repositoryImportRoot); |
| if (importStartingPoint == null) { |
| return; |
| } |
| importStartingPoint.accept(new IResourceVisitor() { |
| |
| @Override |
| public boolean visit(IResource resource) throws CoreException { |
| |
| try { |
| ResourceAndInfo rai = rccf.buildResourceAndInfo(resource, repository); |
| |
| if (rai == null) { |
| // can be a prerequisite |
| return true; |
| } |
| |
| String repositoryPath = rai.getResource().getPath(); |
| |
| FilterResult filterResult = filter.filter(repositoryPath); |
| |
| if (ignoredResources.isIgnored(repositoryPath)) { |
| return false; |
| } |
| |
| if (filterResult == FilterResult.ALLOW) { |
| currentResources.add(resource); |
| return true; |
| } |
| |
| return false; |
| } catch (IOException e) { |
| throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, |
| "Failed reading current project's resources", e)); |
| } |
| } |
| }); |
| |
| logger.trace("Found {0} not ignored local resources", currentResources.size()); |
| } |
| |
| private void removeNotIgnoredAndNotUpdatedResources(IProgressMonitor monitor) throws CoreException { |
| |
| logger.trace("Found {0} resources to clean up", currentResources.size()); |
| |
| for (IResource resource : currentResources) { |
| if (resource.exists()) { |
| logger.trace("Deleting {0}", resource); |
| resource.delete(true, monitor); |
| } |
| } |
| |
| } |
| |
| /** |
| * Crawls the repository and recursively imports founds resources |
| * @param path the current path to import from |
| * @param tracer |
| * @throws JSONException |
| * @throws RepositoryException |
| * @throws CoreException |
| * @throws IOException |
| */ |
| // TODO: This probably should be pushed into the service layer |
| private void crawlChildrenAndImport(String path) |
| throws RepositoryException, CoreException, IOException, SerializationException { |
| |
| logger.trace("crawlChildrenAndImport({0}, {1}, {2}, {3}", repository, path, project, projectRelativePath); |
| |
| ResourceProxy resource = executeCommand(repository.newListChildrenNodeCommand(path)); |
| |
| SerializationData serializationData = builder.buildSerializationData(contentSyncRoot, resource); |
| logger.trace("For resource at path {0} got serialization data {1}", resource.getPath(), serializationData); |
| |
| final List<ResourceProxy> resourceChildren = new LinkedList<>(resource.getChildren()); |
| if (serializationData != null) { |
| |
| IPath serializationFolderPath = contentSyncRootDir.getProjectRelativePath().append( |
| serializationData.getFolderPath()); |
| |
| switch (serializationData.getSerializationKind()) { |
| case FILE: { |
| byte[] contents = executeCommand(repository.newGetNodeCommand(path)); |
| createFile(project, getPathForPlainFileNode(resource, serializationFolderPath), contents); |
| |
| if (serializationData.hasContents()) { |
| createFolder(project, serializationFolderPath); |
| createFile(project, serializationFolderPath.append(serializationData.getFileName()), |
| serializationData.getContents()); |
| |
| // special processing for nt:resource nodes |
| for (Iterator<ResourceProxy> it = resourceChildren.iterator(); it.hasNext();) { |
| ResourceProxy child = it.next(); |
| if (Repository.NT_RESOURCE.equals(child.getProperties().get(Repository.JCR_PRIMARY_TYPE))) { |
| |
| ResourceProxy reloadedChildResource = executeCommand(repository |
| .newListChildrenNodeCommand(child.getPath())); |
| |
| logger.trace( |
| "Skipping direct handling of {0} node at {1} ; will additionally handle {2} direct children", |
| Repository.NT_RESOURCE, child.getPath(), reloadedChildResource.getChildren() |
| .size()); |
| |
| if (reloadedChildResource.getChildren().size() != 0) { |
| |
| String pathName = Text.getName(reloadedChildResource.getPath()); |
| pathName = serializationManager.getOsPath(pathName); |
| createFolder(project, serializationFolderPath.append(pathName)); |
| |
| // 2. recursively handle all resources |
| for (ResourceProxy grandChild : reloadedChildResource.getChildren()) { |
| crawlChildrenAndImport(grandChild.getPath()); |
| } |
| } |
| |
| it.remove(); |
| break; |
| } |
| } |
| } |
| break; |
| } |
| case FOLDER: |
| case METADATA_PARTIAL: { |
| |
| IFolder folder = createFolder(project, serializationFolderPath); |
| |
| parseIgnoreFiles(folder, path); |
| |
| if (serializationData.hasContents()) { |
| createFile(project, serializationFolderPath.append(serializationData.getFileName()), |
| serializationData.getContents()); |
| } |
| break; |
| } |
| |
| case METADATA_FULL: { |
| if (serializationData.hasContents()) { |
| createFile(project, serializationFolderPath.append(serializationData.getFileName()), |
| serializationData.getContents()); |
| } |
| break; |
| } |
| } |
| |
| logger.trace("Resource at {0} has children: {1}", resource.getPath(), resourceChildren); |
| |
| if (serializationData.getSerializationKind() == SerializationKind.METADATA_FULL) { |
| return; |
| } |
| } else { |
| logger.trace("No serialization data found for {0}", resource.getPath()); |
| } |
| |
| ProgressUtils.advance(monitor, 1); |
| |
| for (ResourceProxy child : resourceChildren) { |
| |
| if (ignoredResources.isIgnored(child.getPath())) { |
| continue; |
| } |
| |
| if (filter != null) { |
| FilterResult filterResult = filter.filter(child.getPath()); |
| if (filterResult == FilterResult.DENY) { |
| continue; |
| } |
| } |
| |
| crawlChildrenAndImport(child.getPath()); |
| } |
| } |
| |
| /** |
| * Returns the path for serializing the nt:resource data of a nt:file node |
| * |
| * <p> |
| * The path will be one level above the <tt>serializationFolderPath</tt>, and the name will be the last path segment |
| * of the resource. |
| * </p> |
| * |
| * @param resource The resource |
| * @param serializationFolderPath the folder where the serialization data should be stored |
| * @return the path for the plain file node |
| */ |
| private IPath getPathForPlainFileNode(ResourceProxy resource, IPath serializationFolderPath) { |
| |
| // TODO - can we just use the serializationFolderPath ? |
| |
| String name = serializationManager.getOsPath(Text.getName(resource.getPath())); |
| |
| return serializationFolderPath.removeLastSegments(1).append(name); |
| } |
| |
| private void parseIgnoreFiles(IFolder folder, String path) throws IOException, CoreException { |
| // TODO - the parsing should be extracted |
| IResource vltIgnore = folder.findMember(".vltignore"); |
| if (vltIgnore != null && vltIgnore instanceof IFile) { |
| |
| logger.trace("Found ignore file at {0}", vltIgnore.getFullPath()); |
| |
| |
| try (InputStream contents = ((IFile) vltIgnore).getContents()) { |
| List<String> ignoreLines = IOUtils.readLines(contents); |
| for (String ignoreLine : ignoreLines) { |
| logger.trace("Registering ignore rule {0}:{1}", path, ignoreLine); |
| ignoredResources.registerRegExpIgnoreRule(path, ignoreLine); |
| } |
| } |
| } |
| } |
| |
| private <T> T executeCommand(Command<T> command) throws RepositoryException { |
| |
| Result<T> result = command.execute(); |
| return result.get(); |
| } |
| |
| private IFolder createFolder(IProject project, IPath destinationPath) throws CoreException { |
| |
| IFolder destinationFolder = project.getFolder(destinationPath); |
| if (!destinationFolder.exists()) { |
| logger.trace("Creating folder {0}", destinationFolder.getFullPath()); |
| |
| createParents(destinationFolder.getParent()); |
| destinationFolder.create(true, true, null /* TODO progress monitor */); |
| } |
| |
| destinationFolder.setSessionProperty(ResourceUtil.QN_IMPORT_MODIFICATION_TIMESTAMP, |
| destinationFolder.getModificationStamp()); |
| |
| removeTouchedResource(destinationFolder); |
| |
| return destinationFolder; |
| } |
| |
| private void createParents(IContainer container) throws CoreException { |
| if (container.exists() || container.getType() != IResource.FOLDER) { |
| return; |
| } |
| |
| createParents(container.getParent()); |
| createFolder(container.getProject(), container.getProjectRelativePath()); |
| } |
| |
| private void removeTouchedResource(IResource resource) { |
| |
| IResource current = resource; |
| do { |
| currentResources.remove(current); |
| } while ((current = current.getParent()) != null); |
| } |
| |
| private void createFile(IProject project, IPath path, byte[] node) throws CoreException { |
| if (node==null) { |
| throw new IllegalArgumentException("node must not be null"); |
| } |
| |
| IFile destinationFile = project.getFile(path); |
| |
| logger.trace("Writing content file at {0}", path); |
| |
| if (destinationFile.exists()) { |
| /* TODO progress monitor */ |
| destinationFile.setContents(new ByteArrayInputStream(node), IResource.KEEP_HISTORY, null); |
| } else { |
| /* TODO progress monitor */ |
| if (!destinationFile.getParent().exists()) { |
| createParents(destinationFile.getParent()); |
| } |
| destinationFile.create(new ByteArrayInputStream(node), true, null); |
| } |
| |
| removeTouchedResource(destinationFile); |
| |
| destinationFile.setSessionProperty(ResourceUtil.QN_IMPORT_MODIFICATION_TIMESTAMP, |
| destinationFile.getModificationStamp()); |
| } |
| } |