/*******************************************************************************
* 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.impl.memory;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.util.ContentTypeGuesser;
import org.eclipse.che.api.core.util.ValueHolder;
import org.eclipse.che.api.vfs.server.ContentStream;
import org.eclipse.che.api.vfs.server.LazyIterator;
import org.eclipse.che.api.vfs.server.MountPoint;
import org.eclipse.che.api.vfs.server.Path;
import org.eclipse.che.api.vfs.server.VirtualFile;
import org.eclipse.che.api.vfs.server.VirtualFileFilter;
import org.eclipse.che.api.vfs.server.VirtualFileSystemUser;
import org.eclipse.che.api.vfs.server.VirtualFileVisitor;
import org.eclipse.che.api.vfs.server.observation.CreateEvent;
import org.eclipse.che.api.vfs.server.observation.DeleteEvent;
import org.eclipse.che.api.vfs.server.observation.MoveEvent;
import org.eclipse.che.api.vfs.server.observation.RenameEvent;
import org.eclipse.che.api.vfs.server.observation.UpdateACLEvent;
import org.eclipse.che.api.vfs.server.observation.UpdateContentEvent;
import org.eclipse.che.api.vfs.server.observation.UpdatePropertiesEvent;
import org.eclipse.che.api.vfs.server.search.SearcherProvider;
import org.eclipse.che.api.vfs.server.util.NotClosableInputStream;
import org.eclipse.che.api.vfs.server.util.ZipContent;
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.Folder;
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.VirtualFileSystemInfo;
import org.eclipse.che.api.vfs.shared.dto.VirtualFileSystemInfo.BasicPermissions;
import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.dto.server.DtoFactory;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* In-memory implementation of VirtualFile.
* <p/>
* NOTE: This implementation is not thread safe.
*
* @author andrew00x
*/
public class MemoryVirtualFile implements VirtualFile {
private static final Logger LOG = LoggerFactory.getLogger(MemoryVirtualFile.class);
private static final boolean FILE = false;
private static final boolean FOLDER = true;
private static MemoryVirtualFile newFile(MemoryVirtualFile parent, String name, InputStream content, String mediaType)
throws IOException {
return new MemoryVirtualFile(parent, ObjectIdGenerator.generateId(), name, content, mediaType);
}
private static MemoryVirtualFile newFile(MemoryVirtualFile parent, String name, byte[] content, String mediaType) {
return new MemoryVirtualFile(parent, ObjectIdGenerator.generateId(), name, content, mediaType);
}
private static MemoryVirtualFile newFolder(MemoryVirtualFile parent, String name) {
return new MemoryVirtualFile(parent, ObjectIdGenerator.generateId(), name);
}
//
private final boolean type;
private final String id;
private final Map<String, List<String>> properties;
private final long creationDate;
private final Map<String, VirtualFile> children;
private final MemoryMountPoint mountPoint;
private String name;
private MemoryVirtualFile parent;
private byte[] content;
private long lastModificationDate;
private LockHolder lock;
private Map<Principal, Set<String>> permissionsMap;
private boolean exists = true;
// --- File ---
private MemoryVirtualFile(MemoryVirtualFile parent, String id, String name, InputStream content, String mediaType)
throws IOException {
this(parent, id, name, content == null ? null : ByteStreams.toByteArray(content), mediaType);
}
private MemoryVirtualFile(MemoryVirtualFile parent, String id, String name, byte[] content, String mediaType) {
this.mountPoint = (MemoryMountPoint)parent.getMountPoint();
this.parent = parent;
this.type = FILE;
this.id = id;
this.name = name;
this.permissionsMap = new HashMap<>();
this.properties = new HashMap<>();
this.creationDate = this.lastModificationDate = System.currentTimeMillis();
this.content = content == null ? new byte[0] : content;
if (mediaType != null) {
setMediaType(mediaType);
}
children = Collections.emptyMap();
}
// -- Folder ---
private MemoryVirtualFile(MemoryVirtualFile parent, String id, String name) {
this.mountPoint = (MemoryMountPoint)parent.getMountPoint();
this.parent = parent;
this.type = FOLDER;
this.id = id;
this.name = name;
this.permissionsMap = new HashMap<>();
this.properties = new HashMap<>();
this.creationDate = this.lastModificationDate = System.currentTimeMillis();
children = new HashMap<>();
}
/* root folder */ MemoryVirtualFile(MountPoint mountPoint) {
this.mountPoint = (MemoryMountPoint)mountPoint;
this.type = FOLDER;
this.id = ObjectIdGenerator.generateId();
this.name = "";
this.permissionsMap = new HashMap<>();
final Principal groupPrincipal = DtoFactory.getInstance().createDto(Principal.class);
groupPrincipal.setName("workspace/developer");
groupPrincipal.setType(Principal.Type.GROUP);
final Principal anyPrincipal = DtoFactory.getInstance().createDto(Principal.class)
.withName(VirtualFileSystemInfo.ANY_PRINCIPAL).withType(Principal.Type.USER);
final Set<String> groupPermissions = new HashSet<>(4);
groupPermissions.add(BasicPermissions.ALL.value());
this.permissionsMap.put(groupPrincipal, groupPermissions);
final Set<String> anyPermissions = new HashSet<>(4);
anyPermissions.add(BasicPermissions.READ.value());
this.permissionsMap.put(anyPrincipal, anyPermissions);
this.properties = new HashMap<>();
this.creationDate = this.lastModificationDate = System.currentTimeMillis();
children = new HashMap<>();
}
@Override
public String getId() {
checkExist();
return id;
}
@Override
public String getVersionId() {
checkExist();
return isFile() ? "0" : null;
}
@Override
public String getName() {
checkExist();
return name;
}
public String getPath() {
checkExist();
VirtualFile parent = this.parent;
if (parent == null) {
return "/"; // for root folder
}
String name = this.name;
LinkedList<String> pathSegments = new LinkedList<>();
pathSegments.add(name);
while (parent != null) {
pathSegments.addFirst(parent.getName());
parent = parent.getParent();
}
StringBuilder path = new StringBuilder();
path.append('/');
for (String seg : pathSegments) {
if (path.length() > 1) {
path.append('/');
}
path.append(seg);
}
return path.toString();
}
@Override
public Path getVirtualFilePath() {
return Path.fromString(getPath());
}
@Override
public boolean isFile() {
checkExist();
return type == FILE;
}
@Override
public boolean isFolder() {
checkExist();
return type == FOLDER;
}
@Override
public boolean exists() {
return exists;
}
@Override
public boolean isRoot() {
checkExist();
return parent == null;
}
public long getCreationDate() {
checkExist();
return creationDate;
}
public long getLastModificationDate() {
checkExist();
return lastModificationDate;
}
public VirtualFile getParent() {
checkExist();
return parent;
}
public String getMediaType() {
checkExist();
String mediaType = getPropertyValue("vfs:mimeType");
if (mediaType == null) {
mediaType = isFile() ? ContentTypeGuesser.guessContentType(getName()) : Folder.FOLDER_MIME_TYPE;
}
return mediaType;
}
@Override
public VirtualFile setMediaType(String mediaType) {
checkExist();
if (mediaType == null) {
properties.remove("vfs:mimeType");
} else {
properties.put("vfs:mimeType", Arrays.asList(mediaType));
}
return this;
}
public VirtualFile updateACL(List<AccessControlEntry> acl, boolean override, String lockToken) throws ForbiddenException {
checkExist();
if (!hasPermission(BasicPermissions.UPDATE_ACL.value(), true)) {
throw new ForbiddenException(String.format("Unable update ACL for '%s'. Operation not permitted. ", getPath()));
}
if (isFile() && !validateLockTokenIfLocked(lockToken)) {
throw new ForbiddenException(String.format("Unable update ACL of item '%s'. Item is locked. ", getPath()));
}
if (acl.isEmpty() && !override) {
return this;
}
final Map<Principal, Set<String>> update = override ? new HashMap<Principal, Set<String>>(acl.size()) : getPermissions();
for (AccessControlEntry ace : acl) {
final Principal principal = ace.getPrincipal();
// Do not use 'transport' object directly, copy it instead.
final Principal copyPrincipal = DtoFactory.getInstance().clone(principal);
final List<String> acePermissions = ace.getPermissions();
if (acePermissions == null || acePermissions.isEmpty()) {
update.remove(copyPrincipal);
} else {
Set<String> permissions = update.get(copyPrincipal);
if (permissions == null) {
update.put(copyPrincipal, permissions = new HashSet<>(4));
} else {
permissions.clear();
}
permissions.addAll(acePermissions);
}
}
permissionsMap = update;
lastModificationDate = System.currentTimeMillis();
mountPoint.getEventService().publish(new UpdateACLEvent(mountPoint.getWorkspaceId(), getPath(), isFolder()));
return this;
}
@Override
public LazyIterator<VirtualFile> getVersions(VirtualFileFilter filter) throws ForbiddenException {
checkExist();
if (!isFile()) {
throw new ForbiddenException("Versioning allowed for files only. ");
}
if (filter.accept(this)) {
return LazyIterator.<VirtualFile>singletonIterator(this);
}
return LazyIterator.emptyIterator();
}
@Override
public VirtualFile getVersion(String versionId) throws ForbiddenException, ServerException {
checkExist();
if (!isFile()) {
throw new ForbiddenException("Versioning allowed for files only. ");
}
if ("0".equals(versionId)) {
return this;
}
throw new ServerException("Versioning is not supported. ");
}
@Override
public Map<Principal, Set<String>> getPermissions() {
checkExist();
final Map<Principal, Set<String>> copy = new HashMap<>(permissionsMap.size());
for (Map.Entry<Principal, Set<String>> e : permissionsMap.entrySet()) {
final Principal copyPrincipal = DtoFactory.getInstance().clone(e.getKey());
copy.put(copyPrincipal, new LinkedHashSet<>(e.getValue()));
}
return copy;
}
@Override
public List<AccessControlEntry> getACL() {
checkExist();
final Map<Principal, Set<String>> permissions = getPermissions();
final List<AccessControlEntry> acl = new ArrayList<>(permissions.size());
for (Map.Entry<Principal, Set<String>> e : permissions.entrySet()) {
final Set<String> basicPermissions = e.getValue();
final Principal principal = e.getKey();
final List<String> plainPermissions = new ArrayList<>(basicPermissions);
// principal is already copied in method getPermissions
acl.add(DtoFactory.getInstance().createDto(AccessControlEntry.class)
.withPrincipal(principal).withPermissions(plainPermissions));
}
return acl;
}
public List<Property> getProperties(PropertyFilter filter) {
checkExist();
final List<Property> result = new ArrayList<>();
for (Map.Entry<String, List<String>> e : properties.entrySet()) {
final String name = e.getKey();
if (filter.accept(name)) {
final List<String> value = e.getValue();
final Property property = DtoFactory.getInstance().createDto(Property.class).withName(name);
if (value != null) {
property.setValue(new ArrayList<>(value));
}
result.add(property);
}
}
return result;
}
public VirtualFile updateProperties(List<Property> update, String lockToken) throws ForbiddenException {
checkExist();
if (!hasPermission(BasicPermissions.UPDATE_ACL.value(), true)) {
throw new ForbiddenException(String.format("Unable update properties for '%s'. Operation not permitted. ", getPath()));
}
if (isFile() && !validateLockTokenIfLocked(lockToken)) {
throw new ForbiddenException(String.format("Unable update properties of item '%s'. Item is locked. ", getPath()));
}
for (Property p : update) {
String name = p.getName();
List<String> value = p.getValue();
if (value != null) {
List<String> copy = new ArrayList<>(value);
properties.put(name, copy);
} else {
properties.remove(name);
}
}
lastModificationDate = System.currentTimeMillis();
mountPoint.getEventService().publish(new UpdatePropertiesEvent(mountPoint.getWorkspaceId(), getPath(), isFolder()));
return this;
}
public void accept(VirtualFileVisitor visitor) throws ServerException {
checkExist();
visitor.visit(this);
}
@Override
public LazyIterator<Pair<String, String>> countMd5Sums() throws ServerException {
checkExist();
if (isFile()) {
return LazyIterator.emptyIterator();
}
final List<Pair<String, String>> hashes = new ArrayList<>();
final int trimPathLength = getPath().length() + 1;
final HashFunction hashFunction = Hashing.md5();
final ValueHolder<ServerException> errorHolder = new ValueHolder<>();
accept(new VirtualFileVisitor() {
@Override
public void visit(final VirtualFile virtualFile) {
try {
if (virtualFile.isFile()) {
try (InputStream stream = virtualFile.getContent().getStream()) {
final String hexHash = ByteSource.wrap(ByteStreams.toByteArray(stream)).hash(hashFunction).toString();
hashes.add(Pair.of(hexHash, virtualFile.getPath().substring(trimPathLength)));
} catch (ForbiddenException e) {
throw new ServerException(e.getServiceError());
} catch (IOException e) {
throw new ServerException(e);
}
} else {
final LazyIterator<VirtualFile> children = virtualFile.getChildren(VirtualFileFilter.ALL);
while (children.hasNext()) {
children.next().accept(this);
}
}
} catch (ServerException e) {
errorHolder.set(e);
}
}
});
final ServerException error = errorHolder.get();
if (error != null) {
throw error;
}
return LazyIterator.fromList(hashes);
}
@Override
public LazyIterator<VirtualFile> getChildren(VirtualFileFilter filter) {
checkExist();
if (isFile()) {
return LazyIterator.emptyIterator();
}
if (isRoot()) {
// NOTE: We do not check read permissions when access to ROOT folder.
if (!hasPermission(BasicPermissions.READ.value(), false)) {
// User has not access to ROOT folder.
return LazyIterator.emptyIterator();
}
}
List<VirtualFile> children = doGetChildren(this);
for (Iterator<VirtualFile> i = children.iterator(); i.hasNext(); ) {
VirtualFile virtualFile = i.next();
if (!((MemoryVirtualFile)virtualFile).hasPermission(BasicPermissions.READ.value(), false) || !filter.accept(virtualFile)) {
i.remove();
}
}
Collections.sort(children);
return LazyIterator.fromList(children);
}
private List<VirtualFile> doGetChildren(VirtualFile folder) {
return new ArrayList<>(((MemoryVirtualFile)folder).children.values());
}
@Override
public VirtualFile getChild(String path) throws ForbiddenException {
checkExist();
String[] elements = Path.fromString(path).elements();
MemoryVirtualFile child = (MemoryVirtualFile)children.get(elements[0]);
if (child != null && elements.length > 1) {
for (int i = 1, l = elements.length; i < l && child != null; i++) {
if (child.isFolder()) {
child = (MemoryVirtualFile)child.getChild(elements[i]);
}
}
}
if (child != null) {
if (!child.getPath().endsWith(".codenvy/misc.xml")) {
// Don't check permissions for file "misc.xml" in folder ".codenvy". Dirty huck :( but seems simplest solution for now.
// Need to work with 'misc.xml' independently to user.
if (!child.hasPermission(BasicPermissions.READ.value(), false)) {
throw new ForbiddenException(String.format("We were unable to get an item '%s'. " +
"You do not have the correct permissions to complete this operation. ",
getPath()));
}
}
return child;
}
return null;
}
private boolean addChild(VirtualFile child) {
checkExist();
final String childName = child.getName();
if (children.get(childName) == null) {
children.put(childName, child);
return true;
}
return false;
}
@Override
public ContentStream getContent() throws ForbiddenException {
checkExist();
if (!isFile()) {
throw new ForbiddenException(String.format("We were unable to retrieve the content. Item '%s' is not a file. ", getPath()));
}
if (content == null) {
content = new byte[0];
}
return new ContentStream(getName(), new ByteArrayInputStream(content), getMediaType(), content.length,
new Date(lastModificationDate));
}
@Override
public VirtualFile updateContent(String mediaType, InputStream content, String lockToken) throws ForbiddenException, ServerException {
return updateContent(mediaType, content, lockToken, true);
}
@Override
public VirtualFile updateContent(InputStream content, String lockToken) throws ForbiddenException, ServerException {
return updateContent(null, content, lockToken, false);
}
private VirtualFile updateContent(String mediaType, InputStream content, String lockToken, boolean updateMediaType)
throws ForbiddenException, ServerException {
checkExist();
if (!isFile()) {
throw new ForbiddenException(String.format("We were unable to update the content. Item '%s' is not a file. ", getPath()));
}
if (!getPath().endsWith(".codenvy/misc.xml")) {
// Don't check permissions when update file ".codenvy/misc.xml". Dirty huck :( but seems simplest solution for now.
// Need to work with 'misc.xml' independently to user.
if (!hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(String.format("We were unable to update item '%s'." +
" You do not have the correct permissions to complete this operation.",
getPath()));
}
}
if (isFile() && !validateLockTokenIfLocked(lockToken)) {
throw new ForbiddenException(
String.format("We were unable to update the content of file '%s'. The file is locked. ", getPath()));
}
try {
this.content = ByteStreams.toByteArray(content);
} catch (IOException e) {
throw new ServerException(String.format("We were unable to set the content of '%s'. ", getPath()));
}
if (updateMediaType) {
setMediaType(mediaType);
}
SearcherProvider searcherProvider = mountPoint.getSearcherProvider();
if (searcherProvider != null) {
try {
searcherProvider.getSearcher(mountPoint, true).update(this);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
}
lastModificationDate = System.currentTimeMillis();
mountPoint.getEventService().publish(new UpdateContentEvent(mountPoint.getWorkspaceId(), getPath()));
return this;
}
@Override
public long getLength() {
checkExist();
if (!isFile()) {
return 0;
}
return content.length;
}
@Override
public String getPropertyValue(String name) {
checkExist();
List<Property> properties = getProperties(PropertyFilter.valueOf(name));
if (properties.size() > 0) {
List<String> values = properties.get(0).getValue();
if (!(values == null || values.isEmpty())) {
return values.get(0);
}
}
return null;
}
@Override
public String[] getPropertyValues(String name) {
checkExist();
List<Property> properties = getProperties(PropertyFilter.valueOf(name));
if (properties.size() > 0) {
List<String> values = properties.get(0).getValue();
if (!(values == null || values.isEmpty())) {
return values.toArray(new String[values.size()]);
}
}
return new String[0];
}
@Override
public VirtualFile copyTo(VirtualFile parent) throws ForbiddenException, ConflictException, ServerException {
checkExist();
((MemoryVirtualFile)parent).checkExist();
if (isRoot()) {
throw new ServerException("Unable copy root folder. ");
}
if (!parent.isFolder()) {
throw new ForbiddenException(String.format("Unable create copy of '%s'. Item '%s' specified as parent is not a folder.",
getPath(), parent.getPath()));
}
if (!((MemoryVirtualFile)parent).hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(String.format("Unable copy item '%s' to '%s'. Operation not permitted. ",
getPath(), parent.getPath()));
}
VirtualFile copy = doCopy(parent);
mountPoint.putItem((MemoryVirtualFile)copy);
SearcherProvider searcherProvider = mountPoint.getSearcherProvider();
if (searcherProvider != null) {
try {
searcherProvider.getSearcher(mountPoint, true).add(parent);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
}
mountPoint.getEventService().publish(new CreateEvent(mountPoint.getWorkspaceId(), copy.getPath(), copy.isFolder()));
return copy;
}
private VirtualFile doCopy(VirtualFile parent) throws ConflictException {
VirtualFile virtualFile;
if (isFile()) {
virtualFile = newFile((MemoryVirtualFile)parent, name, Arrays.copyOf(content, content.length), getMediaType());
} else {
virtualFile = newFolder((MemoryVirtualFile)parent, name);
LazyIterator<VirtualFile> children = getChildren(VirtualFileFilter.ALL);
while (children.hasNext()) {
((MemoryVirtualFile)children.next()).doCopy(virtualFile);
}
}
for (Map.Entry<String, List<String>> e : properties.entrySet()) {
String name = e.getKey();
List<String> value = e.getValue();
if (value != null) {
List<String> copy = new ArrayList<>(value);
((MemoryVirtualFile)virtualFile).properties.put(name, copy);
}
}
if (!((MemoryVirtualFile)parent).addChild(virtualFile)) {
throw new ConflictException(String.format("Item '%s' already exists. ", (parent.getPath() + '/' + name)));
}
return virtualFile;
}
@Override
public VirtualFile moveTo(VirtualFile parent, final String lockToken) throws ConflictException, ForbiddenException, ServerException {
checkExist();
((MemoryVirtualFile)parent).checkExist();
if (isRoot()) {
throw new ForbiddenException("Unable move root folder. ");
}
final String myPath = getPath();
final String newParentPath = parent.getPath();
if (!parent.isFolder()) {
throw new ForbiddenException("Unable move item. Item specified as parent is not a folder. ");
}
if (!(((MemoryVirtualFile)parent).hasPermission(BasicPermissions.WRITE.value(), true)
&& hasPermission(BasicPermissions.WRITE.value(), true))) {
throw new ForbiddenException(String.format("Unable move item '%s' to %s. Operation not permitted. ", myPath, newParentPath));
}
final boolean folder = isFolder();
if (folder) {
// Be sure destination folder is not child (direct or not) of moved item.
if (newParentPath.startsWith(myPath)) {
throw new ForbiddenException(
String.format("Unable move item %s to %s. Item may not have itself as parent. ", myPath, newParentPath));
}
final ValueHolder<Exception> errorHolder = new ValueHolder<>();
accept(new VirtualFileVisitor() {
@Override
public void visit(VirtualFile virtualFile) {
try {
if (virtualFile.isFolder()) {
for (VirtualFile childVirtualFile : doGetChildren(virtualFile)) {
childVirtualFile.accept(this);
}
}
if (!((MemoryVirtualFile)virtualFile).hasPermission(BasicPermissions.WRITE.value(), false)) {
throw new ForbiddenException(
String.format("Unable move item '%s'. Operation not permitted. ", virtualFile.getPath()));
}
if (virtualFile.isFile() && virtualFile.isLocked()) {
throw new ForbiddenException(
String.format("Unable move item '%s'. Child item '%s' is locked. ", name, virtualFile.getPath()));
}
} catch (ServerException | ForbiddenException e) {
errorHolder.set(e);
}
}
});
final Exception error = errorHolder.get();
if (error != null) {
if (error instanceof ForbiddenException) {
throw (ForbiddenException)error;
} else if (error instanceof ServerException) {
throw (ServerException)error;
} else {
throw new ServerException(error.getMessage(), error);
}
}
} else {
if (!validateLockTokenIfLocked(lockToken)) {
throw new ForbiddenException(String.format("Unable move item %s. Item is locked. ", myPath));
}
}
if (!((MemoryVirtualFile)parent).addChild(this)) {
throw new ConflictException(String.format("Item '%s' already exists. ", (parent.getPath() + '/' + name)));
}
this.parent.children.remove(getName());
this.parent = (MemoryVirtualFile)parent;
SearcherProvider searcherProvider = mountPoint.getSearcherProvider();
if (searcherProvider != null) {
try {
searcherProvider.getSearcher(mountPoint, true).delete(myPath);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
try {
searcherProvider.getSearcher(mountPoint, true).add(parent);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
}
mountPoint.getEventService().publish(new MoveEvent(mountPoint.getWorkspaceId(), getPath(), myPath, folder));
return this;
}
@Override
public VirtualFile rename(String newName, String newMediaType, String lockToken)
throws ForbiddenException, ConflictException, ServerException {
checkExist();
checkName(newName);
if (isRoot()) {
throw new ForbiddenException("We were unable to rename a root folder.");
}
if (!hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(String.format("We were unable to delete an item '%s'." +
" You do not have the correct permissions to complete this operation.", getPath()));
}
final String myPath = getPath();
final boolean folder = isFolder();
if (folder) {
final ValueHolder<Exception> errorHolder = new ValueHolder<>();
accept(new VirtualFileVisitor() {
@Override
public void visit(VirtualFile virtualFile) {
try {
if (virtualFile.isFolder()) {
for (VirtualFile childVirtualFile : doGetChildren(virtualFile)) {
childVirtualFile.accept(this);
}
}
if (!((MemoryVirtualFile)virtualFile).hasPermission(BasicPermissions.WRITE.value(), false)) {
throw new ForbiddenException(
String.format("We were unable to rename an item '%s'." +
" You do not have the correct permissions to complete this operation.",
virtualFile.getPath()));
}
if (virtualFile.isFile() && virtualFile.isLocked()) {
throw new ForbiddenException(
String.format("We were unable to rename an item '%s'." +
" The child item '%s' is currently locked by the system.", getPath(),
virtualFile.getPath()));
}
} catch (ServerException | ForbiddenException e) {
errorHolder.set(e);
}
}
});
final Exception error = errorHolder.get();
if (error != null) {
if (error instanceof ForbiddenException) {
throw (ForbiddenException)error;
} else if (error instanceof ServerException) {
throw (ServerException)error;
} else {
throw new ServerException(error.getMessage(), error);
}
}
} else {
if (!validateLockTokenIfLocked(lockToken)) {
throw new ForbiddenException(String.format("We were unable to rename an item '%s'." +
" The item is currently locked by the system.", getPath()));
}
}
if (parent.getChild(newName) != null) {
throw new ConflictException(String.format("Item '%s' already exists. ", newName));
}
parent.children.remove(name);
parent.children.put(newName, this);
name = newName;
if (newMediaType != null) {
setMediaType(newMediaType);
}
lastModificationDate = System.currentTimeMillis();
SearcherProvider searcherProvider = mountPoint.getSearcherProvider();
if (searcherProvider != null) {
try {
searcherProvider.getSearcher(mountPoint, true).delete(myPath);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
try {
searcherProvider.getSearcher(mountPoint, true).add(parent);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
}
mountPoint.getEventService().publish(new RenameEvent(mountPoint.getWorkspaceId(), getPath(), myPath, folder));
return this;
}
@Override
public void delete(final String lockToken) throws ForbiddenException, ServerException {
checkExist();
if (isRoot()) {
throw new ForbiddenException("Unable delete root folder. ");
}
if (!hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(String.format("We were unable to delete an item '%s'." +
" You do not have the correct permissions to complete this operation.", getPath()));
}
final String myPath = getPath();
final boolean folder = isFolder();
if (folder) {
final ValueHolder<Exception> errorHolder = new ValueHolder<>();
final List<VirtualFile> toDelete = new ArrayList<>();
accept(new VirtualFileVisitor() {
@Override
public void visit(VirtualFile virtualFile) {
try {
if (virtualFile.isFolder()) {
for (VirtualFile childVirtualFile : doGetChildren(virtualFile)) {
childVirtualFile.accept(this);
}
}
if (!((MemoryVirtualFile)virtualFile).hasPermission(BasicPermissions.WRITE.value(), false)) {
throw new ForbiddenException(
String.format("We were unable to delete an item '%s'." +
" You do not have the correct permissions to complete this operation.",
virtualFile.getPath()));
}
if (virtualFile.isFile() && virtualFile.isLocked()) {
throw new ForbiddenException(String.format("Unable delete item '%s'. Child item '%s' is locked. ",
getPath(), virtualFile.getPath()));
}
toDelete.add(virtualFile);
} catch (ServerException | ForbiddenException e) {
errorHolder.set(e);
}
}
});
final Exception error = errorHolder.get();
if (error != null) {
if (error instanceof ForbiddenException) {
throw (ForbiddenException)error;
} else if (error instanceof ServerException) {
throw (ServerException)error;
} else {
throw new ServerException(error.getMessage(), error);
}
}
for (VirtualFile virtualFile : toDelete) {
mountPoint.deleteItem(virtualFile.getId());
((MemoryVirtualFile)virtualFile).exists = false;
}
} else {
if (!validateLockTokenIfLocked(lockToken)) {
throw new ForbiddenException(String.format("Unable delete item '%s'. Item is locked. ", getPath()));
}
mountPoint.deleteItem(getId());
}
parent.children.remove(name);
exists = false;
parent = null;
SearcherProvider searcherProvider = mountPoint.getSearcherProvider();
if (searcherProvider != null) {
try {
searcherProvider.getSearcher(mountPoint, true).delete(myPath);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
}
mountPoint.getEventService().publish(new DeleteEvent(mountPoint.getWorkspaceId(), myPath, folder));
}
@Override
public ContentStream zip(VirtualFileFilter filter) throws ForbiddenException, ServerException {
checkExist();
if (!isFolder()) {
throw new ForbiddenException(String.format("Unable export to zip. Item '%s' is not a folder. ", getPath()));
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
final ZipOutputStream zipOut = new ZipOutputStream(out);
final LinkedList<VirtualFile> q = new LinkedList<>();
q.add(this);
final int rootZipPathLength = isRoot() ? 1 : (getPath().length() + 1);
while (!q.isEmpty()) {
final LazyIterator<VirtualFile> children = q.pop().getChildren(filter);
while (children.hasNext()) {
VirtualFile current = children.next();
final String zipEntryName = current.getPath().substring(rootZipPathLength);
if (current.isFile()) {
final ZipEntry zipEntry = new ZipEntry(zipEntryName);
zipEntry.setTime(current.getLastModificationDate());
zipOut.putNextEntry(zipEntry);
zipOut.write(((MemoryVirtualFile)current).content);
zipOut.closeEntry();
} else if (current.isFolder()) {
final ZipEntry zipEntry = new ZipEntry(zipEntryName + '/');
zipEntry.setTime(0);
zipOut.putNextEntry(zipEntry);
q.add(current);
zipOut.closeEntry();
}
}
}
zipOut.close();
} catch (IOException e) {
throw new ServerException(e.getMessage(), e);
}
final byte[] zipContent = out.toByteArray();
return new ContentStream(getName() + ".zip", new ByteArrayInputStream(zipContent), "application/zip", zipContent.length,
new Date());
}
@Override
public void unzip(InputStream zipped, boolean overwrite, int stripNumber) throws ForbiddenException, ServerException {
checkExist();
if (!hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(String.format("We were unable to import a ZIP file to '%s' as part of the import." +
" You do not have the correct permissions to complete this operation.", getPath()));
}
ZipInputStream zip = null;
try {
final ZipContent zipContent = ZipContent.newInstance(zipped);
zip = new ZipInputStream(zipContent.zippedData);
// Wrap zip stream to prevent close it. We can pass stream to other method and it can read content of current
// ZipEntry but not able to close original stream of ZIPed data.
InputStream noCloseZip = new NotClosableInputStream(zip);
ZipEntry zipEntry;
while ((zipEntry = zip.getNextEntry()) != null) {
VirtualFile current = this;
Path relPath = Path.fromString(zipEntry.getName());
if (stripNumber > 0) {
int currentLevel = relPath.elements().length;
if (currentLevel <= stripNumber) {
continue;
}
relPath = relPath.subPath(stripNumber);
}
final String name = relPath.getName();
if (relPath.length() > 1) {
// create all required parent directories
for (int i = 0, stop = relPath.length() - 1; i < stop; i++) {
MemoryVirtualFile folder = newFolder((MemoryVirtualFile)current, relPath.element(i));
if (((MemoryVirtualFile)current).addChild(folder)) {
current = folder;
mountPoint.putItem(folder);
} else {
current = current.getChild(relPath.element(i));
}
}
}
if (zipEntry.isDirectory()) {
if (current.getChild(name) == null) {
MemoryVirtualFile folder = newFolder((MemoryVirtualFile)current, name);
((MemoryVirtualFile)current).addChild(folder);
mountPoint.putItem(folder);
mountPoint.getEventService().publish(new CreateEvent(mountPoint.getWorkspaceId(), folder.getPath(), true));
}
} else {
current.getChild(name);
VirtualFile file = current.getChild(name);
if (file != null) {
if (file.isLocked()) {
throw new ForbiddenException(String.format("File '%s' already exists and locked. ", file.getPath()));
}
if (!((MemoryVirtualFile)file).hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(
String.format("We were unable to update file '%s' as part of the import." +
" You do not have the correct permissions to complete this operation.", file.getPath()));
}
if (!overwrite) {
throw new ForbiddenException(String.format("File '%s' already exists. ", file.getPath()));
}
file.updateContent(noCloseZip, null);
mountPoint.getEventService().publish(new UpdateContentEvent(mountPoint.getWorkspaceId(), file.getPath()));
} else {
file = newFile((MemoryVirtualFile)current, name, noCloseZip, ContentTypeGuesser.guessContentType(name));
((MemoryVirtualFile)current).addChild(file);
mountPoint.putItem((MemoryVirtualFile)file);
mountPoint.getEventService().publish(new CreateEvent(mountPoint.getWorkspaceId(), file.getPath(), false));
}
}
zip.closeEntry();
}
SearcherProvider searcherProvider = mountPoint.getSearcherProvider();
if (searcherProvider != null) {
try {
searcherProvider.getSearcher(mountPoint, true).add(this);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
}
} catch (IOException e) {
throw new ServerException(e.getMessage(), e);
} finally {
if (zip != null) {
try {
zip.close();
} catch (IOException ignored) {
}
}
}
}
@Override
public String lock(long timeout) throws ForbiddenException, ConflictException {
checkExist();
if (!isFile()) {
throw new ForbiddenException(String.format("Unable lock '%s'. Locking allowed for files only. ", getPath()));
}
if (!hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(String.format("Unable lock '%s'. Operation not permitted. ", getPath()));
}
final String lockToken = NameGenerator.generate(null, 32);
final LockHolder lock = new LockHolder(lockToken, timeout);
if (this.lock != null) {
throw new ConflictException("File already locked. ");
}
this.lock = lock;
lastModificationDate = System.currentTimeMillis();
return lockToken;
}
@Override
public VirtualFile unlock(String lockToken) throws ForbiddenException, ConflictException {
checkExist();
if (!isFile()) {
throw new ForbiddenException(String.format("Unable unlock '%s'. Locking allowed for files only. ", getPath()));
}
final LockHolder myLock = lock;
if (myLock == null) {
throw new ConflictException("File is not locked. ");
} else if (myLock.expired < System.currentTimeMillis()) {
lock = null;
throw new ConflictException("File is not locked. ");
}
if (myLock.lockToken.equals(lockToken)) {
lock = null;
lastModificationDate = System.currentTimeMillis();
} else {
throw new ForbiddenException("Unable remove lock from file. Lock token does not match. ");
}
lastModificationDate = System.currentTimeMillis();
return this;
}
@Override
public boolean isLocked() {
checkExist();
final LockHolder myLock = lock;
if (lock != null) {
if (myLock.expired < System.currentTimeMillis()) {
// replace lock
lock = null;
return false;
}
return true;
}
return false;
}
@Override
public VirtualFile createFile(String name, String mediaType, InputStream content)
throws ForbiddenException, ConflictException, ServerException {
checkExist();
checkName(name);
if (!isFolder()) {
throw new ForbiddenException("Unable create new file. Item specified as parent is not a folder. ");
}
if (!".codenvy".equals(getName()) && !"misc.xml".equals(name)) {
// Don't check permissions when create file "misc.xml" in folder ".codenvy". Dirty huck :( but seems simplest solution for now.
// Need to work with 'misc.xml' independently to user.
if (!hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(String.format("Unable create new file in '%s'. Operation not permitted. ", getPath()));
}
}
final MemoryVirtualFile newFile;
try {
newFile = newFile(this, name, content, mediaType);
} catch (IOException e) {
throw new ServerException(String.format("Unable set content of '%s'. ", getPath() + e.getMessage()));
}
if (!addChild(newFile)) {
throw new ConflictException(String.format("Item with the name '%s' already exists. ", name));
}
mountPoint.putItem(newFile);
SearcherProvider searcherProvider = mountPoint.getSearcherProvider();
if (searcherProvider != null) {
try {
searcherProvider.getSearcher(mountPoint, true).add(newFile);
} catch (ServerException e) {
LOG.error(e.getMessage(), e);
}
}
mountPoint.getEventService().publish(new CreateEvent(mountPoint.getWorkspaceId(), newFile.getPath(), false));
return newFile;
}
@Override
public VirtualFile createFolder(String name) throws ForbiddenException, ConflictException, ServerException {
checkExist();
checkName(name);
if (!isFolder()) {
throw new ForbiddenException("Unable create new folder. Item specified as parent is not a folder. ");
}
if (!hasPermission(BasicPermissions.WRITE.value(), true)) {
throw new ForbiddenException(String.format("We were unable to create a new folder in '%s' as part of the import. " +
"You do not have the correct permissions to complete this operation. ", getPath()));
}
MemoryVirtualFile newFolder = null;
MemoryVirtualFile current = this;
if (name.indexOf('/') > 0) {
final Path internPath = Path.fromString(name);
for (String element : internPath.elements()) {
MemoryVirtualFile folder = newFolder(current, element);
if (current.addChild(folder)) {
newFolder = folder;
current = folder;
} else {
current = (MemoryVirtualFile)current.getChild(element);
}
}
if (newFolder == null) {
// Folder or folder hierarchy already exists.
throw new ConflictException(String.format("Item with the name '%s' already exists. ", name));
}
} else {
newFolder = newFolder(this, name);
if (!addChild(newFolder)) {
throw new ConflictException(String.format("Item with the name '%s' already exists. ", name));
}
}
mountPoint.putItem(newFolder);
mountPoint.getEventService().publish(new CreateEvent(mountPoint.getWorkspaceId(), newFolder.getPath(), true));
return newFolder;
}
@Override
public MountPoint getMountPoint() {
return mountPoint;
}
@Override
public int compareTo(VirtualFile o) {
// To get nice order of items:
// 1. Regular folders
// 2. Files
if (o == null) {
throw new NullPointerException();
}
if (isFolder()) {
return o.isFolder() ? getName().compareTo(o.getName()) : -1;
} else if (o.isFolder()) {
return 1;
}
return getName().compareTo(o.getName());
}
boolean hasPermission(String permission, boolean checkParent) {
checkExist();
final VirtualFileSystemUser user = mountPoint.getUserContext().getVirtualFileSystemUser();
MemoryVirtualFile current = this;
while (current != null) {
final Map<Principal, Set<String>> objectPermissions = current.getPermissions();
if (!objectPermissions.isEmpty()) {
final Principal userPrincipal =
DtoFactory.getInstance().createDto(Principal.class).withName(user.getUserId()).withType(Principal.Type.USER);
Set<String> userPermissions = objectPermissions.get(userPrincipal);
if (userPermissions != null) {
return userPermissions.contains(permission) || userPermissions.contains(BasicPermissions.ALL.value());
}
Collection<String> groups = user.getGroups();
if (!groups.isEmpty()) {
for (String group : groups) {
final Principal groupPrincipal =
DtoFactory.getInstance().createDto(Principal.class).withName(group).withType(Principal.Type.GROUP);
userPermissions = objectPermissions.get(groupPrincipal);
if (userPermissions != null) {
return userPermissions.contains(permission) || userPermissions.contains(BasicPermissions.ALL.value());
}
}
}
final Principal anyPrincipal = DtoFactory.getInstance().createDto(Principal.class)
.withName(VirtualFileSystemInfo.ANY_PRINCIPAL).withType(Principal.Type.USER);
userPermissions = objectPermissions.get(anyPrincipal);
return userPermissions != null
&& (userPermissions.contains(permission) || userPermissions.contains(BasicPermissions.ALL.value()));
}
if (checkParent) {
current = (MemoryVirtualFile)current.getParent();
} else {
break;
}
}
return true;
}
private void checkExist() {
if (!exists) {
throw new RuntimeException(String.format("Item '%s' already removed. ", name));
}
}
private void checkName(String name) throws ServerException {
if (name == null || name.trim().isEmpty()) {
throw new ServerException("Item's name is not set. ");
}
}
private boolean validateLockTokenIfLocked(String lockToken) {
if (!isLocked()) {
return true;
}
final LockHolder myLock = lock;
return myLock == null || myLock.lockToken.equals(lockToken);
}
private static class LockHolder {
final String lockToken;
final long expired;
LockHolder(String lockToken, long timeout) {
this.lockToken = lockToken;
this.expired = timeout > 0 ? (System.currentTimeMillis() + timeout) : Long.MAX_VALUE;
}
}
}