/*******************************************************************************
* Copyright (c) 2012-2017 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.ServerException;
import org.eclipse.che.api.core.model.project.ProjectConfig;
import org.eclipse.che.api.core.model.project.SourceStorage;
import org.eclipse.che.api.core.model.project.type.Attribute;
import org.eclipse.che.api.core.model.project.type.Value;
import org.eclipse.che.api.project.server.type.AttributeValue;
import org.eclipse.che.api.project.server.type.ProjectTypeDef;
import org.eclipse.che.api.project.server.type.ProjectTypeRegistry;
import org.eclipse.che.api.project.server.type.ValueProvider;
import org.eclipse.che.api.project.server.type.ValueStorageException;
import org.eclipse.che.api.project.server.type.Variable;
import org.eclipse.che.api.vfs.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static java.lang.String.format;
import static org.eclipse.che.api.core.ErrorCodes.ATTRIBUTE_NAME_PROBLEM;
import static org.eclipse.che.api.core.ErrorCodes.NO_PROJECT_CONFIGURED_IN_WS;
import static org.eclipse.che.api.core.ErrorCodes.NO_PROJECT_ON_FILE_SYSTEM;
/**
* Internal Project implementation.
* It is supposed that it is object always consistent.
*
* @author gazarenkov
*/
public class RegisteredProject implements ProjectConfig {
private final List<Problem> problems;
private final Map<String, Value> attributes;
private final FolderEntry folder;
private final ProjectConfig config;
private boolean updated;
private boolean detected;
private final ProjectTypes types;
/**
* Either root folder or config can be null, in this case Project is configured with problem.
*
* @param folder
* root local folder or null
* @param config
* project configuration in workspace
* @param updated
* if this object was updated, i.e. no more synchronized with workspace master
* @param detected
* if this project was detected, initialized when "parent" project initialized
* @param projectTypeRegistry
* project type registry
* @throws ServerException
* when path for project is undefined
*/
RegisteredProject(FolderEntry folder,
ProjectConfig config,
boolean updated,
boolean detected,
ProjectTypeRegistry projectTypeRegistry) throws ServerException {
problems = new ArrayList<>();
attributes = new HashMap<>();
Path path;
if (folder != null) {
path = folder.getPath();
} else if (config != null) {
path = Path.of(config.getPath());
} else {
throw new ServerException("Invalid Project Configuration. Path undefined.");
}
this.folder = folder;
this.config = config == null ? new NewProjectConfigImpl(path) : config;
this.updated = updated;
this.detected = detected;
if (folder == null || folder.isFile()) {
problems.add(new Problem(NO_PROJECT_ON_FILE_SYSTEM, "No project folder on file system " + this.config.getPath()));
}
if (config == null) {
problems.add(new Problem(NO_PROJECT_CONFIGURED_IN_WS, "No project configured in workspace " + this.config.getPath()));
}
// 1. init project types
this.types = new ProjectTypes(this.config.getPath(), this.config.getType(), this.config.getMixins(), projectTypeRegistry, problems);
// 2. init transient (implicit, like git) project types.
types.addTransient(folder);
// 3. initialize attributes
initAttributes();
}
/**
* Initialize project attributes.
* Note: the problem with {@link Problem#code} = 13 will be added when a value for some attribute is not initialized
*/
private void initAttributes() {
// we take only defined attributes, others ignored
for (Map.Entry<String, Attribute> entry : types.getAttributeDefs().entrySet()) {
final Attribute definition = entry.getValue();
final String name = entry.getKey();
AttributeValue value = new AttributeValue(config.getAttributes().get(name));
if (!definition.isVariable()) {
// constant, value always assumed as stated in definition
attributes.put(name, definition.getValue());
} else {
// variable
final Variable variable = (Variable)definition;
// value provided
if (variable.isValueProvided()) {
final ValueProvider valueProvider = variable.getValueProviderFactory().newInstance(folder);
if (folder != null) {
try {
if (!valueProvider.isSettable() || value.isEmpty()) {
// get provided value
value = new AttributeValue(valueProvider.getValues(name));
} else {
// set provided (not empty) value
valueProvider.setValues(name, value.getList());
}
} catch (ValueStorageException e) {
final Problem problem = new Problem(ATTRIBUTE_NAME_PROBLEM,
format("Value for attribute %s is not initialized, caused by: %s",
variable.getId(), e.getLocalizedMessage()));
this.problems.add(problem);
}
} else {
continue;
}
}
if (value.isEmpty() && variable.isRequired()) {
final Problem problem = new Problem(ATTRIBUTE_NAME_PROBLEM,
"Value for required attribute is not initialized " + variable.getId());
this.problems.add(problem);
//throw new ProjectTypeConstraintException("Value for required attribute is not initialized " + variable.getId());
}
if (!value.isEmpty()) {
this.attributes.put(name, value);
}
}
}
}
/**
* @return primary project type
*/
public ProjectTypeDef getProjectType() {
return types.getPrimary();
}
/**
* @return mixin project types
*/
public Map<String, ProjectTypeDef> getMixinTypes() {
return types.getMixins();
}
/**
* @return all project types (primary + mixins, convenient method)
*/
public Map<String, ProjectTypeDef> getTypes() {
return types.getAll();
}
/**
* @return attributes as name / Value Map
*/
public Map<String, Value> getAttributeEntries() {
return attributes;
}
/**
* @return whether this project is synchronized with Workspace storage
* On the other words this project is not updated
*/
public boolean isSynced() {
return !this.updated;
}
/**
* should be called after synchronization with Workspace storage
*/
public void setSync() {
this.updated = false;
}
/**
* @return whether this project is detected using Project Type resolver
* If so it should not be persisted to Workspace storage
*/
public boolean isDetected() {
return detected;
}
/**
* @return root folder or null
*/
public FolderEntry getBaseFolder() {
return folder;
}
/**
* @return problems in case if root or config is null (project is not synced)
*/
public List<Problem> getProblems() {
return problems;
}
/**
* @return list of Problems as a String
*/
public String getProblemsStr() {
StringBuilder builder = new StringBuilder();
int i = 0;
for( RegisteredProject.Problem prb : problems ) {
builder.append("[").append(i++).append("] : ").append(prb.message).append("\n");
}
return builder.toString();
}
/**
* @return non provided attributes, those attributes can be persisted to Workspace storage
*/
public Map<String, List<String>> getPersistableAttributes() {
Map<String, List<String>> attrs = new HashMap<>();
for (HashMap.Entry<String, Value> entry : getAttributeEntries().entrySet()) {
Attribute def = types.getAttributeDefs().get(entry.getKey());
// not provided, not constants
if (def != null &&
((def.isVariable() && ((Variable)def).getValueProviderFactory() == null)))
attrs.put(entry.getKey(), entry.getValue().getList());
}
return attrs;
}
/* ------------------------------------------- */
/* Implementation of ProjectConfig interface */
/* ------------------------------------------- */
@Override
public String getPath() {
return ProjectRegistry.absolutizePath(config.getPath());
}
@Override
public String getName() {
return config.getName();
}
@Override
public String getDescription() {
return config.getDescription();
}
@Override
public SourceStorage getSource() {
return config.getSource();
}
@Override
public String getType() {
return types.getPrimary().getId();
}
@Override
public List<String> getMixins() {
return types.getMixins().values()
.stream()
.filter(ProjectTypeDef::isPersisted)
.map(ProjectTypeDef::getId)
.collect(Collectors.toList());
}
@Override
public Map<String, List<String>> getAttributes() {
Map<String, List<String>> attrs = new HashMap<>();
for (Map.Entry<String, Value> entry : getAttributeEntries().entrySet()) {
attrs.put(entry.getKey(), entry.getValue().getList());
}
return attrs;
}
public static class Problem {
Problem(int code, String message) {
this.code = code;
this.message = message;
}
int code;
String message;
}
}