/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.vfs.server; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.vfs.server.search.QueryExpression; import org.eclipse.che.api.vfs.server.search.SearcherProvider; import org.eclipse.che.api.vfs.server.util.LinksHelper; import org.eclipse.che.api.vfs.shared.ItemType; import org.eclipse.che.api.vfs.shared.PropertyFilter; import org.eclipse.che.api.vfs.shared.dto.AccessControlEntry; import org.eclipse.che.api.vfs.shared.dto.File; import org.eclipse.che.api.vfs.shared.dto.Folder; import org.eclipse.che.api.vfs.shared.dto.Item; import org.eclipse.che.api.vfs.shared.dto.ItemList; import org.eclipse.che.api.vfs.shared.dto.ItemNode; import org.eclipse.che.api.vfs.shared.dto.Lock; import org.eclipse.che.api.vfs.shared.dto.Principal; import org.eclipse.che.api.vfs.shared.dto.Property; import org.eclipse.che.api.vfs.shared.dto.ReplacementSet; import org.eclipse.che.api.vfs.shared.dto.Variable; import org.eclipse.che.api.vfs.shared.dto.VirtualFileSystemInfo; import org.eclipse.che.api.vfs.shared.dto.VirtualFileSystemInfo.ACLCapability; import org.eclipse.che.api.vfs.shared.dto.VirtualFileSystemInfo.BasicPermissions; import org.eclipse.che.commons.lang.Deserializer; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.dto.server.DtoFactory; import org.apache.commons.fileupload.FileItem; import org.everrest.core.impl.provider.multipart.OutputItem; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.HeaderParam; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.regex.Pattern; /** * Base implementation of VirtualFileSystem. * * @author andrew00x */ public abstract class VirtualFileSystemImpl implements VirtualFileSystem { protected final String vfsId; protected final URI baseUri; protected final VirtualFileSystemUserContext userContext; protected final MountPoint mountPoint; protected final SearcherProvider searcherProvider; protected final VirtualFileSystemRegistry vfsRegistry; public VirtualFileSystemImpl(String vfsId, URI baseUri, VirtualFileSystemUserContext userContext, MountPoint mountPoint, SearcherProvider searcherProvider, VirtualFileSystemRegistry vfsRegistry) { this.vfsId = vfsId; this.baseUri = baseUri; this.userContext = userContext; this.mountPoint = mountPoint; this.searcherProvider = searcherProvider; this.vfsRegistry = vfsRegistry; } @Override public MountPoint getMountPoint() { return mountPoint; } @Path("copy/{id}") @Override public Item copy(@PathParam("id") String id, @QueryParam("parentId") String parentId) throws NotFoundException, ForbiddenException, ConflictException, ServerException { final VirtualFile virtualFileCopy = mountPoint.getVirtualFileById(id).copyTo(mountPoint.getVirtualFileById(parentId)); return fromVirtualFile(virtualFileCopy, false, PropertyFilter.ALL_FILTER); } @Path("clone") @Override public Item clone(@QueryParam("srcPath") String srcPath, @QueryParam("srcVfsId") String srcVfsId, @QueryParam("parentPath") String parentPath, @QueryParam("name") String name) throws NotFoundException, ForbiddenException, ConflictException, ServerException { final VirtualFile item = vfsRegistry.getProvider(srcVfsId).getMountPoint(true).getVirtualFile(srcPath); final VirtualFile destination = mountPoint.getVirtualFile(parentPath); if (!destination.isFolder()) { throw new ForbiddenException("Unable to perform cloning. Item specified as parent is not a folder."); } return fromVirtualFile(clone(item, destination, name), false, PropertyFilter.ALL_FILTER); } public static VirtualFile clone(VirtualFile item, VirtualFile destination, String name) throws ForbiddenException, ConflictException, ServerException { if (item.isFile()) { InputStream input = null; try { input = item.getContent().getStream(); return destination.createFile(name != null ? name : item.getName(), item.getMediaType(), input); } finally { if (input != null) { try { input.close(); } catch (IOException ignore) { } } } } else { final VirtualFile newFolder = destination.createFolder(name != null ? name : item.getName()); final LazyIterator<VirtualFile> children = item.getChildren(VirtualFileFilter.ALL); while (children.hasNext()) { clone(children.next(), newFolder, null); } return newFolder; } } @Path("file/{parentId}") @Override public File createFile(@PathParam("parentId") String parentId, @QueryParam("name") String name, @HeaderParam("Content-Type") MediaType mediaType, InputStream content) throws NotFoundException, ForbiddenException, ConflictException, ServerException { final VirtualFile parent = mountPoint.getVirtualFileById(parentId); // Have issue with client side. Always have Content-type header is set even if client doesn't set it. // In this case have Content-type is set with "text/plain; charset=UTF-8" which isn't acceptable. // Have agreement with client to send Content-type header with "application/unknown" value if client doesn't want to specify media // type of new file. In this case server takes care about resolving media type of file. final String mt = mediaType == null || ("application".equals(mediaType.getType()) && "unknown".equals(mediaType.getSubtype())) ? null : mediaType.getType() + '/' + mediaType.getSubtype(); final VirtualFile newVirtualFile = parent.createFile(name, mt, content); return (File)fromVirtualFile(newVirtualFile, false, PropertyFilter.ALL_FILTER); } @Path("folder/{parentId}") @Override public Folder createFolder(@PathParam("parentId") String parentId, @QueryParam("name") String name) throws NotFoundException, ForbiddenException, ConflictException, ServerException { final VirtualFile parent = mountPoint.getVirtualFileById(parentId); final VirtualFile newVirtualFile = parent.createFolder(name); return (Folder)fromVirtualFile(newVirtualFile, false, PropertyFilter.ALL_FILTER); } @Path("delete/{id}") @Override public void delete(@PathParam("id") String id, @QueryParam("lockToken") String lockToken) throws NotFoundException, ForbiddenException, ServerException { final VirtualFile virtualFile = mountPoint.getVirtualFileById(id); if (virtualFile.isRoot()) { throw new ForbiddenException("Unable delete root folder. "); } virtualFile.delete(lockToken); } @Path("acl/{id}") @Override public List<AccessControlEntry> getACL(@PathParam("id") String id) throws NotFoundException, ForbiddenException, ServerException { if (getInfo().getAclCapability() == ACLCapability.NONE) { throw new ServerException("ACL feature is not supported. "); } return mountPoint.getVirtualFileById(id).getACL(); } @Path("children/{id}") @Override public ItemList getChildren(@PathParam("id") String folderId, @DefaultValue("-1") @QueryParam("maxItems") int maxItems, @QueryParam("skipCount") int skipCount, @QueryParam("itemType") String itemType, @DefaultValue("false") @QueryParam("includePermissions") Boolean includePermissions, @DefaultValue(PropertyFilter.NONE) @QueryParam("propertyFilter") PropertyFilter propertyFilter) throws NotFoundException, ForbiddenException, ConflictException, ServerException { if (skipCount < 0) { throw new ConflictException("'skipCount' parameter is negative. "); } final ItemType itemTypeType; if (itemType != null) { try { itemTypeType = ItemType.fromValue(itemType); } catch (IllegalArgumentException e) { throw new ForbiddenException(String.format("Unknown type: %s", itemType)); } } else { itemTypeType = null; } final VirtualFile virtualFile = mountPoint.getVirtualFileById(folderId); if (!virtualFile.isFolder()) { throw new ForbiddenException(String.format("Unable get children. Item '%s' is not a folder. ", virtualFile.getPath())); } final VirtualFileFilter filter; if (itemTypeType == null) { filter = VirtualFileFilter.ALL; } else { filter = new VirtualFileFilter() { @Override public boolean accept(VirtualFile file) { try { return (itemTypeType == ItemType.FILE && file.isFile()) || (itemTypeType == ItemType.FOLDER && file.isFolder()); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } }; } final LazyIterator<VirtualFile> children = virtualFile.getChildren(filter); try { if (skipCount > 0) { children.skip(skipCount); } } catch (NoSuchElementException nse) { throw new ConflictException("'skipCount' parameter is greater then total number of items. "); } final List<Item> items = new ArrayList<>(); for (int count = 0; children.hasNext() && (maxItems < 0 || count < maxItems); count++) { items.add(fromVirtualFile(children.next(), includePermissions, propertyFilter)); } return DtoFactory.getInstance().createDto(ItemList.class).withItems(items).withNumItems(children.size()) .withHasMoreItems(children.hasNext()); } @Override public ItemList getChildren(String folderId, int maxItems, int skipCount, String itemType, boolean includePermissions) throws NotFoundException, ForbiddenException, ConflictException, ServerException { return getChildren(folderId, maxItems, skipCount, itemType, includePermissions, PropertyFilter.ALL_FILTER); } @Path("tree/{id}") @Override public ItemNode getTree(@PathParam("id") String folderId, @DefaultValue("-1") @QueryParam("depth") int depth, @DefaultValue("false") @QueryParam("includePermissions") Boolean includePermissions, @DefaultValue(PropertyFilter.NONE) @QueryParam("propertyFilter") PropertyFilter propertyFilter) throws NotFoundException, ForbiddenException, ServerException { final VirtualFile virtualFile = mountPoint.getVirtualFileById(folderId); if (!virtualFile.isFolder()) { throw new ForbiddenException(String.format("Unable get tree. Item '%s' is not a folder. ", virtualFile.getPath())); } return DtoFactory.getInstance().createDto(ItemNode.class) .withItem(fromVirtualFile(virtualFile, includePermissions, propertyFilter)) .withChildren(getTreeLevel(virtualFile, depth, includePermissions, propertyFilter)); } @Override public ItemNode getTree(String folderId, int depth, boolean includePermissions) throws NotFoundException, ForbiddenException, ServerException { return getTree(folderId, depth, includePermissions, PropertyFilter.ALL_FILTER); } private List<ItemNode> getTreeLevel(VirtualFile virtualFile, int depth, boolean includePermissions, PropertyFilter propertyFilter) throws ServerException { if (depth == 0 || !virtualFile.isFolder()) { return null; } final LazyIterator<VirtualFile> children = virtualFile.getChildren(VirtualFileFilter.ALL); final List<ItemNode> level = new ArrayList<>(children.size()); while (children.hasNext()) { final VirtualFile next = children.next(); level.add(DtoFactory.getInstance().createDto(ItemNode.class) .withItem(fromVirtualFile(next, includePermissions, propertyFilter)) .withChildren(getTreeLevel(next, depth - 1, includePermissions, propertyFilter))); } return level; } @Path("content/{id}") @Override public ContentStream getContent(@PathParam("id") String id) throws NotFoundException, ForbiddenException, ServerException { return mountPoint.getVirtualFileById(id).getContent(); } @Path("contentbypath/{path:.*}") @Override public ContentStream getContent(@PathParam("path") String path, @QueryParam("versionId") String versionId) throws NotFoundException, ForbiddenException, ServerException { return mountPoint.getVirtualFile(path).getContent(); } @Override public abstract VirtualFileSystemInfo getInfo() throws ServerException; @Path("item/{id}") @Override public Item getItem(@PathParam("id") String id, @DefaultValue("false") @QueryParam("includePermissions") Boolean includePermissions, @DefaultValue(PropertyFilter.ALL) @QueryParam("propertyFilter") PropertyFilter propertyFilter) throws NotFoundException, ForbiddenException, ServerException { return fromVirtualFile(mountPoint.getVirtualFileById(id), includePermissions, propertyFilter); } @Override public Item getItem(String id, boolean includePermissions) throws NotFoundException, ForbiddenException, ServerException { return getItem(id, includePermissions, PropertyFilter.ALL_FILTER); } @Path("itembypath/{path:.*}") @Override public Item getItemByPath(@PathParam("path") String path, @QueryParam("versionId") String versionId, @DefaultValue("false") @QueryParam("includePermissions") Boolean includePermissions, @DefaultValue(PropertyFilter.ALL) @QueryParam("propertyFilter") PropertyFilter propertyFilter) throws NotFoundException, ForbiddenException, ServerException { VirtualFile virtualFile = mountPoint.getVirtualFile(path); if (virtualFile.isFile()) { if (versionId != null) { virtualFile = virtualFile.getVersion(versionId); } } else if (versionId != null) { throw new ForbiddenException(String.format("Object '%s' is not a file. Version ID must not be set. ", path)); } return fromVirtualFile(virtualFile, includePermissions, propertyFilter); } @Override public Item getItemByPath(String path, String versionId, boolean includePermissions) throws NotFoundException, ForbiddenException, ServerException { return getItemByPath(path, versionId, includePermissions, PropertyFilter.ALL_FILTER); } @Path("version/{id}/{versionId}") @Override public ContentStream getVersion(@PathParam("id") String id, @PathParam("versionId") String versionId) throws NotFoundException, ForbiddenException, ServerException { return mountPoint.getVirtualFileById(id).getVersion(versionId).getContent(); } @Path("version-history/{id}") @Override public ItemList getVersions(@PathParam("id") String id, @DefaultValue("-1") @QueryParam("maxItems") int maxItems, @QueryParam("skipCount") int skipCount, @DefaultValue(PropertyFilter.ALL) @QueryParam("propertyFilter") PropertyFilter propertyFilter) throws NotFoundException, ForbiddenException, ConflictException, ServerException { if (skipCount < 0) { throw new ConflictException("'skipCount' parameter is negative. "); } final VirtualFile virtualFile = mountPoint.getVirtualFileById(id); if (!virtualFile.isFile()) { throw new ForbiddenException( String.format("Unable get versions of '%s'. Versioning allowed for files only. ", virtualFile.getPath())); } final LazyIterator<VirtualFile> versions = virtualFile.getVersions(VirtualFileFilter.ALL); try { if (skipCount > 0) { versions.skip(skipCount); } } catch (NoSuchElementException nse) { throw new ConflictException("'skipCount' parameter is greater then total number of items. "); } final List<Item> items = new ArrayList<>(); for (int count = 0; versions.hasNext() && (maxItems < 0 || count < maxItems); count++) { items.add(fromVirtualFile(versions.next(), false, propertyFilter)); } return DtoFactory.getInstance().createDto(ItemList.class).withItems(items).withNumItems(versions.size()) .withHasMoreItems(versions.hasNext()); } @Override public ItemList getVersions(String id, int maxItems, int skipCount) throws NotFoundException, ForbiddenException, ConflictException, ServerException { return getVersions(id, maxItems, skipCount, PropertyFilter.ALL_FILTER); } @Path("lock/{id}") @Override public Lock lock(@PathParam("id") String id, @DefaultValue("0") @QueryParam("timeout") long timeout) throws NotFoundException, ForbiddenException, ConflictException, ServerException { if (!getInfo().isLockSupported()) { throw new ServerException("Locking is not supported. "); } final VirtualFileSystemUser user = userContext.getVirtualFileSystemUser(); final VirtualFile virtualFile = mountPoint.getVirtualFileById(id); if (!virtualFile.isFile()) { throw new ForbiddenException(String.format("Unable lock '%s'. Locking allowed for files only. ", virtualFile.getPath())); } final String lockToken = mountPoint.getVirtualFileById(id).lock(timeout); return DtoFactory.getInstance().createDto(Lock.class).withLockToken(lockToken).withOwner(user.getUserId()).withTimeout(timeout); } @Path("move/{id}") @Override public Item move(@PathParam("id") String id, @QueryParam("parentId") String parentId, @QueryParam("lockToken") String lockToken) throws NotFoundException, ForbiddenException, ConflictException, ServerException { return fromVirtualFile(mountPoint.getVirtualFileById(id).moveTo(mountPoint.getVirtualFileById(parentId), lockToken), false, PropertyFilter.ALL_FILTER); } @Path("rename/{id}") @Override public Item rename(@PathParam("id") String id, @QueryParam("mediaType") MediaType newMediaType, @QueryParam("newname") String newName, @QueryParam("lockToken") String lockToken) throws NotFoundException, ForbiddenException, ConflictException, ServerException { if ((newName == null || newName.isEmpty()) && newMediaType == null) { // Nothing to do. Return unchanged object. return getItem(id, false, PropertyFilter.ALL_FILTER); } final VirtualFile origin = mountPoint.getVirtualFileById(id); final VirtualFile renamedVirtualFile = origin.rename(newName, newMediaType == null ? null : newMediaType.toString(), lockToken); return fromVirtualFile(renamedVirtualFile, false, PropertyFilter.ALL_FILTER); } @Path("replace/{path:.*}") @Override public void replace(@PathParam("path") String path, List<ReplacementSet> replacements, @QueryParam("lockToken") String lockToken) throws NotFoundException, ForbiddenException, ConflictException, ServerException { VirtualFile projectRoot = mountPoint.getVirtualFile(path); if (!projectRoot.isFolder()) { throw new ConflictException("Given path must be an project root folder. "); } final Map<String, ReplacementContainer> changesPerFile = new HashMap<>(); // fill changes matrix first for (final ReplacementSet replacement : replacements) { for (final String regex : replacement.getFiles()) { Pattern pattern = Pattern.compile(regex); ItemNode rootNode = getTree(projectRoot.getId(), -1, false, PropertyFilter.ALL_FILTER); LinkedList<ItemNode> q = new LinkedList<>(); q.add(rootNode); while (!q.isEmpty()) { ItemNode node = q.pop(); Item item = node.getItem(); if (item.getItemType().equals(ItemType.FOLDER)) { q.addAll(node.getChildren()); } else if (item.getItemType().equals(ItemType.FILE)) { // for cases like: src/main/java/(.*) String itemInternalPath = item.getPath().substring(projectRoot.getPath().length() + 1); if (pattern.matcher(item.getName()).matches() || pattern.matcher(itemInternalPath).matches()) { ReplacementContainer container = (changesPerFile.get(item.getPath()) != null) ? changesPerFile.get(item.getPath()) : new ReplacementContainer(); for (Variable variable : replacement.getEntries()) { String replaceMode = variable.getReplacemode(); if (replaceMode == null || "variable_singlepass".equals(replaceMode)) { container.getVariableProps().put(variable.getFind(), variable.getReplace()); } else if ("text_multipass".equals(replaceMode)) { container.getTextProps().put(variable.getFind(), variable.getReplace()); } } changesPerFile.put(item.getPath(), container); } } } } } //now apply changes matrix for (Map.Entry<String, ReplacementContainer> entry : changesPerFile.entrySet()) { try { if (entry.getValue().hasReplacements()) { ContentStream cs = mountPoint.getVirtualFile(entry.getKey()).getContent(); String content = IoUtil.readAndCloseQuietly(cs.getStream()); String modified = Deserializer.resolveVariables(content, entry.getValue().getVariableProps(), false); for (Map.Entry<String, String> replacement : entry.getValue().getTextProps().entrySet()) { if (modified.contains(replacement.getKey())) { modified = modified.replace(replacement.getKey(), replacement.getValue()); } } //better to compare big strings by hash codes first if (!(content.hashCode() == modified.hashCode()) || !content.equals(modified)) { mountPoint.getVirtualFile(entry.getKey()) .updateContent(new ByteArrayInputStream(modified.getBytes( StandardCharsets.UTF_8)), lockToken); } } } catch (IOException e) { //LOG.warn(e.getMessage(), e); } } } @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) @Override public ItemList search(MultivaluedMap<String, String> query, @DefaultValue("-1") @QueryParam("maxItems") int maxItems, @QueryParam("skipCount") int skipCount, @DefaultValue(PropertyFilter.ALL) @QueryParam("propertyFilter") PropertyFilter propertyFilter) throws ConflictException, ServerException { if (searcherProvider != null) { if (skipCount < 0) { throw new ConflictException("'skipCount' parameter is negative. "); } final QueryExpression expr = new QueryExpression() .setPath(query.getFirst("path")) .setName(query.getFirst("name")) .setMediaType(query.getFirst("mediaType")) .setText(query.getFirst("text")); final String[] result = searcherProvider.getSearcher(mountPoint, true).search(expr); if (skipCount > 0) { if (skipCount > result.length) { throw new ConflictException("'skipCount' parameter is greater then total number of items. "); } } final int length = maxItems > 0 ? Math.min(result.length, maxItems) : result.length; final List<Item> items = new ArrayList<>(length); for (int i = skipCount; i < length; i++) { String path = result[i]; try { items.add(fromVirtualFile(mountPoint.getVirtualFile(path), false, propertyFilter)); } catch (NotFoundException | ForbiddenException ignored) { } } return DtoFactory.getInstance().createDto(ItemList.class).withItems(items).withNumItems(result.length) .withHasMoreItems(length < result.length); } throw new ServerException("Not supported. "); } @Override public ItemList search(MultivaluedMap<String, String> query, int maxItems, int skipCount) throws ConflictException, ServerException { return search(query, maxItems, skipCount, PropertyFilter.ALL_FILTER); } @Override public ItemList search(@QueryParam("statement") String statement, @DefaultValue("-1") @QueryParam("maxItems") int maxItems, @QueryParam("skipCount") int skipCount) throws ServerException { // No plan to support SQL at the moment. throw new ServerException("Not supported. "); } @Path("unlock/{id}") @Override public void unlock(@PathParam("id") String id, @QueryParam("lockToken") String lockToken) throws NotFoundException, ForbiddenException, ConflictException, ServerException { if (!getInfo().isLockSupported()) { throw new ServerException("Locking is not supported. "); } final VirtualFile virtualFile = mountPoint.getVirtualFileById(id); if (!virtualFile.isFile()) { throw new ConflictException( String.format("Unable unlock '%s'. Item isn't locked. Locking allowed for files only. ", virtualFile.getPath())); } virtualFile.unlock(lockToken); } @Path("acl/{id}") @Override public void updateACL(@PathParam("id") String id, List<AccessControlEntry> acl, @DefaultValue("false") @QueryParam("override") Boolean override, @QueryParam("lockToken") String lockToken) throws NotFoundException, ForbiddenException, ServerException { if (getInfo().getAclCapability() != ACLCapability.MANAGE) { throw new ServerException("Managing of ACL is not supported. "); } mountPoint.getVirtualFileById(id).updateACL(acl, override, lockToken); } @Path("content/{id}") @Override public void updateContent( @PathParam("id") String id, @DefaultValue(MediaType.APPLICATION_OCTET_STREAM) @HeaderParam("Content-Type") MediaType mediaType, InputStream newContent, @QueryParam("lockToken") String lockToken) throws NotFoundException, ForbiddenException, ServerException { // Have issue with client side. Always have Content-type header is set even if client doesn't set it. // In this case have Content-type is set with "text/plain; charset=UTF-8" which isn't acceptable. // Have agreement with client to send Content-type header with "application/unknown" value if client doesn't want to specify media // type of new file. In this case server takes care about resolving media type of file. final String mt = mediaType == null || ("application".equals(mediaType.getType()) && "unknown".equals(mediaType.getSubtype())) ? null : mediaType.getType() + '/' + mediaType.getSubtype(); mountPoint.getVirtualFileById(id).updateContent(mt, newContent, lockToken); } @Path("item/{id}") @Override public Item updateItem(@PathParam("id") String id, List<Property> properties, @QueryParam("lockToken") String lockToken) throws NotFoundException, ForbiddenException, ServerException { final VirtualFile virtualFile = mountPoint.getVirtualFileById(id); virtualFile.updateProperties(properties, lockToken); return fromVirtualFile(virtualFile, false, PropertyFilter.ALL_FILTER); } @Path("export/{folderId}") @Override public ContentStream exportZip(@PathParam("folderId") String folderId) throws NotFoundException, ForbiddenException, ServerException { return exportZip(mountPoint.getVirtualFileById(folderId)); } // For usage from Project API. public static ContentStream exportZip(VirtualFile folder) throws ForbiddenException, ServerException { return folder.zip(VirtualFileFilter.ALL); } @Path("export/{folderId}") @Override public Response exportZip(@PathParam("folderId") String folderId, InputStream in) throws NotFoundException, ForbiddenException, ServerException { return exportZip(mountPoint.getVirtualFileById(folderId), in); } @Path("export/{folderId}") @Override public Response exportZipMultipart(@PathParam("folderId") String folderId, InputStream in) throws NotFoundException, ForbiddenException, ServerException { return exportZipMultipart(mountPoint.getVirtualFileById(folderId), in); } // For usage from Project API. public static Response exportZipMultipart(VirtualFile folder, InputStream in) throws ForbiddenException, ServerException { final List<String> deleted = new LinkedList<>(); final ContentStream zip = exportZip(folder, in, deleted); if (zip == null) { return Response.status(204).build(); } final List<OutputItem> multipart = new LinkedList<>(); // String name, Object entity, MediaType mediaType, String fileName final OutputItem updates = OutputItem.create("updates", zip.getStream(), MediaType.valueOf("application/zip"), zip.getFileName()); updates.getHeaders().putSingle(HttpHeaders.CONTENT_LENGTH, Long.toString(zip.getLength())); multipart.add(updates); if (!deleted.isEmpty()) { multipart.add(OutputItem.create("removed-paths", deleted, MediaType.APPLICATION_JSON_TYPE)); } final String boundary = NameGenerator.generate(null, 8); return Response .ok(new GenericEntity<List<OutputItem>>(multipart) { }, "multipart/form-data; boundary=" + boundary) .lastModified(zip.getLastModificationDate()) .build(); } // For usage from Project API. public static Response exportZip(VirtualFile folder, InputStream in) throws ForbiddenException, ServerException { final List<String> deleted = new LinkedList<>(); final ContentStream zip = exportZip(folder, in, deleted); if (zip == null) { return Response.status(204).build(); } final Response.ResponseBuilder responseBuilder = Response .ok(zip.getStream(), zip.getMimeType()) .lastModified(zip.getLastModificationDate()) .header(HttpHeaders.CONTENT_LENGTH, Long.toString(zip.getLength())) .header("Content-Disposition", "attachment; filename=\"" + zip.getFileName() + '"'); if (!deleted.isEmpty()) { final StringBuilder buff = new StringBuilder(); for (String str : deleted) { if (buff.length() > 0) { buff.append(','); } buff.append(str); } responseBuilder.header("x-removed-paths", deleted.toString()); } return responseBuilder.build(); } // For usage from Project API. protected static ContentStream exportZip(VirtualFile folder, InputStream in, List<String> deleted) throws ForbiddenException, ServerException { final List<Pair<String, String>> remote = new LinkedList<>(); final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line; try { while ((line = reader.readLine()) != null) { String hash = line.substring(0, 32); // 32 is length of MD-5 hash sum int startPath = 33; int l = line.length(); while (startPath < l && Character.isWhitespace(line.charAt(startPath))) { startPath++; } String relPath = line.substring(startPath); remote.add(Pair.of(hash, relPath)); } } catch (IOException e) { throw new ServerException(e.getMessage(), e); } if (remote.isEmpty()) { return folder.zip(VirtualFileFilter.ALL); } final LazyIterator<Pair<String, String>> md5Sums = folder.countMd5Sums(); final int size = md5Sums.size(); final List<Pair<String, String>> local = size > 0 ? new ArrayList<Pair<String, String>>(size) : new ArrayList<Pair<String, String>>(); while (md5Sums.hasNext()) { local.add(md5Sums.next()); } final Comparator<Pair<String, String>> comp = new Comparator<Pair<String, String>>() { @Override public int compare(Pair<String, String> o1, Pair<String, String> o2) { return o1.second.compareTo(o2.second); } }; Collections.sort(remote, comp); Collections.sort(local, comp); int remoteIndex = 0; int localIndex = 0; final List<Pair<String, org.eclipse.che.api.vfs.server.Path>> diff = new LinkedList<>(); while (remoteIndex < remote.size() && localIndex < local.size()) { final Pair<String, String> remoteItem = remote.get(remoteIndex); final Pair<String, String> localItem = local.get(localIndex); // compare path int r = remoteItem.second.compareTo(localItem.second); if (r == 0) { // remote and local file exist, compare md5sum if (!remoteItem.first.equals(localItem.first)) { diff.add(Pair.of(remoteItem.second, folder.getVirtualFilePath().newPath(localItem.second))); } remoteIndex++; localIndex++; } else if (r > 0) { // new file diff.add(Pair.of((String)null, folder.getVirtualFilePath().newPath(localItem.second))); localIndex++; } else { // deleted file diff.add(Pair.of(remoteItem.second, (org.eclipse.che.api.vfs.server.Path)null)); remoteIndex++; } } while (remoteIndex < remote.size()) { diff.add(Pair.of(remote.get(remoteIndex++).second, (org.eclipse.che.api.vfs.server.Path)null)); } while (localIndex < local.size()) { diff.add(Pair.of((String)null, folder.getVirtualFilePath().newPath(local.get(localIndex++).second))); } if (diff.isEmpty()) { return null; } final ContentStream zip = folder.zip(new VirtualFileFilter() { @Override public boolean accept(VirtualFile file) { for (Pair<String, org.eclipse.che.api.vfs.server.Path> pair : diff) { if (pair.second != null && (pair.second.equals(file.getVirtualFilePath()) || pair.second.isChild(file.getVirtualFilePath()))) { return true; } } return false; } }); deleted.clear(); for (Pair<String, org.eclipse.che.api.vfs.server.Path> pair : diff) { if (pair.first != null && pair.second == null) { deleted.add(pair.first); } } return zip; } @Path("import/{parentId}") @Override public void importZip(@PathParam("parentId") String parentId, InputStream in, @DefaultValue("false") @QueryParam("overwrite") Boolean overwrite, @DefaultValue("false") @QueryParam("skipFirstLevel") Boolean skipFirstLevel) throws NotFoundException, ForbiddenException, ConflictException, ServerException { final VirtualFile parent = mountPoint.getVirtualFileById(parentId); importZip(parent, in, overwrite, skipFirstLevel); } // For usage from Project API. public static void importZip(VirtualFile parent, InputStream in, boolean overwrite, boolean skipFirstLevel) throws ForbiddenException, ConflictException, ServerException { int stripNum = skipFirstLevel ? 1 : 0; parent.unzip(in, overwrite, stripNum); } @Path("downloadfile/{id}") @Override public Response downloadFile(@PathParam("id") String id) throws NotFoundException, ForbiddenException, ServerException { final ContentStream content = getContent(id); return Response .ok(content.getStream(), content.getMimeType()) .lastModified(content.getLastModificationDate()) .header(HttpHeaders.CONTENT_LENGTH, Long.toString(content.getLength())) .header("Content-Disposition", "attachment; filename=\"" + content.getFileName() + '"') .build(); } @Path("uploadfile/{parentId}") @Override public Response uploadFile(@PathParam("parentId") String parentId, Iterator<FileItem> formData) throws NotFoundException, ForbiddenException, ConflictException, ServerException { return uploadFile(mountPoint.getVirtualFileById(parentId), formData); } public static Response uploadFile(VirtualFile parent, Iterator<FileItem> formData) throws ForbiddenException, ConflictException, ServerException { try { FileItem contentItem = null; String mediaType = null; String name = null; boolean overwrite = false; while (formData.hasNext()) { FileItem item = formData.next(); if (!item.isFormField()) { if (contentItem == null) { contentItem = item; } else { throw new ServerException("More then one upload file is found but only one should be. "); } } else if ("mimeType".equals(item.getFieldName())) { mediaType = item.getString().trim(); } else if ("name".equals(item.getFieldName())) { name = item.getString().trim(); } else if ("overwrite".equals(item.getFieldName())) { overwrite = Boolean.parseBoolean(item.getString().trim()); } } if (contentItem == null) { throw new ServerException("Cannot find file for upload. "); } if (name == null || name.isEmpty()) { name = contentItem.getName(); } if (mediaType == null || mediaType.isEmpty()) { mediaType = contentItem.getContentType(); } if (mediaType != null) { final MediaType mediaTypeType = MediaType.valueOf(mediaType); mediaType = mediaTypeType.getType() + '/' + mediaTypeType.getSubtype(); } try { try { parent.createFile(name, mediaType, contentItem.getInputStream()); } catch (ConflictException e) { if (!overwrite) { throw new ConflictException("Unable upload file. Item with the same name exists. "); } parent.getChild(name).updateContent(mediaType, contentItem.getInputStream(), null); } } catch (IOException ioe) { throw new ServerException(ioe.getMessage(), ioe); } return Response.ok("", MediaType.TEXT_HTML).build(); } catch (ForbiddenException | ConflictException | ServerException e) { HtmlErrorFormatter.sendErrorAsHTML(e); // never thrown throw e; } } @Path("downloadzip/{folderId}") @Override public Response downloadZip(@PathParam("folderId") String folderId) throws NotFoundException, ForbiddenException, ServerException { final ContentStream zip = exportZip(folderId); return Response // .ok(zip.getStream(), zip.getMimeType()) // .lastModified(zip.getLastModificationDate()) // .header(HttpHeaders.CONTENT_LENGTH, Long.toString(zip.getLength())) // .header("Content-Disposition", "attachment; filename=\"" + zip.getFileName() + '"') // .build(); } @Path("uploadzip/{parentId}") @Override public Response uploadZip(@PathParam("parentId") String parentId, Iterator<FileItem> formData) throws NotFoundException, ForbiddenException, ConflictException, ServerException { return uploadZip(mountPoint.getVirtualFileById(parentId), formData); } public static Response uploadZip(VirtualFile parent, Iterator<FileItem> formData) throws ForbiddenException, ConflictException, ServerException { try { FileItem contentItem = null; boolean overwrite = false; boolean skipFirstLevel = false; while (formData.hasNext()) { FileItem item = formData.next(); if (!item.isFormField()) { if (contentItem == null) { contentItem = item; } else { throw new ServerException("More then one upload file is found but only one should be. "); } } else if ("overwrite".equals(item.getFieldName())) { overwrite = Boolean.parseBoolean(item.getString().trim()); } else if ("skipFirstLevel".equals(item.getFieldName())) { skipFirstLevel = Boolean.parseBoolean(item.getString().trim()); } } if (contentItem == null) { throw new ServerException("Cannot find file for upload. "); } try { importZip(parent, contentItem.getInputStream(), overwrite, skipFirstLevel); } catch (IOException ioe) { throw new ServerException(ioe.getMessage(), ioe); } return Response.ok("", MediaType.TEXT_HTML).build(); } catch (ForbiddenException | ConflictException | ServerException e) { HtmlErrorFormatter.sendErrorAsHTML(e); // never thrown throw e; } } /* ==================================================================== */ protected VirtualFile getVirtualFileByPath(String path) throws NotFoundException, ForbiddenException, ServerException { return mountPoint.getVirtualFile(path); } protected Item fromVirtualFile(VirtualFile virtualFile, boolean includePermissions, PropertyFilter propertyFilter) throws ServerException { return fromVirtualFile(virtualFile, includePermissions, propertyFilter, true); } protected Item fromVirtualFile(VirtualFile virtualFile, boolean includePermissions, PropertyFilter propertyFilter, boolean addLinks) throws ServerException { final String id = virtualFile.getId(); final String name = virtualFile.getName(); final String path = virtualFile.getPath(); final boolean isRoot = virtualFile.isFolder() && virtualFile.isRoot(); final String parentId = isRoot ? null : virtualFile.getParent().getId(); final String mediaType = virtualFile.getMediaType(); final long created = virtualFile.getCreationDate(); Item item; if (virtualFile.isFile()) { final boolean locked = virtualFile.isLocked(); File dtoFile = (File)DtoFactory.getInstance().createDto(File.class) .withVersionId(virtualFile.getVersionId()) .withLength(virtualFile.getLength()) .withLastModificationDate(virtualFile.getLastModificationDate()) .withLocked(locked) .withItemType(ItemType.FILE) .withParentId(parentId) .withId(id) .withName(name) .withPath(path) .withMimeType(mediaType) .withCreationDate(created) .withVfsId(vfsId) .withProperties(virtualFile.getProperties(propertyFilter)); if (addLinks) { dtoFile.setLinks(LinksHelper.createFileLinks(baseUri, vfsId, id, id, path, mediaType, locked, parentId)); } item = dtoFile; } else { Folder dtoFolder = (Folder)DtoFactory.getInstance().createDto(Folder.class) .withItemType(ItemType.FOLDER) .withParentId(parentId) .withId(id) .withName(name) .withPath(path) .withMimeType(mediaType) .withCreationDate(created) .withVfsId(vfsId) .withProperties(virtualFile.getProperties(propertyFilter)); if (addLinks) { dtoFolder.setLinks(LinksHelper.createFolderLinks(baseUri, vfsId, id, isRoot, parentId)); } item = dtoFolder; } if (includePermissions) { VirtualFileSystemUser user = userContext.getVirtualFileSystemUser(); VirtualFile current = virtualFile; while (current != null) { final Map<Principal, Set<String>> objectPermissions = current.getPermissions(); if (!objectPermissions.isEmpty()) { Set<String> userPermissions = new HashSet<>(4); final Principal userPrincipal = DtoFactory.getInstance().createDto(Principal.class).withName(user.getUserId()).withType(Principal.Type.USER); Set<String> permissionsSet = objectPermissions.get(userPrincipal); if (!(permissionsSet == null || permissionsSet.isEmpty())) { userPermissions.addAll(permissionsSet); } final Principal anyPrincipal = DtoFactory.getInstance().createDto(Principal.class) .withName(VirtualFileSystemInfo.ANY_PRINCIPAL).withType(Principal.Type.USER); permissionsSet = objectPermissions.get(anyPrincipal); if (!(permissionsSet == null || permissionsSet.isEmpty())) { userPermissions.addAll(permissionsSet); } for (String group : user.getGroups()) { final Principal groupPrincipal = DtoFactory.getInstance().createDto(Principal.class).withName(group).withType(Principal.Type.GROUP); permissionsSet = objectPermissions.get(groupPrincipal); if (!(permissionsSet == null || permissionsSet.isEmpty())) { userPermissions.addAll(permissionsSet); } } item.setPermissions(new ArrayList<>(userPermissions)); break; } else { current = current.getParent(); } } if (item.getPermissions() == null) { item.setPermissions(Arrays.asList(BasicPermissions.ALL.value())); } } return item; } }