From 2339b0dd6eec1196bdc35fa468e4b067d15a9871 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Tue, 8 Nov 2016 10:27:35 +0200 Subject: [PATCH] CHE-2937. Add ability to create batch of projects --- .../core/model/project/NewProjectConfig.java | 46 ++ .../api/core/model/project/ProjectConfig.java | 2 +- .../ide/api/project/MutableProjectConfig.java | 30 +- .../ide/api/project/NewProjectConfigImpl.java | 173 +++++ .../ide/api/project/ProjectServiceClient.java | 19 + .../api/project/ProjectServiceClientImpl.java | 14 +- .../project/type/ProjectTemplateRegistry.java | 2 +- .../ide/projecttype/wizard/ProjectWizard.java | 2 +- .../CategoriesPagePresenter.java | 35 +- .../presenter/ProjectWizardPresenter.java | 17 +- .../ide/resources/impl/ResourceManager.java | 101 ++- .../projecttype/wizard/ProjectWizardTest.java | 4 +- .../server/rest/ClasspathUpdaterService.java | 5 +- .../client/MavenLocalizationConstant.java | 6 + .../client/wizard/MavenPagePresenter.java | 21 +- .../MavenLocalizationConstant.properties | 3 + .../client/wizard/MavenPagePresenterTest.java | 15 +- .../che/api/project/shared/Constants.java | 25 +- .../project/shared/dto/SourceEstimation.java | 7 +- .../api/project/server/NewProjectConfig.java | 118 --- .../project/server/NewProjectConfigImpl.java | 178 +++++ .../api/project/server/ProjectManager.java | 383 +++++---- .../api/project/server/ProjectRegistry.java | 21 +- .../api/project/server/ProjectService.java | 34 + .../che/api/project/server/ProjectTypes.java | 75 +- .../api/project/server/RegisteredProject.java | 47 +- .../server/WorkspaceProjectsSyncer.java | 15 +- .../project/server/type/ProjectTypeDef.java | 28 +- .../server/type/ProjectTypeResolution.java | 13 + .../server/ProjectManagerWriteTest.java | 725 ++++++++++++++---- .../project/server/ProjectServiceTest.java | 267 +++++-- .../api/project/server/WsAgentTestBase.java | 5 - .../server/batchNewProjectConfigs.json | 68 ++ .../shared/dto/ProjectTemplateDescriptor.java | 14 +- .../shared/dto/NewProjectConfigDto.java | 65 ++ 35 files changed, 1962 insertions(+), 621 deletions(-) create mode 100644 core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/NewProjectConfig.java create mode 100644 ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/NewProjectConfigImpl.java delete mode 100644 wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/NewProjectConfig.java create mode 100644 wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/NewProjectConfigImpl.java create mode 100644 wsagent/che-core-api-project/src/test/resources/org/eclipse/che/api/project/server/batchNewProjectConfigs.json create mode 100644 wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/NewProjectConfigDto.java diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/NewProjectConfig.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/NewProjectConfig.java new file mode 100644 index 0000000000..b735b0c3d5 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/NewProjectConfig.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.core.model.project; + +import java.util.List; +import java.util.Map; + +/** + * Defines configuration for creating new project + * + * @author Roman Nikitenko + */ +public interface NewProjectConfig extends ProjectConfig { + /** Sets project name */ + void setName(String name); + + /** Sets project path */ + void setPath(String path); + + /** Sets project description */ + void setDescription(String description); + + /** Sets primary project type */ + void setType(String type); + + /** Sets mixin project types */ + void setMixins(List mixins); + + /** Sets project attributes */ + void setAttributes(Map> attributes); + + /** Sets options for generator to create project */ + void setOptions(Map options); + + /** Returns options for generator to create project */ + Map getOptions(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/ProjectConfig.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/ProjectConfig.java index 7c311fffa5..9b8f0bfe1e 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/ProjectConfig.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/ProjectConfig.java @@ -33,4 +33,4 @@ public interface ProjectConfig { SourceStorage getSource(); -} \ No newline at end of file +} diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/MutableProjectConfig.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/MutableProjectConfig.java index 3419ea69eb..979e3ce75c 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/MutableProjectConfig.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/MutableProjectConfig.java @@ -12,11 +12,10 @@ package org.eclipse.che.ide.api.project; import com.google.common.annotations.Beta; +import org.eclipse.che.api.core.model.project.NewProjectConfig; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.model.project.SourceStorage; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -37,6 +36,7 @@ public class MutableProjectConfig implements ProjectConfig { private Map> attributes; private MutableSourceStorage sourceStorage; private Map options; + private List projects; public MutableProjectConfig(ProjectConfig source) { name = source.getName(); @@ -90,7 +90,7 @@ public class MutableProjectConfig implements ProjectConfig { @Override public List getMixins() { if (mixins == null) { - mixins = new ArrayList<>(); + mixins = newArrayList(); } return mixins; @@ -128,7 +128,7 @@ public class MutableProjectConfig implements ProjectConfig { public Map getOptions() { if (options == null) { - options = new HashMap<>(); + options = newHashMap(); } return options; } @@ -137,6 +137,28 @@ public class MutableProjectConfig implements ProjectConfig { this.options = options; } + /** + * Returns the list of configurations to creating projects + * + * @return the list of {@link NewProjectConfig} to creating projects + */ + public List getProjects() { + if (projects == null) { + return newArrayList(); + } + return projects; + } + + /** + * Sets the list of configurations to creating projects + * + * @param projects + * the list of {@link NewProjectConfig} to creating projects + */ + public void setProjects(List projects) { + this.projects = projects; + } + public class MutableSourceStorage implements SourceStorage { private String type; private String location; diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/NewProjectConfigImpl.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/NewProjectConfigImpl.java new file mode 100644 index 0000000000..8a383ef202 --- /dev/null +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/NewProjectConfigImpl.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.ide.api.project; + +import org.eclipse.che.api.core.model.project.NewProjectConfig; +import org.eclipse.che.api.core.model.project.SourceStorage; +import org.eclipse.che.api.project.templates.shared.dto.ProjectTemplateDescriptor; +import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@link NewProjectConfig} for creating project + * + * @author Roman Nikitenko + */ +public class NewProjectConfigImpl implements NewProjectConfig { + private String name; + private String path; + private String description; + private String type; + private SourceStorage sourceStorage; + private List mixins; + private Map> attributes; + private Map options; + + /** Constructor for creating project import configuration */ + public NewProjectConfigImpl(String name, + String path, + String description, + String type, + SourceStorage sourceStorage) { + this(name, path, description, type, sourceStorage, null, null, null); + } + + /** Constructor for creating project generator configuration */ + public NewProjectConfigImpl(String name, + String path, + String description, + String type, + Map> attributes, + Map options) { + this(name, path, description, type, null, null, attributes, options); + } + + /** Constructor for creating configuration from project template descriptor */ + public NewProjectConfigImpl(ProjectTemplateDescriptor descriptor) { + this(descriptor.getName(), + descriptor.getPath(), + descriptor.getDescription(), + descriptor.getProjectType(), + descriptor.getSource(), + descriptor.getMixins(), + descriptor.getAttributes(), + descriptor.getOptions()); + } + + /** Constructor for creating configuration from DTO object */ + public NewProjectConfigImpl(NewProjectConfigDto dto) { + this(dto.getName(), + dto.getPath(), + dto.getDescription(), + dto.getType(), + dto.getSource(), + dto.getMixins(), + dto.getAttributes(), + dto.getOptions()); + } + + public NewProjectConfigImpl(String name, + String path, + String description, + String type, + SourceStorage sourceStorage, + List mixins, + Map> attributes, + Map options) { + this.name = name; + this.path = path; + this.description = description; + this.type = type; + this.sourceStorage = sourceStorage; + this.mixins = mixins; + this.attributes = attributes != null ? attributes : new HashMap>(); + this.options = options != null ? options : new HashMap(); + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getPath() { + return path; + } + + @Override + public void setPath(String path) { + this.path = path; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public void setDescription(String description) { + this.description = description; + } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(String type) { + this.type = type; + } + + @Override + public List getMixins() { + return mixins != null ? mixins : new ArrayList(); + } + + @Override + public void setMixins(List mixins) { + this.mixins = mixins; + } + + @Override + public Map> getAttributes() { + return attributes != null ? attributes : new HashMap>(); + } + + @Override + public void setAttributes(Map> attributes) { + this.attributes = attributes; + } + + @Override + public Map getOptions() { + return options != null ? options : new HashMap(); + } + + @Override + public void setOptions(Map options) { + this.options = options; + } + + @Override + public SourceStorage getSource() { + return sourceStorage; + } +} diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/ProjectServiceClient.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/ProjectServiceClient.java index 4580805df4..91c23e6578 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/ProjectServiceClient.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/ProjectServiceClient.java @@ -14,6 +14,7 @@ import org.eclipse.che.api.project.shared.dto.ItemReference; import org.eclipse.che.api.project.shared.dto.SourceEstimation; import org.eclipse.che.api.project.shared.dto.TreeElement; import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.ide.resource.Path; @@ -92,6 +93,24 @@ public interface ProjectServiceClient { */ Promise createProject(ProjectConfigDto configuration, Map options); + /** + * Create batch of projects according to their configurations. + *

+ * Notes: a project will be created by importing when project configuration contains {@link SourceStorageDto} + * object, otherwise this one will be created corresponding its {@link NewProjectConfigDto}: + *

  • - {@link NewProjectConfigDto} object contains only one mandatory {@link NewProjectConfigDto#setPath(String)} field. + * In this case Project will be created as project of "blank" type
  • + *
  • - a project will be created as project of "blank" type when declared primary project type is not registered,
  • + *
  • - a project will be created without mixin project type when declared mixin project type is not registered
  • + *
  • - for creating a project by generator {@link NewProjectConfigDto#getOptions()} should be specified.
  • + * + * @param configurations + * the list of configurations to creating projects + * @return {@link Promise} with the list of {@link ProjectConfigDto} + * @see ProjectConfigDto + */ + Promise> createBatchProjects(List configurations); + /** * Returns the item description by given {@code path}. * diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/ProjectServiceClientImpl.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/ProjectServiceClientImpl.java index 3b52a42fd1..ead24c8972 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/ProjectServiceClientImpl.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/ProjectServiceClientImpl.java @@ -26,6 +26,7 @@ import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper; import org.eclipse.che.api.promises.client.callback.PromiseHelper; +import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.ide.MimeType; @@ -65,7 +66,8 @@ import static org.eclipse.che.ide.rest.HTTPHeader.CONTENT_TYPE; */ public class ProjectServiceClientImpl implements ProjectServiceClient { - private static final String PROJECT = "/project"; + private static final String PROJECT = "/project"; + private static final String BATCH_PROJECTS = "/batch"; private static final String ITEM = "/item"; private static final String TREE = "/tree"; @@ -213,6 +215,16 @@ public class ProjectServiceClientImpl implements ProjectServiceClient { .send(unmarshaller.newUnmarshaller(ProjectConfigDto.class)); } + @Override + public Promise> createBatchProjects(List configurations) { + final String url = getBaseUrl() + BATCH_PROJECTS; + final String loaderMessage = configurations.size() > 1 ? "Creating the batch of projects..." : "Creating project..."; + return reqFactory.createPostRequest(url, configurations) + .header(ACCEPT, MimeType.APPLICATION_JSON) + .loader(loaderFactory.newLoader(loaderMessage)) + .send(unmarshaller.newListUnmarshaller(ProjectConfigDto.class)); + } + /** {@inheritDoc} */ @Override public Promise createFile(Path path, String content) { diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/type/ProjectTemplateRegistry.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/type/ProjectTemplateRegistry.java index ed9343c935..cb35ab357c 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/type/ProjectTemplateRegistry.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/project/type/ProjectTemplateRegistry.java @@ -16,7 +16,7 @@ import javax.validation.constraints.NotNull; import java.util.List; /** - * Registry for {@link org.eclipse.che.api.project.templates.shared.dto.ProjectTemplateDescriptor}s. + * Registry for {@link ProjectTemplateDescriptor}s. * * @author Artem Zatsarynnyi */ diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/ProjectWizard.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/ProjectWizard.java index 016118d971..cac215c67a 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/ProjectWizard.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/ProjectWizard.java @@ -99,7 +99,7 @@ public class ProjectWizard extends AbstractWizard { }); } else if (mode == IMPORT) { appContext.getWorkspaceRoot() - .importProject() + .newProject() .withBody(dataObject) .send() .thenPromise(new Function>() { diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/categoriespage/CategoriesPagePresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/categoriespage/CategoriesPagePresenter.java index 3f7e54166d..2b5f8519b5 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/categoriespage/CategoriesPagePresenter.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/categoriespage/CategoriesPagePresenter.java @@ -13,9 +13,13 @@ package org.eclipse.che.ide.projecttype.wizard.categoriespage; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.inject.Inject; +import org.eclipse.che.api.core.model.project.NewProjectConfig; import org.eclipse.che.api.project.shared.dto.ProjectTypeDto; import org.eclipse.che.api.project.templates.shared.dto.ProjectTemplateDescriptor; +import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.project.MutableProjectConfig; +import org.eclipse.che.ide.api.project.NewProjectConfigImpl; import org.eclipse.che.ide.api.project.type.ProjectTemplateRegistry; import org.eclipse.che.ide.api.project.type.ProjectTypeRegistry; import org.eclipse.che.ide.api.project.type.wizard.PreSelectedProjectTypeManager; @@ -23,12 +27,12 @@ import org.eclipse.che.ide.api.project.type.wizard.ProjectWizardMode; import org.eclipse.che.ide.api.project.type.wizard.ProjectWizardRegistry; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.api.wizard.AbstractWizardPage; -import org.eclipse.che.ide.api.project.MutableProjectConfig; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.resources.selector.SelectPathPresenter; import org.eclipse.che.ide.resources.selector.SelectionPathHandler; import org.eclipse.che.ide.util.NameUtils; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -162,13 +166,20 @@ public class CategoriesPagePresenter extends AbstractWizardPage configDtoList = projectTemplate.getProjects(); + if (newProjectPath.equals("/")) { + return; + } + + final String templatePath = projectTemplate.getPath(); + final List updatedConfigs = new ArrayList<>(configDtoList.size()); + for (NewProjectConfigDto configDto : configDtoList) { + final NewProjectConfig newConfig = new NewProjectConfigImpl(configDto); + final String projectPath = configDto.getPath(); + if (projectPath.startsWith(templatePath)) { + final String path = projectPath.replaceFirst(templatePath, newProjectPath); + newConfig.setPath(path); + } + updatedConfigs.add(newConfig); + } + dataObject.setProjects(updatedConfigs); + } + private void loadProjectTypesAndTemplates() { List projectTypes = projectTypeRegistry.getProjectTypes(); Map> typesByCategory = new HashMap<>(); diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/presenter/ProjectWizardPresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/presenter/ProjectWizardPresenter.java index 4f2ee61ce4..1d019156d0 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/presenter/ProjectWizardPresenter.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/projecttype/wizard/presenter/ProjectWizardPresenter.java @@ -14,12 +14,14 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; +import org.eclipse.che.api.core.model.project.NewProjectConfig; import org.eclipse.che.api.project.shared.dto.AttributeDto; import org.eclipse.che.api.project.shared.dto.ProjectTypeDto; import org.eclipse.che.api.project.templates.shared.dto.ProjectTemplateDescriptor; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.dialogs.DialogFactory; import org.eclipse.che.ide.api.project.MutableProjectConfig; +import org.eclipse.che.ide.api.project.NewProjectConfigImpl; import org.eclipse.che.ide.api.project.type.wizard.ProjectWizardMode; import org.eclipse.che.ide.api.project.type.wizard.ProjectWizardRegistrar; import org.eclipse.che.ide.api.project.type.wizard.ProjectWizardRegistry; @@ -181,7 +183,15 @@ public class ProjectWizardPresenter implements Wizard.UpdateDelegate, if (wizardMode == UPDATE) { newProject.setAttributes(prevData.getAttributes()); } else { - List attributes = projectType.getAttributes(); + final MutableProjectConfig.MutableSourceStorage sourceStorage = prevData.getSource(); + if (sourceStorage != null) { // some values should be cleared when user switch between categories + sourceStorage.setLocation(""); + sourceStorage.setType(""); + sourceStorage.getParameters().clear(); + } + prevData.getProjects().clear(); + + final List attributes = projectType.getAttributes(); Map> prevDataAttributes = prevData.getAttributes(); Map> newAttributes = new HashMap<>(); for (AttributeDto attribute : attributes) { @@ -203,8 +213,9 @@ public class ProjectWizardPresenter implements Wizard.UpdateDelegate, wizard.navigateToFirst(); // set dataObject's values from projectTemplate - dataObject.setType(projectTemplate.getProjectType()); - dataObject.setSource(projectTemplate.getSource()); + final NewProjectConfig newProjectConfig = new NewProjectConfigImpl(projectTemplate); + dataObject.setType(newProjectConfig.getType()); + dataObject.setSource(newProjectConfig.getSource()); } /** Creates or returns project wizard for the specified projectType with the given dataObject. */ diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/resources/impl/ResourceManager.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/resources/impl/ResourceManager.java index 250913b405..db3e404f1d 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/resources/impl/ResourceManager.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/resources/impl/ResourceManager.java @@ -16,6 +16,7 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.google.web.bindery.event.shared.EventBus; +import org.eclipse.che.api.core.model.project.NewProjectConfig; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.model.project.SourceStorage; import org.eclipse.che.api.core.rest.shared.dto.Link; @@ -27,6 +28,7 @@ import org.eclipse.che.api.promises.client.FunctionException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseProvider; import org.eclipse.che.api.promises.client.js.Promises; +import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectProblemDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; @@ -56,6 +58,7 @@ import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.util.Arrays; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -263,22 +266,22 @@ public final class ResourceManager { * @since 4.4.0 */ protected Promise update(final Path path, final ProjectRequest request) { - + final ProjectConfig projectConfig = request.getBody(); + final SourceStorage source = projectConfig.getSource(); final SourceStorageDto sourceDto = dtoFactory.createDto(SourceStorageDto.class); - - if (request.getBody().getSource() != null) { - sourceDto.setLocation(request.getBody().getSource().getLocation()); - sourceDto.setType(request.getBody().getSource().getType()); - sourceDto.setParameters(request.getBody().getSource().getParameters()); + if (source != null) { + sourceDto.setLocation(source.getLocation()); + sourceDto.setType(source.getType()); + sourceDto.setParameters(source.getParameters()); } final ProjectConfigDto dto = dtoFactory.createDto(ProjectConfigDto.class) - .withName(request.getBody().getName()) + .withName(projectConfig.getName()) .withPath(path.toString()) - .withDescription(request.getBody().getDescription()) - .withType(request.getBody().getType()) - .withMixins(request.getBody().getMixins()) - .withAttributes(request.getBody().getAttributes()) + .withDescription(projectConfig.getDescription()) + .withType(projectConfig.getType()) + .withMixins(projectConfig.getMixins()) + .withAttributes(projectConfig.getAttributes()) .withSource(sourceDto); return ps.updateProject(dto).thenPromise(new Function>() { @@ -410,21 +413,9 @@ public final class ResourceManager { checkArgument(typeRegistry.getProjectType(createRequest.getBody().getType()) != null, "Invalid project type"); final Path path = Path.valueOf(createRequest.getBody().getPath()); - return findResource(path, true).thenPromise(new Function, Promise>() { @Override public Promise apply(Optional resource) throws FunctionException { - - final MutableProjectConfig projectConfig = (MutableProjectConfig)createRequest.getBody(); - final ProjectConfigDto dto = dtoFactory.createDto(ProjectConfigDto.class) - .withName(projectConfig.getName()) - .withPath(path.toString()) - .withDescription(projectConfig.getDescription()) - .withType(projectConfig.getType()) - .withMixins(projectConfig.getMixins()) - .withAttributes(projectConfig.getAttributes()); - - if (resource.isPresent()) { if (resource.get().isProject()) { throw new IllegalStateException("Project already exists"); @@ -435,22 +426,32 @@ public final class ResourceManager { return update(path, createRequest); } - return ps.createProject(dto, projectConfig.getOptions()).thenPromise(new Function>() { + final MutableProjectConfig projectConfig = (MutableProjectConfig)createRequest.getBody(); + final List projectConfigList = projectConfig.getProjects(); + projectConfigList.add(asDto(projectConfig)); + final List configDtoList = asDto(projectConfigList); + + return ps.createBatchProjects(configDtoList).thenPromise(new Function, Promise>() { @Override - public Promise apply(ProjectConfigDto config) throws FunctionException { - final Project newResource = resourceFactory.newProjectImpl(config, ResourceManager.this); - store.register(newResource); + public Promise apply(final List configList) throws FunctionException { return ps.getProjects().then(new Function, Project>() { @Override public Project apply(List updatedConfiguration) throws FunctionException { - //cache new configs cachedConfigs = updatedConfiguration.toArray(new ProjectConfigDto[updatedConfiguration.size()]); - eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(newResource, ADDED | DERIVED))); + for (ProjectConfigDto projectConfigDto : configList) { + if (projectConfigDto.getPath().equals(path.toString())) { + final Project newResource = resourceFactory.newProjectImpl(projectConfigDto, ResourceManager.this); + store.register(newResource); + eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(newResource, ADDED | DERIVED))); - return newResource; + return newResource; + } + } + + throw new IllegalStateException("Created project is not found"); } }); } @@ -459,6 +460,46 @@ public final class ResourceManager { }); } + private NewProjectConfigDto asDto(MutableProjectConfig config) { + final SourceStorage source = config.getSource(); + final SourceStorageDto sourceStorageDto = dtoFactory.createDto(SourceStorageDto.class) + .withType(source.getType()) + .withLocation(source.getLocation()) + .withParameters(source.getParameters()); + + return dtoFactory.createDto(NewProjectConfigDto.class) + .withName(config.getName()) + .withPath(config.getPath()) + .withDescription(config.getDescription()) + .withSource(sourceStorageDto) + .withType(config.getType()) + .withMixins(config.getMixins()) + .withAttributes(config.getAttributes()) + .withOptions(config.getOptions()); + } + + private List asDto(List configList) { + List result = new ArrayList<>(configList.size()); + for (NewProjectConfig config : configList) { + final SourceStorage source = config.getSource(); + final SourceStorageDto sourceStorageDto = dtoFactory.createDto(SourceStorageDto.class) + .withType(source.getType()) + .withLocation(source.getLocation()) + .withParameters(source.getParameters()); + + result.add(dtoFactory.createDto(NewProjectConfigDto.class) + .withName(config.getName()) + .withPath(config.getPath()) + .withDescription(config.getDescription()) + .withSource(sourceStorageDto) + .withType(config.getType()) + .withMixins(config.getMixins()) + .withAttributes(config.getAttributes()) + .withOptions(config.getOptions())); + } + return result; + } + protected Promise importProject(final Project.ProjectRequest importRequest) { checkArgument(checkProjectName(importRequest.getBody().getName()), "Invalid project name"); checkNotNull(importRequest.getBody().getSource(), "Null source configuration occurred"); diff --git a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/projecttype/wizard/ProjectWizardTest.java b/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/projecttype/wizard/ProjectWizardTest.java index 6588869e91..75f540b65a 100644 --- a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/projecttype/wizard/ProjectWizardTest.java +++ b/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/projecttype/wizard/ProjectWizardTest.java @@ -139,7 +139,7 @@ public class ProjectWizardTest { public void shouldImportProjectSuccessfully() throws Exception { prepareWizard(IMPORT); - when(workspaceRoot.importProject()).thenReturn(createProjectRequest); + when(workspaceRoot.newProject()).thenReturn(createProjectRequest); when(createProjectRequest.withBody(any(ProjectConfig.class))).thenReturn(createProjectRequest); when(createProjectRequest.send()).thenReturn(createProjectPromise); when(createProjectPromise.then(any(Operation.class))).thenReturn(createProjectPromise); @@ -159,7 +159,7 @@ public class ProjectWizardTest { public void shouldFailOnImportProject() throws Exception { prepareWizard(IMPORT); - when(workspaceRoot.importProject()).thenReturn(createProjectRequest); + when(workspaceRoot.newProject()).thenReturn(createProjectRequest); when(createProjectRequest.withBody(any(ProjectConfig.class))).thenReturn(createProjectRequest); when(createProjectRequest.send()).thenReturn(createProjectPromise); when(createProjectPromise.then(any(Operation.class))).thenReturn(createProjectPromise); diff --git a/plugins/plugin-java/che-plugin-java-plain/che-plugin-java-plain-server/src/main/java/org/eclipse/che/plugin/java/plain/server/rest/ClasspathUpdaterService.java b/plugins/plugin-java/che-plugin-java-plain/che-plugin-java-plain-server/src/main/java/org/eclipse/che/plugin/java/plain/server/rest/ClasspathUpdaterService.java index 1d575a42e0..64513fdbbb 100644 --- a/plugins/plugin-java/che-plugin-java-plain/che-plugin-java-plain-server/src/main/java/org/eclipse/che/plugin/java/plain/server/rest/ClasspathUpdaterService.java +++ b/plugins/plugin-java/che-plugin-java-plain/che-plugin-java-plain-server/src/main/java/org/eclipse/che/plugin/java/plain/server/rest/ClasspathUpdaterService.java @@ -16,7 +16,8 @@ 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.NewProjectConfig; +import org.eclipse.che.api.core.model.project.NewProjectConfig; +import org.eclipse.che.api.project.server.NewProjectConfigImpl; import org.eclipse.che.api.project.server.ProjectManager; import org.eclipse.che.api.project.server.ProjectRegistry; import org.eclipse.che.api.project.server.RegisteredProject; @@ -104,7 +105,7 @@ public class ClasspathUpdaterService { ServerException { RegisteredProject project = projectRegistry.getProject(projectPath); - NewProjectConfig projectConfig = new NewProjectConfig(projectPath, + NewProjectConfig projectConfig = new NewProjectConfigImpl(projectPath, project.getName(), project.getType(), project.getSource()); diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/MavenLocalizationConstant.java b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/MavenLocalizationConstant.java index e45c49ef7a..3be0207de9 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/MavenLocalizationConstant.java +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/MavenLocalizationConstant.java @@ -61,4 +61,10 @@ public interface MavenLocalizationConstant extends Messages { @Key("window.loader.title") String windowLoaderTitle(); + + @Key("maven.page.estimate.errorMessage") + String mavenPageEstimateErrorMessage(); + + @Key("maven.page.errorDialog.title") + String mavenPageErrorDialogTitle(); } diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/wizard/MavenPagePresenter.java b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/wizard/MavenPagePresenter.java index 8a34d04a22..43d3fd4e5e 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/wizard/MavenPagePresenter.java +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/wizard/MavenPagePresenter.java @@ -27,6 +27,7 @@ import org.eclipse.che.ide.api.wizard.AbstractWizardPage; import org.eclipse.che.ide.util.loging.Log; import org.eclipse.che.plugin.maven.client.MavenArchetype; import org.eclipse.che.plugin.maven.client.MavenExtension; +import org.eclipse.che.plugin.maven.client.MavenLocalizationConstant; import javax.validation.constraints.NotNull; import java.util.Arrays; @@ -62,18 +63,21 @@ import static org.eclipse.che.plugin.maven.shared.MavenAttributes.VERSION; */ public class MavenPagePresenter extends AbstractWizardPage implements MavenPageView.ActionDelegate { - private final MavenPageView view; - private final DialogFactory dialogFactory; - private final AppContext appContext; + private final MavenPageView view; + private final DialogFactory dialogFactory; + private final AppContext appContext; + private final MavenLocalizationConstant localization; @Inject public MavenPagePresenter(MavenPageView view, DialogFactory dialogFactory, - AppContext appContext) { + AppContext appContext, + MavenLocalizationConstant localization) { super(); this.view = view; this.dialogFactory = dialogFactory; this.appContext = appContext; + this.localization = localization; view.setDelegate(this); } @@ -104,6 +108,13 @@ public class MavenPagePresenter extends AbstractWizardPage container.get().estimate(MAVEN_ID).then(new Operation() { @Override public void apply(SourceEstimation estimation) throws OperationException { + if (!estimation.isMatched()) { + final String resolution = estimation.getResolution(); + final String errorMessage = resolution.isEmpty() ? localization.mavenPageEstimateErrorMessage() : resolution; + dialogFactory.createMessageDialog(localization.mavenPageErrorDialogTitle(), errorMessage, null).show(); + return; + } + Map> estimatedAttributes = estimation.getAttributes(); List artifactIdValues = estimatedAttributes.get(ARTIFACT_ID); if (artifactIdValues != null && !artifactIdValues.isEmpty()) { @@ -136,7 +147,7 @@ public class MavenPagePresenter extends AbstractWizardPage }).catchError(new Operation() { @Override public void apply(PromiseError arg) throws OperationException { - dialogFactory.createMessageDialog("Not valid Maven project", arg.getMessage(), null).show(); + dialogFactory.createMessageDialog(localization.mavenPageErrorDialogTitle(), arg.getMessage(), null).show(); Log.error(MavenPagePresenter.class, arg); } }); diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/resources/org/eclipse/che/plugin/maven/client/MavenLocalizationConstant.properties b/plugins/plugin-maven/che-plugin-maven-ide/src/main/resources/org/eclipse/che/plugin/maven/client/MavenLocalizationConstant.properties index 51bf92a9a0..84a5d69c97 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/main/resources/org/eclipse/che/plugin/maven/client/MavenLocalizationConstant.properties +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/resources/org/eclipse/che/plugin/maven/client/MavenLocalizationConstant.properties @@ -36,3 +36,6 @@ loader.action.name=Dependencies resolver loader.action.description=Maven Dependencies Resolver window.loader.title=Resolving dependencies +##### Wizard Maven Page ##### +maven.page.errorDialog.title=Not valid Maven project +maven.page.estimate.errorMessage=Source code not matches Maven project type requirements diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/test/java/org/eclipse/che/plugin/maven/client/wizard/MavenPagePresenterTest.java b/plugins/plugin-maven/che-plugin-maven-ide/src/test/java/org/eclipse/che/plugin/maven/client/wizard/MavenPagePresenterTest.java index 4fe2d31cc1..09492665fd 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/test/java/org/eclipse/che/plugin/maven/client/wizard/MavenPagePresenterTest.java +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/test/java/org/eclipse/che/plugin/maven/client/wizard/MavenPagePresenterTest.java @@ -22,6 +22,7 @@ import org.eclipse.che.ide.api.dialogs.DialogFactory; import org.eclipse.che.ide.api.dialogs.MessageDialog; import org.eclipse.che.ide.api.project.MutableProjectConfig; import org.eclipse.che.ide.api.resources.Container; +import org.eclipse.che.plugin.maven.client.MavenLocalizationConstant; import org.eclipse.che.plugin.maven.shared.MavenAttributes; import org.junit.Before; import org.junit.Test; @@ -52,13 +53,15 @@ public class MavenPagePresenterTest { private final static String TEXT = "to be or not to be"; @Mock - private MavenPageView view; + private MavenPageView view; @Mock - private EventBus eventBus; + private EventBus eventBus; @Mock - private DialogFactory dialogFactory; + private DialogFactory dialogFactory; @Mock - private AppContext appContext; + private AppContext appContext; + @Mock + private MavenLocalizationConstant localization; @InjectMocks private MavenPagePresenter mavenPagePresenter; @@ -106,11 +109,13 @@ public class MavenPagePresenterTest { @Test public void warningWindowShouldBeShowedIfProjectEstimationHasSomeError() throws Exception { + final String dialogTitle = "Not valid Maven project"; PromiseError promiseError = mock(PromiseError.class); MessageDialog messageDialog = mock(MessageDialog.class); context.put(WIZARD_MODE_KEY, UPDATE.toString()); when(promiseError.getMessage()).thenReturn(TEXT); + when(localization.mavenPageErrorDialogTitle()).thenReturn(dialogTitle); when(dialogFactory.createMessageDialog(anyString(), anyString(), anyObject())).thenReturn(messageDialog); when(sourceEstimationPromise.then(Matchers.>anyObject())).thenReturn(sourceEstimationPromise); when(sourceEstimationPromise.catchError(Matchers.>anyObject())).thenReturn(sourceEstimationPromise); @@ -124,7 +129,7 @@ public class MavenPagePresenterTest { containerArgumentErrorCapture.getValue().apply(promiseError); verify(promiseError).getMessage(); - verify(dialogFactory).createMessageDialog("Not valid Maven project", TEXT, null); + verify(dialogFactory).createMessageDialog(dialogTitle, TEXT, null); verify(messageDialog).show(); } diff --git a/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/Constants.java b/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/Constants.java index 1a8b8f617f..5ff457b76b 100644 --- a/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/Constants.java +++ b/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/Constants.java @@ -15,19 +15,20 @@ package org.eclipse.che.api.project.shared; */ public class Constants { - public static final String BLANK_ID = "blank"; - public static final String ZIP_IMPORTER_ID = "zip"; - public static final String VCS_PROVIDER_NAME = "vcs.provider.name"; + public static final String BLANK_ID = "blank"; + public static final String ZIP_IMPORTER_ID = "zip"; + public static final String VCS_PROVIDER_NAME = "vcs.provider.name"; // rels for known project links - public static final String LINK_REL_GET_PROJECTS = "get projects"; - public static final String LINK_REL_CREATE_PROJECT = "create project"; - public static final String LINK_REL_UPDATE_PROJECT = "update project"; - public static final String LINK_REL_EXPORT_ZIP = "zipball sources"; - public static final String LINK_REL_CHILDREN = "children"; - public static final String LINK_REL_TREE = "tree"; - public static final String LINK_REL_DELETE = "delete"; - public static final String LINK_REL_GET_CONTENT = "get content"; - public static final String LINK_REL_UPDATE_CONTENT = "update content"; + public static final String LINK_REL_GET_PROJECTS = "get projects"; + public static final String LINK_REL_CREATE_PROJECT = "create project"; + public static final String LINK_REL_CREATE_BATCH_PROJECTS = "create batch of projects"; + public static final String LINK_REL_UPDATE_PROJECT = "update project"; + public static final String LINK_REL_EXPORT_ZIP = "zipball sources"; + public static final String LINK_REL_CHILDREN = "children"; + public static final String LINK_REL_TREE = "tree"; + public static final String LINK_REL_DELETE = "delete"; + public static final String LINK_REL_GET_CONTENT = "get content"; + public static final String LINK_REL_UPDATE_CONTENT = "update content"; public static final String LINK_REL_PROJECT_TYPES = "project types"; diff --git a/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/SourceEstimation.java b/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/SourceEstimation.java index d2054f131d..b7f2fd1000 100644 --- a/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/SourceEstimation.java +++ b/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/SourceEstimation.java @@ -24,7 +24,6 @@ import java.util.Map; @DTO public interface SourceEstimation { - /** Gets unique id of type of project. */ @ApiModelProperty(value = "type ID", position = 1) String getType(); @@ -44,4 +43,10 @@ public interface SourceEstimation { SourceEstimation withMatched(boolean matched); + + /** Gets resolution - the reason that source code not matches project type requirements. */ + @ApiModelProperty(value = "Resolution", position = 4) + String getResolution(); + + SourceEstimation withResolution(String resolution); } diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/NewProjectConfig.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/NewProjectConfig.java deleted file mode 100644 index d7036ca761..0000000000 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/NewProjectConfig.java +++ /dev/null @@ -1,118 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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.model.project.ProjectConfig; -import org.eclipse.che.api.core.model.project.SourceStorage; -import org.eclipse.che.api.project.server.type.BaseProjectType; -import org.eclipse.che.api.vfs.Path; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author gazarenkov - */ -public class NewProjectConfig implements ProjectConfig { - - private final String path; - private final String type; - private final List mixins; - private final String name; - private final String description; - private final Map> attributes; - private final SourceStorage origin; - - /** - * Full qualified constructor - * - * @param path - * @param type - * @param mixins - * @param name - * @param description - * @param attributes - * @param origin - */ - public NewProjectConfig(String path, - String type, - List mixins, - String name, - String description, - Map> attributes, - SourceStorage origin) { - this.path = path; - this.type = (type == null) ? BaseProjectType.ID : type; - this.mixins = (mixins == null) ? new ArrayList<>() : mixins; - this.name = name; - this.description = description; - this.attributes = (attributes == null) ? new HashMap<>() : attributes; - this.origin = origin; - } - - /** - * Constructor for project import - * - * @param path - * @param name - * @param type - * @param origin - */ - public NewProjectConfig(String path, String name, String type, SourceStorage origin) { - this(path, type, null, name, null, null, origin); - } - - /** - * Constructor for reinit - * - * @param path - */ - public NewProjectConfig(Path path) { - this(path.toString(), null, null, path.getName(), null, null, null); - } - - @Override - public String getName() { - return name; - } - - @Override - public String getPath() { - return path; - } - - @Override - public String getDescription() { - return description; - } - - @Override - public String getType() { - return type; - } - - @Override - public List getMixins() { - return mixins; - } - - @Override - public Map> getAttributes() { - return attributes; - } - - @Override - public SourceStorage getSource() { - return origin; - } -} diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/NewProjectConfigImpl.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/NewProjectConfigImpl.java new file mode 100644 index 0000000000..8bb56351c1 --- /dev/null +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/NewProjectConfigImpl.java @@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.model.project.NewProjectConfig; +import org.eclipse.che.api.core.model.project.SourceStorage; +import org.eclipse.che.api.project.server.type.BaseProjectType; +import org.eclipse.che.api.vfs.Path; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.newHashMap; + +/** + * Implementation of {@link NewProjectConfig} for creating project + * + * @author gazarenkov + */ +public class NewProjectConfigImpl implements NewProjectConfig { + + private String path; + private String type; + private List mixins; + private String name; + private String description; + private Map> attributes; + private Map options; + private SourceStorage origin; + + /** + * Full qualified constructor + * + * @param path + * project path + * @param type + * project type + * @param mixins + * mixin project types + * @param name + * project name + * @param description + * project description + * @param attributes + * project attributes + * @param options + * options for generator for creating project + * @param origin + * source configuration + */ + public NewProjectConfigImpl(String path, + String type, + List mixins, + String name, + String description, + Map> attributes, + Map options, + SourceStorage origin) { + this.path = path; + this.type = (type == null) ? BaseProjectType.ID : type; + this.mixins = (mixins == null) ? new ArrayList<>() : mixins; + this.name = name; + this.description = description; + this.attributes = (attributes == null) ? newHashMap() : attributes; + this.options = (options == null) ? newHashMap() : options; + this.origin = origin; + } + + /** + * Constructor for project import + * + * @param path + * project path + * @param name + * project name + * @param type + * project type + * @param origin + * source configuration + */ + public NewProjectConfigImpl(String path, String name, String type, SourceStorage origin) { + this(path, type, null, name, null, null, null, origin); + } + + /** + * Constructor for reinit + * + * @param path + */ + public NewProjectConfigImpl(Path path) { + this(path.toString(), null, null, path.getName(), null, null, null, null); + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getPath() { + return path; + } + + @Override + public void setPath(String path) { + this.path = path; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public void setDescription(String description) { + this.description = description; + } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(String type) { + this.type = type; + } + + @Override + public List getMixins() { + return mixins != null ? mixins : newArrayList(); + } + + @Override + public void setMixins(List mixins) { + this.mixins = mixins; + } + + @Override + public Map> getAttributes() { + return attributes != null ? attributes : newHashMap(); + } + + @Override + public void setAttributes(Map> attributes) { + this.attributes = attributes; + } + + @Override + public SourceStorage getSource() { + return origin; + } + + @Override + public void setOptions(Map options) { + this.options = options; + } + + @Override + public Map getOptions() { + return options != null ? options : newHashMap(); + } +} diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectManager.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectManager.java index 66495bedbd..5c771b5907 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectManager.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectManager.java @@ -12,16 +12,19 @@ package org.eclipse.che.api.project.server; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.eclipse.che.api.core.BadRequestException; 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.core.UnauthorizedException; +import org.eclipse.che.api.core.model.project.NewProjectConfig; 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.ProjectType; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.util.LineConsumerFactory; +import org.eclipse.che.api.project.server.RegisteredProject.Problem; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.api.project.server.handlers.CreateProjectHandler; import org.eclipse.che.api.project.server.handlers.ProjectHandlerRegistry; @@ -33,7 +36,6 @@ import org.eclipse.che.api.project.server.type.BaseProjectType; 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.ProjectTypeResolution; -import org.eclipse.che.api.project.server.type.ValueStorageException; import org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType; import org.eclipse.che.api.vfs.Path; import org.eclipse.che.api.vfs.VirtualFile; @@ -61,6 +63,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; /** * Facade for all project related operations. @@ -196,7 +202,7 @@ public final class ProjectManager { public RegisteredProject getProject(String projectPath) throws ServerException, NotFoundException { final RegisteredProject project = projectRegistry.getProject(projectPath); if (project == null) { - throw new NotFoundException(String.format("Project '%s' doesn't exist.", projectPath)); + throw new NotFoundException(format("Project '%s' doesn't exist.", projectPath)); } return project; @@ -229,67 +235,179 @@ public final class ProjectManager { throw new ConflictException("Path for new project should be defined "); } - final String path = ProjectRegistry.absolutizePath(projectConfig.getPath()); - if (projectConfig.getType() == null) { - throw new ConflictException("Project Type is not defined " + path); + throw new ConflictException("Project Type is not defined " + projectConfig.getPath()); } + final String path = ProjectRegistry.absolutizePath(projectConfig.getPath()); if (projectRegistry.getProject(path) != null) { - throw new ConflictException("Project config already exists " + path); + throw new ConflictException("Project config already exists for " + path); } - final CreateProjectHandler generator = handlers.getCreateProjectHandler(projectConfig.getType()); - FolderEntry projectFolder; - if (generator != null) { - Map valueMap = new HashMap<>(); - Map> attributes = projectConfig.getAttributes(); - if (attributes != null) { - for (Map.Entry> entry : attributes.entrySet()) { - valueMap.put(entry.getKey(), new AttributeValue(entry.getValue())); - } - } - if (options == null) { - options = new HashMap<>(); - } - Path projectPath = Path.of(path); - generator.onCreateProject(projectPath, valueMap, options); - projectFolder = new FolderEntry(vfs.getRoot().getChild(projectPath), projectRegistry); - } else { - projectFolder = new FolderEntry(vfs.getRoot().createFolder(path), projectRegistry); - } - - final RegisteredProject project; - try { - project = projectRegistry.putProject(projectConfig, projectFolder, true, false); - } catch (Exception e) { - // rollback project folder - - projectFolder.getVirtualFile().delete(); - throw e; - } - - // unlike imported it is not appropriate for newly created project to have problems - if(!project.getProblems().isEmpty()) { - - // rollback project folder - projectFolder.getVirtualFile().delete(); - // remove project entry - projectRegistry.removeProjects(projectConfig.getPath()); - throw new ServerException("Problems occured: " + project.getProblemsStr()); - } - - - workspaceProjectsHolder.sync(projectRegistry); - - projectRegistry.fireInitHandlers(project); - - return project; + return doCreateProject(projectConfig, options); } finally { projectTreeChangesDetector.resume(); } } + /** Note: Use {@link ProjectTreeChangesDetector#suspend()} and {@link ProjectTreeChangesDetector#resume()} while creating a project */ + private RegisteredProject doCreateProject(ProjectConfig projectConfig, Map options) throws ConflictException, + ForbiddenException, + ServerException, + NotFoundException { + final String path = ProjectRegistry.absolutizePath(projectConfig.getPath()); + final CreateProjectHandler generator = handlers.getCreateProjectHandler(projectConfig.getType()); + FolderEntry projectFolder; + if (generator != null) { + Map valueMap = new HashMap<>(); + Map> attributes = projectConfig.getAttributes(); + if (attributes != null) { + for (Map.Entry> entry : attributes.entrySet()) { + valueMap.put(entry.getKey(), new AttributeValue(entry.getValue())); + } + } + if (options == null) { + options = new HashMap<>(); + } + Path projectPath = Path.of(path); + generator.onCreateProject(projectPath, valueMap, options); + projectFolder = new FolderEntry(vfs.getRoot().getChild(projectPath), projectRegistry); + } else { + projectFolder = new FolderEntry(vfs.getRoot().createFolder(path), projectRegistry); + } + + final RegisteredProject project = projectRegistry.putProject(projectConfig, projectFolder, true, false); + workspaceProjectsHolder.sync(projectRegistry); + projectRegistry.fireInitHandlers(project); + + return project; + } + + /** + * Create batch of projects according to their configurations. + *

    + * Notes: - a project will be created by importing when project configuration contains {@link SourceStorage} object, + * otherwise this one will be created corresponding its {@link NewProjectConfig}: + *

  • - {@link NewProjectConfig} object contains only one mandatory {@link NewProjectConfig#setPath(String)} field. + * In this case Project will be created as project of {@link BaseProjectType} type
  • + *
  • - a project will be created as project of {@link BaseProjectType} type with {@link Problem#code} = 12 + * when declared primary project type is not registered,
  • + *
  • - a project will be created with {@link Problem#code} = 12 and without mixin project type + * when declared mixin project type is not registered
  • + *
  • - for creating a project by generator {@link NewProjectConfig#getOptions()} should be specified.
  • + * + * @param projectConfigList + * the list of configurations to create projects + * @param rewrite + * whether rewrite or not (throw exception otherwise) if such a project exists + * @return the list of new projects + * @throws BadRequestException + * when {@link NewProjectConfig} object not contains mandatory {@link NewProjectConfig#setPath(String)} field. + * @throws ConflictException + * when the same path project exists and {@code rewrite} is {@code false} + * @throws ForbiddenException + * when trying to overwrite the project and this one contains at least one locked file + * @throws NotFoundException + * when parent folder does not exist + * @throws UnauthorizedException + * if user isn't authorized to access to location at importing source code + * @throws ServerException + * if other error occurs + */ + public List createBatchProjects(List projectConfigList, boolean rewrite) + throws BadRequestException, ConflictException, ForbiddenException, NotFoundException, ServerException, UnauthorizedException, + IOException { + projectTreeChangesDetector.suspend(); + try { + final List projects = new ArrayList<>(projectConfigList.size()); + validateProjectConfigurations(projectConfigList, rewrite); + + final List sortedConfigList = projectConfigList + .stream() + .sorted((config1, config2) -> config1.getPath().compareTo(config2.getPath())) + .collect(Collectors.toList()); + + for (NewProjectConfig projectConfig : sortedConfigList) { + RegisteredProject registeredProject; + final String pathToProject = projectConfig.getPath(); + + //creating project(by config or by importing source code) + try { + final SourceStorage sourceStorage = projectConfig.getSource(); + if (sourceStorage != null && !isNullOrEmpty(sourceStorage.getLocation())) { + doImportProject(pathToProject, sourceStorage, rewrite); + } else if (!isVirtualFileExist(pathToProject)) { + registeredProject = doCreateProject(projectConfig, projectConfig.getOptions()); + projects.add(registeredProject); + continue; + } + } catch (Exception e) { + if (!isVirtualFileExist(pathToProject)) {//project folder is absent + rollbackCreatingBatchProjects(projects); + throw e; + } + } + + //update project + if (isVirtualFileExist(pathToProject)) { + try { + registeredProject = updateProject(projectConfig); + } catch (Exception e) { + registeredProject = projectRegistry.putProject(projectConfig, asFolder(pathToProject), true, false); + registeredProject.getProblems().add(new Problem(14, "The project is not updated, caused by " + e.getLocalizedMessage())); + } + } else { + registeredProject = projectRegistry.putProject(projectConfig, null, true, false); + } + + projects.add(registeredProject); + } + + return projects; + + } finally { + projectTreeChangesDetector.resume(); + } + } + + private void rollbackCreatingBatchProjects(List projects) { + for (RegisteredProject project : projects) { + try { + final FolderEntry projectFolder = project.getBaseFolder(); + if (projectFolder != null) { + projectFolder.getVirtualFile().delete(); + } + projectRegistry.removeProjects(project.getPath()); + } catch (Exception e) { + LOG.warn(e.getLocalizedMessage()); + } + } + } + + private void validateProjectConfigurations(List projectConfigList, boolean rewrite) + throws NotFoundException, ServerException, ConflictException, ForbiddenException, BadRequestException { + + for (NewProjectConfig projectConfig : projectConfigList) { + final String pathToProject = projectConfig.getPath(); + if (isNullOrEmpty(pathToProject)) { + throw new BadRequestException("Path for new project should be defined"); + } + + final String path = ProjectRegistry.absolutizePath(pathToProject); + final RegisteredProject registeredProject = projectRegistry.getProject(path); + if (registeredProject != null && rewrite) { + delete(path); + } else if (registeredProject != null) { + throw new ConflictException(format("Project config already exists for %s", path)); + } + + final String projectTypeId = projectConfig.getType(); + if (isNullOrEmpty(projectTypeId)) { + projectConfig.setType(BaseProjectType.ID); + } + } + } + /** * Updating project means: * - getting the project (should exist) @@ -311,31 +429,17 @@ public final class ProjectManager { ServerException, NotFoundException, ConflictException { - String path = newConfig.getPath(); - + final String path = newConfig.getPath(); if (path == null) { throw new ConflictException("Project path is not defined"); } final FolderEntry baseFolder = asFolder(path); - - // If a project does not exist in the target path, create a new one if (baseFolder == null) { - throw new NotFoundException(String.format("Folder '%s' doesn't exist.", path)); + throw new NotFoundException(format("Folder '%s' doesn't exist.", path)); } - ProjectConfig oldConfig = projectRegistry.getProject(path); - final RegisteredProject project = projectRegistry.putProject(newConfig, baseFolder, true, false); - - // unlike imported it is not appropriate for updated project to have problems - if(!project.getProblems().isEmpty()) { - - // rollback project folder - projectRegistry.putProject(oldConfig, baseFolder, false, false); - throw new ServerException("Problems occured: " + project.getProblemsStr()); - } - workspaceProjectsHolder.sync(projectRegistry); projectRegistry.fireInitHandlers(project); @@ -371,57 +475,67 @@ public final class ProjectManager { NotFoundException { projectTreeChangesDetector.suspend(); try { - final ProjectImporter importer = importers.getImporter(sourceStorage.getType()); - if (importer == null) { - throw new NotFoundException(String.format("Unable import sources project from '%s'. Sources type '%s' is not supported.", - sourceStorage.getLocation(), sourceStorage.getType())); - } - - // Preparing websocket output publisher to broadcast output of import process to the ide clients while importing - final LineConsumerFactory outputOutputConsumerFactory = - () -> new ProjectImportOutputWSLineConsumer(path, workspaceProjectsHolder.getWorkspaceId(), 300); - - String normalizePath = (path.startsWith("/")) ? path : "/".concat(path); - FolderEntry folder = asFolder(normalizePath); - if (folder != null && !rewrite) { - throw new ConflictException(String.format("Project %s already exists ", path)); - } - - if (folder == null) { - folder = getProjectsRoot().createFolder(normalizePath); - } - - try { - importer.importSources(folder, sourceStorage, outputOutputConsumerFactory); - } catch (final Exception e) { - folder.remove(); - throw e; - } - - final String name = folder.getPath().getName(); - for (ProjectConfig project : workspaceProjectsHolder.getProjects()) { - if (normalizePath.equals(project.getPath())) { - // TODO Needed for factory project importing with keepDir. It needs to find more appropriate solution - List innerProjects = projectRegistry.getProjects(normalizePath); - for (String innerProject : innerProjects) { - RegisteredProject registeredProject = projectRegistry.getProject(innerProject); - projectRegistry.putProject(registeredProject, asFolder(registeredProject.getPath()), true, false); - } - RegisteredProject rp = projectRegistry.putProject(project, folder, true, false); - workspaceProjectsHolder.sync(projectRegistry); - return rp; - } - } - - RegisteredProject rp = projectRegistry - .putProject(new NewProjectConfig(normalizePath, name, BaseProjectType.ID, sourceStorage), folder, true, false); - workspaceProjectsHolder.sync(projectRegistry); - return rp; + return doImportProject(path, sourceStorage, rewrite); } finally { projectTreeChangesDetector.resume(); } } + /** Note: Use {@link ProjectTreeChangesDetector#suspend()} and {@link ProjectTreeChangesDetector#resume()} while importing source code */ + private RegisteredProject doImportProject(String path, SourceStorage sourceStorage, boolean rewrite) throws ServerException, + IOException, + ForbiddenException, + UnauthorizedException, + ConflictException, + NotFoundException { + final ProjectImporter importer = importers.getImporter(sourceStorage.getType()); + if (importer == null) { + throw new NotFoundException(format("Unable import sources project from '%s'. Sources type '%s' is not supported.", + sourceStorage.getLocation(), sourceStorage.getType())); + } + + // Preparing websocket output publisher to broadcast output of import process to the ide clients while importing + final LineConsumerFactory outputOutputConsumerFactory = + () -> new ProjectImportOutputWSLineConsumer(path, workspaceProjectsHolder.getWorkspaceId(), 300); + + String normalizePath = (path.startsWith("/")) ? path : "/".concat(path); + FolderEntry folder = asFolder(normalizePath); + if (folder != null && !rewrite) { + throw new ConflictException(format("Project %s already exists ", path)); + } + + if (folder == null) { + folder = getProjectsRoot().createFolder(normalizePath); + } + + try { + importer.importSources(folder, sourceStorage, outputOutputConsumerFactory); + } catch (final Exception e) { + folder.remove(); + throw e; + } + + final String name = folder.getPath().getName(); + for (ProjectConfig project : workspaceProjectsHolder.getProjects()) { + if (normalizePath.equals(project.getPath())) { + // TODO Needed for factory project importing with keepDir. It needs to find more appropriate solution + List innerProjects = projectRegistry.getProjects(normalizePath); + for (String innerProject : innerProjects) { + RegisteredProject registeredProject = projectRegistry.getProject(innerProject); + projectRegistry.putProject(registeredProject, asFolder(registeredProject.getPath()), true, false); + } + RegisteredProject rp = projectRegistry.putProject(project, folder, true, false); + workspaceProjectsHolder.sync(projectRegistry); + return rp; + } + } + + RegisteredProject rp = projectRegistry + .putProject(new NewProjectConfigImpl(normalizePath, name, BaseProjectType.ID, sourceStorage), folder, true, false); + workspaceProjectsHolder.sync(projectRegistry); + return rp; + } + /** * Estimates if the folder can be treated as a project of particular type * @@ -431,11 +545,9 @@ public final class ProjectManager { * @return resolution object * @throws ServerException * @throws NotFoundException - * @throws ValueStorageException */ public ProjectTypeResolution estimateProject(String path, String projectTypeId) throws ServerException, - NotFoundException, - ValueStorageException { + NotFoundException { final ProjectTypeDef projectType = projectTypeRegistry.getProjectType(projectTypeId); if (projectType == null) { throw new NotFoundException("Project Type to estimate needed."); @@ -468,13 +580,9 @@ public final class ProjectManager { continue; } - try { - final ProjectTypeResolution resolution = estimateProject(path, type.getId()); - if (resolution.matched()) { - resolutions.add(resolution); - } - } catch (ValueStorageException e) { - LOG.warn(e.getLocalizedMessage(), e); + final ProjectTypeResolution resolution = estimateProject(path, type.getId()); + if (resolution.matched()) { + resolutions.add(resolution); } } @@ -606,13 +714,14 @@ public final class ProjectManager { if (move.isProject()) { final RegisteredProject project = projectRegistry.getProject(itemPath); - NewProjectConfig projectConfig = new NewProjectConfig(newItem.getPath().toString(), - project.getType(), - project.getMixins(), - newName, - project.getDescription(), - project.getAttributes(), - project.getSource()); + NewProjectConfig projectConfig = new NewProjectConfigImpl(newItem.getPath().toString(), + project.getType(), + project.getMixins(), + newName, + project.getDescription(), + project.getAttributes(), + null, + project.getSource()); if (move instanceof FolderEntry) { projectRegistry.removeProjects(project.getPath()); @@ -623,6 +732,10 @@ public final class ProjectManager { return move; } + boolean isVirtualFileExist(String path) throws ServerException { + return asVirtualFileEntry(path) != null; + } + FolderEntry asFolder(String path) throws NotFoundException, ServerException { final VirtualFileEntry entry = asVirtualFileEntry(path); if (entry == null) { @@ -630,13 +743,13 @@ public final class ProjectManager { } if (!entry.isFolder()) { - throw new NotFoundException(String.format("Item '%s' isn't a folder. ", path)); + throw new NotFoundException(format("Item '%s' isn't a folder. ", path)); } return (FolderEntry)entry; } - VirtualFileEntry asVirtualFileEntry(String path) throws NotFoundException, ServerException { + VirtualFileEntry asVirtualFileEntry(String path) throws ServerException { final String apath = ProjectRegistry.absolutizePath(path); final FolderEntry root = getProjectsRoot(); return root.getChild(apath); @@ -649,7 +762,7 @@ public final class ProjectManager { } if (!entry.isFile()) { - throw new NotFoundException(String.format("Item '%s' isn't a file. ", path)); + throw new NotFoundException(format("Item '%s' isn't a file. ", path)); } return (FileEntry)entry; @@ -676,7 +789,7 @@ public final class ProjectManager { } searcher.add(file); } catch (Exception e) { - LOG.warn(String.format("Project: %s", project.getPath()), e.getMessage()); + LOG.warn(format("Project: %s", project.getPath()), e.getMessage()); } }); } diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectRegistry.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectRegistry.java index 00916233e0..486dea8642 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectRegistry.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectRegistry.java @@ -14,6 +14,7 @@ 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.core.model.project.NewProjectConfig; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.project.server.handlers.ProjectHandlerRegistry; @@ -181,15 +182,12 @@ public class ProjectRegistry { * whether this is automatically detected or explicitly defined project * @return project * @throws ServerException - * @throws ConflictException - * @throws NotFoundException + * when path for project is undefined */ RegisteredProject putProject(ProjectConfig config, FolderEntry folder, boolean updated, - boolean detected) throws ServerException, - ConflictException, - NotFoundException { + boolean detected) throws ServerException { final RegisteredProject project = new RegisteredProject(folder, config, updated, detected, this.projectTypeRegistry); projects.put(project.getPath(), project); @@ -223,8 +221,7 @@ public class ProjectRegistry { * Attributes defined with particular Project Type * If incoming Project Type is primary and: * - If the folder located on projectPath is a Project, its Primary PT will be converted to incoming PT - * - If the folder located on projectPath is NOT a Project the folder will be converted to "detected" Project - * with incoming Primary PT + * - If the folder located on projectPath is NOT a Project the folder will be converted to "detected" Project with incoming Primary PT * If incoming Project Type is mixin and: * - If the folder located on projectPath is a Project, this PT will be added (if not already there) to its Mixin PTs * - If the folder located on projectPath is NOT a Project - ConflictException will be thrown @@ -268,7 +265,7 @@ public class ProjectRegistry { final String path = absolutizePath(projectPath); final String name = Path.of(projectPath).getName(); - conf = new NewProjectConfig(path, type, newMixins, name, name, null, null); + conf = new NewProjectConfigImpl(path, type, newMixins, name, name, null, null, null); return putProject(conf, root.getChildFolder(path), true, true); } @@ -283,12 +280,13 @@ public class ProjectRegistry { newType = type; } - conf = new NewProjectConfig(project.getPath(), + conf = new NewProjectConfigImpl(project.getPath(), newType, newMixins, project.getName(), project.getDescription(), project.getAttributes(), + null, project.getSource()); return putProject(conf, project.getBaseFolder(), true, project.isDetected()); @@ -339,12 +337,13 @@ public class ProjectRegistry { newType = BaseProjectType.ID; } - final NewProjectConfig conf = new NewProjectConfig(project.getPath(), + final NewProjectConfig conf = new NewProjectConfigImpl(project.getPath(), newType, newMixins, project.getName(), project.getDescription(), project.getAttributes(), + null, project.getSource()); return putProject(conf, project.getBaseFolder(), true, project.isDetected()); @@ -367,7 +366,7 @@ public class ProjectRegistry { putProject(null, folder, true, false); } } - } catch (ServerException | ConflictException | NotFoundException e) { + } catch (ServerException e) { LOG.warn(e.getLocalizedMessage()); } } diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectService.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectService.java index 45bacf07b4..a6f98a2fd0 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectService.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectService.java @@ -43,6 +43,7 @@ import org.eclipse.che.api.vfs.search.QueryExpression; import org.eclipse.che.api.vfs.search.SearchResult; import org.eclipse.che.api.vfs.search.SearchResultEntry; import org.eclipse.che.api.vfs.search.Searcher; +import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.commons.env.EnvironmentContext; @@ -90,6 +91,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static org.eclipse.che.api.core.util.LinksHelper.createLink; import static org.eclipse.che.api.project.server.DtoConverter.asDto; import static org.eclipse.che.api.project.shared.Constants.LINK_REL_CHILDREN; +import static org.eclipse.che.api.project.shared.Constants.LINK_REL_CREATE_BATCH_PROJECTS; import static org.eclipse.che.api.project.shared.Constants.LINK_REL_CREATE_PROJECT; import static org.eclipse.che.api.project.shared.Constants.LINK_REL_DELETE; import static org.eclipse.che.api.project.shared.Constants.LINK_REL_GET_CONTENT; @@ -206,6 +208,37 @@ public class ProjectService extends Service { return injectProjectLinks(configDto); } + @POST + @Path("/batch") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Creates batch of projects according to their configurations", + notes = "A project will be created by importing when project configuration contains source object. " + + "For creating a project by generator options should be specified.", + response = ProjectConfigDto.class) + @ApiResponses({@ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 400, message = "Path for new project should be defined"), + @ApiResponse(code = 403, message = "Operation is forbidden"), + @ApiResponse(code = 409, message = "Project with specified name already exist in workspace"), + @ApiResponse(code = 500, message = "Server error")}) + @GenerateLink(rel = LINK_REL_CREATE_BATCH_PROJECTS) + public List createBatchProjects( + @Description("list of descriptors for projects") List projectConfigList, + @ApiParam(value = "Force rewrite existing project", allowableValues = "true,false") + @QueryParam("force") boolean rewrite) + throws ConflictException, ForbiddenException, ServerException, NotFoundException, IOException, UnauthorizedException, + BadRequestException { + + List result = new ArrayList<>(projectConfigList.size()); + for (RegisteredProject registeredProject : projectManager.createBatchProjects(projectConfigList, rewrite)) { + ProjectConfigDto projectConfig = injectProjectLinks(asDto(registeredProject)); + result.add(projectConfig); + + eventService.publish(new ProjectCreatedEvent(workspace, registeredProject.getPath())); + } + return result; + } + @PUT @Path("/{path:.*}") @Consumes(MediaType.APPLICATION_JSON) @@ -271,6 +304,7 @@ public class ProjectService extends Service { return DtoFactory.newDto(SourceEstimation.class) .withType(projectType) .withMatched(resolution.matched()) + .withResolution(resolution.getResolution()) .withAttributes(attributes); } diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectTypes.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectTypes.java index 270efb459e..8338fa6604 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectTypes.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectTypes.java @@ -11,13 +11,10 @@ package org.eclipse.che.api.project.server; import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.project.type.Attribute; import org.eclipse.che.api.project.server.RegisteredProject.Problem; -import org.eclipse.che.api.project.server.type.ProjectTypeConstraintException; 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.ValueStorageException; import java.util.ArrayList; import java.util.HashMap; @@ -26,6 +23,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static com.google.common.collect.Lists.newArrayList; +import static java.lang.String.format; + /** * @author gazarenkov */ @@ -33,37 +33,40 @@ public class ProjectTypes { private final String projectPath; private final ProjectTypeRegistry projectTypeRegistry; - private ProjectTypeDef primary; + private ProjectTypeDef primary; private final Map mixins; private final Map all; private final Map attributeDefs; + private final List problems; ProjectTypes(String projectPath, - String type, - List mixinTypes, - ProjectTypeRegistry projectTypeRegistry, - List problems) throws ProjectTypeConstraintException, NotFoundException { + String type, + List mixinTypes, + ProjectTypeRegistry projectTypeRegistry, + List problems) { mixins = new HashMap<>(); all = new HashMap<>(); attributeDefs = new HashMap<>(); + this.problems = problems != null ? problems : newArrayList(); this.projectTypeRegistry = projectTypeRegistry; this.projectPath = projectPath; ProjectTypeDef tmpPrimary; if (type == null) { - problems.add(new Problem(12, "No primary type defined for " + projectPath + " Base Project Type assigned.")); + this.problems.add(new Problem(12, "No primary type defined for " + projectPath + " Base Project Type assigned.")); tmpPrimary = ProjectTypeRegistry.BASE_TYPE; } else { try { tmpPrimary = projectTypeRegistry.getProjectType(type); } catch (NotFoundException e) { - problems.add(new Problem(12, "Primary type " + type + " defined for " + projectPath + " is not registered. Base Project Type assigned.")); + this.problems.add(new Problem(12, "Primary type " + type + " defined for " + projectPath + + " is not registered. Base Project Type assigned.")); tmpPrimary = ProjectTypeRegistry.BASE_TYPE; } if (!tmpPrimary.isPrimaryable()) { - problems.add(new Problem(12, "Project type " + tmpPrimary.getId() + " is not allowable to be primary type. Base Project Type assigned.")); + this.problems.add(new Problem(12, "Project type " + tmpPrimary.getId() + " is not allowable to be primary type. Base Project Type assigned.")); tmpPrimary = ProjectTypeRegistry.BASE_TYPE; } } @@ -90,12 +93,12 @@ public class ProjectTypes { try { mixin = projectTypeRegistry.getProjectType(mixinFromConfig); } catch (NotFoundException e) { - problems.add(new Problem(12, "Project type " + mixinFromConfig + " is not registered. Skipped.")); + this.problems.add(new Problem(12, "Project type " + mixinFromConfig + " is not registered. Skipped.")); continue; } if (!mixin.isMixable()) { - problems.add(new Problem(12, "Project type " + mixin + " is not allowable to be mixin. It not mixable. Skipped.")); + this.problems.add(new Problem(12, "Project type " + mixin + " is not allowable to be mixin. It not mixable. Skipped.")); continue; } @@ -105,14 +108,15 @@ public class ProjectTypes { // detect duplicated attributes for (Attribute attr : mixin.getAttributes()) { - if (attributeDefs.containsKey(attr.getName())) { - - problems.add(new Problem(13, "Attribute name conflict. Duplicated attributes detected " + projectPath + - " Attribute " + attr.getName() + " declared in " + mixin.getId() + " already declared in " + - attributeDefs.get(attr.getName()).getProjectType()+" Skipped.")); + final String attrName = attr.getName(); + if (attributeDefs.containsKey(attrName)) { + this.problems.add(new Problem(13, + format("Attribute name conflict. Duplicated attributes detected for %s. " + + "Attribute %s declared in %s already declared in %s. Skipped.", + projectPath, attrName, mixin.getId(), attributeDefs.get(attrName).getProjectType()))); continue; } - attributeDefs.put(attr.getName(), attr); + attributeDefs.put(attrName, attr); } // Silently remove repeated items from mixins if any @@ -147,22 +151,22 @@ public class ProjectTypes { void reset(Set attributesToDel) { Set ptsToDel = new HashSet<>(); - for(Attribute attr : attributesToDel) { + for (Attribute attr : attributesToDel) { ptsToDel.add(attr.getProjectType()); } - Set attrNamesToDel = new HashSet<>(); - for(String pt : ptsToDel) { + Set attrNamesToDel = new HashSet<>(); + for (String pt : ptsToDel) { ProjectTypeDef typeDef = all.get(pt); - for(Attribute attrDef: typeDef.getAttributes()) { + for (Attribute attrDef : typeDef.getAttributes()) { attrNamesToDel.add(attrDef.getName()); } } // remove project types - for(String typeId : ptsToDel) { + for (String typeId : ptsToDel) { this.all.remove(typeId); - if(this.primary.getId().equals(typeId)) { + if (this.primary.getId().equals(typeId)) { this.primary = ProjectTypeRegistry.BASE_TYPE; this.all.put(ProjectTypeRegistry.BASE_TYPE.getId(), ProjectTypeRegistry.BASE_TYPE); } else { @@ -171,30 +175,27 @@ public class ProjectTypes { } // remove attributes - for(String attr : attrNamesToDel) { + for (String attr : attrNamesToDel) { this.attributeDefs.remove(attr); } } - void addTransient(FolderEntry projectFolder) throws ServerException, - NotFoundException, - ProjectTypeConstraintException, - ValueStorageException { + void addTransient(FolderEntry projectFolder) { for (ProjectTypeDef pt : projectTypeRegistry.getProjectTypes()) { // NOTE: Only mixable types allowed if (pt.isMixable() && !pt.isPersisted() && pt.resolveSources(projectFolder).matched()) { all.put(pt.getId(), pt); mixins.put(pt.getId(), pt); for (Attribute attr : pt.getAttributes()) { - if (attributeDefs.containsKey(attr.getName())) { - throw new ProjectTypeConstraintException( - "Attribute name conflict. Duplicated attributes detected " + projectPath + - " Attribute " + attr.getName() + " declared in " + pt.getId() + " already declared in " + - attributeDefs.get(attr.getName()).getProjectType() - ); + final String attrName = attr.getName(); + if (attributeDefs.containsKey(attrName)) { + problems.add(new Problem(13, + format("Attribute name conflict. Duplicated attributes detected for %s. " + + "Attribute %s declared in %s already declared in %s. Skipped.", + projectPath, attrName, pt.getId(), attributeDefs.get(attrName).getProjectType()))); } - attributeDefs.put(attr.getName(), attr); + attributeDefs.put(attrName, attr); } } } diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/RegisteredProject.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/RegisteredProject.java index 641e4f0afb..d3d60fdc61 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/RegisteredProject.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/RegisteredProject.java @@ -10,14 +10,12 @@ *******************************************************************************/ package org.eclipse.che.api.project.server; -import org.eclipse.che.api.core.NotFoundException; 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.ProjectTypeConstraintException; 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; @@ -27,12 +25,12 @@ import org.eclipse.che.api.vfs.Path; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; +import static java.lang.String.format; + /** * Internal Project implementation. * It is supposed that it is object always consistent. @@ -63,15 +61,14 @@ public class RegisteredProject implements ProjectConfig { * 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 NotFoundException, - ProjectTypeConstraintException, - ServerException, - ValueStorageException { + ProjectTypeRegistry projectTypeRegistry) throws ServerException { problems = new ArrayList<>(); attributes = new HashMap<>(); @@ -85,7 +82,7 @@ public class RegisteredProject implements ProjectConfig { } this.folder = folder; - this.config = (config == null) ? new NewProjectConfig(path) : config; + this.config = config == null ? new NewProjectConfigImpl(path) : config; this.updated = updated; this.detected = detected; @@ -97,6 +94,7 @@ public class RegisteredProject implements ProjectConfig { problems.add(new Problem(11, "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); @@ -110,15 +108,9 @@ public class RegisteredProject implements ProjectConfig { /** * Initialize project attributes. - * - * @throws ValueStorageException - * @throws ProjectTypeConstraintException - * @throws ServerException - * @throws NotFoundException + * Note: the problem with {@link Problem#code} = 13 will be added when a value for some attribute is not initialized */ - private void initAttributes() throws ValueStorageException, ProjectTypeConstraintException, ServerException, NotFoundException { - - Set invalidAttributes = new HashSet<>(); + private void initAttributes() { // we take only defined attributes, others ignored for (Map.Entry entry : types.getAttributeDefs().entrySet()) { @@ -140,12 +132,17 @@ public class RegisteredProject implements ProjectConfig { if (folder != null) { - 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()); + 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) { + this.problems.add(new Problem(13, format("Value for attribute %s is not initialized, caused by: %s", + variable.getId(), e.getLocalizedMessage()))); } } else { @@ -155,7 +152,6 @@ public class RegisteredProject implements ProjectConfig { if (value.isEmpty() && variable.isRequired()) { this.problems.add(new Problem(13, "Value for required attribute is not initialized " + variable.getId())); - invalidAttributes.add(variable); //throw new ProjectTypeConstraintException("Value for required attribute is not initialized " + variable.getId()); } @@ -164,9 +160,6 @@ public class RegisteredProject implements ProjectConfig { } } } - - types.reset(invalidAttributes); - } /** diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/WorkspaceProjectsSyncer.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/WorkspaceProjectsSyncer.java index 48bfe61c9c..390b561f59 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/WorkspaceProjectsSyncer.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/WorkspaceProjectsSyncer.java @@ -48,13 +48,14 @@ public abstract class WorkspaceProjectsSyncer { if(!project.isSynced() && !project.isDetected()) { - final ProjectConfig config = new NewProjectConfig(project.getPath(), - project.getType(), - project.getMixins(), - project.getName(), - project.getDescription(), - project.getPersistableAttributes(), - project.getSource()); + final ProjectConfig config = new NewProjectConfigImpl(project.getPath(), + project.getType(), + project.getMixins(), + project.getName(), + project.getDescription(), + project.getPersistableAttributes(), + null, + project.getSource()); boolean found = false; for(ProjectConfig r : remote) { diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/type/ProjectTypeDef.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/type/ProjectTypeDef.java index 44b262383b..f665dd8a8a 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/type/ProjectTypeDef.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/type/ProjectTypeDef.java @@ -20,6 +20,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static com.google.common.collect.Maps.newHashMap; +import static java.lang.String.format; + /** * @author gazarenkov */ @@ -169,7 +172,7 @@ public abstract class ProjectTypeDef implements ProjectType { this.ancestors.add(ancestor); } - public ProjectTypeResolution resolveSources(FolderEntry projectFolder) throws ValueStorageException { + public ProjectTypeResolution resolveSources(FolderEntry projectFolder) { Map matchAttrs = new HashMap<>(); for (Map.Entry entry : attributes.entrySet()) { Attribute attr = entry.getValue(); @@ -178,11 +181,22 @@ public abstract class ProjectTypeDef implements ProjectType { Variable var = (Variable)attr; ValueProviderFactory factory = var.getValueProviderFactory(); if (factory != null) { - Value value = new AttributeValue(factory.newInstance(projectFolder).getValues(name)); - if (value.isEmpty()) { + + Value value; + String errorMessage = ""; + try { + value = new AttributeValue(factory.newInstance(projectFolder).getValues(name)); + } catch (ValueStorageException e) { + value = null; + errorMessage = e.getLocalizedMessage(); + } + + if (value == null || value.isEmpty()) { if (var.isRequired()) { // this PT is not match - return new DefaultResolution(id, new HashMap<>(), false); + errorMessage = errorMessage.isEmpty() ? format("Value for required attribute %s is not initialized", name) + : errorMessage; + return new DefaultResolution(id, errorMessage); } } else { // add one more matched attribute @@ -204,6 +218,12 @@ public abstract class ProjectTypeDef implements ProjectType { this.match = match; } + /** Use this one when source code not matches project type requirements */ + public DefaultResolution(String type, String resolution) { + super(type, newHashMap(), resolution); + this.match = false; + } + @Override public boolean matched() { return match; diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/type/ProjectTypeResolution.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/type/ProjectTypeResolution.java index ea448ffba9..6c154a27e4 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/type/ProjectTypeResolution.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/type/ProjectTypeResolution.java @@ -21,10 +21,16 @@ public abstract class ProjectTypeResolution { private String type; private Map attributes; + private String resolution; public ProjectTypeResolution(String type, Map attributes) { + this(type, attributes, ""); + } + + public ProjectTypeResolution(String type, Map attributes, String resolution) { this.type = type; this.attributes = attributes; + this.resolution = resolution; } /** @@ -34,6 +40,13 @@ public abstract class ProjectTypeResolution { return type; } + /** + * @return the reason that current source code NOT matches project type requirements + */ + public String getResolution() { + return resolution; + } + /** * @return true if current source code in generally matches project type requirements * by default (but not necessarily) it may check if there are all required provided attributes diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectManagerWriteTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectManagerWriteTest.java index 55005265d9..d3a61e6eed 100644 --- a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectManagerWriteTest.java +++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectManagerWriteTest.java @@ -10,21 +10,25 @@ *******************************************************************************/ package org.eclipse.che.api.project.server; +import org.eclipse.che.api.core.BadRequestException; 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.core.model.project.NewProjectConfig; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.model.project.SourceStorage; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.core.util.LineConsumerFactory; import org.eclipse.che.api.core.util.ValueHolder; +import org.eclipse.che.api.project.server.RegisteredProject.Problem; import org.eclipse.che.api.project.server.handlers.CreateProjectHandler; import org.eclipse.che.api.project.server.importer.ProjectImporter; import org.eclipse.che.api.project.server.type.AttributeValue; import org.eclipse.che.api.project.server.type.BaseProjectType; import org.eclipse.che.api.project.server.type.Variable; import org.eclipse.che.api.vfs.Path; +import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.dto.server.DtoFactory; @@ -33,8 +37,10 @@ import org.junit.Test; 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.HashMap; import java.util.List; import java.util.Map; @@ -43,6 +49,7 @@ import java.util.zip.ZipOutputStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -52,7 +59,7 @@ import static org.junit.Assert.fail; * @author gazarenkov */ public class ProjectManagerWriteTest extends WsAgentTestBase { - + private static final String FILE_CONTENT = "to be or not to be"; @Before public void setUp() throws Exception { @@ -66,14 +73,412 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { projectTypeRegistry.registerProjectType(new PTsettableVP()); projectHandlerRegistry.register(new SrcGenerator()); - } + @Test + public void testCreateBatchProjectsByConfigs() throws Exception { + final String projectPath1 = "/testProject1"; + final String projectPath2 = "/testProject2"; + + final NewProjectConfig config1 = createProjectConfigObject("testProject1", projectPath1, BaseProjectType.ID, null); + final NewProjectConfig config2 = createProjectConfigObject("testProject2", projectPath2, BaseProjectType.ID, null); + + final List configs = new ArrayList<>(2); + configs.add(config1); + configs.add(config2); + + pm.createBatchProjects(configs, false); + + checkProjectExist(projectPath1); + checkProjectExist(projectPath2); + assertEquals(2, projectRegistry.getProjects().size()); + } + + @Test + public void testCreateBatchProjectsByImportingSourceCode() throws Exception { + final String projectPath1 = "/testProject1"; + final String projectPath2 = "/testProject2"; + final String importType1 = "importType1"; + final String importType2 = "importType2"; + + final String [] paths1 = {"folder1/", "folder1/file1.txt"}; + final List children1 = new ArrayList<>(Arrays.asList(paths1)); + registerImporter(importType1, prepareZipArchiveBasedOn(children1)); + + final String [] paths2 = {"folder2/", "folder2/file2.txt"}; + final List children2 = new ArrayList<>(Arrays.asList(paths2)); + registerImporter(importType2, prepareZipArchiveBasedOn(children2)); + + final SourceStorageDto source1 = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(importType1); + final NewProjectConfigDto config1 = createProjectConfigObject("testProject1", projectPath1, BaseProjectType.ID, source1); + + final SourceStorageDto source2 = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(importType2); + final NewProjectConfigDto config2 = createProjectConfigObject("testProject2", projectPath2, BaseProjectType.ID, source2); + + final List configs = new ArrayList<>(2); + configs.add(config1); + configs.add(config2); + + pm.createBatchProjects(configs, false); + + final RegisteredProject project1 = projectRegistry.getProject(projectPath1); + final FolderEntry projectFolder1 = project1.getBaseFolder(); + checkProjectExist(projectPath1); + checkChildrenFor(projectFolder1, children1); + + final RegisteredProject project2 = projectRegistry.getProject(projectPath2); + final FolderEntry projectFolder2 = project2.getBaseFolder(); + checkProjectExist(projectPath2); + checkChildrenFor(projectFolder2, children2); + } + + @Test + public void testCreateProjectWhenSourceCodeIsNotReachable() throws Exception { + final String projectPath = "/testProject"; + final SourceStorageDto source = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType("importType"); + final NewProjectConfigDto config = createProjectConfigObject("testProject1", projectPath, BaseProjectType.ID, source); + + final List configs = new ArrayList<>(1); + configs.add(config); + + try { + pm.createBatchProjects(configs, false); + fail("Exception should be thrown when source code is not reachable"); + } catch (Exception e) { + assertEquals(0, projectRegistry.getProjects().size()); + assertNull(projectRegistry.getProject(projectPath)); + assertNull(pm.getProjectsRoot().getChild(projectPath)); + } + } + + @Test + public void shouldRollbackCreatingBatchProjects() throws Exception { + // we should rollback operation of creating batch projects when we have not source code for at least one project + // For example: two projects were success created, but we could not get source code for third configuration + // At this use case we should rollback the operation and clean up all created projects + + final String projectPath1 = "/testProject1"; + final String projectPath2 = "/testProject2"; + final String projectPath3 = "/testProject3"; + final String importType1 = "importType1"; + final String importType2 = "importType2"; + + final String [] paths1 = {"folder1/", "folder1/file1.txt"}; + final List children1 = new ArrayList<>(Arrays.asList(paths1)); + registerImporter(importType1, prepareZipArchiveBasedOn(children1)); + + final String [] paths2 = {"folder2/", "folder2/file2.txt"}; + final List children2 = new ArrayList<>(Arrays.asList(paths2)); + registerImporter(importType2, prepareZipArchiveBasedOn(children2)); + + final SourceStorageDto source1 = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(importType1); + final NewProjectConfigDto config1 = createProjectConfigObject("testProject1", projectPath1, BaseProjectType.ID, source1); + + final SourceStorageDto source2 = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(importType2); + final NewProjectConfigDto config2 = createProjectConfigObject("testProject2", projectPath2, BaseProjectType.ID, source2); + + final SourceStorageDto source = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType("importType"); + final NewProjectConfigDto config3 = createProjectConfigObject("testProject3", projectPath3, BaseProjectType.ID, source); + + final List configs = new ArrayList<>(2); + configs.add(config1); //will be success created + configs.add(config2); //will be success created + configs.add(config3); //we be failed - we have not registered importer - source code will not be imported + + try { + pm.createBatchProjects(configs, false); + fail("We should rollback operation of creating batch projects when we could not get source code for at least one project"); + } catch (Exception e) { + assertEquals(0, projectRegistry.getProjects().size()); + + assertNull(projectRegistry.getProject(projectPath1)); + assertNull(pm.getProjectsRoot().getChild(projectPath1)); + + assertNull(projectRegistry.getProject(projectPath2)); + assertNull(pm.getProjectsRoot().getChild(projectPath2)); + + assertNull(projectRegistry.getProject(projectPath3)); + assertNull(pm.getProjectsRoot().getChild(projectPath3)); + } + } + + @Test + public void testCreateBatchProjectsWithInnerProject() throws Exception { + final String rootProjectPath = "/testProject1"; + final String innerProjectPath = "/testProject1/innerProject"; + final String importType = "importType1"; + final String innerProjectType = "pt2"; + + Map> attributes = new HashMap<>(); + attributes.put("pt2-var2", new AttributeValue("test").getList()); + + final String [] paths1 = {"folder1/", "folder1/file1.txt"}; + final String [] paths2 = {"innerProject/", "innerProject/folder2/", "innerProject/folder2/file2.txt"}; + final List children1 = Arrays.asList(paths1); + final List children2 = Arrays.asList(paths2); + final List children = new ArrayList<>(children1); + children.addAll(children2); + registerImporter(importType, prepareZipArchiveBasedOn(children)); + + SourceStorageDto source = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(importType); + NewProjectConfigDto config1 = createProjectConfigObject("testProject1", rootProjectPath, BaseProjectType.ID, source); + NewProjectConfigDto config2 = createProjectConfigObject("innerProject", innerProjectPath, innerProjectType, null); + config2.setAttributes(attributes); + + List configs = new ArrayList<>(2); + configs.add(config1); + configs.add(config2); + + pm.createBatchProjects(configs, false); + + RegisteredProject rootProject = projectRegistry.getProject(rootProjectPath); + FolderEntry rootProjectFolder = rootProject.getBaseFolder(); + RegisteredProject innerProject = projectRegistry.getProject(innerProjectPath); + FolderEntry innerProjectFolder = innerProject.getBaseFolder(); + + + assertNotNull(rootProject); + assertTrue(rootProjectFolder.getVirtualFile().exists()); + assertEquals(rootProjectPath, rootProject.getPath()); + checkChildrenFor(rootProjectFolder, children1); + + assertNotNull(innerProject); + assertTrue(innerProjectFolder.getVirtualFile().exists()); + assertEquals(innerProjectPath, innerProject.getPath()); + assertEquals(innerProjectType, innerProject.getType()); + checkChildrenFor(rootProjectFolder, children2); + } + + @Test + public void testCreateBatchProjectsWithInnerProjectWhenInnerProjectContainsSource() throws Exception { + final String rootProjectPath = "/rootProject"; + final String innerProjectPath = "/rootProject/innerProject"; + final String rootImportType = "rootImportType"; + final String innerImportType = "innerImportType"; + + final String innerProjectType = "pt2"; + + Map> attributes = new HashMap<>(); + attributes.put("pt2-var2", new AttributeValue("test").getList()); + + final String [] paths1 = {"folder1/", "folder1/file1.txt"}; + final List children1 = new ArrayList<>(Arrays.asList(paths1)); + registerImporter(rootImportType, prepareZipArchiveBasedOn(children1)); + + final String [] paths2 = {"folder2/", "folder2/file2.txt"}; + final List children2 = new ArrayList<>(Arrays.asList(paths2)); + registerImporter(innerImportType, prepareZipArchiveBasedOn(children2)); + + final SourceStorageDto source1 = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(rootImportType); + final NewProjectConfigDto config1 = createProjectConfigObject("testProject1", rootProjectPath, BaseProjectType.ID, source1); + + final SourceStorageDto source2 = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(innerImportType); + final NewProjectConfigDto config2 = createProjectConfigObject("testProject2", innerProjectPath, innerProjectType, source2); + config2.setAttributes(attributes); + + final List configs = new ArrayList<>(2); + configs.add(config2); + configs.add(config1); + + pm.createBatchProjects(configs, false); + + RegisteredProject rootProject = projectRegistry.getProject(rootProjectPath); + FolderEntry rootProjectFolder = rootProject.getBaseFolder(); + checkProjectExist(rootProjectPath); + checkChildrenFor(rootProjectFolder, children1); + + RegisteredProject innerProject = projectRegistry.getProject(innerProjectPath); + FolderEntry innerProjectFolder = innerProject.getBaseFolder(); + assertNotNull(innerProject); + assertTrue(innerProjectFolder.getVirtualFile().exists()); + assertEquals(innerProjectPath, innerProject.getPath()); + assertEquals(innerProjectType, innerProject.getType()); + checkChildrenFor(innerProjectFolder, children2); + } + + @Test + public void testCreateBatchProjectsWithMixInnerProjects() throws Exception { // Projects should be sorted by path before creating + final String [] paths = {"/1/z", "/2/z", "/1/d", "/2", "/1", "/1/a"}; + final List projectsPaths = new ArrayList<>(Arrays.asList(paths)); + + final List configs = new ArrayList<>(projectsPaths.size()); + for (String path : projectsPaths) { + configs.add(createProjectConfigObject(path.substring(path.length() - 1, path.length()), path, BaseProjectType.ID, null)); + } + + pm.createBatchProjects(configs, false); + + for (String path : projectsPaths) { + checkProjectExist(path); + } + } + + @Test + public void testCreateBatchProjectsWhenConfigContainsOnlyPath() + throws Exception { // NewProjectConfig object contains only one mandatory Project Path field + + final String projectPath1 = "/testProject1"; + final String projectPath2 = "/testProject2"; + + final NewProjectConfig config1 = createProjectConfigObject(null, projectPath1, null, null); + final NewProjectConfig config2 = createProjectConfigObject(null, projectPath2, null, null); + + final List configs = new ArrayList<>(2); + configs.add(config1); + configs.add(config2); + + pm.createBatchProjects(configs, false); + + checkProjectExist(projectPath1); + checkProjectExist(projectPath2); + assertEquals(2, projectRegistry.getProjects().size()); + } + + @Test + public void shouldThrowBadRequestExceptionAtCreatingBatchProjectsWhenConfigNotContainsPath() + throws Exception { //Path is mandatory field for NewProjectConfig + final SourceStorageDto source = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType("importType"); + final NewProjectConfig config = createProjectConfigObject("project", null, BaseProjectType.ID, source); + + final List configs = new ArrayList<>(1); + configs.add(config); + + try { + pm.createBatchProjects(configs, false); + fail("BadRequestException should be thrown : path field is mandatory"); + } catch (BadRequestException e) { + assertEquals(0, projectRegistry.getProjects().size()); + } + } + + @Test + public void shouldThrowConflictExceptionAtCreatingBatchProjectsWhenProjectWithPathAlreadyExist() throws Exception { + final String path = "/somePath"; + final NewProjectConfig config = createProjectConfigObject("project", path, BaseProjectType.ID, null); + + final List configs = new ArrayList<>(1); + configs.add(config); + + pm.createBatchProjects(configs, false); + checkProjectExist(path); + assertEquals(1, projectRegistry.getProjects().size()); + + try { + pm.createBatchProjects(configs, false); + fail("ConflictException should be thrown : Project config with the same path is already exists"); + } catch (ConflictException e) { + assertEquals(1, projectRegistry.getProjects().size()); + } + } + + @Test + public void shouldCreateParentFolderAtCreatingProjectWhenParentDoesNotExist() throws Exception { + final String nonExistentParentPath = "/rootProject"; + final String innerProjectPath = "/rootProject/innerProject"; + + final NewProjectConfig config = createProjectConfigObject(null, innerProjectPath, null, null); + + final List configs = new ArrayList<>(2); + configs.add(config); + + pm.createBatchProjects(configs, false); + + checkProjectExist(nonExistentParentPath); + checkProjectExist(innerProjectPath); + assertEquals(2, projectRegistry.getProjects().size()); + } + + @Test + public void shouldRewriteProjectAtCreatingBatchProjectsWhenProjectAlreadyExist() throws Exception { + final String projectPath = "/testProject"; + final String importType1 = "importType1"; + final String importType2 = "importType2"; + + final String [] paths1 = {"folder1/", "folder1/file1.txt"}; + final List children1 = new ArrayList<>(Arrays.asList(paths1)); + registerImporter(importType1, prepareZipArchiveBasedOn(children1)); + + final String [] paths2 = {"folder2/", "folder2/file2.txt"}; + final List children2 = new ArrayList<>(Arrays.asList(paths2)); + registerImporter(importType2, prepareZipArchiveBasedOn(children2)); + + final SourceStorageDto source1 = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(importType1); + final NewProjectConfigDto config1 = createProjectConfigObject("testProject1", projectPath, "blank", source1); + + final SourceStorageDto source2 = DtoFactory.newDto(SourceStorageDto.class).withLocation("someLocation").withType(importType2); + final NewProjectConfigDto config2 = createProjectConfigObject("testProject2", projectPath, "blank", source2); + + final List configs = new ArrayList<>(1); + configs.add(config1); + + pm.createBatchProjects(configs, false); + + final FolderEntry projectFolder1 = projectRegistry.getProject(projectPath).getBaseFolder(); + checkProjectExist(projectPath); + checkChildrenFor(projectFolder1, children1); + assertEquals(1, projectRegistry.getProjects().size()); + + configs.clear(); + configs.add(config2); + pm.createBatchProjects(configs, true); + + final FolderEntry projectFolder2 = projectRegistry.getProject(projectPath).getBaseFolder(); + checkProjectExist(projectPath); + checkChildrenFor(projectFolder2, children2); + assertEquals(1, projectRegistry.getProjects().size()); + assertNull(projectFolder2.getChild("folder1/")); + assertNull(projectFolder2.getChild("folder1/file1.txt")); + } + + @Test + public void shouldSetBlankTypeAtCreatingBatchProjectsWhenConfigContainsUnregisteredProjectType() + throws Exception {// If declared primary PT is not registered, project is created as Blank, with Problem 12 + + final String projectPath = "/testProject"; + final String projectType = "unregisteredProjectType"; + + final NewProjectConfig config = createProjectConfigObject("projectName", projectPath, projectType, null); + + final List configs = new ArrayList<>(1); + configs.add(config); + + pm.createBatchProjects(configs, false); + + final RegisteredProject project = projectRegistry.getProject(projectPath); + final List problems = project.getProblems(); + checkProjectExist(projectPath); + assertNotEquals(projectType, project.getType()); + assertEquals(1, problems.size()); + assertEquals(12, problems.get(0).code); + assertEquals(1, projectRegistry.getProjects().size()); + } + + @Test + public void shouldCreateBatchProjectsWithoutMixinPTWhenThisOneIsUnregistered() + throws Exception {// If declared mixin PT is not registered, project is created w/o it, with Problem 12 + + final String projectPath = "/testProject"; + final String mixinPType = "unregistered"; + + final NewProjectConfig config = createProjectConfigObject("projectName", projectPath, BaseProjectType.ID, null); + config.getMixins().add(mixinPType); + + final List configs = new ArrayList<>(1); + configs.add(config); + + pm.createBatchProjects(configs, false); + + final RegisteredProject project = projectRegistry.getProject(projectPath); + final List problems = project.getProblems(); + checkProjectExist(projectPath); + assertEquals(1, problems.size()); + assertEquals(12, problems.get(0).code); + assertTrue(project.getMixins().isEmpty()); + assertEquals(1, projectRegistry.getProjects().size()); + } @Test public void testCreateProject() throws Exception { - - Map> attrs = new HashMap<>(); List v = new ArrayList<>(); v.add("meV"); @@ -95,74 +500,85 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { assertEquals("/createProject", project.getPath()); assertEquals(2, project.getAttributeEntries().size()); assertEquals("meV", project.getAttributeEntries().get("var1").getString()); - } @Test - public void testCreateProjectInvalidAttribute() throws Exception { - - ProjectConfig pc = new NewProjectConfig("/testCreateProjectInvalidAttributes", "pt2", null, "name", "descr", null, null); - - try { - pm.createProject(pc, null); - fail("ProjectTypeConstraintException should be thrown : pt-var2 attribute is mandatory"); - } catch (ServerException e) { - // - } - - } - - - @Test - public void testCreateProjectWithRequiredProvidedAttribute() throws Exception { - + public void testCreateProjectWithInvalidAttribute() throws Exception { // SPECS: - // If project type has provided required attributes, - // respective CreateProjectHandler MUST be provided + // Project will be created with problem code = 13(Value for required attribute is not initialized) + // when required attribute is not initialized + final String path = "/testCreateProjectInvalidAttributes"; + final String projectType = "pt2"; + final NewProjectConfig config = new NewProjectConfigImpl(path, projectType, null, "name", "descr", null, null, null); - Map> attributes = new HashMap<>(); + pm.createProject(config, null); + + RegisteredProject project = projectRegistry.getProject(path); + assertNotNull(project); + assertNotNull(pm.getProjectsRoot().getChild(path)); + assertEquals(projectType, project.getType()); + + List problems = project.getProblems(); + assertNotNull(problems); + assertFalse(problems.isEmpty()); + assertEquals(1, problems.size()); + assertEquals(13, problems.get(0).code); + } + + + @Test + public void testCreateProjectWithRequiredProvidedAttributeWhenGivenProjectTypeHasGenerator() throws Exception { + final String path = "/testCreateProjectWithRequiredProvidedAttribute"; + final String projectTypeId = "pt3"; + final Map> attributes = new HashMap<>(); attributes.put("pt2-var2", new AttributeValue("test").getList()); - ProjectConfig pc = - new NewProjectConfig("/testCreateProjectWithRequiredProvidedAttribute", "pt3", null, "name", "descr", attributes, null); + final ProjectConfig pc = new NewProjectConfigImpl(path, projectTypeId, null, "name", "descr", attributes, null, null); pm.createProject(pc, null); - RegisteredProject project = projectRegistry.getProject("testCreateProjectWithRequiredProvidedAttribute"); - assertEquals("pt3", project.getType()); + final RegisteredProject project = projectRegistry.getProject(path); + assertEquals(projectTypeId, project.getType()); assertNotNull(project.getBaseFolder().getChild("file1")); assertEquals("pt2-provided1", project.getAttributes().get("pt2-provided1").get(0)); - } @Test - public void testFailCreateProjectWithNoRequiredGenerator() throws Exception { - + public void testCreateProjectWithRequiredProvidedAttributeWhenGivenProjectTypeHasNotGenerator() throws Exception { // SPECS: - // If there are no respective CreateProjectHandler ServerException will be thrown + // Project will be created with problem code = 13 (Value for required attribute is not initialized) + // when project type has provided required attributes + // but have not respective generator(CreateProjectHandler) - ProjectConfig pc = new NewProjectConfig("/testFailCreateProjectWithNoRequiredGenerator", "pt4", null, "name", "descr", null, null); - - try { - pm.createProject(pc, null); - fail("ProjectTypeConstraintException: Value for required attribute is not initialized pt4:pt4-provided1"); - } catch (ServerException e) { - } + final String path = "/testCreateProjectWithRequiredProvidedAttribute"; + final String projectTypeId = "pt4"; + final ProjectConfig pc = new NewProjectConfigImpl(path, projectTypeId, null, "name", "descr", null, null, null); + pm.createProject(pc, null); + final RegisteredProject project = projectRegistry.getProject(path); + final List children = project.getBaseFolder().getChildren(); + final List problems = project.getProblems(); + assertNotNull(project); + assertNotNull(pm.getProjectsRoot().getChild(path)); + assertEquals(projectTypeId, project.getType()); + assertTrue(children.isEmpty()); + assertTrue(project.getAttributes().isEmpty()); + assertFalse(problems.isEmpty()); + assertEquals(1, problems.size()); + assertEquals(13, problems.get(0).code); } @Test public void testSamePathProjectCreateFailed() throws Exception { - // SPECS: // If there is a project with the same path ConflictException will be thrown on create project - ProjectConfig pc = new NewProjectConfig("/testSamePathProjectCreateFailed", "blank", null, "name", "descr", null, null); + ProjectConfig pc = new NewProjectConfigImpl("/testSamePathProjectCreateFailed", "blank", null, "name", "descr", null, null, null); pm.createProject(pc, null); - pc = new NewProjectConfig("/testSamePathProjectCreateFailed", "blank", null, "name", "descr", null, null); + pc = new NewProjectConfigImpl("/testSamePathProjectCreateFailed", "blank", null, "name", "descr", null, null, null); try { pm.createProject(pc, null); @@ -171,94 +587,109 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { } assertNotNull(projectRegistry.getProject("/testSamePathProjectCreateFailed")); - } @Test public void testInvalidPTProjectCreateFailed() throws Exception { + // SPECS: + // project will be created as project of "blank" type + // with problem code 12(Primary type "someType" is not registered. Base Project Type assigned.) + // when primary project type is not registered in PT registry + final String path = "/testInvalidPTProjectCreateFailed"; + ProjectConfig pc = new NewProjectConfigImpl(path, "invalid", null, "name", "descr", null, null, null); + + pm.createProject(pc, null); + + RegisteredProject project = projectRegistry.getProject(path); + assertNotNull(project); + assertNotNull(pm.getProjectsRoot().getChild(path)); + assertEquals(BaseProjectType.ID, project.getType()); + + List problems = project.getProblems(); + assertNotNull(problems); + assertFalse(problems.isEmpty()); + assertEquals(1, problems.size()); + assertEquals(12, problems.get(0).code); + + //clean up + project.getBaseFolder().getVirtualFile().delete(); + projectRegistry.removeProjects(path); + assertNull(projectRegistry.getProject(path)); + // SPECS: - // If either primary or some mixin project type is not registered in PT registry - // project creation failed with NotFoundException - - ProjectConfig pc = new NewProjectConfig("/testInvalidPTProjectCreateFailed", "invalid", null, "name", "descr", null, null); - - try { - pm.createProject(pc, null); - fail("NotFoundException: Project Type not found: invalid"); - } catch (ServerException e) { - } - - assertNull(projectRegistry.getProject("/testInvalidPTProjectCreateFailed")); - assertNull(pm.getProjectsRoot().getChild("/testInvalidPTProjectCreateFailed")); - //assertNull(projectRegistry.folder("/testInvalidPTProjectCreateFailed")); - - // check mixin as well + // project will be created without mixin project type and + // with problem code 12(Project type "someType" is not registered. Skipped.) + // when mixin project type is not registered in PT registry List ms = new ArrayList<>(); ms.add("invalid"); - pc = new NewProjectConfig("/testInvalidPTProjectCreateFailed", "blank", ms, "name", "descr", null, null); - - try { - pm.createProject(pc, null); - fail("NotFoundException: Project Type not found: invalid"); - } catch (ServerException e) { - } + pc = new NewProjectConfigImpl(path, "blank", ms, "name", "descr", null, null, null); + pm.createProject(pc, null); + project = projectRegistry.getProject(path); + assertNotNull(project); + assertNotNull(pm.getProjectsRoot().getChild(path)); + assertTrue(project.getMixins().isEmpty()); + problems = project.getProblems(); + assertNotNull(problems); + assertFalse(problems.isEmpty()); + assertEquals(1, problems.size()); + assertEquals(12, problems.get(0).code); } @Test public void testConflictAttributesProjectCreateFailed() throws Exception { - // SPECS: - // If there are attributes with the same name in primary and mixin PT or between mixins - // Project creation failed with ProjectTypeConstraintException + // project will be created with problem code 13(Attribute name conflict) + // when there are attributes with the same name in primary and mixin PT or between mixins + final String path = "/testConflictAttributesProjectCreateFailed"; + final String projectTypeId = "pt2"; + final String mixin = "m2"; + final List ms = new ArrayList<>(1); + ms.add(mixin); - List ms = new ArrayList<>(); - ms.add("m2"); + ProjectConfig pc = new NewProjectConfigImpl(path, projectTypeId, ms, "name", "descr", null, null, null); + pm.createProject(pc, null); - ProjectConfig pc = new NewProjectConfig("/testConflictAttributesProjectCreateFailed", "pt2", ms, "name", "descr", null, null); - try { - pm.createProject(pc, null); - fail("ProjectTypeConstraintException: Attribute name conflict. Duplicated attributes detected /testConflictAttributesProjectCreateFailed Attribute pt2-const1 declared in m2 already declared in pt2"); - } catch (ServerException e) { - } + final RegisteredProject project = projectRegistry.getProject(path); + assertNotNull(project); + assertNotNull(pm.getProjectsRoot().getChild(path)); - //assertNull(projectRegistry.folder("/testConflictAttributesProjectCreateFailed")); - - assertNull(pm.getProjectsRoot().getChild("/testConflictAttributesProjectCreateFailed")); + final List mixins = project.getMixins(); + assertFalse(mixins.isEmpty()); + assertEquals(mixin, mixins.get(0)); + final List problems = project.getProblems(); + assertNotNull(problems); + assertFalse(problems.isEmpty()); + assertEquals(13, problems.get(0).code); } @Test public void testInvalidConfigProjectCreateFailed() throws Exception { - // SPECS: // If project path is not defined // Project creation failed with ConflictException - ProjectConfig pc = new NewProjectConfig(null, "pt2", null, "name", "descr", null, null); + ProjectConfig pc = new NewProjectConfigImpl(null, "pt2", null, "name", "descr", null, null, null); try { pm.createProject(pc, null); fail("ConflictException: Path for new project should be defined "); } catch (ConflictException e) { } - - } @Test public void testCreateInnerProject() throws Exception { - - - ProjectConfig pc = new NewProjectConfig("/testCreateInnerProject", BaseProjectType.ID, null, "name", "descr", null, null); + ProjectConfig pc = new NewProjectConfigImpl("/testCreateInnerProject", BaseProjectType.ID, null, "name", "descr", null, null, null); pm.createProject(pc, null); - pc = new NewProjectConfig("/testCreateInnerProject/inner", BaseProjectType.ID, null, "name", "descr", null, null); + pc = new NewProjectConfigImpl("/testCreateInnerProject/inner", BaseProjectType.ID, null, "name", "descr", null, null, null); pm.createProject(pc, null); assertNotNull(projectRegistry.getProject("/testCreateInnerProject/inner")); @@ -267,13 +698,12 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { // If there are no parent folder it will be created - pc = new NewProjectConfig("/nothing/inner", BaseProjectType.ID, null, "name", "descr", null, null); + pc = new NewProjectConfigImpl("/nothing/inner", BaseProjectType.ID, null, "name", "descr", null, null, null); pm.createProject(pc, null); assertNotNull(projectRegistry.getProject("/nothing/inner")); assertNotNull(projectRegistry.getProject("/nothing")); assertNotNull(pm.getProjectsRoot().getChildFolder("/nothing")); - } @@ -281,14 +711,14 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { public void testUpdateProjectWithPersistedAttributes() throws Exception { Map> attributes = new HashMap<>(); - ProjectConfig pc = new NewProjectConfig("/testUpdateProject", BaseProjectType.ID, null, "name", "descr", null, null); + ProjectConfig pc = new NewProjectConfigImpl("/testUpdateProject", BaseProjectType.ID, null, "name", "descr", null, null, null); RegisteredProject p = pm.createProject(pc, null); assertEquals(BaseProjectType.ID, p.getType()); assertEquals("name", p.getName()); attributes.put("pt2-var2", new AttributeValue("updated").getList()); - ProjectConfig pc1 = new NewProjectConfig("/testUpdateProject", "pt2", null, "updatedName", "descr", attributes, null); + ProjectConfig pc1 = new NewProjectConfigImpl("/testUpdateProject", "pt2", null, "updatedName", "descr", attributes, null, null); p = pm.updateProject(pc1); @@ -301,38 +731,30 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { @Test public void testUpdateProjectWithProvidedAttributes() throws Exception { + // SPECS: Project should be updated with problem code = 13 when value for required attribute is not initialized Map> attributes = new HashMap<>(); attributes.put("pt2-var2", new AttributeValue("test").getList()); - ProjectConfig pc = new NewProjectConfig("/testUpdateProject", "pt2", null, "name", "descr", attributes, null); - RegisteredProject p = pm.createProject(pc, null); + ProjectConfig pc = new NewProjectConfigImpl("/testUpdateProject", "pt2", null, "name", "descr", attributes, null, null); + pm.createProject(pc, null); - // SPECS: - // If project type is updated with one required provided attributes - // those attributes should be provided before update - - pc = new NewProjectConfig("/testUpdateProject", "pt3", null, "updatedName", "descr", attributes, null); - - try { - pm.updateProject(pc); - fail("ProjectTypeConstraintException: Value for required attribute is not initialized pt3:pt2-provided1 "); - } catch (ServerException e) { - } + pc = new NewProjectConfigImpl("/testUpdateProject", "pt3", null, "updatedName", "descr", attributes, null, null); - p.getBaseFolder().createFolder("file1"); - p = pm.updateProject(pc); - assertEquals(new AttributeValue("pt2-provided1"), p.getAttributeEntries().get("pt2-provided1")); - + RegisteredProject project = pm.updateProject(pc); + final List problems = project.getProblems(); + assertNotNull(problems); + assertFalse(problems.isEmpty()); + assertEquals(1, problems.size()); + assertEquals(13, problems.get(0).code); } @Test public void testUpdateProjectOnRawFolder() throws Exception { - - ProjectConfig pc = new NewProjectConfig("/testUpdateProjectOnRawFolder", BaseProjectType.ID, null, "name", "descr", null, null); + ProjectConfig pc = new NewProjectConfigImpl("/testUpdateProjectOnRawFolder", BaseProjectType.ID, null, "name", "descr", null, null, null); pm.createProject(pc, null); String folderPath = "/testUpdateProjectOnRawFolder/folder"; pm.getProjectsRoot().createFolder(folderPath); @@ -340,18 +762,15 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { // SPECS: // If update is called on raw folder new project should be created - pc = new NewProjectConfig(folderPath, BaseProjectType.ID, null, "raw", "descr", null, null); + pc = new NewProjectConfigImpl(folderPath, BaseProjectType.ID, null, "raw", "descr", null, null, null); pm.updateProject(pc); assertEquals(BaseProjectType.ID, pm.getProject(folderPath).getType()); - - } @Test public void testInvalidUpdateConfig() throws Exception { - - ProjectConfig pc = new NewProjectConfig(null, BaseProjectType.ID, null, "name", "descr", null, null); + ProjectConfig pc = new NewProjectConfigImpl(null, BaseProjectType.ID, null, "name", "descr", null, null, null); try { pm.updateProject(pc); @@ -359,7 +778,7 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { } catch (ConflictException e) { } - pc = new NewProjectConfig("/nothing", BaseProjectType.ID, null, "name", "descr", null, null); + pc = new NewProjectConfigImpl("/nothing", BaseProjectType.ID, null, "name", "descr", null, null, null); try { pm.updateProject(pc); fail("NotFoundException: Project '/nothing' doesn't exist."); @@ -371,9 +790,9 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { @Test public void testDeleteProject() throws Exception { - ProjectConfig pc = new NewProjectConfig("/testDeleteProject", BaseProjectType.ID, null, "name", "descr", null, null); + ProjectConfig pc = new NewProjectConfigImpl("/testDeleteProject", BaseProjectType.ID, null, "name", "descr", null, null, null); pm.createProject(pc, null); - pc = new NewProjectConfig("/testDeleteProject/inner", BaseProjectType.ID, null, "name", "descr", null, null); + pc = new NewProjectConfigImpl("/testDeleteProject/inner", BaseProjectType.ID, null, "name", "descr", null, null, null); pm.createProject(pc, null); assertNotNull(projectRegistry.getProject("/testDeleteProject/inner")); @@ -384,13 +803,11 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { assertNull(projectRegistry.getProject("/testDeleteProject")); assertNull(pm.getProjectsRoot().getChild("/testDeleteProject/inner")); //assertNull(projectRegistry.folder("/testDeleteProject/inner")); - } @Test public void testDeleteProjectEvent() throws Exception { - - ProjectConfig pc = new NewProjectConfig("/testDeleteProject", BaseProjectType.ID, null, "name", "descr", null, null); + ProjectConfig pc = new NewProjectConfigImpl("/testDeleteProject", BaseProjectType.ID, null, "name", "descr", null, null, null); pm.createProject(pc, null); String[] deletedPath = new String[1]; @@ -401,13 +818,11 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { pm.delete("/testDeleteProject"); assertEquals("/testDeleteProject", deletedPath[0]); - } @Test public void testImportProject() throws Exception { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); String fileContent = "to be or not to be"; ZipOutputStream zipOut = new ZipOutputStream(bout); @@ -433,7 +848,6 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { assertNotNull(project.getBaseFolder().getChild("file1")); assertEquals(fileContent, project.getBaseFolder().getChild("file1").getVirtualFile().getContentAsString()); - } @Test @@ -467,11 +881,11 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { @Test public void testProvidedAttributesNotSerialized() throws Exception { - Map> attributes = new HashMap<>(); attributes.put("pt2-var2", new AttributeValue("test2").getList()); attributes.put("pt2-var1", new AttributeValue("test1").getList()); - ProjectConfig pc = new NewProjectConfig("/testProvidedAttributesNotSerialized", "pt3", null, "name", "descr", attributes, null); + ProjectConfig pc = + new NewProjectConfigImpl("/testProvidedAttributesNotSerialized", "pt3", null, "name", "descr", attributes, null, null); pm.createProject(pc, null); @@ -493,10 +907,9 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { @Test public void testSettableValueProvider() throws Exception { - assertTrue(((Variable)projectTypeRegistry.getProjectType("settableVPPT").getAttribute("my")).isValueProvided()); - ProjectConfig pc = new NewProjectConfig("/testSettableValueProvider", "settableVPPT", null, "", "", new HashMap<>(), null); + ProjectConfig pc = new NewProjectConfigImpl("/testSettableValueProvider", "settableVPPT", null, "", "", new HashMap<>(), null, null); pm.createProject(pc, null); @@ -507,18 +920,64 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { Map> attributes = new HashMap<>(); attributes.put("my", new AttributeValue("set").getList()); - pc = new NewProjectConfig("/testSettableValueProvider", "settableVPPT", null, "", "", attributes, null); + pc = new NewProjectConfigImpl("/testSettableValueProvider", "settableVPPT", null, "", "", attributes, null, null); pm.updateProject(pc); project = pm.getProject("/testSettableValueProvider"); assertEquals("set", project.getAttributes().get("my").get(0)); - } /* ---------------------------------- */ /* private */ /* ---------------------------------- */ + private void checkProjectExist(String projectPath) { + RegisteredProject project = projectRegistry.getProject(projectPath); + FolderEntry projectFolder = project.getBaseFolder(); + assertNotNull(project); + assertTrue(projectFolder.getVirtualFile().exists()); + assertEquals(projectPath, project.getPath()); + assertEquals(BaseProjectType.ID, project.getType()); + } + + private void checkChildrenFor(FolderEntry projectFolder, List children) throws ServerException, ForbiddenException { + for (String path : children) { + assertNotNull(projectFolder.getChild(path)); + if (path.contains("file")) { + String fileContent = projectFolder.getChild(path).getVirtualFile().getContentAsString(); + assertEquals(FILE_CONTENT, fileContent); + } + } + } + + private InputStream prepareZipArchiveBasedOn(List paths) throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ZipOutputStream zipOut = new ZipOutputStream(bout); + + for (String path : paths) { + zipOut.putNextEntry(new ZipEntry(path)); + + if (path.contains("file")) { + zipOut.write(FILE_CONTENT.getBytes()); + } + } + zipOut.close(); + return new ByteArrayInputStream(bout.toByteArray()); + } + + private NewProjectConfigDto createProjectConfigObject(String projectName, + String projectPath, + String projectType, + SourceStorageDto sourceStorage) { + return DtoFactory.newDto(NewProjectConfigDto.class) + .withPath(projectPath) + .withName(projectName) + .withType(projectType) + .withDescription("description") + .withSource(sourceStorage) + .withAttributes(new HashMap<>()); + } + private void registerImporter(String importType, InputStream zip) throws Exception { final ValueHolder folderHolder = new ValueHolder<>(); importerRegistry.register(new ProjectImporter() { @@ -570,7 +1029,6 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { throws ForbiddenException, ConflictException, ServerException { FolderEntry baseFolder = new FolderEntry(vfsProvider.getVirtualFileSystem().getRoot().createFolder(projectPath.toString())); baseFolder.createFolder("file1"); - } @Override @@ -578,5 +1036,4 @@ public class ProjectManagerWriteTest extends WsAgentTestBase { return "pt3"; } } - } diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java index 37e9da38d2..3cc0599621 100644 --- a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java +++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.che.api.project.server; +import com.google.common.io.ByteStreams; + import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; @@ -29,6 +31,7 @@ import org.eclipse.che.api.project.server.handlers.ProjectHandlerRegistry; import org.eclipse.che.api.project.server.importer.ProjectImporter; import org.eclipse.che.api.project.server.importer.ProjectImporterRegistry; import org.eclipse.che.api.project.server.type.AttributeValue; +import org.eclipse.che.api.project.server.type.ProjectTypeConstraintException; 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.ReadonlyValueProvider; @@ -52,7 +55,6 @@ import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; -import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.lang.ws.rs.ExtMediaType; import org.eclipse.che.commons.subject.SubjectImpl; @@ -81,6 +83,7 @@ import javax.ws.rs.core.Application; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.nio.charset.Charset; @@ -106,6 +109,7 @@ import java.util.stream.IntStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import static java.lang.String.format; import static java.util.Collections.singletonList; import static javax.ws.rs.HttpMethod.DELETE; import static javax.ws.rs.HttpMethod.GET; @@ -115,6 +119,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.TEXT_PLAIN; import static org.eclipse.che.commons.lang.ws.rs.ExtMediaType.APPLICATION_ZIP; import static org.everrest.core.ApplicationContext.anApplicationContext; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; @@ -414,24 +419,9 @@ public class ProjectServiceTest { @Test public void testCreateProject() throws Exception { - - - - phRegistry.register(new CreateProjectHandler() { - @Override - public void onCreateProject(Path projectPath, Map attributes, Map options) - throws ForbiddenException, ConflictException, ServerException { - FolderEntry projectFolder = new FolderEntry(vfsProvider.getVirtualFileSystem().getRoot().createFolder("new_project")); - projectFolder.createFolder("a"); - projectFolder.createFolder("b"); - projectFolder.createFile("test.txt", "test".getBytes(Charset.defaultCharset())); - } - - @Override - public String getProjectType() { - return "testCreateProject"; - } - }); + final String projectName = "new_project"; + final String projectType = "testCreateProject"; + phRegistry.register(createProjectHandlerFor(projectName, projectType)); Map> headers = new HashMap<>(); headers.put("Content-Type", singletonList(APPLICATION_JSON)); @@ -450,9 +440,9 @@ public class ProjectServiceTest { final ProjectConfigDto newProjectConfig = DtoFactory.getInstance().createDto(ProjectConfigDto.class) .withPath("/new_project") - .withName("new_project") + .withName(projectName) .withDescription("new project") - .withType("testCreateProject") + .withType(projectType) .withAttributes(attributeValues) .withSource(DtoFactory.getInstance().createDto(SourceStorageDto.class)); projects.add(newProjectConfig); @@ -467,11 +457,11 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); ProjectConfigDto result = (ProjectConfigDto)response.getEntity(); assertNotNull(result); - assertEquals(result.getName(), "new_project"); + assertEquals(result.getName(), projectName); assertEquals(result.getPath(), "/new_project"); assertEquals(result.getDescription(), newProjectConfig.getDescription()); assertEquals(result.getType(), newProjectConfig.getType()); - assertEquals(result.getType(), "testCreateProject"); + assertEquals(result.getType(), projectType); Map> attributes = result.getAttributes(); assertNotNull(attributes); assertEquals(attributes.size(), 1); @@ -492,11 +482,63 @@ public class ProjectServiceTest { assertNotNull(project.getBaseFolder().getChild("a")); assertNotNull(project.getBaseFolder().getChild("b")); assertNotNull(project.getBaseFolder().getChild("test.txt")); - - } + @Test + public void testCreateBatchProjects() throws Exception { + //prepare first project + final String projectName1 = "testProject1"; + final String projectTypeId1 = "testProjectType1"; + final String projectPath1 = "/testProject1"; + createTestProjectType(projectTypeId1); + phRegistry.register(createProjectHandlerFor(projectName1, projectTypeId1)); + + //prepare inner project + final String innerProjectName = "innerProject"; + final String innerProjectTypeId = "testProjectType2"; + final String innerProjectPath = "/testProject1/innerProject"; + + createTestProjectType(innerProjectTypeId); + phRegistry.register(createProjectHandlerFor(innerProjectName, innerProjectTypeId)); + + //prepare project to import + final String importProjectName = "testImportProject"; + final String importProjectTypeId = "testImportProjectType"; + final String importProjectPath = "/testImportProject"; + final String importType = "importType"; + final String [] paths = {"a", "b", "test.txt"}; + + final List children = new ArrayList<>(Arrays.asList(paths)); + registerImporter(importType, prepareZipArchiveBasedOn(children)); + createTestProjectType(importProjectTypeId); + + Map> headers = new HashMap<>(); + headers.put("Content-Type", singletonList(APPLICATION_JSON)); + + try (InputStream content = getClass().getResourceAsStream("batchNewProjectConfigs.json")) { + ContainerResponse response = launcher.service(POST, + "http://localhost:8080/api/project/batch", + "http://localhost:8080/api", + headers, + ByteStreams.toByteArray(content), null); + + assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); + + final List result = (List)response.getEntity(); + assertNotNull(result); + assertEquals(result.size(), 3); + + final ProjectConfigDto importProjectConfig = result.get(0); + checkProjectIsCreated(importProjectName, importProjectPath, importProjectTypeId, importProjectConfig); + + final ProjectConfigDto config1 = result.get(1); + checkProjectIsCreated(projectName1, projectPath1, projectTypeId1, config1); + + final ProjectConfigDto innerProjectConfig = result.get(2); + checkProjectIsCreated(innerProjectName, innerProjectPath, innerProjectTypeId, innerProjectConfig); + } + } @Test public void testUpdateProject() throws Exception { @@ -636,10 +678,9 @@ public class ProjectServiceTest { ptRegistry.registerProjectType(pt); - ContainerResponse response = - launcher.service(GET, String.format("http://localhost:8080/api/project/estimate/%s?type=%s", - "testEstimateProjectGood", "testEstimateProjectPT"), - "http://localhost:8080/api", null, null, null); + ContainerResponse response = launcher.service(GET, format("http://localhost:8080/api/project/estimate/%s?type=%s", + "testEstimateProjectGood", "testEstimateProjectPT"), + "http://localhost:8080/api", null, null, null); assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); //noinspection unchecked SourceEstimation result = (SourceEstimation)response.getEntity(); @@ -648,14 +689,15 @@ public class ProjectServiceTest { assertEquals(result.getAttributes().get("calculated_attribute").get(0), "checked"); // if project not matched - response = launcher.service(GET, String.format("http://localhost:8080/api/project/estimate/%s?type=%s", - "testEstimateProjectBad", "testEstimateProjectPT"), + response = launcher.service(GET, format("http://localhost:8080/api/project/estimate/%s?type=%s", + "testEstimateProjectBad", "testEstimateProjectPT"), "http://localhost:8080/api", null, null, null); - assertEquals(response.getStatus(), 409, "Error: " + response.getEntity()); - String msg = JsonHelper.parseJson(response.getEntity().toString()).getElement("message").getStringValue(); - assertEquals(errMessage, msg); - + assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); + //noinspection unchecked + result = (SourceEstimation)response.getEntity(); + assertFalse(result.isMatched()); + assertEquals(result.getAttributes().size(), 0); } @@ -697,8 +739,8 @@ public class ProjectServiceTest { ptRegistry.registerProjectType(pt); ContainerResponse response = - launcher.service(GET, String.format("http://localhost:8080/api/project/resolve/%s", - "testEstimateProjectGood"), + launcher.service(GET, format("http://localhost:8080/api/project/resolve/%s", + "testEstimateProjectGood"), "http://localhost:8080/api", null, null, null); assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); List result = (List) response.getEntity(); @@ -746,7 +788,7 @@ public class ProjectServiceTest { " \"type\": \"%s\"\n" + "}"; - byte[] b = String.format(json, importType).getBytes(Charset.defaultCharset()); + byte[] b = format(json, importType).getBytes(Charset.defaultCharset()); ContainerResponse response = launcher.service(POST, "http://localhost:8080/api/project/import/new_project", "http://localhost:8080/api", headers, b, null); @@ -878,7 +920,7 @@ public class ProjectServiceTest { public void testCreateFolderInRoot() throws Exception { String folder = "my_folder"; ContainerResponse response = launcher.service(POST, - String.format("http://localhost:8080/api/project/folder/%s", folder), + format("http://localhost:8080/api/project/folder/%s", folder), "http://localhost:8080/api", null, null, null); assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); ItemReference fileItem = (ItemReference)response.getEntity(); @@ -887,7 +929,7 @@ public class ProjectServiceTest { assertEquals(fileItem.getPath(), "/" + folder); validateFolderLinks(fileItem); assertEquals(response.getHttpHeaders().getFirst("Location"), - URI.create(String.format("http://localhost:8080/api/project/children/%s", folder))); + URI.create(format("http://localhost:8080/api/project/children/%s", folder))); } @Test @@ -1052,7 +1094,8 @@ public class ProjectServiceTest { String overwrittenContent = "that is the question"; ((FolderEntry)myProject.getBaseFolder().getChild("a/b")).createFile(originFileName, originContent.getBytes(Charset.defaultCharset())); - ((FolderEntry)myProject.getBaseFolder().getChild("a/b/c")).createFile(destinationFileName, overwrittenContent.getBytes(Charset.defaultCharset())); + ((FolderEntry)myProject.getBaseFolder().getChild("a/b/c")).createFile(destinationFileName, + overwrittenContent.getBytes(Charset.defaultCharset())); Map> headers = new HashMap<>(); headers.put(CONTENT_TYPE, singletonList(APPLICATION_JSON)); @@ -1127,9 +1170,9 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); assertEquals(response.getHttpHeaders().getFirst("Location"), URI.create( - String.format("http://localhost:8080/api/project/children/my_project/a/b/c/%s", renamedFolder))); + format("http://localhost:8080/api/project/children/my_project/a/b/c/%s", renamedFolder))); assertNotNull(myProject.getBaseFolder().getChild("a/b/test.txt")); - assertNotNull(myProject.getBaseFolder().getChild(String.format("a/b/c/%s/test.txt", renamedFolder))); + assertNotNull(myProject.getBaseFolder().getChild(format("a/b/c/%s/test.txt", renamedFolder))); } @Test @@ -1165,11 +1208,11 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); assertEquals(response.getHttpHeaders().getFirst("Location"), URI.create( - String.format("http://localhost:8080/api/project/children/my_project/a/b/c/%s", renamedFolder))); + format("http://localhost:8080/api/project/children/my_project/a/b/c/%s", renamedFolder))); assertNotNull(myProject.getBaseFolder().getChild("a/b/test.txt")); - assertNotNull(myProject.getBaseFolder().getChild(String.format("a/b/c/%s/test.txt", renamedFolder))); + assertNotNull(myProject.getBaseFolder().getChild(format("a/b/c/%s/test.txt", renamedFolder))); assertEquals(myProject.getBaseFolder().getChild("a/b/test.txt").getName(), - myProject.getBaseFolder().getChild(String.format("a/b/c/%s/%s", renamedFolder, originFileName)).getName()); + myProject.getBaseFolder().getChild(format("a/b/c/%s/%s", renamedFolder, originFileName)).getName()); } @Test @@ -1210,8 +1253,8 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); assertEquals(response.getHttpHeaders().getFirst("Location"), URI.create( - String.format("http://localhost:8080/api/project/file/my_project/a/b/c/%s", destinationName))); - VirtualFileEntry theTargetFile = myProject.getBaseFolder().getChild(String.format("a/b/c/%s", destinationName)); + format("http://localhost:8080/api/project/file/my_project/a/b/c/%s", destinationName))); + VirtualFileEntry theTargetFile = myProject.getBaseFolder().getChild(format("a/b/c/%s", destinationName)); assertNotNull(theTargetFile); // new } @@ -1238,8 +1281,8 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); assertEquals(response.getHttpHeaders().getFirst("Location"), URI.create( - String.format("http://localhost:8080/api/project/file/my_project/a/b/%s", destinationName))); - VirtualFileEntry theTargetFile = myProject.getBaseFolder().getChild(String.format("a/b/%s", destinationName)); + format("http://localhost:8080/api/project/file/my_project/a/b/%s", destinationName))); + VirtualFileEntry theTargetFile = myProject.getBaseFolder().getChild(format("a/b/%s", destinationName)); assertNotNull(theTargetFile); // new } @@ -1333,8 +1376,8 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); assertEquals(response.getHttpHeaders().getFirst("Location"), URI.create( - String.format("http://localhost:8080/api/project/children/my_project/a/b/c/%s", renamedFolder))); - assertNotNull(myProject.getBaseFolder().getChild(String.format("a/b/c/%s/test.txt", renamedFolder))); + format("http://localhost:8080/api/project/children/my_project/a/b/c/%s", renamedFolder))); + assertNotNull(myProject.getBaseFolder().getChild(format("a/b/c/%s/test.txt", renamedFolder))); } @Test @@ -1360,8 +1403,8 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); assertEquals(response.getHttpHeaders().getFirst("Location"), URI.create( - String.format("http://localhost:8080/api/project/children/my_project/a/%s", renamedFolder))); - assertNotNull(myProject.getBaseFolder().getChild(String.format("a/%s/test.txt", renamedFolder))); + format("http://localhost:8080/api/project/children/my_project/a/%s", renamedFolder))); + assertNotNull(myProject.getBaseFolder().getChild(format("a/%s/test.txt", renamedFolder))); } @Test @@ -1397,8 +1440,8 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); assertEquals(response.getHttpHeaders().getFirst("Location"), URI.create( - String.format("http://localhost:8080/api/project/children/my_project/a/b/c/%s", renamedFolder))); - assertNotNull(myProject.getBaseFolder().getChild(String.format("a/b/c/%s/test.txt", renamedFolder))); + format("http://localhost:8080/api/project/children/my_project/a/b/c/%s", renamedFolder))); + assertNotNull(myProject.getBaseFolder().getChild(format("a/b/c/%s/test.txt", renamedFolder))); } @Test @@ -1416,7 +1459,7 @@ public class ProjectServiceTest { Map> headers = new HashMap<>(); headers.put(CONTENT_TYPE, singletonList(ExtMediaType.APPLICATION_ZIP)); ContainerResponse response = launcher.service(POST, - String.format("http://localhost:8080/api/project/import/my_project/a/b"), + format("http://localhost:8080/api/project/import/my_project/a/b"), "http://localhost:8080/api", headers, zip, null); assertEquals(response.getStatus(), 201, "Error: " + response.getEntity()); assertEquals(response.getHttpHeaders().getFirst("Location"), @@ -1520,8 +1563,8 @@ public class ProjectServiceTest { @Test public void testGetMissingItem() throws Exception { ContainerResponse response = launcher.service(GET, - "http://localhost:8080/api/project/item/some_missing_project/a/b", - "http://localhost:8080/api", null, null, null); + "http://localhost:8080/api/project/item/some_missing_project/a/b", + "http://localhost:8080/api", null, null, null); assertEquals(response.getStatus(), 404, "Error: " + response.getEntity()); } @@ -1731,8 +1774,10 @@ public class ProjectServiceTest { "to" + URL_ENCODED_SPACE + "be" + URL_ENCODED_QUOTES; RegisteredProject myProject = pm.getProject("my_project"); - myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes(Charset.defaultCharset())); - myProject.getBaseFolder().createFolder("a/b").createFile("test.txt", "Pay attention! To be or to be that is the question".getBytes(Charset.defaultCharset())); + myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes( + Charset.defaultCharset())); + myProject.getBaseFolder().createFolder("a/b").createFile("test.txt", "Pay attention! To be or to be that is the question".getBytes( + Charset.defaultCharset())); myProject.getBaseFolder().createFolder("c").createFile("_test", "Pay attention! To be or to not be that is the question".getBytes(Charset.defaultCharset())); ContainerResponse response = @@ -1755,11 +1800,14 @@ public class ProjectServiceTest { "the" + URL_ENCODED_QUOTES + URL_ENCODED_SPACE + AND_OPERATOR + URL_ENCODED_SPACE + "question" + URL_ENCODED_ASTERISK; RegisteredProject myProject = pm.getProject("my_project"); - myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes(Charset.defaultCharset())); + myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes( + Charset.defaultCharset())); myProject.getBaseFolder().createFolder("a/b") - .createFile("containsSearchTextAlso.txt", "Pay attention! To be or not to be that is the questionS".getBytes(Charset.defaultCharset())); + .createFile("containsSearchTextAlso.txt", + "Pay attention! To be or not to be that is the questionS".getBytes(Charset.defaultCharset())); myProject.getBaseFolder().createFolder("c") - .createFile("notContainsSearchText", "Pay attention! To be or to not be that is the questEon".getBytes(Charset.defaultCharset())); + .createFile("notContainsSearchText", + "Pay attention! To be or to not be that is the questEon".getBytes(Charset.defaultCharset())); ContainerResponse response = launcher.service(GET,"http://localhost:8080/api/project/search/my_project" + queryToSearch, @@ -1780,7 +1828,8 @@ public class ProjectServiceTest { String queryToSearch = "?text=" + "question" + URL_ENCODED_ASTERISK; RegisteredProject myProject = pm.getProject("my_project"); - myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes(Charset.defaultCharset())); + myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes( + Charset.defaultCharset())); myProject.getBaseFolder().createFolder("a/b") .createFile("containsSearchTextAlso.txt", "Pay attention! To be or not to be that is the questionS".getBytes(Charset.defaultCharset())); myProject.getBaseFolder().createFolder("c") @@ -1806,10 +1855,12 @@ public class ProjectServiceTest { "question" + URL_ENCODED_SPACE + NOT_OPERATOR + URL_ENCODED_SPACE + URL_ENCODED_QUOTES + "attention!" + URL_ENCODED_QUOTES; RegisteredProject myProject = pm.getProject("my_project"); - myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes(Charset.defaultCharset())); + myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes( + Charset.defaultCharset())); myProject.getBaseFolder().createFolder("b") .createFile("notContainsSearchText", "Pay attention! To be or not to be that is the question".getBytes(Charset.defaultCharset())); - myProject.getBaseFolder().createFolder("c").createFile("alsoNotContainsSearchText", "To be or to not be that is the ...".getBytes(Charset.defaultCharset())); + myProject.getBaseFolder().createFolder("c").createFile("alsoNotContainsSearchText", + "To be or to not be that is the ...".getBytes(Charset.defaultCharset())); ContainerResponse response = launcher.service(GET, "http://localhost:8080/api/project/search/my_project" + queryToSearch, @@ -1838,7 +1889,8 @@ public class ProjectServiceTest { URL_ENCODED_BACKSLASH + ':' + "projectName=test"; RegisteredProject myProject = pm.getProject("my_project"); myProject.getBaseFolder().createFolder("x/y") - .createFile("test.txt", "http://localhost:8080/ide/dev6?action=createProject:projectName=test".getBytes(Charset.defaultCharset())); + .createFile("test.txt", + "http://localhost:8080/ide/dev6?action=createProject:projectName=test".getBytes(Charset.defaultCharset())); ContainerResponse response = launcher.service(GET, "http://localhost:8080/api/project/search/my_project" + queryToSearch, "http://localhost:8080/api", null, null, null); @@ -1864,11 +1916,11 @@ public class ProjectServiceTest { assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); List result = (List)response.getEntity(); assertEquals(result.size(), 2); - assertEqualsNoOrder(new Object[] { + assertEqualsNoOrder(new Object[]{ result.get(0).getPath(), result.get(1).getPath() }, - new Object[] { + new Object[]{ "/my_project/a/b/test.txt", "/my_project/x/y/test.txt" }); @@ -1895,12 +1947,12 @@ public class ProjectServiceTest { Link link = item.getLink("delete"); assertNotNull(link); assertEquals(link.getMethod(), DELETE); - assertEquals(link.getHref(), "http://localhost:8080/api/project" + item.getPath()); + assertEquals(link.getHref(), "http://localhost:8080/api/project" + item.getPath()); link = item.getLink("update content"); assertNotNull(link); assertEquals(link.getMethod(), PUT); assertEquals(link.getConsumes(), "*/*"); - assertEquals(link.getHref(), "http://localhost:8080/api/project" + "/file" + item.getPath()); + assertEquals(link.getHref(), "http://localhost:8080/api/project" + "/file" + item.getPath()); } private void validateFolderLinks(ItemReference item) { @@ -1974,6 +2026,77 @@ public class ProjectServiceTest { } } + private InputStream prepareZipArchiveBasedOn(List paths) throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ZipOutputStream zipOut = new ZipOutputStream(bout); + + for (String path : paths) { + zipOut.putNextEntry(new ZipEntry(path)); + } + zipOut.close(); + return new ByteArrayInputStream(bout.toByteArray()); + } + + private void checkProjectIsCreated(String expectedName, String expectedPath, String expectedType, ProjectConfigDto actualConfig) + throws ServerException, NotFoundException { + final String projectDescription = "someDescription"; + assertEquals(actualConfig.getName(), expectedName); + assertEquals(actualConfig.getPath(), expectedPath); + assertEquals(actualConfig.getDescription(), projectDescription); + assertEquals(actualConfig.getType(), expectedType); + + final String expectedAttribute = "new_test_attribute"; + final String expectedAttributeValue = "some_attribute_value"; + final Map> attributes = actualConfig.getAttributes(); + assertNotNull(attributes); + assertEquals(attributes.size(), 1); + assertEquals(attributes.get(expectedAttribute), singletonList(expectedAttributeValue)); + + validateProjectLinks(actualConfig); + + RegisteredProject project = pm.getProject(expectedPath); + assertNotNull(project); + assertEquals(project.getDescription(), projectDescription); + assertEquals(project.getProjectType().getId(), expectedType); + String attributeVal = project.getAttributeEntries().get(expectedAttribute).getString(); + assertNotNull(attributeVal); + assertEquals(attributeVal, expectedAttributeValue); + + assertNotNull(project.getBaseFolder().getChild("a")); + assertNotNull(project.getBaseFolder().getChild("b")); + assertNotNull(project.getBaseFolder().getChild("test.txt")); + } + + private void createTestProjectType(final String projectTypeId) throws ProjectTypeConstraintException { + final ProjectTypeDef pt = new ProjectTypeDef(projectTypeId, "my project type", true, false) { + { + addConstantDefinition("new_test_attribute", "attr description", "some_attribute_value"); + } + }; + ptRegistry.registerProjectType(pt); + } + + private CreateProjectHandler createProjectHandlerFor(final String projectName, final String projectTypeId) { + return new CreateProjectHandler() { + @Override + public void onCreateProject(Path projectPath, Map attributes, Map options) + throws ForbiddenException, ConflictException, ServerException { + final String pathToProject = projectPath.toString(); + final String pathToParent = pathToProject.substring(0, pathToProject.lastIndexOf("/")); + final FolderEntry projectFolder = new FolderEntry( + vfsProvider.getVirtualFileSystem().getRoot().getChild(Path.of(pathToParent)).createFolder(projectName)); + projectFolder.createFolder("a"); + projectFolder.createFolder("b"); + projectFolder.createFile("test.txt", "test".getBytes(Charset.defaultCharset())); + } + + @Override + public String getProjectType() { + return projectTypeId; + } + }; + } + private class LocalProjectType extends ProjectTypeDef { private LocalProjectType(String typeId, String typeName) { super(typeId, typeName, true, false); diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/WsAgentTestBase.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/WsAgentTestBase.java index 45d969f649..b9e2e1ca1f 100644 --- a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/WsAgentTestBase.java +++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/WsAgentTestBase.java @@ -10,15 +10,11 @@ *******************************************************************************/ 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.ServerException; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.project.server.handlers.CreateProjectHandler; import org.eclipse.che.api.project.server.handlers.ProjectHandlerRegistry; import org.eclipse.che.api.project.server.importer.ProjectImporterRegistry; -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.ReadonlyValueProvider; @@ -26,7 +22,6 @@ import org.eclipse.che.api.project.server.type.SettableValueProvider; import org.eclipse.che.api.project.server.type.ValueProvider; import org.eclipse.che.api.project.server.type.ValueProviderFactory; import org.eclipse.che.api.project.server.type.ValueStorageException; -import org.eclipse.che.api.vfs.Path; import org.eclipse.che.api.vfs.impl.file.DefaultFileWatcherNotificationHandler; import org.eclipse.che.api.vfs.impl.file.FileTreeWatcher; import org.eclipse.che.api.vfs.impl.file.FileWatcherNotificationHandler; diff --git a/wsagent/che-core-api-project/src/test/resources/org/eclipse/che/api/project/server/batchNewProjectConfigs.json b/wsagent/che-core-api-project/src/test/resources/org/eclipse/che/api/project/server/batchNewProjectConfigs.json new file mode 100644 index 0000000000..ace7d2e43d --- /dev/null +++ b/wsagent/che-core-api-project/src/test/resources/org/eclipse/che/api/project/server/batchNewProjectConfigs.json @@ -0,0 +1,68 @@ +[ + { + "name": "innerProject", + "displayName": "innerProject", + "path": "/testProject1/innerProject", + "description": "someDescription", + "type": "testProjectType2", + "mixins": [], + "attributes": { + "new_test_attribute": [ + "some_attribute_value" + ] + }, + "modules": [], + "problems": [], + "source": { + "type": "", + "location": "", + "parameters": {} + }, + "commands": [], + "links": [] + }, + { + "name": "testProject1", + "displayName": "testProject1", + "path": "/testProject1", + "description": "someDescription", + "type": "testProjectType1", + "mixins": [], + "attributes": { + "new_test_attribute": [ + "some_attribute_value" + ] + }, + "modules": [], + "problems": [], + "source": { + "type": "", + "location": "", + "parameters": {} + }, + "commands": [], + "links": [] + }, + { + "name": "testImportProject", + "displayName": "testImportProject", + "path": "/testImportProject", + "description": "someDescription", + "type": "testImportProjectType", + "mixins": [], + "attributes": { + "new_test_attribute": [ + "some_attribute_value" + ] + }, + "modules": [], + "problems": [], + "source": { + "type": "importType", + "location": "someLocation", + "parameters": {} + }, + "commands": [], + "links": [] + } +] diff --git a/wsmaster/che-core-api-project-templates-shared/src/main/java/org/eclipse/che/api/project/templates/shared/dto/ProjectTemplateDescriptor.java b/wsmaster/che-core-api-project-templates-shared/src/main/java/org/eclipse/che/api/project/templates/shared/dto/ProjectTemplateDescriptor.java index 13428c2662..c3be0a03f7 100644 --- a/wsmaster/che-core-api-project-templates-shared/src/main/java/org/eclipse/che/api/project/templates/shared/dto/ProjectTemplateDescriptor.java +++ b/wsmaster/che-core-api-project-templates-shared/src/main/java/org/eclipse/che/api/project/templates/shared/dto/ProjectTemplateDescriptor.java @@ -12,7 +12,7 @@ package org.eclipse.che.api.project.templates.shared.dto; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.machine.shared.dto.CommandDto; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; +import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectProblemDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.dto.shared.DTO; @@ -110,4 +110,16 @@ public interface ProjectTemplateDescriptor { void setTags(List tags); ProjectTemplateDescriptor withTags(List tags); + + List getProjects(); + + void setProjects(List projects); + + ProjectTemplateDescriptor withProjects(List projects); + + Map getOptions(); + + void setOptions(Map options); + + ProjectTemplateDescriptor withOptions(Map options); } diff --git a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/NewProjectConfigDto.java b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/NewProjectConfigDto.java new file mode 100644 index 0000000000..149c323bf4 --- /dev/null +++ b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/NewProjectConfigDto.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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.workspace.shared.dto; + +import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.project.NewProjectConfig; +import org.eclipse.che.api.core.rest.shared.dto.Link; +import org.eclipse.che.dto.shared.DTO; + +import java.util.List; +import java.util.Map; + +import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; + +/** + * Data transfer object (DTO) for creating of project. + * + * @author Roman Nikitenko + */ +@DTO +public interface NewProjectConfigDto extends ProjectConfigDto, NewProjectConfig { + @Override + @FactoryParameter(obligation = OPTIONAL) + String getName(); + + @Override + @FactoryParameter(obligation = OPTIONAL) + String getType(); + + @Override + @FactoryParameter(obligation = OPTIONAL) + SourceStorageDto getSource(); + + @Override + @FactoryParameter(obligation = OPTIONAL) + Map getOptions(); + + NewProjectConfigDto withName(String name); + + NewProjectConfigDto withPath(String path); + + NewProjectConfigDto withDescription(String description); + + NewProjectConfigDto withType(String type); + + NewProjectConfigDto withMixins(List mixins); + + NewProjectConfigDto withAttributes(Map> attributes); + + NewProjectConfigDto withSource(SourceStorageDto source); + + NewProjectConfigDto withLinks(List links); + + NewProjectConfigDto withProblems(List problems); + + NewProjectConfigDto withOptions(Map options); +}