/******************************************************************************* * 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.project.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.project.server.handlers.GetItemHandler; import org.eclipse.che.api.project.server.type.Attribute; import org.eclipse.che.api.project.server.type.AttributeValue; import org.eclipse.che.api.project.server.type.ProjectType; import org.eclipse.che.api.project.server.type.Variable; import org.eclipse.che.api.project.shared.Builders; import org.eclipse.che.api.project.shared.Runners; import org.eclipse.che.api.project.shared.dto.SourceEstimation; import org.eclipse.che.api.vfs.server.VirtualFile; import org.eclipse.che.api.vfs.shared.dto.AccessControlEntry; import org.eclipse.che.api.vfs.shared.dto.Principal; import org.eclipse.che.api.vfs.shared.dto.VirtualFileSystemInfo.BasicPermissions; import org.eclipse.che.dto.server.DtoFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * Server side representation for codenvy project. * * @author andrew00x * @author Eugene Voevodin */ public class Project { private final FolderEntry baseFolder; private final ProjectManager manager; private final Modules modules; public Project(FolderEntry baseFolder, ProjectManager manager) { this.baseFolder = baseFolder; this.manager = manager; modules = new Modules(); } /** Gets id of workspace which this project belongs to. */ public String getWorkspace() { return baseFolder.getWorkspace(); } /** Gets name of project. */ public String getName() { return baseFolder.getName(); } /** Gets path of project. */ public String getPath() { return baseFolder.getPath(); } /** Gets base folder of project. */ public FolderEntry getBaseFolder() { return baseFolder; } /** Gets creation date of project in unix format or {@code -1} if creation date is unknown. */ public long getCreationDate() throws ServerException { return getMisc().getCreationDate(); } /** Gets most recent modification date of project in unix format or {@code -1} if modification date is unknown. */ public long getModificationDate() throws ServerException { return getMisc().getModificationDate(); } /** @see ProjectMisc */ public ProjectMisc getMisc() throws ServerException { return manager.getProjectMisc(this); } /** @see ProjectMisc */ public void saveMisc(ProjectMisc misc) throws ServerException { manager.saveProjectMisc(this, misc); } public ProjectConfig getConfig() throws ServerException, ValueStorageException, ProjectTypeConstraintException, InvalidValueException { final ProjectJson projectJson = ProjectJson.load(this); ProjectTypes types = new ProjectTypes(projectJson.getType(), projectJson.getMixinTypes()); types.addTransient(); final Map<String, AttributeValue> attributes = new HashMap<>(); for (ProjectType t : types.all.values()) { for (Attribute attr : t.getAttributes()) { if (attr.isVariable()) { Variable var = (Variable)attr; final ValueProviderFactory factory = var.getValueProviderFactory(); List<String> val; if (factory != null) { val = factory.newInstance(baseFolder).getValues(var.getName()); if (val == null) throw new ProjectTypeConstraintException( "Value Provider must not produce NULL value of variable " + var.getId()); } else { val = projectJson.getAttributes().get(attr.getName()); } if (val == null || val.isEmpty()) { if (var.isRequired()) throw new ProjectTypeConstraintException( "No Value nor ValueProvider defined for required variable " + var.getId()); // else just not add it } else { attributes.put(var.getName(), new AttributeValue(val)); } } else { // Constant attributes.put(attr.getName(), attr.getValue()); } } } Builders builders = (projectJson.getBuilders() == null) ? new Builders(types.primary.getDefaultBuilder()) : projectJson.getBuilders(); Runners runners = (projectJson.getRunners() == null) ? new Runners(types.primary.getDefaultRunner()) : projectJson.getRunners(); // return new ProjectConfig(projectJson.getDescription(), projectJson.getType(), // attributes, runners, builders, projectJson.getMixinTypes()); return new ProjectConfig(projectJson.getDescription(), types.primary.getId(), attributes, runners, builders, types.mixinIds()); } /** * Updates Project Config making all necessary validations * * @param update * @throws ServerException * @throws ValueStorageException * @throws ProjectTypeConstraintException * @throws InvalidValueException */ public final void updateConfig(ProjectConfig update) throws ServerException, ValueStorageException, ProjectTypeConstraintException, InvalidValueException { final ProjectJson projectJson = new ProjectJson(); ProjectTypes types = new ProjectTypes(update.getTypeId(), update.getMixinTypes()); types.removeTransient(); projectJson.setType(types.primary.getId()); projectJson.setBuilders(update.getBuilders()); projectJson.setRunners(update.getRunners()); projectJson.setDescription(update.getDescription()); ArrayList<String> ms = new ArrayList<>(); ms.addAll(types.mixins.keySet()); projectJson.setMixinTypes(ms); // update attributes HashMap<String, AttributeValue> checkVariables = new HashMap<>(); for (String attributeName : update.getAttributes().keySet()) { AttributeValue attributeValue = update.getAttributes().get(attributeName); // Try to Find definition in all the types Attribute definition = null; for (ProjectType t : types.all.values()) { definition = t.getAttribute(attributeName); if (definition != null) break; } // initialize provided attributes if (definition != null && definition.isVariable()) { Variable var = (Variable)definition; final ValueProviderFactory valueProviderFactory = var.getValueProviderFactory(); // calculate provided values if (valueProviderFactory != null) { valueProviderFactory.newInstance(baseFolder).setValues(var.getName(), attributeValue.getList()); } if (attributeValue == null && var.isRequired()) throw new ProjectTypeConstraintException("Required attribute value is initialized with null value " + var.getId()); // store non-provided values into JSON if (valueProviderFactory == null) projectJson.getAttributes().put(definition.getName(), attributeValue.getList()); checkVariables.put(attributeName, attributeValue); } } for (ProjectType t : types.all.values()) { for (Attribute attr : t.getAttributes()) { if (attr.isVariable()) { // check if required variables initialized // if(attr.isRequired() && attr.getValue() == null) { if (!checkVariables.containsKey(attr.getName()) && attr.isRequired()) { throw new ProjectTypeConstraintException("Required attribute value is initialized with null value " + attr.getId()); } } else { // add constants projectJson.getAttributes().put(attr.getName(), attr.getValue().getList()); } } } // Default builders and runners // NOTE we take it from Primary type only (for the time) // TODO? let's see for Machine API if (projectJson.getBuilders().getDefault() == null) projectJson.getBuilders().setDefault(types.primary.getDefaultBuilder()); if (projectJson.getRunners().getDefault() == null) projectJson.getRunners().setDefault(types.primary.getDefaultRunner()); projectJson.save(this); } /** * Gets visibility of this project, either 'private' or 'public'. Project is considered to be 'public' if any user has read access to * it. */ public String getVisibility() throws ServerException { final List<AccessControlEntry> acl = baseFolder.getVirtualFile().getACL(); if (acl.isEmpty()) { return "public"; } final Principal guest = DtoFactory.getInstance().createDto(Principal.class).withName("any").withType(Principal.Type.USER); for (AccessControlEntry ace : acl) { if (guest.equals(ace.getPrincipal()) && ace.getPermissions().contains("read")) { return "public"; } } return "private"; } /** * Updates project privacy. * * @see #getVisibility() */ public void setVisibility(String projectVisibility) throws ServerException, ForbiddenException { switch (projectVisibility) { case "private": final List<AccessControlEntry> acl = new ArrayList<>(1); final Principal developer = DtoFactory.getInstance().createDto(Principal.class) .withName("workspace/developer") .withType(Principal.Type.GROUP); acl.add(DtoFactory.getInstance().createDto(AccessControlEntry.class) .withPrincipal(developer) .withPermissions(Arrays.asList("all"))); baseFolder.getVirtualFile().updateACL(acl, true, null); break; case "public": // Remove ACL. Default behaviour of underlying virtual filesystem: everyone can read but can't update. baseFolder.getVirtualFile().updateACL(Collections.<AccessControlEntry>emptyList(), true, null); break; } } static final String ALL_PERMISSIONS = BasicPermissions.ALL.value(); static final String[] ALL_PERMISSIONS_LIST = {BasicPermissions.READ.value(), BasicPermissions.WRITE.value(), BasicPermissions.UPDATE_ACL.value(), "build", "run"}; /** * Gets security restriction applied to this project. Method returns empty {@code List} is project doesn't have any security * restriction. */ public List<AccessControlEntry> getPermissions() throws ServerException { return getPermissions(baseFolder.getVirtualFile()); } private List<AccessControlEntry> getPermissions(VirtualFile virtualFile) throws ServerException { while (virtualFile != null) { final List<AccessControlEntry> acl = virtualFile.getACL(); if (!acl.isEmpty()) { for (AccessControlEntry ace : acl) { final List<String> permissions = ace.getPermissions(); // replace "all" shortcut with list if (permissions.remove(ALL_PERMISSIONS)) { final Set<String> set = new LinkedHashSet<>(permissions); Collections.addAll(set, ALL_PERMISSIONS_LIST); permissions.clear(); permissions.addAll(set); } } return acl; } else { virtualFile = virtualFile.getParent(); } } return new ArrayList<>(4); } /** * Sets permissions to project. * * @param acl * list of {@link org.eclipse.che.api.vfs.shared.dto.AccessControlEntry} */ public void setPermissions(List<AccessControlEntry> acl) throws ServerException, ForbiddenException { final VirtualFile virtualFile = baseFolder.getVirtualFile(); if (virtualFile.getACL().isEmpty()) { // Add permissions from closest parent file if project don't have own. final List<AccessControlEntry> l = new LinkedList<>(); l.addAll(acl); l.addAll(getPermissions(virtualFile.getParent())); virtualFile.updateACL(l, true, null); } else { baseFolder.getVirtualFile().updateACL(acl, false, null); } } public VirtualFileEntry getItem(String path) throws ProjectTypeConstraintException, ValueStorageException, ServerException, NotFoundException, ForbiddenException { final VirtualFileEntry entry = getVirtualFileEntry(path); GetItemHandler handler = manager.getHandlers().getGetItemHandler(getConfig().getTypeId()); if (handler != null) handler.onGetItem(entry); //getConfig().getMixinTypes() return entry; } /** * @return list of paths to modules */ public Modules getModules() { return modules; } private VirtualFileEntry getVirtualFileEntry(String path) throws NotFoundException, ForbiddenException, ServerException { final FolderEntry root = manager.getProjectsRoot(this.getWorkspace()); final VirtualFileEntry entry = root.getChild(path); if (entry == null) { throw new NotFoundException(String.format("Path '%s' doesn't exist.", path)); } return entry; } public class Modules { private final String MODULES_PATH = ".codenvy/modules"; public void remove(String path) throws ForbiddenException, ServerException, ConflictException { Set<String> all = read(); all.remove(path); write(all); } public void add(String path) throws ForbiddenException, ServerException, ConflictException { Set<String> all = read(); all.add(path); write(all); } public Set<String> get() throws ForbiddenException, ServerException { return read(); } private Set<String> read() throws ForbiddenException, ServerException { HashSet<String> modules = new HashSet<>(); VirtualFileEntry file = null; file = baseFolder.getChild(MODULES_PATH); if (file == null || file.isFolder()) return modules; try { BufferedReader in = new BufferedReader(new InputStreamReader(((FileEntry)file).getInputStream())); while (in.ready()) { modules.add(in.readLine()); } in.close(); } catch (IOException e) { throw new ServerException(e); } return modules; } private void write(Set<String> modules) throws ForbiddenException, ServerException, ConflictException { VirtualFileEntry file = null; file = baseFolder.getChild(MODULES_PATH); if (file == null && !modules.isEmpty()) file = ((FolderEntry)baseFolder.getChild(".codenvy")).createFile("modules", new byte[0], "text/plain"); // if(modules.isEmpty() && file != null) // file.remove(); String all = ""; for (String path : modules) { all += (path + "\n"); } ((FileEntry)file).updateContent(all.getBytes()); } } private class ProjectTypes { ProjectType primary; Map<String, ProjectType> mixins = new HashMap<>(); Map<String, ProjectType> all = new HashMap<>(); ProjectTypes(String pt, List<String> mss) throws ProjectTypeConstraintException { if (pt == null) throw new ProjectTypeConstraintException("No primary type defined for " + getWorkspace() + " : " + getPath()); primary = manager.getProjectTypeRegistry().getProjectType(pt); if (primary == null) throw new ProjectTypeConstraintException("No project type registered for " + pt); if (!primary.canBePrimary()) throw new ProjectTypeConstraintException("Project type " + primary.getId() + " is not allowable to be primary type"); all.put(primary.getId(), primary); if (mss == null) mss = new ArrayList<>(); // temporary storage to detect duplicated attributes HashMap<String, Attribute> tmpAttrs = new HashMap<>(); for (Attribute attr : primary.getAttributes()) { tmpAttrs.put(attr.getName(), attr); } for (String m : mss) { if (!m.equals(primary.getId())) { ProjectType mixin = manager.getProjectTypeRegistry().getProjectType(m); if (mixin == null) throw new ProjectTypeConstraintException("No project type registered for " + m); if (!mixin.canBeMixin()) throw new ProjectTypeConstraintException("Project type " + mixin + " is not allowable to be mixin"); // detect duplicated attributes for (Attribute attr : mixin.getAttributes()) { if (tmpAttrs.containsKey(attr.getName())) throw new ProjectTypeConstraintException( "Attribute name conflict. Duplicated attributes detected " + getPath() + " Attribute " + attr.getName() + " declared in " + mixin.getId() + " already declared in " + tmpAttrs.get(attr.getName()).getProjectType()); tmpAttrs.put(attr.getName(), attr); } // Silently remove repeated items from mixins if any mixins.put(m, mixin); all.put(m, mixin); } } } void removeTransient() { HashSet<String> toRemove = new HashSet<>(); for (ProjectType mt : all.values()) { if (!mt.isPersisted()) toRemove.add(mt.getId()); } for (String id : toRemove) { all.remove(id); mixins.remove(id); } } void addTransient() throws ServerException { List<SourceEstimation> estimations; try { estimations = manager.resolveSources(baseFolder.getWorkspace(), baseFolder.getPath(), true); } catch (Exception e) { throw new ServerException(e); } for (SourceEstimation est : estimations) { ProjectType type = manager.getProjectTypeRegistry().getProjectType(est.getType()); // NOTE: Only mixable types allowed if (type.canBeMixin()) { all.put(type.getId(), type); mixins.put(type.getId(), type); } } } List<String> mixinIds() { return new ArrayList<>(mixins.keySet()); } } }