From e872904fa7c6db5e74c024403c0037b8cc1098c5 Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Thu, 17 Sep 2020 17:10:48 +0300 Subject: [PATCH 01/10] REST APIs that allows saving/edit/delete/search/share devfiles Signed-off-by: Sergii Kabashniuk --- .../model/workspace/devfile/UserDevfile.java | 33 ++ wsmaster/che-core-api-devfile-shared/pom.xml | 141 +++++ .../che/api/devfile/shared/Constants.java | 22 + .../devfile/shared/dto/UserDevfileDto.java | 59 ++ .../shared/event/DevfileCreatedEvent.java | 31 + .../shared/event/DevfileDeletedEvent.java | 30 + .../shared/event/DevfileUpdatedEvent.java | 31 + wsmaster/che-core-api-devfile/pom.xml | 200 +++++++ .../api/devfile/server/DevfileService.java | 304 ++++++++++ .../server/DevfileServiceLinksInjector.java | 45 ++ .../che/api/devfile/server/DtoConverter.java | 32 + .../server/UserDevfileEntityProvider.java | 125 ++++ .../devfile/server/UserDevfileManager.java | 184 ++++++ .../event/BeforeDevfileRemovedEvent.java | 33 ++ .../devfile/server/jpa/JpaUserDevfileDao.java | 420 ++++++++++++++ .../server/jpa/UserDevfileJpaModule.java | 26 + .../server/model/impl/UserDevfileImpl.java | 210 +++++++ .../devfile/server/spi/UserDevfileDao.java | 111 ++++ .../DevfileServiceLinksInjectorTest.java | 61 ++ .../devfile/server/DevfileServiceTest.java | 545 ++++++++++++++++++ .../devfile/server/TestObjectGenerator.java | 217 +++++++ .../server/UserDevfileManagerTest.java | 194 +++++++ .../server/jpa/UserDevfileTckModule.java | 89 +++ .../server/spi/tck/UserDevfileDaoTest.java | 517 +++++++++++++++++ ...org.eclipse.che.commons.test.tck.TckModule | 1 + .../src/test/resources/logback-test.xml | 26 + .../server/urlfactory/URLFactoryBuilder.java | 12 +- .../DefaultFactoryParameterResolverTest.java | 6 +- .../urlfactory/URLFactoryBuilderTest.java | 10 +- .../server/WorkspaceEntityProvider.java | 10 +- .../server/devfile/DevfileEntityProvider.java | 10 +- .../server/devfile/DevfileModule.java | 1 - ...DevfileManager.java => DevfileParser.java} | 6 +- .../server/devfile/DevfileService.java | 61 -- .../model/impl/devfile/MetadataImpl.java | 5 + .../server/WorkspaceEntityProviderTest.java | 8 +- .../server/WorkspaceServiceTest.java | 12 + .../devfile/DevfileEntityProviderTest.java | 10 +- ...anagerTest.java => DevfileParserTest.java} | 23 +- .../server/devfile/DevfileServiceTest.java | 61 -- .../che-schema/7.19.0/1__userdevfile.sql | 29 + wsmaster/pom.xml | 2 + 42 files changed, 3782 insertions(+), 171 deletions(-) create mode 100644 core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java create mode 100644 wsmaster/che-core-api-devfile-shared/pom.xml create mode 100644 wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/Constants.java create mode 100644 wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/dto/UserDevfileDto.java create mode 100644 wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileCreatedEvent.java create mode 100644 wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileDeletedEvent.java create mode 100644 wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileUpdatedEvent.java create mode 100644 wsmaster/che-core-api-devfile/pom.xml create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileService.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjector.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DtoConverter.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileManager.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java create mode 100644 wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjectorTest.java create mode 100644 wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java create mode 100644 wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/TestObjectGenerator.java create mode 100644 wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java create mode 100644 wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileTckModule.java create mode 100644 wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java create mode 100644 wsmaster/che-core-api-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule create mode 100644 wsmaster/che-core-api-devfile/src/test/resources/logback-test.xml rename wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/{DevfileManager.java => DevfileParser.java} (99%) delete mode 100644 wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java rename wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/{DevfileManagerTest.java => DevfileParserTest.java} (91%) delete mode 100644 wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java create mode 100644 wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.19.0/1__userdevfile.sql diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java new file mode 100644 index 00000000000..b8586dc5fe8 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.core.model.workspace.devfile; + +/** Devfile that persisted in permanent storage. */ +public interface UserDevfile { + /** Returns the identifier of this persisted devfile instance. It is mandatory and unique. */ + String getId(); + + /** Returns the name of devfile. It is mandatory. */ + String getName(); + + /** + * Returns the namespace of the current devfile instance. Devfile name is unique for devfiles in + * the same namespace. + */ + String getNamespace(); + + /** Returns description of devfile */ + String getDescription(); + + /** Returns devfile content */ + Devfile getDevfile(); +} diff --git a/wsmaster/che-core-api-devfile-shared/pom.xml b/wsmaster/che-core-api-devfile-shared/pom.xml new file mode 100644 index 00000000000..c972509a53d --- /dev/null +++ b/wsmaster/che-core-api-devfile-shared/pom.xml @@ -0,0 +1,141 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 7.19.0-SNAPSHOT + + che-core-api-devfile-shared + jar + Che Core :: API :: Devfile :: Shared + + ${project.build.directory}/generated-sources/dto/ + + + + com.google.code.gson + gson + + + com.google.guava + guava + + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-api-dto + + + org.eclipse.che.core + che-core-api-model + + + org.eclipse.che.core + che-core-api-workspace-shared + + + + + + org.eclipse.che.core + che-core-api-dto-maven-plugin + ${project.version} + + + process-sources + + generate + + + + + + org.eclipse.che.core + che-core-api-devfile-shared + ${project.version} + + + org.eclipse.che.core + che-core-api-model + ${project.version} + + + org.eclipse.che.core + che-core-api-workspace-shared + ${project.version} + + + + + org.eclipse.che.api.devfile.shared.dto + + ${dto-generator-out-directory} + org.eclipse.che.api.devfile.server.dto.DtoServerImpls + server + + + + maven-compiler-plugin + + + pre-compile + generate-sources + + compile + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-resource + process-sources + + add-resource + + + + + ${dto-generator-out-directory}/META-INF + META-INF + + + + + + add-source + process-sources + + add-source + + + + ${dto-generator-out-directory} + + + + + + + + diff --git a/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/Constants.java b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/Constants.java new file mode 100644 index 00000000000..b33fcf568ba --- /dev/null +++ b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/Constants.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.shared; + +import com.google.common.annotations.Beta; + +/** Constants for Devfile API */ +@Beta +public final class Constants { + public static final String LINK_REL_SELF = "self"; + + private Constants() {} +} diff --git a/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/dto/UserDevfileDto.java b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/dto/UserDevfileDto.java new file mode 100644 index 00000000000..425cafdb5dd --- /dev/null +++ b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/dto/UserDevfileDto.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.shared.dto; + +import com.google.common.annotations.Beta; +import java.util.List; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; +import org.eclipse.che.api.core.rest.shared.dto.Link; +import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; +import org.eclipse.che.dto.shared.DTO; + +@DTO +@Beta +public interface UserDevfileDto extends UserDevfile, Hyperlinks { + + void setId(String id); + + UserDevfileDto withId(String id); + + String getId(); + + void setName(String name); + + UserDevfileDto withName(String name); + + String getName(); + + void setDescription(String name); + + UserDevfileDto withDescription(String name); + + String getDescription(); + + @Override + DevfileDto getDevfile(); + + void setDevfile(DevfileDto devfile); + + @Override + String getNamespace(); + + void setNamespace(String namespace); + + UserDevfileDto withNamespace(String namespace); + + UserDevfileDto withDevfile(DevfileDto devfile); + + UserDevfileDto withLinks(List links); +} diff --git a/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileCreatedEvent.java b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileCreatedEvent.java new file mode 100644 index 00000000000..ff81751bba9 --- /dev/null +++ b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileCreatedEvent.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.shared.event; + +import com.google.common.annotations.Beta; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventOrigin; + +/** Informs about persisted devfile creation. */ +@EventOrigin("devfile") +@Beta +public class DevfileCreatedEvent { + private final UserDevfile userDevfile; + + public DevfileCreatedEvent(UserDevfile userDevfile) { + this.userDevfile = userDevfile; + } + + public UserDevfile getUserDevfile() { + return userDevfile; + } +} diff --git a/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileDeletedEvent.java b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileDeletedEvent.java new file mode 100644 index 00000000000..fdbafbc8a0f --- /dev/null +++ b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileDeletedEvent.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.shared.event; + +import com.google.common.annotations.Beta; +import org.eclipse.che.api.core.notification.EventOrigin; + +/** Informs about persisted devfile removal. */ +@EventOrigin("devfile") +@Beta +public class DevfileDeletedEvent { + private final String id; + + public DevfileDeletedEvent(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileUpdatedEvent.java b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileUpdatedEvent.java new file mode 100644 index 00000000000..d263140e251 --- /dev/null +++ b/wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileUpdatedEvent.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.shared.event; + +import com.google.common.annotations.Beta; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventOrigin; + +/** Informs about persisted devfile update. */ +@EventOrigin("devfile") +@Beta +public class DevfileUpdatedEvent { + private final UserDevfile userDevfile; + + public DevfileUpdatedEvent(UserDevfile userDevfile) { + this.userDevfile = userDevfile; + } + + public UserDevfile getUserDevfile() { + return userDevfile; + } +} diff --git a/wsmaster/che-core-api-devfile/pom.xml b/wsmaster/che-core-api-devfile/pom.xml new file mode 100644 index 00000000000..aaa8b8d2375 --- /dev/null +++ b/wsmaster/che-core-api-devfile/pom.xml @@ -0,0 +1,200 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 7.19.0-SNAPSHOT + + che-core-api-devfile + jar + Che Core :: API :: Devfile + + + com.fasterxml.jackson.core + jackson-databind + + + com.google.guava + guava + + + com.google.inject + guice + + + com.google.inject.extensions + guice-persist + + + io.swagger + swagger-annotations + + + javax.annotation + javax.annotation-api + + + javax.inject + javax.inject + + + javax.ws.rs + javax.ws.rs-api + + + org.eclipse.che.core + che-core-api-account + + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-api-devfile-shared + + + org.eclipse.che.core + che-core-api-dto + + + org.eclipse.che.core + che-core-api-model + + + org.eclipse.che.core + che-core-api-user + + + org.eclipse.che.core + che-core-api-workspace + + + org.eclipse.che.core + che-core-api-workspace-shared + + + org.eclipse.che.core + che-core-commons-lang + + + org.eclipse.che.core + che-core-db + + + org.slf4j + slf4j-api + + + org.eclipse.persistence + javax.persistence + provided + + + ch.qos.logback + logback-classic + test + + + com.h2database + h2 + test + + + com.jayway.restassured + rest-assured + test + + + org.eclipse.che.core + che-core-commons-json + test + + + org.eclipse.che.core + che-core-commons-test + test + + + org.eclipse.che.core + che-core-db-vendor-h2 + test + + + org.eclipse.che.core + che-core-sql-schema + test + + + org.eclipse.persistence + org.eclipse.persistence.core + test + + + org.eclipse.persistence + org.eclipse.persistence.jpa + test + + + org.everrest + everrest-assured + test + + + org.everrest + everrest-core + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-testng + test + + + org.testng + testng + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + **/spi/tck/*.* + **/devfile/server/TestObjectGenerator.* + + + + + + + + diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileService.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileService.java new file mode 100644 index 00000000000..7de22637947 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileService.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static java.util.stream.Collectors.toList; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.che.api.devfile.server.DtoConverter.asDto; +import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; +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.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.rest.Service; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; +import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.commons.lang.URLEncodedUtils; +import org.eclipse.che.dto.server.DtoFactory; + +/** Defines Devfile REST API. */ +@Api(value = "/devfile", description = "Devfile REST API") +@Path("/devfile") +public class DevfileService extends Service { + + private final DevfileSchemaProvider schemaCachedProvider; + private final UserDevfileManager userDevfileManager; + private final DevfileServiceLinksInjector linksInjector; + + @Inject + public DevfileService( + DevfileSchemaProvider schemaCachedProvider, + UserDevfileManager userDevfileManager, + DevfileServiceLinksInjector linksInjector) { + this.userDevfileManager = userDevfileManager; + this.linksInjector = linksInjector; + this.schemaCachedProvider = schemaCachedProvider; + } + + /** + * Retrieves the json schema. + * + * @return json schema + */ + @GET + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Retrieves current version of devfile JSON schema") + @ApiResponses({ + @ApiResponse(code = 200, message = "The schema successfully retrieved"), + @ApiResponse(code = 500, message = "Internal server error occurred") + }) + public Response getSchema() throws ServerException { + try { + return Response.ok(schemaCachedProvider.getSchemaContent(CURRENT_API_VERSION)).build(); + } catch (IOException e) { + throw new ServerException(e); + } + } + + @Path("/devfile") + @POST + @Consumes({APPLICATION_JSON, "text/yaml", "text/x-yaml"}) + @Produces(APPLICATION_JSON) + @ApiOperation( + value = "Creates a new persistent Devfile from yaml representation", + consumes = "application/json, text/yaml, text/x-yaml", + produces = APPLICATION_JSON, + nickname = "createFromDevfileYaml", + response = UserDevfileDto.class) + @ApiResponses({ + @ApiResponse(code = 201, message = "The devfile successfully created"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 403, message = "The user does not have access to create a new devfile"), + @ApiResponse(code = 409, message = "Conflict error occurred during the devfile creation"), + @ApiResponse(code = 500, message = "Internal server error occurred") + }) + public Response createFromDevfileYaml( + @ApiParam(value = "The devfile to create", required = true) DevfileDto devfile) + throws ConflictException, BadRequestException, ForbiddenException, NotFoundException, + ServerException { + requiredNotNull(devfile, "Devfile"); + return Response.status(201) + .entity( + linksInjector.injectLinks( + asDto( + userDevfileManager.createDevfile( + DtoFactory.newDto(UserDevfileDto.class) + .withDevfile(devfile) + .withName(NameGenerator.generate("devfile-", 16)))), + getServiceContext())) + .build(); + } + + @POST + @Consumes({APPLICATION_JSON}) + @Produces(APPLICATION_JSON) + @ApiOperation( + value = "Creates a new persistent Devfile", + consumes = "application/json", + produces = APPLICATION_JSON, + nickname = "create", + response = UserDevfileDto.class) + @ApiResponses({ + @ApiResponse(code = 201, message = "The devfile successfully created"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 403, message = "The user does not have access to create a new devfile"), + @ApiResponse( + code = 409, + message = + "Conflict error occurred during the devfile creation" + + "(e.g. The devfile with such name already exists)"), + @ApiResponse(code = 500, message = "Internal server error occurred") + }) + public Response createFromUserDevfile( + @ApiParam(value = "The devfile to create", required = true) UserDevfileDto userDevfileDto) + throws ConflictException, BadRequestException, ForbiddenException, NotFoundException, + ServerException { + requiredNotNull(userDevfileDto, "Devfile"); + return Response.status(201) + .entity( + linksInjector.injectLinks( + asDto(userDevfileManager.createDevfile(userDevfileDto)), getServiceContext())) + .build(); + } + + @GET + @Path("/{id}") + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Get devfile by its identifier") + @ApiResponses({ + @ApiResponse(code = 200, message = "The response contains requested workspace entity"), + @ApiResponse(code = 404, message = "The devfile with specified id does not exist"), + @ApiResponse(code = 403, message = "The user is not allowed to read devfile"), + @ApiResponse(code = 500, message = "Internal server error occurred") + }) + public UserDevfileDto getById( + @ApiParam(value = "UserDevfile identifier") @PathParam("id") String id) + throws NotFoundException, ServerException, ForbiddenException, BadRequestException { + requiredNotNull(id, "id"); + return linksInjector.injectLinks(asDto(userDevfileManager.getById(id)), getServiceContext()); + } + + @GET + @Path("list") + @Produces(APPLICATION_JSON) + @ApiOperation( + value = "Get devfiles which user can read", + notes = + "This operation can be performed only by authorized user. " + + "It is possible to add additional constraints for the desired devfiles by specifying\n" + + "multiple query parameters that is representing fields of the devfile. All constrains\n" + + "would be combined with \"And\" condition. Also, it is possible to specify 'like:' prefix\n" + + "for the query parameters. In this case instead of an exact match would be used SQL pattern like search.\n" + + "Examples id=sdfsdf5&devfile.meta.name=like:%dfdf&", + response = UserDevfileDto.class, + responseContainer = "List") + @ApiResponses({ + @ApiResponse(code = 200, message = "The devfiles successfully fetched"), + @ApiResponse(code = 500, message = "Internal server error occurred during devfiles fetching") + }) + public Response getUserDevfiles( + @ApiParam("The number of the items to skip") @DefaultValue("0") @QueryParam("skipCount") + Integer skipCount, + @ApiParam("The limit of the items in the response, default is 30, maximum 60") + @DefaultValue("30") + @QueryParam("maxItems") + Integer maxItems, + @ApiParam( + "A list of fields and directions of sort. By default items would be sorted by id. Example id:asc,name:desc.") + @QueryParam("order") + String order) + throws ServerException, BadRequestException { + final Set skip = ImmutableSet.of("token", "skipCount", "maxItems", "order"); + Map> queryParams = URLEncodedUtils.parse(uriInfo.getRequestUri()); + final List> query = + queryParams + .entrySet() + .stream() + .filter(param -> !param.getValue().isEmpty()) + .filter(param -> !skip.contains(param.getKey())) + .map(entry -> Pair.of(entry.getKey(), entry.getValue().iterator().next())) + .collect(toList()); + List> searchOrder = Collections.emptyList(); + if (order != null && !order.isEmpty()) { + try { + searchOrder = + Splitter.on(",") + .trimResults() + .omitEmptyStrings() + .withKeyValueSeparator(":") + .split(order) + .entrySet() + .stream() + .map(e -> Pair.of(e.getKey(), e.getValue())) + .collect(toList()); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Invalid `order` query parameter format." + e.getMessage()); + } + } + Page userDevfilesPage = + userDevfileManager.getUserDevfiles(maxItems, skipCount, query, searchOrder); + + List list = + userDevfilesPage + .getItems() + .stream() + .map(DtoConverter::asDto) + .map(dto -> linksInjector.injectLinks(asDto(dto), getServiceContext())) + .collect(toList()); + + return Response.ok().entity(list).header("Link", createLinkHeader(userDevfilesPage)).build(); + } + + @PUT + @Path("/{id}") + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Update the devfile by replacing all the existing data with update") + @ApiResponses({ + @ApiResponse(code = 200, message = "The devfile successfully updated"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 403, message = "The user does not have access to update the devfile"), + @ApiResponse( + code = 409, + message = + "Conflict error occurred during devfile update" + + "(e.g. Workspace with such name already exists)"), + @ApiResponse(code = 500, message = "Internal server error occurred") + }) + public UserDevfileDto update( + @ApiParam("The devfile id") @PathParam("id") String id, + @ApiParam(value = "The devfile update", required = true) UserDevfileDto update) + throws BadRequestException, ServerException, ForbiddenException, NotFoundException, + ConflictException { + requiredNotNull(update, "User Devfile configuration"); + update.setId(id); + return linksInjector.injectLinks( + asDto(userDevfileManager.updateUserDevfile(update)), getServiceContext()); + } + + @DELETE + @Path("/{id}") + @ApiOperation(value = "Removes the devfile") + @ApiResponses({ + @ApiResponse(code = 204, message = "The devfile successfully removed"), + @ApiResponse(code = 403, message = "The user does not have access to remove the devfile"), + @ApiResponse(code = 500, message = "Internal server error occurred") + }) + public void delete(@ApiParam("The devfile id") @PathParam("id") String id) + throws BadRequestException, ServerException, ForbiddenException { + userDevfileManager.removeUserDevfile(id); + } + + /** + * Checks object reference is not {@code null} + * + * @param object object reference to check + * @param subject used as subject of exception message "{subject} required" + * @throws BadRequestException when object reference is {@code null} + */ + private void requiredNotNull(Object object, String subject) throws BadRequestException { + if (object == null) { + throw new BadRequestException(subject + " required"); + } + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjector.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjector.java new file mode 100644 index 00000000000..cf5c76cd8f6 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjector.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableList; +import javax.inject.Singleton; +import javax.ws.rs.HttpMethod; +import org.eclipse.che.api.core.rest.ServiceContext; +import org.eclipse.che.api.core.util.LinksHelper; +import org.eclipse.che.api.devfile.shared.Constants; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; + +/** Helps to inject {@link DevfileService} related links. */ +@Beta +@Singleton +public class DevfileServiceLinksInjector { + public UserDevfileDto injectLinks(UserDevfileDto userDevfileDto, ServiceContext serviceContext) { + return userDevfileDto.withLinks( + ImmutableList.of( + LinksHelper.createLink( + HttpMethod.GET, + serviceContext + .getBaseUriBuilder() + .clone() + .path(DevfileService.class) + .path(DevfileService.class, "getById") + .build(userDevfileDto.getId()) + .toString(), + null, + APPLICATION_JSON, + Constants.LINK_REL_SELF))); + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DtoConverter.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DtoConverter.java new file mode 100644 index 00000000000..a09196a2ec2 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DtoConverter.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static org.eclipse.che.dto.server.DtoFactory.newDto; + +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; + +/** Helps to convert to/from DTOs related to user devfile. */ +public class DtoConverter { + public static UserDevfileDto asDto(UserDevfile userDevfile) { + DevfileDto devfileDto = + org.eclipse.che.api.workspace.server.DtoConverter.asDto(userDevfile.getDevfile()); + return newDto(UserDevfileDto.class) + .withId(userDevfile.getId()) + .withDevfile(devfileDto) + .withNamespace(userDevfile.getNamespace()) + .withName(userDevfile.getName()) + .withDescription(userDevfile.getDescription()); + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java new file mode 100644 index 00000000000..9cf9e0cb718 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; +import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; +import org.eclipse.che.dto.server.DtoFactory; + +/** + * Entity provider for {@link UserDevfileDto}. Performs schema validation of devfile part of the + * user devfile before actual {@link UserDevfileDto} creation. + */ +@Singleton +@Provider +@Produces({APPLICATION_JSON}) +@Consumes({APPLICATION_JSON}) +public class UserDevfileEntityProvider + implements MessageBodyReader, MessageBodyWriter { + + private DevfileParser devfileParser; + private ObjectMapper mapper = new ObjectMapper(); + + @Inject + public UserDevfileEntityProvider(DevfileParser devfileParser) { + this.devfileParser = devfileParser; + } + + @Override + public boolean isReadable( + Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type == UserDevfileDto.class; + } + + @Override + public UserDevfileDto readFrom( + Class type, + Type genericType, + Annotation[] annotations, + MediaType mediaType, + MultivaluedMap httpHeaders, + InputStream entityStream) + throws IOException, WebApplicationException { + try { + JsonNode wsNode = mapper.readTree(entityStream); + JsonNode devfileNode = wsNode.path("devfile"); + if (!devfileNode.isNull() && !devfileNode.isMissingNode()) { + devfileParser.parseJson(devfileNode.toString()); + } else { + throw new BadRequestException("Mandatory field `devfile` is not defined."); + } + return DtoFactory.getInstance().createDtoFromJson(wsNode.toString(), UserDevfileDto.class); + } catch (DevfileFormatException e) { + throw new BadRequestException(e.getMessage()); + } catch (IOException e) { + throw new WebApplicationException(e.getMessage(), e); + } + } + + @Override + public boolean isWriteable( + Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return UserDevfileDto.class.isAssignableFrom(type); + } + + @Override + public long getSize( + UserDevfileDto userDevfileDto, + Class type, + Type genericType, + Annotation[] annotations, + MediaType mediaType) { + return -1; + } + + @Override + public void writeTo( + UserDevfileDto userDevfileDto, + Class type, + Type genericType, + Annotation[] annotations, + MediaType mediaType, + MultivaluedMap httpHeaders, + OutputStream entityStream) + throws IOException, WebApplicationException { + httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "public, no-cache, no-store, no-transform"); + try (Writer w = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) { + w.write(DtoFactory.getInstance().toJson(userDevfileDto)); + w.flush(); + } + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileManager.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileManager.java new file mode 100644 index 00000000000..5587c5bd8c5 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileManager.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import java.util.List; +import java.util.Optional; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.account.api.AccountManager; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.Devfile; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.api.devfile.shared.event.DevfileCreatedEvent; +import org.eclipse.che.api.devfile.shared.event.DevfileUpdatedEvent; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.lang.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Facade for {@link UserDevfile} related operations. */ +@Beta +@Singleton +public class UserDevfileManager { + private static final Logger LOG = LoggerFactory.getLogger(UserDevfileManager.class); + private final UserDevfileDao userDevfileDao; + private final EventService eventService; + private final AccountManager accountManager; + + @Inject + public UserDevfileManager( + AccountManager accountManager, UserDevfileDao userDevfileDao, EventService eventService) { + this.accountManager = accountManager; + this.userDevfileDao = userDevfileDao; + this.eventService = eventService; + } + + /** + * Stores {@link Devfile} instance + * + * @param userDevfile instance of user devfile which would be stored + * @return new persisted devfile instance + * @throws ConflictException when any conflict occurs (e.g Devfile with such name already exists + * for {@code owner}) + * @throws NullPointerException when {@code devfile} is null + * @throws ServerException when any other error occurs + */ + public UserDevfile createDevfile(UserDevfile userDevfile) + throws ServerException, NotFoundException, ConflictException { + requireNonNull(userDevfile, "Required non-null userdevfile"); + requireNonNull(userDevfile.getDevfile(), "Required non-null devfile"); + String name = + userDevfile.getName() != null + ? userDevfile.getName() + : NameGenerator.generate("devfile-", 5); + UserDevfile result = + userDevfileDao.create( + new UserDevfileImpl( + NameGenerator.generate("id-", 16), + accountManager.getByName( + EnvironmentContext.getCurrent().getSubject().getUserName()), + name, + userDevfile.getDescription(), + userDevfile.getDevfile())); + LOG.debug( + "UserDevfile '{}' with id '{}' created by user '{}'", + result.getName(), + result.getId(), + EnvironmentContext.getCurrent().getSubject().getUserName()); + eventService.publish(new DevfileCreatedEvent(result)); + return result; + } + + /** + * Gets UserDevfile by given id. + * + * @param id userdevfile identifier + * @return userdevfile instance + * @throws NullPointerException when {@code id} is null + * @throws NotFoundException when userdevfile with given id not found + * @throws ServerException when any server errors occurs + */ + public UserDevfile getById(String id) throws NotFoundException, ServerException { + requireNonNull(id); + Optional result = userDevfileDao.getById(id); + return result.orElseThrow( + () -> new NotFoundException(format("Devfile with id '%s' doesn't exist", id))); + } + + /** + * Gets list of UserDevfiles in given namespace. + * + * @param namespace devfiles namespace + * @return list of devfiles in given namespace. Always returns list(even when there are no devfile + * in given namespace), never null + * @throws NullPointerException when {@code namespace} is null + * @throws ServerException when any other error occurs during workspaces fetching + */ + public Page getByNamespace(String namespace, int maxItems, long skipCount) + throws ServerException { + requireNonNull(namespace, "Required non-null namespace"); + final Page devfilesPage = + userDevfileDao.getByNamespace(namespace, maxItems, skipCount); + return devfilesPage; + } + /** + * Updates an existing user devfile in accordance to the new configuration. + * + *

Note: Replace strategy is used for user devfile update, it means that existing devfile data + * will be replaced with given {@code update}. + * + * @param update user devfile update + * @return updated user devfile + * @throws NullPointerException when {@code update} is null + * @throws ConflictException when any conflict occurs. + * @throws NotFoundException when user devfile with given id not found + * @throws ServerException when any server error occurs + */ + public UserDevfile updateUserDevfile(UserDevfile update) + throws ConflictException, NotFoundException, ServerException { + requireNonNull(update); + Optional result = userDevfileDao.update(update); + UserDevfile devfile = + result.orElseThrow( + () -> + new NotFoundException( + format("Devfile with id '%s' doesn't exist", update.getId()))); + LOG.debug( + "UserDevfile '{}' with id '{}' update by user '{}'", + devfile.getName(), + devfile.getId(), + EnvironmentContext.getCurrent().getSubject().getUserName()); + eventService.publish(new DevfileUpdatedEvent(devfile)); + return devfile; + } + + /** + * Removes stored {@link UserDevfile} by given id. + * + * @param id user devfile identifier + * @throws NullPointerException when {@code id} is null + * @throws ServerException when any server errors occurs + */ + public void removeUserDevfile(String id) throws ServerException { + requireNonNull(id); + userDevfileDao.remove(id); + LOG.debug( + "UserDevfile with id '{}' removed by user '{}'", + id, + EnvironmentContext.getCurrent().getSubject().getUserName()); + } + + /** + * Gets list of devfiles. Parameters, returned values and possible exceptions are the same as in + * {@link UserDevfileDao#getDevfiles(int, int, List, List)} + */ + public Page getUserDevfiles( + int maxItems, + int skipCount, + List> filter, + List> order) + throws ServerException { + return userDevfileDao.getDevfiles(maxItems, skipCount, filter, order); + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java new file mode 100644 index 00000000000..47fa4b108cd --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server.event; + +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventOrigin; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.core.db.cascade.event.RemoveEvent; + +/** Published before {@link UserDevfile user devfile} removed. */ +@EventOrigin("user") +public class BeforeDevfileRemovedEvent extends RemoveEvent { + + private final UserDevfileImpl userDevfile; + + public BeforeDevfileRemovedEvent(UserDevfileImpl userDevfile) { + this.userDevfile = userDevfile; + } + + /** Returns user which is going to be removed. */ + public UserDevfileImpl getUserDevfile() { + return userDevfile; + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java new file mode 100644 index 00000000000..2dacb895397 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server.jpa; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; +import static org.eclipse.che.api.core.Pages.iterate; +import static org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao.UserDevfileSearchQueryBuilder.newBuilder; + +import com.google.common.annotations.Beta; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.inject.persist.Transactional; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import org.eclipse.che.account.event.BeforeAccountRemovedEvent; +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.UserDevfileManager; +import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.core.db.cascade.CascadeEventSubscriber; +import org.eclipse.che.core.db.jpa.DuplicateKeyException; +import org.eclipse.che.core.db.jpa.IntegrityConstraintViolationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** JPA based implementation of {@link UserDevfileDao}. */ +@Singleton +@Beta +public class JpaUserDevfileDao implements UserDevfileDao { + private static final Logger LOG = LoggerFactory.getLogger(JpaUserDevfileDao.class); + + protected final Provider managerProvider; + protected final AccountDao accountDao; + protected final EventService eventService; + + public static final List> DEFAULT_ORDER = + ImmutableList.of(new Pair<>("id", "ASC")); + + @Inject + public JpaUserDevfileDao( + Provider managerProvider, AccountDao accountDao, EventService eventService) { + this.managerProvider = managerProvider; + this.accountDao = accountDao; + this.eventService = eventService; + } + + @Override + public UserDevfile create(UserDevfile userDevfile) + throws ConflictException, ServerException, NotFoundException { + requireNonNull(userDevfile); + try { + Account account = accountDao.getByName(userDevfile.getNamespace()); + UserDevfileImpl userDevfileImpl = new UserDevfileImpl(userDevfile, account); + doCreate(userDevfileImpl); + return userDevfileImpl; + } catch (DuplicateKeyException ex) { + throw new ConflictException( + format( + "Devfile with name '%s' already exists in current account '%s'", + userDevfile.getName(), userDevfile.getNamespace())); + } catch (IntegrityConstraintViolationException ex) { + throw new ConflictException( + "Could not create devfile with creator that refers on non-existent user"); + } catch (RuntimeException ex) { + throw new ServerException(ex.getMessage(), ex); + } + } + + @Override + public Optional update(UserDevfile userDevfile) + throws ConflictException, ServerException, NotFoundException { + requireNonNull(userDevfile); + try { + Account account = accountDao.getByName(userDevfile.getNamespace()); + return doUpdate(new UserDevfileImpl(userDevfile, account)).map(UserDevfileImpl::new); + } catch (DuplicateKeyException ex) { + throw new ConflictException( + format( + "Devfile with name '%s' already exists in current account '%s'", + userDevfile.getName(), userDevfile.getNamespace())); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + public void remove(String id) throws ServerException { + requireNonNull(id); + try { + doRemove(id); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + @Transactional(rollbackOn = {ServerException.class, RuntimeException.class}) + public Optional getById(String id) throws ServerException { + requireNonNull(id); + try { + final UserDevfileImpl devfile = managerProvider.get().find(UserDevfileImpl.class, id); + if (devfile == null) { + return Optional.empty(); + } + return Optional.of(new UserDevfileImpl(devfile)); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Transactional(rollbackOn = {ServerException.class, RuntimeException.class}) + @Override + public Page getByNamespace(String namespace, int maxItems, long skipCount) + throws ServerException { + requireNonNull(namespace, "Required non-null namespace"); + try { + final EntityManager manager = managerProvider.get(); + final List list = + manager + .createNamedQuery("UserDevfile.getByNamespace", UserDevfileImpl.class) + .setParameter("namespace", namespace) + .setMaxResults(maxItems) + .setFirstResult((int) skipCount) + .getResultList() + .stream() + .map(UserDevfileImpl::new) + .collect(Collectors.toList()); + final long count = + manager + .createNamedQuery("UserDevfile.getByNamespaceCount", Long.class) + .setParameter("namespace", namespace) + .getSingleResult(); + return new Page<>(list, skipCount, maxItems, count); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional(rollbackOn = {ServerException.class}) + public Page getDevfiles( + int maxItems, + int skipCount, + List> filter, + List> order) + throws ServerException { + + checkArgument(maxItems > 0, "The number of items has to be positive."); + checkArgument( + skipCount >= 0, + "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); + + return doGetDevfiles( + maxItems, skipCount, filter, order, () -> newBuilder(managerProvider.get())); + } + + @Transactional(rollbackOn = {ServerException.class}) + protected Page doGetDevfiles( + int maxItems, + int skipCount, + List> filter, + List> order, + Supplier queryBuilderSupplier) + throws ServerException { + if (filter != null && !filter.isEmpty()) { + List> invalidFilter = + filter.stream().filter(p -> !p.first.equalsIgnoreCase("name")).collect(toList()); + if (!invalidFilter.isEmpty()) { + throw new IllegalArgumentException( + "Filtering allowed only on `name`. But got: " + invalidFilter); + } + } + List> effectiveOrder = DEFAULT_ORDER; + if (order != null && !order.isEmpty()) { + List> invalidOrder = + order + .stream() + .filter(p -> !p.first.equalsIgnoreCase("name") && !p.first.equalsIgnoreCase("id")) + .collect(toList()); + if (!invalidOrder.isEmpty()) { + throw new IllegalArgumentException( + "Order allowed only on `name`. But got: " + invalidOrder); + } + + List> invalidSortOrder = + order + .stream() + .filter(p -> !p.second.equalsIgnoreCase("asc") && !p.second.equalsIgnoreCase("desc")) + .collect(Collectors.toList()); + if (!invalidSortOrder.isEmpty()) { + throw new IllegalArgumentException( + "Invalid sort order direction. Possible values 'asc' or 'desc'. But got: " + + invalidSortOrder); + } + effectiveOrder = order; + } + try { + final long count = + queryBuilderSupplier.get().withFilter(filter).buildCountQuery().getSingleResult(); + + if (count == 0) { + return new Page<>(emptyList(), skipCount, maxItems, count); + } + List result = + queryBuilderSupplier + .get() + .withFilter(filter) + .withOrder(effectiveOrder) + .withMaxItems(maxItems) + .withSkipCount(skipCount) + .buildSelectItemsQuery() + .getResultList() + .stream() + .map(UserDevfileImpl::new) + .collect(toList()); + return new Page<>(result, skipCount, maxItems, count); + + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public long getTotalCount() throws ServerException { + try { + return managerProvider + .get() + .createNamedQuery("UserDevfile.getTotalCount", Long.class) + .getSingleResult(); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Transactional + protected void doCreate(UserDevfileImpl devfile) { + final EntityManager manager = managerProvider.get(); + manager.persist(devfile); + manager.flush(); + } + + @Transactional + protected Optional doUpdate(UserDevfileImpl update) { + final EntityManager manager = managerProvider.get(); + if (manager.find(UserDevfileImpl.class, update.getId()) == null) { + return Optional.empty(); + } + UserDevfileImpl merged = manager.merge(update); + manager.flush(); + return Optional.of(merged); + } + + @Transactional(rollbackOn = {RuntimeException.class, ServerException.class}) + protected void doRemove(String id) throws ServerException { + final EntityManager manager = managerProvider.get(); + final UserDevfileImpl devfile = manager.find(UserDevfileImpl.class, id); + if (devfile != null) { + eventService + .publish(new BeforeDevfileRemovedEvent(new UserDevfileImpl(devfile))) + .propagateException(); + manager.remove(devfile); + manager.flush(); + } + } + + public static class UserDevfileSearchQueryBuilder { + protected EntityManager entityManager; + protected int maxItems; + protected int skipCount; + protected String filter; + protected Map params; + protected String order; + + public UserDevfileSearchQueryBuilder(EntityManager entityManager) { + this.entityManager = entityManager; + this.params = new HashMap<>(); + } + + public static UserDevfileSearchQueryBuilder newBuilder(EntityManager entityManager) { + return new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(entityManager); + } + + public UserDevfileSearchQueryBuilder withMaxItems(int maxItems) { + this.maxItems = maxItems; + return this; + } + + public UserDevfileSearchQueryBuilder withSkipCount(int skipCount) { + this.skipCount = skipCount; + return this; + } + + public UserDevfileSearchQueryBuilder withFilter(List> filter) { + if (filter == null || filter.isEmpty()) { + this.filter = ""; + return this; + } + final StringJoiner matcher = new StringJoiner(" AND ", " WHERE ", " "); + int i = 0; + for (Pair attribute : filter) { + + final String parameterName = "parameterName" + i++; + if (attribute.second.startsWith("like:")) { + params.put(parameterName, attribute.second.substring(5)); + matcher.add("userdevfile." + attribute.first + " LIKE :" + parameterName); + } else { + params.put(parameterName, attribute.second); + matcher.add("userdevfile." + attribute.first + " = :" + parameterName); + } + } + this.filter = matcher.toString(); + return this; + } + + public UserDevfileSearchQueryBuilder withOrder(List> order) { + if (order == null || order.isEmpty()) { + this.order = ""; + return this; + } + final StringJoiner matcher = new StringJoiner(", ", " ORDER BY ", " "); + order.forEach(pair -> matcher.add("userdevfile." + pair.first + " " + pair.second)); + this.order = matcher.toString(); + + return this; + } + + public TypedQuery buildCountQuery() { + StringBuilder query = + new StringBuilder() + .append("SELECT ") + .append(" COUNT(userdevfile) ") + .append("FROM UserDevfile userdevfile") + .append(filter); + TypedQuery typedQuery = entityManager.createQuery(query.toString(), Long.class); + params.forEach((k, v) -> typedQuery.setParameter(k, v)); + return typedQuery; + } + + public TypedQuery buildSelectItemsQuery() { + + StringBuilder query = + new StringBuilder() + .append("SELECT ") + .append(" userdevfile ") + .append("FROM UserDevfile userdevfile") + .append(filter) + .append(order); + TypedQuery typedQuery = + entityManager + .createQuery(query.toString(), UserDevfileImpl.class) + .setFirstResult(skipCount) + .setMaxResults(maxItems); + params.forEach((k, v) -> typedQuery.setParameter(k, v)); + return typedQuery; + } + } + + @Singleton + public static class RemoveUserDevfileBeforeAccountRemovedEventSubscriber + extends CascadeEventSubscriber { + + @Inject private EventService eventService; + @Inject private UserDevfileManager userDevfileManager; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this, BeforeAccountRemovedEvent.class); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this, BeforeAccountRemovedEvent.class); + } + + @Override + public void onCascadeEvent(BeforeAccountRemovedEvent event) throws Exception { + for (UserDevfile userDevfile : + iterate( + (maxItems, skipCount) -> + userDevfileManager.getByNamespace( + event.getAccount().getName(), maxItems, skipCount))) { + userDevfileManager.removeUserDevfile(userDevfile.getId()); + } + } + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java new file mode 100644 index 00000000000..f705408968a --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server.jpa; + +import com.google.common.annotations.Beta; +import com.google.inject.AbstractModule; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; + +@Beta +public class UserDevfileJpaModule extends AbstractModule { + @Override + protected void configure() { + bind(UserDevfileDao.class).to(JpaUserDevfileDao.class); + bind(JpaUserDevfileDao.RemoveUserDevfileBeforeAccountRemovedEventSubscriber.class) + .asEagerSingleton(); + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java new file mode 100644 index 00000000000..16212380d4f --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server.model.impl; + +import com.google.common.annotations.Beta; +import java.util.Objects; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.model.workspace.devfile.Devfile; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; + +@Entity(name = "UserDevfile") +@Table(name = "userdevfile") +@NamedQueries({ + @NamedQuery( + name = "UserDevfile.getByNamespace", + query = "SELECT d FROM UserDevfile d WHERE d.account.name = :namespace"), + @NamedQuery( + name = "UserDevfile.getByNamespaceCount", + query = "SELECT COUNT(d) " + "FROM UserDevfile d " + "WHERE d.account.name = :namespace "), + @NamedQuery(name = "UserDevfile.getAll", query = "SELECT d FROM UserDevfile d ORDER BY d.id"), + @NamedQuery(name = "UserDevfile.getTotalCount", query = "SELECT COUNT(d) FROM UserDevfile d"), +}) +@Beta +public class UserDevfileImpl implements UserDevfile { + + private static final MetadataImpl FAKE_META = new MetadataImpl("name"); + + @Id + @Column(name = "id", nullable = false) + private String id; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "devfile_id") + private DevfileImpl devfile; + + @Column(name = "meta_generated_name") + private String metaGeneratedName; + + @Column(name = "meta_name") + private String metaName; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "description") + private String description; + + @ManyToOne + @JoinColumn(name = "accountid", nullable = false) + private AccountImpl account; + + public UserDevfileImpl() {} + + public UserDevfileImpl(String id, Account account, UserDevfile userDevfile) { + this( + id, account, userDevfile.getName(), userDevfile.getDescription(), userDevfile.getDevfile()); + } + + public UserDevfileImpl(UserDevfile userDevfile, Account account) { + this(userDevfile.getId(), account, userDevfile); + } + + public UserDevfileImpl(UserDevfileImpl userDevfile) { + this( + userDevfile.id, + userDevfile.account, + userDevfile.getName(), + userDevfile.getDescription(), + userDevfile.getDevfile()); + } + + public UserDevfileImpl( + String id, Account account, String name, String description, Devfile devfile) { + this.id = id; + this.account = new AccountImpl(account); + this.name = name; + this.description = description; + this.devfile = new DevfileImpl(devfile); + syncMeta(); + } + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getNamespace() { + return account.getName(); + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public Devfile getDevfile() { + return new DevfileImpl( + devfile.getApiVersion(), + devfile.getProjects(), + devfile.getComponents(), + devfile.getCommands(), + devfile.getAttributes(), + new MetadataImpl(metaName, metaGeneratedName)); + } + + public void setDevfile(DevfileImpl devfile) { + this.devfile = devfile; + syncMeta(); + } + + public AccountImpl getAccount() { + return account; + } + + public void setAccount(AccountImpl account) { + this.account = account; + } + + private void syncMeta() { + MetadataImpl metadata = devfile.getMetadata(); + metaGeneratedName = metadata.getGenerateName(); + metaName = metadata.getName(); + devfile.setMetadata(FAKE_META); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserDevfileImpl that = (UserDevfileImpl) o; + return Objects.equals(id, that.id) + && Objects.equals(devfile, that.devfile) + && Objects.equals(metaGeneratedName, that.metaGeneratedName) + && Objects.equals(metaName, that.metaName) + && Objects.equals(name, that.name) + && Objects.equals(description, that.description) + && Objects.equals(account, that.account); + } + + @Override + public int hashCode() { + return Objects.hash(id, devfile, metaGeneratedName, metaName, name, description, account); + } + + @Override + public String toString() { + return "UserDevfileImpl{" + + "id='" + + id + + '\'' + + ", devfile=" + + devfile + + ", metaGeneratedName='" + + metaGeneratedName + + '\'' + + ", metaName='" + + metaName + + '\'' + + ", name='" + + name + + '\'' + + ", description='" + + description + + '\'' + + ", account=" + + account + + '}'; + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java new file mode 100644 index 00000000000..7de64f58086 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server.spi; + +import com.google.common.annotations.Beta; +import java.util.List; +import java.util.Optional; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.commons.lang.Pair; + +/** Defines data access object contract for {@code UserDevfileImpl}. */ +@Beta +public interface UserDevfileDao { + + /** + * Creates Devfile. + * + * @param devfile devfile to create + * @return created devfile + * @throws NullPointerException when {@code devfile} is null + * @throws ServerException when any other error occurs + */ + UserDevfile create(UserDevfile devfile) + throws ServerException, ConflictException, NotFoundException; + + /** + * Updates devfile to the new entity, using replacement strategy. + * + * @param devfile devfile to update + * @return updated devfile + * @throws NullPointerException when {@code devfile} is null + * @throws ConflictException when any conflict situation occurs + * @throws ServerException when any other error occurs + */ + Optional update(UserDevfile devfile) + throws ConflictException, ServerException, NotFoundException; + + /** + * Removes devfile. + * + * @param id devfile identifier + * @throws NullPointerException when {@code id} is null + * @throws ServerException when any other error occurs + */ + void remove(String id) throws ServerException; + + /** + * Gets devfile by identifier. + * + * @param id devfile identifier + * @return devfile instance, never null + * @throws NullPointerException when {@code id} is null + * @throws ServerException when any other error occurs + */ + Optional getById(String id) throws ServerException; + + /** + * Gets list of UserDevfiles in given namespace. + * + * @param namespace devfiles namespace + * @return list of devfiles in given namespace. Always returns list(even when there are no devfile + * in given namespace), never null + * @throws NullPointerException when {@code namespace} is null + * @throws ServerException when any other error occurs during workspaces fetching + */ + Page getByNamespace(String namespace, int maxItems, long skipCount) + throws ServerException; + + /** + * Gets all devfiles which user can read filtered by given parameters in a given order + * + * @param maxItems the maximum number of workspaces to return + * @param skipCount the number of workspaces to skip + * @param filter additional conditions for the desired devfiles. Conditions represented as pairs + * of the filed and the value. All pairs would be joined with AND condition. Value of + * the pair can start with 'like:' prefix. In this case would be used LIKE query, + * otherwise = condition. + * @param order - a list of fields and directions of sort. By default items would be sorted by id. + * @return list of devfiles which user can read, never null + * @throws ServerException when any other error occurs during devfile fetching + * @throws IllegalArgumentException when maxItems < 1 or skipCount < 0 or sort order is not 'asc' + * or 'desc'. + */ + Page getDevfiles( + int maxItems, + int skipCount, + List> filter, + List> order) + throws ServerException; + + /** + * Get the count of all user devfiles from the persistent layer. + * + * @return workspace count + * @throws ServerException when any error occurs + */ + long getTotalCount() throws ServerException; +} diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjectorTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjectorTest.java new file mode 100644 index 00000000000..731a52f3312 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjectorTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriBuilder; +import org.eclipse.che.api.core.rest.ServiceContext; +import org.eclipse.che.api.devfile.shared.Constants; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.dto.server.DtoFactory; +import org.everrest.core.impl.uri.UriBuilderImpl; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(MockitoTestNGListener.class) +public class DevfileServiceLinksInjectorTest { + private static final String URI_BASE = "http://localhost:8080"; + private static final String SERVICE_PATH = "/devfile"; + + @Mock ServiceContext context; + + @BeforeMethod + public void setUp() { + final UriBuilder uriBuilder = new UriBuilderImpl(); + uriBuilder.uri(URI_BASE); + + when(context.getBaseUriBuilder()).thenReturn(uriBuilder); + } + + @Test + public void shouldInjectLinks() { + // given + final UserDevfileDto userDevfileDto = DtoFactory.newDto(UserDevfileDto.class).withId("id123"); + DevfileServiceLinksInjector linksInjector = new DevfileServiceLinksInjector(); + // when + final UserDevfileDto withLinks = linksInjector.injectLinks(userDevfileDto, context); + // then + assertEquals(withLinks.getLinks().size(), 1); + assertNotNull(withLinks.getLink(Constants.LINK_REL_SELF)); + assertEquals(withLinks.getLinks().get(0).getMethod(), HttpMethod.GET); + assertEquals(withLinks.getLinks().get(0).getHref(), URI_BASE + SERVICE_PATH + "/id123"); + assertEquals(withLinks.getLinks().get(0).getProduces(), MediaType.APPLICATION_JSON); + } +} diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java new file mode 100644 index 00000000000..c6229b6957e --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java @@ -0,0 +1,545 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static com.jayway.restassured.RestAssured.given; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.che.api.devfile.server.TestObjectGenerator.TEST_ACCOUNT; +import static org.eclipse.che.api.devfile.server.TestObjectGenerator.TEST_SUBJECT; +import static org.eclipse.che.api.devfile.server.TestObjectGenerator.USER_DEVFILE_ID; +import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; +import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; +import static org.everrest.assured.JettyHttpServer.SECURE_PATH; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.jayway.restassured.response.Response; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.rest.ApiExceptionMapper; +import org.eclipse.che.api.core.rest.CheJsonProvider; +import org.eclipse.che.api.core.rest.ServiceContext; +import org.eclipse.che.api.core.rest.WebApplicationExceptionMapper; +import org.eclipse.che.api.core.rest.shared.dto.ServiceError; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; +import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; +import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator; +import org.eclipse.che.api.workspace.server.devfile.validator.DevfileSchemaValidator; +import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; +import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.dto.server.DtoFactory; +import org.everrest.assured.EverrestJetty; +import org.everrest.core.Filter; +import org.everrest.core.GenericContainerRequest; +import org.everrest.core.RequestFilter; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners({EverrestJetty.class, MockitoTestNGListener.class}) +public class DevfileServiceTest { + + @SuppressWarnings("unused") // is declared for deploying by everrest-assured + ApiExceptionMapper exceptionMapper = new ApiExceptionMapper(); + + WebApplicationExceptionMapper exceptionMapper2 = new WebApplicationExceptionMapper(); + + private DevfileSchemaProvider schemaProvider = new DevfileSchemaProvider(); + + private static final EnvironmentFilter FILTER = new EnvironmentFilter(); + + private DevfileParser devfileParser = + new DevfileParser( + new DevfileSchemaValidator(new DevfileSchemaProvider()), + new DevfileIntegrityValidator(Collections.emptyMap())); + DevfileEntityProvider devfileEntityProvider = new DevfileEntityProvider(devfileParser); + UserDevfileEntityProvider userDevfileEntityProvider = + new UserDevfileEntityProvider(devfileParser); + private CheJsonProvider jsonProvider = new CheJsonProvider(new HashSet<>()); + + @Mock UserDevfileDao userDevfileDao; + @Mock UserDevfileManager userDevfileManager; + @Mock EventService eventService; + @Mock DevfileServiceLinksInjector linksInjector; + + DevfileService userDevfileService; + + @Test + public void shouldRetrieveSchema() throws Exception { + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/devfile"); + + assertEquals(response.getStatusCode(), 200); + assertEquals( + response.getBody().asString(), schemaProvider.getSchemaContent(CURRENT_API_VERSION)); + } + + @BeforeMethod + public void setup() { + this.userDevfileService = new DevfileService(schemaProvider, userDevfileManager, linksInjector); + lenient() + .when(linksInjector.injectLinks(any(UserDevfileDto.class), any(ServiceContext.class))) + .thenAnswer((Answer) invocation -> invocation.getArgument(0)); + } + + @Test(dataProvider = "validUserDevfiles") + public void shouldCreateUserDevfileFromJson(UserDevfileDto userDevfileDto) throws Exception { + final UserDevfileImpl userDevfileImpl = + new UserDevfileImpl("id-123123", TEST_ACCOUNT, userDevfileDto); + + when(userDevfileManager.createDevfile(any(UserDevfile.class))).thenReturn(userDevfileImpl); + + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(DtoFactory.getInstance().toJson(userDevfileDto)) + .when() + .post(SECURE_PATH + "/devfile"); + + assertEquals(response.getStatusCode(), 201); + UserDevfileDto dto = unwrapDto(response, UserDevfileDto.class); + assertEquals(dto.getNamespace(), TEST_ACCOUNT.getName()); + assertEquals(new UserDevfileImpl(dto, TEST_ACCOUNT), userDevfileImpl); + verify(userDevfileManager).createDevfile(any(UserDevfile.class)); + } + + @Test(dataProvider = "invalidUserDevfiles") + public void shouldFailToCreateInvalidUserDevfileFromJson( + UserDevfileDto userDevfileDto, String expectedErrorMessage) throws Exception { + + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(DtoFactory.getInstance().toJson(userDevfileDto)) + .when() + .log() + .all() + .post(SECURE_PATH + "/devfile"); + + assertEquals(response.getStatusCode(), 400); + ServiceError error = unwrapDto(response, ServiceError.class); + assertNotNull(error); + assertEquals(error.getMessage(), expectedErrorMessage); + verifyNoMoreInteractions(userDevfileManager); + } + + @Test + public void shouldGetUserDevfileById() throws Exception { + final UserDevfileImpl userDevfile = TestObjectGenerator.createUserDevfile(); + when(userDevfileManager.getById(eq("id-22323"))).thenReturn(userDevfile); + + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .when() + .expect() + .statusCode(200) + .get(SECURE_PATH + "/devfile/id-22323"); + + assertEquals( + new UserDevfileImpl(unwrapDto(response, UserDevfileDto.class), TEST_ACCOUNT), userDevfile); + verify(userDevfileManager).getById(eq("id-22323")); + verify(linksInjector).injectLinks(any(), any()); + } + + @Test + public void shouldThrowNotFoundExceptionWhenUserDevfileIsNotExistOnGetById() throws Exception { + + final String errMessage = format("UserDevfile with id %s is not found", USER_DEVFILE_ID); + doThrow(new NotFoundException(errMessage)).when(userDevfileManager).getById(anyString()); + + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .expect() + .statusCode(404) + .when() + .get(SECURE_PATH + "/devfile/" + USER_DEVFILE_ID); + + assertEquals(unwrapDto(response, ServiceError.class).getMessage(), errMessage); + } + + @Test + public void shouldThrowNotFoundExceptionWhenUpdatingNonExistingUserDevfile() throws Exception { + // given + final UserDevfile userDevfile = + DtoConverter.asDto(TestObjectGenerator.createUserDevfile("devfile-name")); + + doThrow(new NotFoundException(format("User devfile with id %s is not found.", USER_DEVFILE_ID))) + .when(userDevfileManager) + .updateUserDevfile(any(UserDevfile.class)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .body(DtoFactory.getInstance().toJson(userDevfile)) + .when() + .put(SECURE_PATH + "/devfile/" + USER_DEVFILE_ID); + // then + assertEquals(response.getStatusCode(), 404); + assertEquals( + unwrapDto(response, ServiceError.class).getMessage(), + format("User devfile with id %s is not found.", USER_DEVFILE_ID)); + } + + @Test + public void shouldBeAbleToUpdateUserDevfile() throws Exception { + // given + final UserDevfileDto devfileDto = TestObjectGenerator.createUserDevfileDto(); + final UserDevfileImpl userDevfileImpl = new UserDevfileImpl(devfileDto, TEST_ACCOUNT); + when(userDevfileManager.updateUserDevfile(any(UserDevfile.class))).thenReturn(userDevfileImpl); + + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .body(DtoFactory.getInstance().toJson(devfileDto)) + .log() + .all() + .when() + .put(SECURE_PATH + "/devfile/" + devfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 200); + assertEquals( + new UserDevfileImpl(unwrapDto(response, UserDevfileDto.class), TEST_ACCOUNT), + userDevfileImpl); + verify(userDevfileManager).updateUserDevfile(devfileDto); + verify(linksInjector).injectLinks(any(), any()); + } + + @Test(dataProvider = "invalidUserDevfiles") + public void shouldFailToUpdateWithInvalidUserDevfile( + UserDevfileDto userDevfileDto, String expectedErrorMessage) throws Exception { + // given + userDevfileDto = userDevfileDto.withId("id-123"); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .body(DtoFactory.getInstance().toJson(userDevfileDto)) + .log() + .all() + .when() + .put(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 400); + ServiceError error = unwrapDto(response, ServiceError.class); + assertNotNull(error); + assertEquals(error.getMessage(), expectedErrorMessage); + verifyZeroInteractions(userDevfileManager); + verifyZeroInteractions(linksInjector); + } + + @Test + public void shouldOverrideIdOnUpdateUserDevfile() throws Exception { + // given + final UserDevfileDto devfileDto = TestObjectGenerator.createUserDevfileDto(); + final UserDevfileImpl userDevfileImpl = new UserDevfileImpl(devfileDto, TEST_ACCOUNT); + + final String newID = NameGenerator.generate("id", 24); + final UserDevfileImpl expectedUserDevfileImpl = + new UserDevfileImpl(newID, TEST_ACCOUNT, userDevfileImpl); + final UserDevfileDto expectedDto = + org.eclipse.che.api.devfile.server.DtoConverter.asDto(expectedUserDevfileImpl); + when(userDevfileManager.updateUserDevfile(any(UserDevfile.class))) + .thenReturn(expectedUserDevfileImpl); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .body(DtoFactory.getInstance().toJson(devfileDto)) + .when() + .put(SECURE_PATH + "/devfile/" + newID); + // then + assertEquals(response.getStatusCode(), 200); + assertEquals( + new UserDevfileImpl(unwrapDto(response, UserDevfileDto.class), TEST_ACCOUNT), + expectedUserDevfileImpl); + verify(userDevfileManager).updateUserDevfile(expectedDto); + verify(linksInjector).injectLinks(any(), any()); + } + + @Test + public void shouldRemoveUserDevfileByGivenIdentifier() throws Exception { + // given + // when + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .expect() + .statusCode(204) + .when() + .delete(SECURE_PATH + "/devfile/" + USER_DEVFILE_ID); + // then + verify(userDevfileManager).removeUserDevfile(USER_DEVFILE_ID); + } + + @Test + public void shouldNotThrowAnyExceptionWhenRemovingNonExistingUserDevfile() throws Exception { + // given + Mockito.doNothing().when(userDevfileManager).removeUserDevfile(anyString()); + // when + Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .delete(SECURE_PATH + "/devfile/" + USER_DEVFILE_ID); + // then + assertEquals(response.getStatusCode(), 204); + } + + @Test + public void shouldGetUserDevfilesAvailableToUser() throws Exception { + // given + final UserDevfileDto devfileDto = TestObjectGenerator.createUserDevfileDto(); + final UserDevfileImpl userDevfileImpl = new UserDevfileImpl(devfileDto, TEST_ACCOUNT); + doReturn(new Page<>(ImmutableList.of(userDevfileImpl), 0, 1, 1)) + .when(userDevfileManager) + .getUserDevfiles(anyInt(), anyInt(), anyList(), anyList()); + + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .when() + .expect() + .statusCode(200) + .get(SECURE_PATH + "/devfile/list"); + // then + final List res = unwrapDtoList(response, UserDevfileDto.class); + assertEquals(res.size(), 1); + assertEquals(res.get(0).withLinks(emptyList()), devfileDto); + verify(userDevfileManager).getUserDevfiles(eq(30), eq(0), anyList(), anyList()); + } + + @Test + public void shouldBeAbleToSetLimitAndOffsetOnUserDevfileSearch() throws Exception { + // given + doReturn(new Page<>(Collections.emptyList(), 0, 1, 0)) + .when(userDevfileManager) + .getUserDevfiles(anyInt(), anyInt(), anyList(), anyList()); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .queryParam("maxItems", 5) + .queryParam("skipCount", 52) + .when() + .expect() + .statusCode(200) + .get(SECURE_PATH + "/devfile/list"); + // then + verify(userDevfileManager).getUserDevfiles(eq(5), eq(52), anyList(), anyList()); + } + + @Test + public void shouldBeAbleToSetFiltertOnUserDevfileSearch() throws Exception { + // given + doReturn(new Page<>(Collections.emptyList(), 0, 1, 0)) + .when(userDevfileManager) + .getUserDevfiles(anyInt(), anyInt(), anyList(), anyList()); + Map parameters = + ImmutableMap.of("id", "sdfsdf5", "devfile.meta.name", "like:%dfdf"); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .queryParameter("id", "sdfsdf5") + .queryParameter("devfile.meta.name", "like:%dfdf") + .when() + .expect() + .statusCode(200) + .get(SECURE_PATH + "/devfile/list"); + // then + Class>> listClass = + (Class>>) (Class) ArrayList.class; + ArgumentCaptor>> filterCaptor = ArgumentCaptor.forClass(listClass); + verify(userDevfileManager).getUserDevfiles(eq(30), eq(0), filterCaptor.capture(), anyList()); + assertEquals( + filterCaptor.getValue(), + ImmutableList.of(new Pair("devfile.meta.name", "like:%dfdf"), new Pair("id", "sdfsdf5"))); + } + + @Test + public void shouldBeAbleToSetOrderOnUserDevfileSearch() throws Exception { + // given + doReturn(new Page<>(Collections.emptyList(), 0, 1, 0)) + .when(userDevfileManager) + .getUserDevfiles(anyInt(), anyInt(), anyList(), anyList()); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .queryParameter("order", "id:asc,name:desc") + .when() + .expect() + .statusCode(200) + .get(SECURE_PATH + "/devfile/list"); + // then + Class>> listClass = + (Class>>) (Class) ArrayList.class; + ArgumentCaptor>> orderCaptor = ArgumentCaptor.forClass(listClass); + verify(userDevfileManager).getUserDevfiles(eq(30), eq(0), anyList(), orderCaptor.capture()); + assertEquals( + orderCaptor.getValue(), ImmutableList.of(new Pair("id", "asc"), new Pair("name", "desc"))); + } + + @DataProvider + public Object[][] validUserDevfiles() { + return new Object[][] { + { + newDto(UserDevfileDto.class) + .withName("My devfile") + .withDescription("Devfile description") + .withDevfile( + newDto(DevfileDto.class) + .withApiVersion("1.0.0") + .withMetadata(newDto(MetadataDto.class).withName("name"))) + }, + { + newDto(UserDevfileDto.class) + .withName(null) + .withDescription("Devfile description") + .withDevfile( + newDto(DevfileDto.class) + .withApiVersion("1.0.0") + .withMetadata(newDto(MetadataDto.class).withName("name"))) + }, + { + newDto(UserDevfileDto.class) + .withName("My devfile") + .withDevfile( + newDto(DevfileDto.class) + .withApiVersion("1.0.0") + .withMetadata(newDto(MetadataDto.class).withName("name"))) + }, + { + newDto(UserDevfileDto.class) + .withName("My devfile") + .withDescription("Devfile description") + .withDevfile( + newDto(DevfileDto.class) + .withApiVersion("1.0.0") + .withMetadata(newDto(MetadataDto.class).withGenerateName("gen-"))) + }, + {DtoConverter.asDto(TestObjectGenerator.createUserDevfile())} + }; + } + + @DataProvider + public Object[][] invalidUserDevfiles() { + return new Object[][] { + { + newDto(UserDevfileDto.class) + .withName("My devfile") + .withDescription("Devfile description") + .withDevfile(null), + "Mandatory field `devfile` is not defined." + }, + { + newDto(UserDevfileDto.class) + .withName("My devfile") + .withDescription("Devfile description") + .withDevfile( + newDto(DevfileDto.class) + .withApiVersion(null) + .withMetadata(newDto(MetadataDto.class).withName("name"))), + "Devfile schema validation failed. Error: The object must have a property whose name is \"apiVersion\"." + } + }; + } + + private static T unwrapDto(Response response, Class dtoClass) { + return DtoFactory.getInstance().createDtoFromJson(response.asString(), dtoClass); + } + + private static List unwrapDtoList(Response response, Class dtoClass) + throws IOException { + return new ArrayList<>( + DtoFactory.getInstance().createListDtoFromJson(response.body().asInputStream(), dtoClass)); + } + + @Filter + public static class EnvironmentFilter implements RequestFilter { + + public void doFilter(GenericContainerRequest request) { + EnvironmentContext.getCurrent().setSubject(TEST_SUBJECT); + } + } +} diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/TestObjectGenerator.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/TestObjectGenerator.java new file mode 100644 index 00000000000..09711260250 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/TestObjectGenerator.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.commons.subject.SubjectImpl; + +public class TestObjectGenerator { + + public static final String TEST_CHE_NAMESPACE = "user"; + public static final String CURRENT_USER_ID = NameGenerator.generate("usrid", 6); + public static final Subject TEST_SUBJECT = + new SubjectImpl(TEST_CHE_NAMESPACE, CURRENT_USER_ID, "token", false); + public static final String USER_DEVFILE_ID = NameGenerator.generate("usrd", 16); + public static final AccountImpl TEST_ACCOUNT = + new AccountImpl("acc-id042u3ui3oi", TEST_CHE_NAMESPACE, "test"); + + public static UserDevfileDto createUserDevfileDto() { + return DtoConverter.asDto(createUserDevfile(NameGenerator.generate("name", 6))); + } + + public static UserDevfileImpl createUserDevfile() { + return createUserDevfile(NameGenerator.generate("name", 6)); + } + + public static UserDevfileImpl createUserDevfile(String name) { + return createUserDevfile(NameGenerator.generate("id", 6), name); + } + + public static UserDevfileImpl createUserDevfile(String id, String name) { + return new UserDevfileImpl(id, TEST_ACCOUNT, name, "devfile description", createDevfile(name)); + } + + public static UserDevfileImpl createUserDevfile(String id, Account account, String name) { + return new UserDevfileImpl(id, account, name, "devfile description", createDevfile(name)); + } + + public static UserDevfileImpl createUserDevfile(Account account) { + return createUserDevfile( + NameGenerator.generate("id", 6), account, NameGenerator.generate("name", 6)); + } + + public static DevfileImpl createDevfile(String generatedName) { + return createDevfile(null, generatedName); + } + + public static DevfileImpl createDevfileWithName(String name) { + return createDevfile(name, null); + } + + private static DevfileImpl createDevfile(String name, String generatedName) { + String effectiveName = MoreObjects.firstNonNull(name, generatedName); + SourceImpl source1 = + new SourceImpl( + "type1", + "http://location", + "branch1", + "point1", + "tag1", + "commit1", + "sparseCheckoutDir1"); + ProjectImpl project1 = new ProjectImpl("project1", source1, "path1"); + + SourceImpl source2 = + new SourceImpl( + "type2", + "http://location", + "branch2", + "point2", + "tag2", + "commit2", + "sparseCheckoutDir2"); + ProjectImpl project2 = new ProjectImpl("project2", source2, "path2"); + + ActionImpl action1 = + new ActionImpl("exec1", "component1", "run.sh", "/home/user/1", null, null); + ActionImpl action2 = + new ActionImpl("exec2", "component2", "run.sh", "/home/user/2", null, null); + + CommandImpl command1 = + new CommandImpl( + effectiveName + "-1", singletonList(action1), singletonMap("attr1", "value1"), null); + CommandImpl command2 = + new CommandImpl( + effectiveName + "-2", singletonList(action2), singletonMap("attr2", "value2"), null); + + EntrypointImpl entrypoint1 = + new EntrypointImpl( + "parentName1", + singletonMap("parent1", "selector1"), + "containerName1", + asList("command1", "command2"), + asList("arg1", "arg2")); + + EntrypointImpl entrypoint2 = + new EntrypointImpl( + "parentName2", + singletonMap("parent2", "selector2"), + "containerName2", + asList("command3", "command4"), + asList("arg3", "arg4")); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume1 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name1", "path1"); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume2 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name2", "path2"); + + EnvImpl env1 = new EnvImpl("name1", "value1"); + EnvImpl env2 = new EnvImpl("name2", "value2"); + + EndpointImpl endpoint1 = new EndpointImpl("name1", 1111, singletonMap("key1", "value1")); + EndpointImpl endpoint2 = new EndpointImpl("name2", 2222, singletonMap("key2", "value2")); + + ComponentImpl component1 = + new ComponentImpl( + "kubernetes", + "component1", + null, + null, + null, + null, + "refcontent1", + ImmutableMap.of("app.kubernetes.io/component", "db"), + null, + null, + null, + null, + null, + null, + false, + false, + null, + null, + null, + asList(env1, env2), + null); + component1.setSelector(singletonMap("key1", "value1")); + + ComponentImpl component2 = + new ComponentImpl( + "dockerimage", + "component2", + null, + null, + null, + null, + null, + null, + null, + "image", + "256G", + null, + "3", + "180m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + ComponentImpl component3 = + new ComponentImpl( + "chePlugin", + "check/terminal-sample/0.0.1", + ImmutableMap.of( + "java.home", + "/home/user/jdk11aertwertert", + "java.boolean", + "true", + "java.long", + "123444L")); + MetadataImpl metadata = new MetadataImpl(name); + metadata.setGenerateName(generatedName); + DevfileImpl devfile = + new DevfileImpl( + "1.0.0", + asList(project1, project2), + asList(component1, component2, component3), + asList(command1, command2), + singletonMap("attribute1", "value1"), + metadata); + + return devfile; + } +} diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java new file mode 100644 index 00000000000..5a95f5c3225 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.Arrays.asList; +import static org.eclipse.che.api.devfile.server.TestObjectGenerator.TEST_ACCOUNT; +import static org.eclipse.che.api.devfile.server.TestObjectGenerator.TEST_CHE_NAMESPACE; +import static org.eclipse.che.api.devfile.server.TestObjectGenerator.createUserDevfile; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; + +import java.util.Collections; +import java.util.Optional; +import org.eclipse.che.account.api.AccountManager; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.api.devfile.shared.event.DevfileCreatedEvent; +import org.eclipse.che.api.devfile.shared.event.DevfileDeletedEvent; +import org.eclipse.che.api.devfile.shared.event.DevfileUpdatedEvent; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(value = MockitoTestNGListener.class) +public class UserDevfileManagerTest { + @Mock UserDevfileDao userDevfileDao; + @Mock EventService eventService; + @Mock AccountManager accountManager; + @InjectMocks UserDevfileManager userDevfileManager; + + @Captor private ArgumentCaptor userDevfileArgumentCaptor; + @Captor private ArgumentCaptor devfileCreatedEventCaptor; + @Captor private ArgumentCaptor devfileDeletedEventCaptor; + @Captor private ArgumentCaptor devfileUpdatedEventCaptor; + + @BeforeMethod + public void setUp() throws Exception { + EnvironmentContext.getCurrent().setSubject(TestObjectGenerator.TEST_SUBJECT); + lenient().doReturn(TEST_ACCOUNT).when(accountManager).getByName(eq(TEST_CHE_NAMESPACE)); + } + + @Test + public void shouldGenerateUserDevfileIdOnCreation() throws Exception { + // given + final UserDevfileImpl userDevfile = + new UserDevfileImpl(null, TEST_ACCOUNT, createUserDevfile()); + when(userDevfileDao.create(any(UserDevfileImpl.class))) + .thenAnswer(invocationOnMock -> invocationOnMock.getArguments()[0]); + // when + UserDevfile actual = userDevfileManager.createDevfile(userDevfile); + // then + verify(userDevfileDao).create(userDevfileArgumentCaptor.capture()); + assertFalse(isNullOrEmpty(userDevfileArgumentCaptor.getValue().getId())); + assertEquals(new UserDevfileImpl(null, TEST_ACCOUNT, actual), userDevfile); + } + + @Test + public void shouldSendDevfileCreatedEventOnCreation() throws Exception { + // given + final UserDevfileImpl userDevfile = + new UserDevfileImpl(null, TEST_ACCOUNT, createUserDevfile()); + when(userDevfileDao.create(any(UserDevfileImpl.class))) + .thenAnswer(invocationOnMock -> invocationOnMock.getArguments()[0]); + // when + UserDevfile expected = userDevfileManager.createDevfile(userDevfile); + // then + verify(eventService).publish(devfileCreatedEventCaptor.capture()); + assertEquals(expected, devfileCreatedEventCaptor.getValue().getUserDevfile()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingUserDevfileByNullId() throws Exception { + userDevfileManager.getById(null); + } + + @Test + public void shouldGetUserDevfileById() throws Exception { + // given + final Optional toFetch = Optional.of(createUserDevfile()); + when(userDevfileDao.getById(eq("id123"))).thenReturn(toFetch); + + // when + final UserDevfile fetched = userDevfileManager.getById("id123"); + // then + assertEquals(fetched, toFetch.get()); + verify(userDevfileDao).getById("id123"); + } + + @Test( + expectedExceptions = NotFoundException.class, + expectedExceptionsMessageRegExp = "Devfile with id 'id123' doesn't exist") + public void shouldThrowNotFoundExceptionOnGetUserDevfileByIdIfNotFound() throws Exception { + // given + doReturn(Optional.empty()).when(userDevfileDao).getById(eq("id123")); + // when + userDevfileManager.getById("id123"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenUpdatingUserDevfileByNullId() throws Exception { + userDevfileManager.updateUserDevfile(null); + } + + @Test + public void shouldUpdateUserDevfile() throws Exception { + // given + final UserDevfileImpl userDevfile = createUserDevfile(); + when(userDevfileDao.update(any(UserDevfileImpl.class))) + .thenAnswer(invocationOnMock -> Optional.of(invocationOnMock.getArguments()[0])); + // when + userDevfileManager.updateUserDevfile(userDevfile); + // then + verify(userDevfileDao).update(eq(userDevfile)); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundIfUserDevfileIsNotFoundOnUpdate() throws Exception { + // given + final UserDevfileImpl userDevfile = createUserDevfile(); + Mockito.doReturn(Optional.empty()).when(userDevfileDao).update(any(UserDevfileImpl.class)); + // when + userDevfileManager.updateUserDevfile(userDevfile); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhendeleteUserDevfileByNullId() throws Exception { + userDevfileManager.removeUserDevfile(null); + } + + @Test + public void shouldRemoveUserDevfile() throws Exception { + // given + final UserDevfileImpl userDevfile = createUserDevfile(); + // when + userDevfileManager.removeUserDevfile(userDevfile.getId()); + // then + verify(userDevfileDao).remove(userDevfile.getId()); + } + + @Test + public void shouldSendDevfileUpdatedEventOnUpdateDevfile() throws Exception { + // given + final UserDevfileImpl userDevfile = createUserDevfile(); + when(userDevfileDao.update(any(UserDevfileImpl.class))) + .thenAnswer(invocationOnMock -> Optional.of(invocationOnMock.getArguments()[0])); + // when + userDevfileManager.updateUserDevfile(userDevfile); + // then + verify(eventService).publish(devfileUpdatedEventCaptor.capture()); + assertEquals(userDevfile, devfileUpdatedEventCaptor.getValue().getUserDevfile()); + } + + @Test + public void shouldBeAbleToGetUserDevfilesAvailableToUser() throws ServerException { + // given + final UserDevfileImpl userDevfile = createUserDevfile(); + final UserDevfileImpl userDevfile2 = createUserDevfile(); + when(userDevfileDao.getDevfiles(2, 30, Collections.emptyList(), Collections.emptyList())) + .thenReturn(new Page<>(asList(userDevfile, userDevfile2), 0, 2, 2)); + // when + Page actual = + userDevfileManager.getUserDevfiles(2, 30, Collections.emptyList(), Collections.emptyList()); + // then + assertEquals(actual.getItems().size(), 2); + } +} diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileTckModule.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileTckModule.java new file mode 100644 index 00000000000..02ea85656ad --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileTckModule.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server.jpa; + +import com.google.inject.TypeLiteral; +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.account.spi.jpa.JpaAccountDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.workspace.server.devfile.SerializableConverter; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl; +import org.eclipse.che.commons.test.db.H2DBTestServer; +import org.eclipse.che.commons.test.db.H2JpaCleaner; +import org.eclipse.che.commons.test.db.PersistTestModuleBuilder; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.core.db.DBInitializer; +import org.eclipse.che.core.db.h2.jpa.eclipselink.H2ExceptionHandler; +import org.eclipse.che.core.db.schema.SchemaInitializer; +import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer; +import org.h2.Driver; + +/** Tck module for UserDevfile test. */ +public class UserDevfileTckModule extends TckModule { + + @Override + protected void configure() { + H2DBTestServer server = H2DBTestServer.startDefault(); + install( + new PersistTestModuleBuilder() + .setDriver(Driver.class) + .runningOn(server) + .addEntityClasses( + UserImpl.class, + AccountImpl.class, + UserDevfileImpl.class, + DevfileImpl.class, + ActionImpl.class, + CommandImpl.class, + ComponentImpl.class, + DevfileImpl.class, + EndpointImpl.class, + EntrypointImpl.class, + EnvImpl.class, + ProjectImpl.class, + SourceImpl.class, + VolumeImpl.class) + .addClass(SerializableConverter.class) + .setExceptionHandler(H2ExceptionHandler.class) + .setProperty("eclipselink.logging.level", "OFF") + .build()); + bind(DBInitializer.class).asEagerSingleton(); + bind(SchemaInitializer.class) + .toInstance(new FlywaySchemaInitializer(server.getDataSource(), "che-schema")); + bind(TckResourcesCleaner.class).toInstance(new H2JpaCleaner(server)); + + bind(UserDevfileDao.class).to(JpaUserDevfileDao.class); + bind(AccountDao.class).to(JpaAccountDao.class); + + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(AccountImpl.class)); + } +} diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java new file mode 100644 index 00000000000..69f4b2d6833 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java @@ -0,0 +1,517 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server.spi.tck; + +import static java.lang.Math.min; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.eclipse.che.api.devfile.server.TestObjectGenerator.createUserDevfile; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Optional; +import java.util.stream.Stream; +import javax.inject.Inject; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.TestObjectGenerator; +import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.commons.test.tck.TckListener; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(TckListener.class) +@Test(suiteName = UserDevfileDaoTest.SUITE_NAME) +public class UserDevfileDaoTest { + + public static final String SUITE_NAME = "DevfileDaoTck"; + private static final int ENTRY_COUNT = 10; + private static final int COUNT_OF_ACCOUNTS = 6; + + private UserDevfileImpl[] devfiles; + private AccountImpl[] accounts; + + @Inject private EventService eventService; + + @Inject private UserDevfileDao userDevfileDaoDao; + + @Inject private TckRepository devfileTckRepository; + + @Inject private TckRepository userTckRepository; + + @Inject private TckRepository accountRepo; + + @BeforeMethod + public void setUp() throws Exception { + accounts = new AccountImpl[COUNT_OF_ACCOUNTS]; + for (int i = 0; i < COUNT_OF_ACCOUNTS; i++) { + accounts[i] = new AccountImpl("accountId" + i, "accountName" + i, "test"); + } + + devfiles = new UserDevfileImpl[ENTRY_COUNT]; + for (int i = 0; i < ENTRY_COUNT; i++) { + AccountImpl account = accounts[i / 2]; + devfiles[i] = + createUserDevfile( + NameGenerator.generate("id-" + i + "-", 6), + account, + NameGenerator.generate("devfileName-" + i, 6)); + } + accountRepo.createAll(Stream.of(accounts).map(AccountImpl::new).collect(toList())); + devfileTckRepository.createAll(Stream.of(devfiles).map(UserDevfileImpl::new).collect(toList())); + } + + @AfterMethod + public void cleanUp() throws Exception { + devfileTckRepository.removeAll(); + accountRepo.removeAll(); + } + + @Test + public void shouldGetUserDevfileById() throws Exception { + final UserDevfileImpl devfile = devfiles[0]; + + assertEquals(userDevfileDaoDao.getById(devfile.getId()), Optional.of(devfile)); + } + + @Test(dependsOnMethods = "shouldGetUserDevfileById") + public void shouldCreateUserDevfile() throws Exception { + // given + final UserDevfileImpl devfile = createUserDevfile(accounts[0]); + // when + userDevfileDaoDao.create(devfile); + + assertEquals( + userDevfileDaoDao.getById(devfile.getId()), Optional.of(new UserDevfileImpl(devfile))); + } + + @Test + public void shouldCreateUserDevfileWithNullDescription() throws Exception { + // given + final UserDevfileImpl devfile = createUserDevfile(accounts[0]); + devfile.setDescription(null); + // when + userDevfileDaoDao.create(devfile); + + Optional devfileOptional = userDevfileDaoDao.getById(devfile.getId()); + assertTrue(devfileOptional.isPresent()); + assertNull(devfileOptional.get().getDescription()); + assertEquals(devfileOptional, Optional.of(new UserDevfileImpl(devfile))); + } + + @Test + public void shouldCreateUserDevfileWithEmptyMataName() throws Exception { + // given + final UserDevfileImpl devfile = createUserDevfile(accounts[0]); + DevfileImpl newDevfile = new DevfileImpl(devfile.getDevfile()); + MetadataImpl newMeta = new MetadataImpl(); + newMeta.setGenerateName("gener-"); + newDevfile.setMetadata(newMeta); + devfile.setDevfile(newDevfile); + // when + userDevfileDaoDao.create(devfile); + + Optional devfileOptional = userDevfileDaoDao.getById(devfile.getId()); + assertTrue(devfileOptional.isPresent()); + UserDevfile actual = devfileOptional.get(); + assertNull(actual.getDevfile().getMetadata().getName()); + assertNotNull(actual.getDevfile().getMetadata().getGenerateName()); + assertEquals(devfileOptional, Optional.of(new UserDevfileImpl(devfile))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenCreateNullDevfile() throws Exception { + userDevfileDaoDao.create(null); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldThrowConflictExceptionWhenCreatingUserDevfileWithExistingId() throws Exception { + // given + final UserDevfileImpl devfile = createUserDevfile(accounts[0]); + final UserDevfileImpl existing = devfiles[0]; + devfile.setId(existing.getId()); + // when + userDevfileDaoDao.create(devfile); + // then + } + + @Test + public void shouldUpdateUserDevfile() throws Exception { + // given + + DevfileImpl newDevfile = TestObjectGenerator.createDevfile("newUpdate"); + newDevfile.setApiVersion("V15.0"); + newDevfile.setProjects( + ImmutableList.of( + new ProjectImpl( + "projectUp2", + new SourceImpl( + "typeUp2", + "http://location", + "branch2", + "point2", + "tag2", + "commit2", + "sparseCheckoutDir2"), + "path2"))); + newDevfile.setComponents(ImmutableList.of(new ComponentImpl("type3", "id54"))); + newDevfile.setCommands( + ImmutableList.of( + new CommandImpl( + new CommandImpl( + "cmd1", + Collections.singletonList( + new ActionImpl( + "exe44", "compo2nent2", "run.sh", "/home/user/2", null, null)), + Collections.singletonMap("attr1", "value1"), + null)))); + newDevfile.setAttributes(ImmutableMap.of("key2", "val34")); + newDevfile.setMetadata(new MetadataImpl("myNewName")); + final UserDevfileImpl update = devfiles[0]; + update.setDevfile(newDevfile); + // when + userDevfileDaoDao.update(update); + // then + assertEquals(userDevfileDaoDao.getById(update.getId()), Optional.of(update)); + } + + @Test + public void shouldNotUpdateWorkspaceWhichDoesNotExist() throws Exception { + // given + final UserDevfileImpl userDevfile = devfiles[0]; + userDevfile.setId("non-existing-devfile"); + // when + Optional result = userDevfileDaoDao.update(userDevfile); + // then + assertFalse(result.isPresent()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenUpdatingNull() throws Exception { + userDevfileDaoDao.update(null); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGetByIdNull() throws Exception { + userDevfileDaoDao.getById(null); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenDeleteNull() throws Exception { + userDevfileDaoDao.getById(null); + } + + @Test(dependsOnMethods = "shouldGetUserDevfileById") + public void shouldRemoveDevfile() throws Exception { + final String userDevfileId = devfiles[0].getId(); + userDevfileDaoDao.remove(userDevfileId); + Optional result = userDevfileDaoDao.getById(userDevfileId); + + assertFalse(result.isPresent()); + } + + @Test + public void shouldDoNothingWhenRemovingNonExistingUserDevfile() throws Exception { + userDevfileDaoDao.remove("non-existing"); + } + + @Test + public void shouldBeAbleToGetAvailableToUserDevfiles() throws ServerException { + // given + // when + final Page result = + userDevfileDaoDao.getDevfiles(30, 0, Collections.emptyList(), Collections.emptyList()); + // then + assertEquals(new HashSet<>(result.getItems()), new HashSet<>(asList(devfiles))); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void shouldThrowIllegalStateExceptionOnNegativeLimit() throws Exception { + userDevfileDaoDao.getDevfiles(0, -2, Collections.emptyList(), Collections.emptyList()); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void shouldThrowIllegalStateExceptionOnNegativeSkipCount() throws Exception { + userDevfileDaoDao.getDevfiles(-2, 0, Collections.emptyList(), Collections.emptyList()); + } + + @Test + public void shouldBeAbleToGetAvailableToUserDevfilesWithFilter() throws ServerException { + // given + // when + final Page result = + userDevfileDaoDao.getDevfiles( + 30, + 0, + ImmutableList.of(new Pair<>("name", "like:devfileName%")), + Collections.emptyList()); + // then + assertEquals(new HashSet<>(result.getItems()), new HashSet<>(asList(devfiles))); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void shouldNotAllowSearchWithInvalidFilter() throws ServerException { + // given + // when + final Page result = + userDevfileDaoDao.getDevfiles( + 30, + 0, + ImmutableList.of( + new Pair<>("id", "like:dev%"), + new Pair<>("devfile.metadata.something", "like:devfileName%")), + Collections.emptyList()); + // then + } + + @Test + public void shouldBeAbleToGetAvailableToUserDevfilesWithFilter2() + throws ServerException, NotFoundException, ConflictException { + // given + final UserDevfileImpl update = devfiles[0]; + update.setName("New345Name"); + userDevfileDaoDao.update(update); + // when + final Page result = + userDevfileDaoDao.getDevfiles( + 30, 0, ImmutableList.of(new Pair<>("name", "like:%w345N%")), Collections.emptyList()); + // then + assertEquals(new HashSet<>(result.getItems()), ImmutableSet.of(update)); + } + + @Test + public void shouldBeAbleToGetAvailableToUserDevfilesWithFilterAndLimit() + throws ServerException, NotFoundException, ConflictException { + // given + final UserDevfileImpl update = devfiles[0]; + update.setName("New345Name"); + userDevfileDaoDao.update(update); + // when + final Page result = + userDevfileDaoDao.getDevfiles( + 12, + 0, + ImmutableList.of(new Pair<>("name", "like:devfileName%")), + Collections.emptyList()); + // then + assertEquals(result.getItems().size(), 9); + } + + @Test + public void shouldBeAbleToGetDevfilesSortedById() + throws ServerException, NotFoundException, ConflictException { + // given + UserDevfileImpl[] expected = + Arrays.stream(devfiles) + .sorted(Comparator.comparing(UserDevfileImpl::getId)) + .toArray(UserDevfileImpl[]::new); + // when + final Page result = + userDevfileDaoDao.getDevfiles( + devfiles.length, 0, Collections.emptyList(), ImmutableList.of(new Pair<>("id", "asc"))); + // then + assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected); + } + + @Test + public void shouldBeAbleToGetDevfilesSortedByIdReverse() + throws ServerException, NotFoundException, ConflictException { + // given + UserDevfileImpl[] expected = + Arrays.stream(devfiles) + .sorted(Comparator.comparing(UserDevfileImpl::getId).reversed()) + .toArray(UserDevfileImpl[]::new); + // when + final Page result = + userDevfileDaoDao.getDevfiles( + devfiles.length, + 0, + Collections.emptyList(), + ImmutableList.of(new Pair<>("id", "desc"))); + // then + assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected); + } + + @Test + public void shouldBeAbleToGetDevfilesSortedByName() + throws ServerException, NotFoundException, ConflictException { + // given + UserDevfileImpl[] expected = + Arrays.stream(devfiles) + .sorted(Comparator.comparing(UserDevfileImpl::getName)) + .toArray(UserDevfileImpl[]::new); + // when + final Page result = + userDevfileDaoDao.getDevfiles( + devfiles.length, + 0, + Collections.emptyList(), + ImmutableList.of(new Pair<>("name", "asc"))); + // then + assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected); + } + + @Test + public void shouldSendDevfileDeletedEventOnRemoveUserDevfile() throws Exception { + // given + final String userDevfileId = devfiles[0].getId(); + final boolean[] isNotified = new boolean[] {false}; + eventService.subscribe(event -> isNotified[0] = true, BeforeDevfileRemovedEvent.class); + // when + userDevfileDaoDao.remove(userDevfileId); + // then + assertTrue(isNotified[0], "Event subscriber notified"); + } + + @Test(dataProvider = "boundsdataprovider") + public void shouldBeAbleToGetDevfilesSortedByNameWithSpecificMaxItemsAndSkipCount( + int maxitems, int skipCount) throws ServerException, NotFoundException, ConflictException { + // given + UserDevfileImpl[] expected = + Arrays.stream( + Arrays.copyOfRange(devfiles, skipCount, min(devfiles.length, skipCount + maxitems))) + .sorted(Comparator.comparing(UserDevfileImpl::getId)) + .toArray(UserDevfileImpl[]::new); + // when + final Page result = + userDevfileDaoDao.getDevfiles( + maxitems, + skipCount, + Collections.emptyList(), + ImmutableList.of(new Pair<>("id", "asc"))); + // then + assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected); + } + + @DataProvider + public Object[][] boundsdataprovider() { + return new Object[][] { + {1, 1}, + {1, 4}, + {4, 5}, + {6, 8}, + {1, ENTRY_COUNT}, + {ENTRY_COUNT, ENTRY_COUNT}, + {ENTRY_COUNT, 1}, + {ENTRY_COUNT, 8} + }; + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "The number of items has to be positive.") + public void shouldNotAllowZeroMaxItemsToSearch() + throws ServerException, NotFoundException, ConflictException { + // given + // when + userDevfileDaoDao.getDevfiles(0, 0, Collections.emptyList(), Collections.emptyList()); + // then + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "The number of items has to be positive.") + public void shouldNotAllowNegativeMaxItemsToSearch() + throws ServerException, NotFoundException, ConflictException { + // given + // when + userDevfileDaoDao.getDevfiles(-5, 0, Collections.emptyList(), Collections.emptyList()); + // then + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = + "The number of items to skip can't be negative or greater than 2147483647") + public void shouldNotAllowNegativeItemsToSkipToSearch() + throws ServerException, NotFoundException, ConflictException { + // given + // when + userDevfileDaoDao.getDevfiles(5, -1, Collections.emptyList(), Collections.emptyList()); + // then + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = + "Invalid sort order direction\\. Possible values 'asc' or 'desc'\\." + + " But got: \\[\\{first=name, second=ddd}, \\{first=id, second=bla}]") + public void shouldFailOnInvalidSortOrder() + throws ServerException, NotFoundException, ConflictException { + // given + // when + userDevfileDaoDao.getDevfiles( + 5, + 4, + Collections.emptyList(), + ImmutableList.of( + new Pair<>("id", "asc"), + new Pair<>("name", "ddd"), + new Pair<>("name", "DesC"), + new Pair<>("id", "bla"))); + // then + } + + @Test + public void shouldGetDevfilesByNamespace() throws Exception { + final UserDevfileImpl devfile1 = devfiles[0]; + final UserDevfileImpl devfile2 = devfiles[1]; + assertEquals(devfile1.getNamespace(), devfile2.getNamespace(), "Namespaces must be the same"); + + final Page found = userDevfileDaoDao.getByNamespace(devfile1.getNamespace(), 6, 0); + + assertEquals(found.getTotalItemsCount(), 2); + assertEquals(found.getItemsCount(), 2); + assertEquals(new HashSet<>(found.getItems()), new HashSet<>(asList(devfile1, devfile2))); + } + + @Test + public void emptyListShouldBeReturnedWhenThereAreNoDevfilesInGivenNamespace() throws Exception { + assertTrue(userDevfileDaoDao.getByNamespace("non-existing-namespace", 30, 0).isEmpty()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingDevfilesByNullNamespace() throws Exception { + userDevfileDaoDao.getByNamespace(null, 30, 0); + } +} diff --git a/wsmaster/che-core-api-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/wsmaster/che-core-api-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 00000000000..7e85050a9b8 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.api.devfile.server.jpa.UserDevfileTckModule diff --git a/wsmaster/che-core-api-devfile/src/test/resources/logback-test.xml b/wsmaster/che-core-api-devfile/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..2250aaa5aa7 --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/test/resources/logback-test.xml @@ -0,0 +1,26 @@ + + + + + + + %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n + + + + + + + diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java index 11d6121adb5..5f18aa257b7 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java @@ -28,7 +28,7 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.workspace.server.DtoConverter; -import org.eclipse.che.api.workspace.server.devfile.DevfileManager; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; @@ -53,18 +53,18 @@ public class URLFactoryBuilder { private final String defaultChePlugins; private final URLFetcher urlFetcher; - private final DevfileManager devfileManager; + private final DevfileParser devfileParser; @Inject public URLFactoryBuilder( @Named("che.factory.default_editor") String defaultCheEditor, @Named("che.factory.default_plugins") String defaultChePlugins, URLFetcher urlFetcher, - DevfileManager devfileManager) { + DevfileParser devfileParser) { this.defaultCheEditor = defaultCheEditor; this.defaultChePlugins = defaultChePlugins; this.urlFetcher = urlFetcher; - this.devfileManager = devfileManager; + this.devfileParser = devfileParser; } /** @@ -115,8 +115,8 @@ public Optional createFactoryFromDevfile( continue; } try { - DevfileImpl devfile = devfileManager.parseYaml(devfileYamlContent, overrideProperties); - devfileManager.resolveReference(devfile, fileContentProvider); + DevfileImpl devfile = devfileParser.parseYaml(devfileYamlContent, overrideProperties); + devfileParser.resolveReference(devfile, fileContentProvider); devfile = ensureToUseGenerateName(devfile); FactoryDto factoryDto = diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java index d2329849830..cb9b1e70a4f 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java @@ -31,7 +31,7 @@ import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; -import org.eclipse.che.api.workspace.server.devfile.DevfileManager; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider; import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; @@ -76,10 +76,10 @@ public void shouldResolveRelativeFiles() throws Exception { DevfileIntegrityValidator integrityValidator = new DevfileIntegrityValidator(validators); - DevfileManager devfileManager = new DevfileManager(validator, integrityValidator); + DevfileParser devfileParser = new DevfileParser(validator, integrityValidator); URLFactoryBuilder factoryBuilder = - new URLFactoryBuilder("editor", "plugin", urlFetcher, devfileManager); + new URLFactoryBuilder("editor", "plugin", urlFetcher, devfileParser); DefaultFactoryParameterResolver res = new DefaultFactoryParameterResolver(factoryBuilder, urlFetcher); diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java index 5061cf95ab7..654723a7bc9 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java @@ -35,7 +35,7 @@ import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.api.workspace.server.devfile.DevfileManager; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; @@ -68,7 +68,7 @@ public class URLFactoryBuilderTest { /** Grab content of URLs */ @Mock private URLFetcher urlFetcher; - @Mock private DevfileManager devfileManager; + @Mock private DevfileParser devfileParser; /** Tested instance. */ private URLFactoryBuilder urlFactoryBuilder; @@ -76,7 +76,7 @@ public class URLFactoryBuilderTest { @BeforeClass public void setUp() { this.urlFactoryBuilder = - new URLFactoryBuilder(defaultEditor, defaultPlugin, urlFetcher, devfileManager); + new URLFactoryBuilder(defaultEditor, defaultPlugin, urlFetcher, devfileParser); } @Test @@ -132,7 +132,7 @@ public void checkWithCustomDevfileAndRecipe() throws Exception { workspaceConfigImpl.setDefaultEnv("name"); when(urlFetcher.fetchSafely(anyString())).thenReturn("random_content"); - when(devfileManager.parseYaml(anyString(), anyMap())).thenReturn(devfile); + when(devfileParser.parseYaml(anyString(), anyMap())).thenReturn(devfile); FactoryDto factory = urlFactoryBuilder @@ -192,7 +192,7 @@ public String location() { return "http://foo.bar/anything"; } })); - when(devfileManager.parseYaml(anyString(), anyMap())).thenReturn(devfile); + when(devfileParser.parseYaml(anyString(), anyMap())).thenReturn(devfile); when(urlFetcher.fetchSafely(anyString())).thenReturn("anything"); FactoryDto factory = urlFactoryBuilder diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java index 335a2f1cdbf..ec577fc641a 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java @@ -35,7 +35,7 @@ import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; -import org.eclipse.che.api.workspace.server.devfile.DevfileManager; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; @@ -54,12 +54,12 @@ public class WorkspaceEntityProvider implements MessageBodyReader, MessageBodyWriter { - private DevfileManager devfileManager; + private DevfileParser devfileParser; private ObjectMapper mapper = new ObjectMapper(); @Inject - public WorkspaceEntityProvider(DevfileManager devfileManager) { - this.devfileManager = devfileManager; + public WorkspaceEntityProvider(DevfileParser devfileParser) { + this.devfileParser = devfileParser; } @Override @@ -81,7 +81,7 @@ public WorkspaceDto readFrom( JsonNode wsNode = mapper.readTree(entityStream); JsonNode devfileNode = wsNode.path("devfile"); if (!devfileNode.isNull() && !devfileNode.isMissingNode()) { - devfileManager.parseJson(devfileNode.toString()); + devfileParser.parseJson(devfileNode.toString()); } return DtoFactory.getInstance().createDtoFromJson(wsNode.toString(), WorkspaceDto.class); } catch (DevfileFormatException e) { diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java index 282c376b8c1..73ebd7e00be 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java @@ -55,11 +55,11 @@ public class DevfileEntityProvider implements MessageBodyReader, MessageBodyWriter { - private DevfileManager devfileManager; + private DevfileParser devfileParser; @Inject - public DevfileEntityProvider(DevfileManager devfileManager) { - this.devfileManager = devfileManager; + public DevfileEntityProvider(DevfileParser devfileParser) { + this.devfileParser = devfileParser; } @Override @@ -81,13 +81,13 @@ public DevfileDto readFrom( try { if (mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) { return asDto( - devfileManager.parseJson( + devfileParser.parseJson( CharStreams.toString( new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType))))); } else if (mediaType.isCompatible(MediaType.valueOf("text/yaml")) || mediaType.isCompatible(MediaType.valueOf("text/x-yaml"))) { return asDto( - devfileManager.parseYaml( + devfileParser.parseYaml( CharStreams.toString( new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType))))); } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java index ab49aff0bc7..8155c83c339 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java @@ -24,7 +24,6 @@ public class DevfileModule extends AbstractModule { @Override protected void configure() { - bind(DevfileService.class); bind(DevfileEntityProvider.class); DevfileBindings.onWorkspaceApplierBinder( diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileManager.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java similarity index 99% rename from wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileManager.java rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java index 49c893aa0f2..dc731cb8306 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileManager.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java @@ -45,7 +45,7 @@ */ @Beta @Singleton -public class DevfileManager { +public class DevfileParser { private ObjectMapper yamlMapper; private ObjectMapper jsonMapper; @@ -54,7 +54,7 @@ public class DevfileManager { private final OverridePropertiesApplier overridePropertiesApplier; @Inject - public DevfileManager( + public DevfileParser( DevfileSchemaValidator schemaValidator, DevfileIntegrityValidator integrityValidator) { this( schemaValidator, @@ -64,7 +64,7 @@ public DevfileManager( } @VisibleForTesting - DevfileManager( + DevfileParser( DevfileSchemaValidator schemaValidator, DevfileIntegrityValidator integrityValidator, ObjectMapper yamlMapper, diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java deleted file mode 100644 index 35c57824325..00000000000 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.workspace.server.devfile; - -import static javax.ws.rs.core.MediaType.APPLICATION_JSON; -import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; - -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import java.io.IOException; -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Response; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.rest.Service; -import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; - -@Api(value = "/devfile", description = "Devfile REST API") -@Path("/devfile") -public class DevfileService extends Service { - - private DevfileSchemaProvider schemaCachedProvider; - - @Inject - public DevfileService(DevfileSchemaProvider schemaCachedProvider) { - this.schemaCachedProvider = schemaCachedProvider; - } - - /** - * Retrieves the json schema. - * - * @return json schema - */ - @GET - @Produces(APPLICATION_JSON) - @ApiOperation(value = "Retrieves current version of devfile JSON schema") - @ApiResponses({ - @ApiResponse(code = 200, message = "The schema successfully retrieved"), - @ApiResponse(code = 500, message = "Internal server error occurred") - }) - public Response getSchema() throws ServerException { - try { - return Response.ok(schemaCachedProvider.getSchemaContent(CURRENT_API_VERSION)).build(); - } catch (IOException e) { - throw new ServerException(e); - } - } -} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java index 78b035abab3..45f5ef51eaf 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java @@ -35,6 +35,11 @@ public MetadataImpl(String name) { this.name = name; } + public MetadataImpl(String name, String generateName) { + this.name = name; + this.generateName = generateName; + } + public MetadataImpl(Metadata metadata) { this.name = metadata.getName(); this.generateName = metadata.getGenerateName(); diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java index 66dfdee5846..d2006f80607 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java @@ -20,7 +20,7 @@ import java.nio.charset.StandardCharsets; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedHashMap; -import org.eclipse.che.api.workspace.server.devfile.DevfileManager; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; @@ -34,14 +34,14 @@ @Listeners(MockitoTestNGListener.class) public class WorkspaceEntityProviderTest { - @Mock private DevfileManager devfileManager; + @Mock private DevfileParser devfileParser; @InjectMocks private WorkspaceEntityProvider workspaceEntityProvider; @Test public void shouldBuildDtoFromValidJson() throws Exception { - when(devfileManager.parseJson(anyString())).thenReturn(new DevfileImpl()); + when(devfileParser.parseJson(anyString())).thenReturn(new DevfileImpl()); WorkspaceDto actual = newDto(WorkspaceDto.class).withDevfile(newDto(DevfileDto.class)); @@ -54,6 +54,6 @@ public void shouldBuildDtoFromValidJson() throws Exception { new ByteArrayInputStream( DtoFactory.getInstance().toJson(actual).getBytes(StandardCharsets.UTF_8))); - verify(devfileManager).parseJson(anyString()); + verify(devfileParser).parseJson(anyString()); } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java index ef58bf575be..ed1a6efd825 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java @@ -69,7 +69,12 @@ import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.CheJsonProvider; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; +import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; +import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator; +import org.eclipse.che.api.workspace.server.devfile.validator.DevfileSchemaValidator; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; @@ -139,6 +144,13 @@ public class WorkspaceServiceTest { @SuppressWarnings("unused") // is declared for deploying by everrest-assured private CheJsonProvider jsonProvider = new CheJsonProvider(Collections.emptySet()); + @SuppressWarnings("unused") // is declared for deploying by everrest-assured + private DevfileEntityProvider devfileEntityProvider = + new DevfileEntityProvider( + new DevfileParser( + new DevfileSchemaValidator(new DevfileSchemaProvider()), + new DevfileIntegrityValidator(Collections.emptyMap()))); + @Mock private WorkspaceManager wsManager; @Mock private MachineTokenProvider machineTokenProvider; @Mock private WorkspaceLinksGenerator linksGenerator; diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java index d4fde146f34..5a8982dd3a3 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java @@ -29,14 +29,14 @@ @Listeners(MockitoTestNGListener.class) public class DevfileEntityProviderTest { - @Mock private DevfileManager devfileManager; + @Mock private DevfileParser devfileParser; @InjectMocks private DevfileEntityProvider devfileEntityProvider; @Test public void shouldBuildDtoFromValidYaml() throws Exception { - when(devfileManager.parseYaml(anyString())).thenReturn(new DevfileImpl()); + when(devfileParser.parseYaml(anyString())).thenReturn(new DevfileImpl()); devfileEntityProvider.readFrom( DevfileDto.class, @@ -46,13 +46,13 @@ public void shouldBuildDtoFromValidYaml() throws Exception { new MultivaluedHashMap<>(), getClass().getClassLoader().getResourceAsStream("devfile/devfile.yaml")); - verify(devfileManager).parseYaml(anyString()); + verify(devfileParser).parseYaml(anyString()); } @Test public void shouldBuildDtoFromValidJson() throws Exception { - when(devfileManager.parseJson(anyString())).thenReturn(new DevfileImpl()); + when(devfileParser.parseJson(anyString())).thenReturn(new DevfileImpl()); devfileEntityProvider.readFrom( DevfileDto.class, @@ -62,7 +62,7 @@ public void shouldBuildDtoFromValidJson() throws Exception { new MultivaluedHashMap<>(), getClass().getClassLoader().getResourceAsStream("devfile/devfile.json")); - verify(devfileManager).parseJson(anyString()); + verify(devfileParser).parseJson(anyString()); } @Test( diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileManagerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileParserTest.java similarity index 91% rename from wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileManagerTest.java rename to wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileParserTest.java index 72349535f06..4afa2eeed4a 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileManagerTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileParserTest.java @@ -43,7 +43,7 @@ import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) -public class DevfileManagerTest { +public class DevfileParserTest { private static final String DEVFILE_YAML_CONTENT = "devfile yaml stub"; @@ -56,13 +56,12 @@ public class DevfileManagerTest { @Mock private JsonNode devfileJsonNode; private DevfileImpl devfile; - private DevfileManager devfileManager; + private DevfileParser devfileParser; @BeforeMethod public void setUp() throws Exception { devfile = new DevfileImpl(); - devfileManager = - new DevfileManager(schemaValidator, integrityValidator, yamlMapper, jsonMapper); + devfileParser = new DevfileParser(schemaValidator, integrityValidator, yamlMapper, jsonMapper); lenient().when(jsonMapper.treeToValue(any(), eq(DevfileImpl.class))).thenReturn(devfile); lenient().when(yamlMapper.treeToValue(any(), eq(DevfileImpl.class))).thenReturn(devfile); @@ -72,7 +71,7 @@ public void setUp() throws Exception { @Test public void testValidateAndParse() throws Exception { // when - DevfileImpl parsed = devfileManager.parseYaml(DEVFILE_YAML_CONTENT); + DevfileImpl parsed = devfileParser.parseYaml(DEVFILE_YAML_CONTENT); // then assertEquals(parsed, devfile); @@ -93,7 +92,7 @@ public void testInitializingDevfileMapsAfterParsing() throws Exception { devfile.getComponents().add(component); // when - DevfileImpl parsed = devfileManager.parseYaml(DEVFILE_YAML_CONTENT); + DevfileImpl parsed = devfileParser.parseYaml(DEVFILE_YAML_CONTENT); // then assertNotNull(parsed.getCommands().get(0).getAttributes()); @@ -113,7 +112,7 @@ public void shouldResolveReferencesIntoReferenceContentForFactories() throws Exc devfile.getComponents().add(component); // when - devfileManager.resolveReference(devfile, contentProvider); + devfileParser.resolveReference(devfile, contentProvider); // then verify(contentProvider).fetchContent(eq("myfile.yaml")); @@ -125,7 +124,7 @@ public void shouldResolveReferencesIntoReferenceContentForFactories() throws Exc expectedExceptionsMessageRegExp = "Unable to parse Devfile - provided source is empty") public void shouldThrowDevfileExceptionWhenEmptyObjectProvided() throws Exception { // when - devfileManager.parseJson("{}"); + devfileParser.parseJson("{}"); } @Test( @@ -133,7 +132,7 @@ public void shouldThrowDevfileExceptionWhenEmptyObjectProvided() throws Exceptio expectedExceptionsMessageRegExp = "Unable to parse Devfile - provided source is empty") public void shouldThrowDevfileExceptionWhenEmptySourceProvided() throws Exception { // when - devfileManager.parseJson(""); + devfileParser.parseJson(""); } @Test( @@ -150,7 +149,7 @@ public void shouldThrowDevfileExceptionWhenReferenceIsNotResolvable() throws Exc devfile.getComponents().add(component); // when - devfileManager.resolveReference(devfile, contentProvider); + devfileParser.resolveReference(devfile, contentProvider); // then exception is thrown } @@ -163,7 +162,7 @@ public void shouldThrowExceptionWhenExceptionOccurredDuringSchemaValidation() th doThrow(new DevfileFormatException("non valid")).when(schemaValidator).validate(any()); // when - devfileManager.parseYaml(DEVFILE_YAML_CONTENT); + devfileParser.parseYaml(DEVFILE_YAML_CONTENT); } @Test( @@ -177,6 +176,6 @@ public void shouldThrowExceptionWhenErrorOccurredDuringDevfileParsing() throws E doThrow(jsonException).when(jsonMapper).treeToValue(any(), any()); // when - devfileManager.parseJson(DEVFILE_YAML_CONTENT); + devfileParser.parseJson(DEVFILE_YAML_CONTENT); } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java deleted file mode 100644 index e8512ea736c..00000000000 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.workspace.server.devfile; - -import static com.jayway.restassured.RestAssured.given; -import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; -import static org.everrest.assured.JettyHttpServer.*; -import static org.testng.Assert.assertEquals; - -import com.jayway.restassured.response.Response; -import org.eclipse.che.api.core.rest.ApiExceptionMapper; -import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; -import org.eclipse.che.commons.subject.Subject; -import org.eclipse.che.commons.subject.SubjectImpl; -import org.everrest.assured.EverrestJetty; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -@Listeners({EverrestJetty.class, MockitoTestNGListener.class}) -public class DevfileServiceTest { - - @SuppressWarnings("unused") // is declared for deploying by everrest-assured - private ApiExceptionMapper exceptionMapper; - - private DevfileSchemaProvider schemaProvider = new DevfileSchemaProvider(); - - private static final Subject SUBJECT = new SubjectImpl("user", "user123", "token", false); - - @SuppressWarnings("unused") - private DevfileService devFileService; - - @BeforeMethod - public void initService() { - this.devFileService = new DevfileService(schemaProvider); - } - - @Test - public void shouldRetrieveSchema() throws Exception { - final Response response = - given() - .auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .when() - .get(SECURE_PATH + "/devfile"); - - assertEquals(response.getStatusCode(), 200); - assertEquals( - response.getBody().asString(), schemaProvider.getSchemaContent(CURRENT_API_VERSION)); - } -} diff --git a/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.19.0/1__userdevfile.sql b/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.19.0/1__userdevfile.sql new file mode 100644 index 00000000000..d30776e6975 --- /dev/null +++ b/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.19.0/1__userdevfile.sql @@ -0,0 +1,29 @@ +-- +-- Copyright (c) 2012-2020 Red Hat, Inc. +-- This program and the accompanying materials are made +-- available under the terms of the Eclipse Public License 2.0 +-- which is available at https://www.eclipse.org/legal/epl-2.0/ +-- +-- SPDX-License-Identifier: EPL-2.0 +-- +-- Contributors: +-- Red Hat, Inc. - initial API and implementation +-- + + +-- add userdevfile table +CREATE TABLE userdevfile ( + id VARCHAR(255) NOT NULL UNIQUE, + accountid VARCHAR(255) NOT NULL, + devfile_id BIGINT NOT NULL UNIQUE, + meta_generated_name VARCHAR(255) , + meta_name VARCHAR(255) , + name VARCHAR(255) NOT NULL , + description TEXT , + PRIMARY KEY (id) +); +CREATE INDEX index_userdevfile_devfile_id ON userdevfile (devfile_id); +CREATE INDEX index_userdevfile_name ON userdevfile(name); +ALTER TABLE userdevfile ADD CONSTRAINT unq_userdevfile_0 UNIQUE (name, accountid); +ALTER TABLE userdevfile ADD CONSTRAINT fx_userdevfile_accountid FOREIGN KEY (accountid) REFERENCES account (id); +ALTER TABLE userdevfile ADD CONSTRAINT fk_userdevfile_devfile_id FOREIGN KEY (devfile_id) REFERENCES devfile (id); \ No newline at end of file diff --git a/wsmaster/pom.xml b/wsmaster/pom.xml index a4eee22f9d8..98b92f6d41f 100644 --- a/wsmaster/pom.xml +++ b/wsmaster/pom.xml @@ -33,6 +33,8 @@ che-core-api-workspace che-core-api-workspace-activity che-core-api-user-shared + che-core-api-devfile-shared + che-core-api-devfile che-core-api-account che-core-api-user che-core-api-factory-shared From 2278504419e09581fc9fe30cde7c6a0c40083d6d Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Thu, 17 Sep 2020 17:11:22 +0300 Subject: [PATCH 02/10] Devfile integration tests Signed-off-by: Sergii Kabashniuk --- wsmaster/integration-tests/mysql-tck/pom.xml | 11 +++++++++++ .../mysql-tck/src/test/java/MySqlTckModule.java | 8 ++++++++ wsmaster/integration-tests/postgresql-tck/pom.xml | 11 +++++++++++ .../src/test/java/PostgreSqlTckModule.java | 7 +++++++ 4 files changed, 37 insertions(+) diff --git a/wsmaster/integration-tests/mysql-tck/pom.xml b/wsmaster/integration-tests/mysql-tck/pom.xml index 6224dc6e753..480b00b80cd 100644 --- a/wsmaster/integration-tests/mysql-tck/pom.xml +++ b/wsmaster/integration-tests/mysql-tck/pom.xml @@ -90,6 +90,17 @@ che-core-api-account test + + org.eclipse.che.core + che-core-api-devfile + tests + test + + + org.eclipse.che.core + che-core-api-devfile + test + org.eclipse.che.core che-core-api-model diff --git a/wsmaster/integration-tests/mysql-tck/src/test/java/MySqlTckModule.java b/wsmaster/integration-tests/mysql-tck/src/test/java/MySqlTckModule.java index 62d564dd89d..a0a735ad518 100644 --- a/wsmaster/integration-tests/mysql-tck/src/test/java/MySqlTckModule.java +++ b/wsmaster/integration-tests/mysql-tck/src/test/java/MySqlTckModule.java @@ -24,6 +24,9 @@ import org.eclipse.che.account.spi.AccountDao; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.account.spi.jpa.JpaAccountDao; +import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.api.ssh.server.jpa.JpaSshDao; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.api.ssh.server.spi.SshDao; @@ -135,6 +138,7 @@ protected void configure() { SignatureKeyImpl.class, SignatureKeyPairImpl.class, // devfile + UserDevfileImpl.class, ActionImpl.class, org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl.class, ComponentImpl.class, @@ -196,6 +200,10 @@ protected void configure() { bind(WorkspaceActivityDao.class).to(JpaWorkspaceActivityDao.class); bind(new TypeLiteral>() {}).toInstance(new WorkspaceRepository()); + bind(UserDevfileDao.class).to(JpaUserDevfileDao.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + // sign keys bind(SignatureKeyDao.class).to(JpaSignatureKeyDao.class); bind(new TypeLiteral>() {}) diff --git a/wsmaster/integration-tests/postgresql-tck/pom.xml b/wsmaster/integration-tests/postgresql-tck/pom.xml index 0806c01d93b..9b528eaa0ac 100644 --- a/wsmaster/integration-tests/postgresql-tck/pom.xml +++ b/wsmaster/integration-tests/postgresql-tck/pom.xml @@ -80,6 +80,17 @@ che-core-api-account test + + org.eclipse.che.core + che-core-api-devfile + tests + test + + + org.eclipse.che.core + che-core-api-devfile + test + org.eclipse.che.core che-core-api-model diff --git a/wsmaster/integration-tests/postgresql-tck/src/test/java/PostgreSqlTckModule.java b/wsmaster/integration-tests/postgresql-tck/src/test/java/PostgreSqlTckModule.java index 3859e69c937..82290130524 100644 --- a/wsmaster/integration-tests/postgresql-tck/src/test/java/PostgreSqlTckModule.java +++ b/wsmaster/integration-tests/postgresql-tck/src/test/java/PostgreSqlTckModule.java @@ -24,6 +24,9 @@ import org.eclipse.che.account.spi.AccountDao; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.account.spi.jpa.JpaAccountDao; +import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.api.ssh.server.jpa.JpaSshDao; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.api.ssh.server.spi.SshDao; @@ -130,6 +133,7 @@ protected void configure() { WorkspaceActivity.class, VolumeImpl.class, // devfile + UserDevfileImpl.class, ActionImpl.class, org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl.class, ComponentImpl.class, @@ -191,6 +195,9 @@ protected void configure() { bind(WorkspaceActivityDao.class).to(JpaWorkspaceActivityDao.class); bind(new TypeLiteral>() {}).toInstance(new WorkspaceRepository()); + bind(UserDevfileDao.class).to(JpaUserDevfileDao.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); // k8s runtimes bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(KubernetesRuntimeState.class)); From e89ff75c621e19f8e034b256cc94ef3ab1d93503 Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Thu, 17 Sep 2020 17:12:45 +0300 Subject: [PATCH 03/10] Devfile permission implementation Signed-off-by: Sergii Kabashniuk --- .../che-multiuser-permission-devfile/pom.xml | 131 ++++++- .../devfile/DevfilePermissionsFilter.java | 49 --- .../UserDevfileApiPermissionsModule.java | 35 ++ ...UserDevfileCreatorPermissionsProvider.java | 71 ++++ .../devfile/server/UserDevfileDomain.java | 35 ++ .../filters/UserDevfilePermissionsFilter.java | 74 ++++ .../jpa/MultiuserUserDevfileJpaModule.java | 50 +++ .../server/model/UserDevfilePermission.java | 25 ++ .../model/impl/UserDevfilePermissionImpl.java | 146 ++++++++ .../server/spi/UserDevfilePermissionDao.java | 85 +++++ .../spi/jpa/JpaUserDevfilePermissionDao.java | 239 +++++++++++++ .../spi/jpa/MultiuserJpaUserDevfileDao.java | 132 ++++++++ .../devfile/server/TestObjectGenerator.java | 202 +++++++++++ .../UserDevfilePermissionsFilterTest.java | 320 ++++++++++++++++++ .../jpa/MultiuserJpaUserDevfileDaoTest.java | 145 ++++++++ .../server/jpa/UserDevfileTckModule.java | 117 +++++++ .../EntityManagerExceptionInterceptor.java | 28 ++ .../devfile/server/spi/jpa/JpaTckModule.java | 117 +++++++ .../jpa/JpaUserDevfilePermissionDaoTest.java | 115 +++++++ ...UserDevfileRemovedEventSubscriberTest.java | 143 ++++++++ .../spi/tck/UserDevfilePermissionDaoTest.java | 251 ++++++++++++++ .../devfile/DevfilePermissionsFilterTest.java | 87 ----- ...org.eclipse.che.commons.test.tck.TckModule | 1 + .../factory/FactoryPermissionsFilterTest.java | 22 -- .../1.1__add_userdevfile_permissions.sql | 40 +++ 25 files changed, 2493 insertions(+), 167 deletions(-) delete mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/DevfilePermissionsFilter.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileApiPermissionsModule.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileDomain.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilter.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/UserDevfilePermission.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/impl/UserDevfilePermissionImpl.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/TestObjectGenerator.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserJpaUserDevfileDaoTest.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/UserDevfileTckModule.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/EntityManagerExceptionInterceptor.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/tck/UserDevfilePermissionDaoTest.java delete mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permissions/devfile/DevfilePermissionsFilterTest.java create mode 100644 multiuser/permission/che-multiuser-permission-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule create mode 100644 multiuser/sql-schema/src/main/resources/che-schema/7.19.0/1.1__add_userdevfile_permissions.sql diff --git a/multiuser/permission/che-multiuser-permission-devfile/pom.xml b/multiuser/permission/che-multiuser-permission-devfile/pom.xml index 091bf3aa16f..abea0a99655 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/pom.xml +++ b/multiuser/permission/che-multiuser-permission-devfile/pom.xml @@ -22,6 +22,18 @@ che-multiuser-permission-devfile Che Multiuser :: Devfile Permissions + + com.google.guava + guava + + + com.google.inject + guice + + + javax.annotation + javax.annotation-api + javax.inject javax.inject @@ -30,31 +42,94 @@ javax.ws.rs javax.ws.rs-api + + org.eclipse.che.core + che-core-api-account + org.eclipse.che.core che-core-api-core + + org.eclipse.che.core + che-core-api-devfile + + + org.eclipse.che.core + che-core-api-devfile-shared + + + org.eclipse.che.core + che-core-api-model + + + org.eclipse.che.core + che-core-api-user + org.eclipse.che.core che-core-api-workspace + + org.eclipse.che.core + che-core-commons-annotations + + + org.eclipse.che.core + che-core-commons-lang + org.eclipse.che.core che-core-commons-test + + org.everrest + everrest-core + + + org.slf4j + slf4j-api + + + com.google.inject.extensions + guice-persist + provided + + + org.eclipse.che.core + che-core-db + provided + org.eclipse.che.multiuser che-multiuser-api-permission + provided - org.everrest - everrest-core + org.eclipse.che.multiuser + che-multiuser-api-permission-shared + provided + + + org.eclipse.persistence + javax.persistence + provided + + + aopalliance + aopalliance + test ch.qos.logback logback-classic test + + com.h2database + h2 + test + com.jayway.restassured rest-assured @@ -67,7 +142,37 @@ org.eclipse.che.core - che-core-api-factory-shared + che-core-commons-inject + test + + + org.eclipse.che.core + che-core-commons-json + test + + + org.eclipse.che.core + che-core-db-vendor-h2 + test + + + org.eclipse.che.core + che-core-sql-schema + test + + + org.eclipse.che.multiuser + che-multiuser-sql-schema + test + + + org.eclipse.persistence + org.eclipse.persistence.core + test + + + org.eclipse.persistence + org.eclipse.persistence.jpa test @@ -75,6 +180,11 @@ everrest-assured test + + org.flywaydb + flyway-core + test + org.mockito mockito-core @@ -93,17 +203,20 @@ + org.apache.maven.plugins - maven-dependency-plugin + maven-jar-plugin - - analyze + + test-jar + - - org.eclipse.che.multiuser:che-multiuser-api-permission - + + **/spi/tck/*.* + **/TestObjectGenerator.* + diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/DevfilePermissionsFilter.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/DevfilePermissionsFilter.java deleted file mode 100644 index 22713ffad1b..00000000000 --- a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/DevfilePermissionsFilter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.multiuser.permission.devfile; - -import javax.inject.Inject; -import javax.ws.rs.Path; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.workspace.server.WorkspaceManager; -import org.eclipse.che.api.workspace.server.devfile.DevfileService; -import org.eclipse.che.everrest.CheMethodInvokerFilter; -import org.everrest.core.Filter; -import org.everrest.core.resource.GenericResourceMethod; - -/** Restricts access to methods of {@link DevfileService} by user's permissions. */ -@Filter -@Path("/devfile{path:(/.*)?}") -public class DevfilePermissionsFilter extends CheMethodInvokerFilter { - - public static final String GET_SCHEMA_METHOD = "getSchema"; - - private final WorkspaceManager workspaceManager; - - @Inject - public DevfilePermissionsFilter(WorkspaceManager workspaceManager) { - this.workspaceManager = workspaceManager; - } - - @Override - protected void filter(GenericResourceMethod genericResourceMethod, Object[] arguments) - throws ForbiddenException { - final String methodName = genericResourceMethod.getMethod().getName(); - switch (methodName) { - // public methods - case GET_SCHEMA_METHOD: - return; - default: - throw new ForbiddenException("The user does not have permission to perform this operation"); - } - } -} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileApiPermissionsModule.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileApiPermissionsModule.java new file mode 100644 index 00000000000..a6d7f8ddf36 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileApiPermissionsModule.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import com.google.inject.name.Names; +import org.eclipse.che.multiuser.api.permission.server.SuperPrivilegesChecker; +import org.eclipse.che.multiuser.api.permission.shared.model.PermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.filters.UserDevfilePermissionsFilter; + +public class UserDevfileApiPermissionsModule extends AbstractModule { + + @Override + protected void configure() { + bind(UserDevfilePermissionsFilter.class); + bind(UserDevfileCreatorPermissionsProvider.class).asEagerSingleton(); + + Multibinder.newSetBinder( + binder(), + PermissionsDomain.class, + Names.named(SuperPrivilegesChecker.SUPER_PRIVILEGED_DOMAINS)) + .addBinding() + .to(UserDevfileDomain.class); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java new file mode 100644 index 00000000000..a9eea681b53 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server; + +import java.util.ArrayList; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.devfile.shared.event.DevfileCreatedEvent; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Adds permissions for creator after user devfile creation */ +@Singleton +public class UserDevfileCreatorPermissionsProvider implements EventSubscriber { + private static final Logger LOG = + LoggerFactory.getLogger(UserDevfileCreatorPermissionsProvider.class); + + private final UserDevfilePermissionDao userDevfilePermissionDao; + private final EventService eventService; + + @Inject + public UserDevfileCreatorPermissionsProvider( + EventService eventService, UserDevfilePermissionDao userDevfilePermissionDao) { + this.userDevfilePermissionDao = userDevfilePermissionDao; + this.eventService = eventService; + } + + @PostConstruct + void subscribe() { + eventService.subscribe(this); + } + + @PreDestroy + void unsubscribe() { + eventService.subscribe(this); + } + + @Override + public void onEvent(DevfileCreatedEvent event) { + try { + userDevfilePermissionDao.store( + new UserDevfilePermissionImpl( + event.getUserDevfile().getId(), + EnvironmentContext.getCurrent().getSubject().getUserId(), + new ArrayList<>(new UserDevfileDomain().getAllowedActions()))); + } catch (ServerException e) { + LOG.error( + "Can't add creator's permissions for user devfile with id '" + + event.getUserDevfile().getId() + + "'", + e); + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileDomain.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileDomain.java new file mode 100644 index 00000000000..865a393c9de --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileDomain.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; + +/** Domain for storing devfile's permissions */ +public class UserDevfileDomain extends AbstractPermissionsDomain { + public static final String READ = "read"; + public static final String DELETE = "delete"; + public static final String UPDATE = "update"; + public static final String DOMAIN_ID = "devfile"; + + public UserDevfileDomain() { + super(DOMAIN_ID, ImmutableList.of(READ, DELETE, UPDATE)); + } + + @Override + public UserDevfilePermissionImpl doCreateInstance( + String userId, String instanceId, List allowedActions) { + return new UserDevfilePermissionImpl(instanceId, userId, allowedActions); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilter.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilter.java new file mode 100644 index 00000000000..b084f080961 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilter.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.filters; + +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DELETE; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DOMAIN_ID; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.READ; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.UPDATE; + +import com.google.common.annotations.VisibleForTesting; +import javax.inject.Inject; +import javax.ws.rs.Path; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.devfile.server.DevfileService; +import org.eclipse.che.api.devfile.server.UserDevfileManager; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.everrest.CheMethodInvokerFilter; +import org.everrest.core.Filter; +import org.everrest.core.resource.GenericResourceMethod; + +/** + * Restricts access to methods of {@link DevfileService} by users' permissions. + * + *

Filter contains rules for protecting of all methods of {@link DevfileService}.
+ * In case when requested method is unknown filter throws {@link ForbiddenException} + */ +@Filter +@Path("/devfile{path:(/.*)?}") +public class UserDevfilePermissionsFilter extends CheMethodInvokerFilter { + private final UserDevfileManager userDevfileManager; + + @Inject + public UserDevfilePermissionsFilter(UserDevfileManager userDevfileManager) { + this.userDevfileManager = userDevfileManager; + } + + @Override + public void filter(GenericResourceMethod genericResourceMethod, Object[] arguments) + throws ForbiddenException { + final String methodName = genericResourceMethod.getMethod().getName(); + switch (methodName) { + case "getById": + doCheckPermission(DOMAIN_ID, ((String) arguments[0]), READ); + break; + case "update": + doCheckPermission(DOMAIN_ID, ((String) arguments[0]), UPDATE); + break; + case "delete": + doCheckPermission(DOMAIN_ID, ((String) arguments[0]), DELETE); + break; + case "createFromDevfileYaml": + case "createFromUserDevfile": + case "getUserDevfiles": + case "getSchema": + return; + default: + throw new ForbiddenException("The user does not have permission to perform this operation"); + } + } + + @VisibleForTesting + void doCheckPermission(String domain, String instance, String action) throws ForbiddenException { + EnvironmentContext.getCurrent().getSubject().checkPermission(domain, instance, action); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java new file mode 100644 index 00000000000..b56fe4a988f --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.jpa; + +import com.google.inject.AbstractModule; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.Multibinder; +import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao.RemoveUserDevfileBeforeAccountRemovedEventSubscriber; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions; +import org.eclipse.che.multiuser.api.permission.server.spi.PermissionsDao; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserRemovedEventSubscriber; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.MultiuserJpaUserDevfileDao; + +public class MultiuserUserDevfileJpaModule extends AbstractModule { + + @Override + protected void configure() { + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(UserDevfileDao.class).to(MultiuserJpaUserDevfileDao.class); + + bind(RemoveUserDevfileBeforeAccountRemovedEventSubscriber.class).asEagerSingleton(); + bind(RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber.class) + .asEagerSingleton(); + bind(RemoveUserDevfilePermissionsBeforeUserRemovedEventSubscriber.class).asEagerSingleton(); + + bind(new TypeLiteral>() {}) + .to(UserDevfileDomain.class); + + Multibinder> daos = + Multibinder.newSetBinder( + binder(), new TypeLiteral>() {}); + daos.addBinding().to(JpaUserDevfilePermissionDao.class); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/UserDevfilePermission.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/UserDevfilePermission.java new file mode 100644 index 00000000000..97fdf87cb16 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/UserDevfilePermission.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.model; + +import java.util.List; + +public interface UserDevfilePermission { + /** Returns user id */ + String getUserId(); + + /** Returns user devfile id */ + String getUserDevfileId(); + + /** Returns list of user devfile actions which can be performed by current user */ + List getActions(); +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/impl/UserDevfilePermissionImpl.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/impl/UserDevfilePermissionImpl.java new file mode 100644 index 00000000000..85b27cc6fdd --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/impl/UserDevfilePermissionImpl.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.model.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.QueryHint; +import javax.persistence.Table; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; + +/** Data object for {@link UserDevfilePermission} */ +@Entity(name = "UserDevfilePermission") +@NamedQueries({ + @NamedQuery( + name = "UserDevfilePermission.getByUserDevfileId", + query = + "SELECT permission " + + "FROM UserDevfilePermission permission " + + "WHERE permission.userDevfileId = :userDevfileId "), + @NamedQuery( + name = "UserDevfilePermission.getCountByUserDevfileId", + query = + "SELECT COUNT(permission) " + + "FROM UserDevfilePermission permission " + + "WHERE permission.userDevfileId = :userDevfileId "), + @NamedQuery( + name = "UserDevfilePermission.getByUserId", + query = + "SELECT permission " + + "FROM UserDevfilePermission permission " + + "WHERE permission.userId = :userId "), + @NamedQuery( + name = "UserDevfilePermission.getByUserAndUserDevfileId", + query = + "SELECT permission " + + "FROM UserDevfilePermission permission " + + "WHERE permission.userId = :userId " + + "AND permission.userDevfileId = :userDevfileId ", + hints = {@QueryHint(name = "eclipselink.query-results-cache", value = "true")}) +}) +@Table(name = "che_userdevfile_permissions") +public class UserDevfilePermissionImpl extends AbstractPermissions + implements UserDevfilePermission { + + @Column(name = "userdevfile_id") + private String userDevfileId; + + @ManyToOne + @JoinColumn(name = "userdevfile_id", insertable = false, updatable = false) + private UserDevfileImpl userDevfile; + + @ElementCollection(fetch = FetchType.EAGER) + @Column(name = "actions") + @CollectionTable( + name = "che_userdevfile_permissions_actions", + joinColumns = @JoinColumn(name = "userdevfile_permissions_id")) + protected List actions; + + public UserDevfilePermissionImpl() {} + + public UserDevfilePermissionImpl(String userDevfileId, String userId, List actions) { + super(userId); + this.userDevfileId = userDevfileId; + if (actions != null) { + this.actions = new ArrayList<>(actions); + } + } + + public UserDevfilePermissionImpl(UserDevfilePermission userDevfilePermission) { + this( + userDevfilePermission.getUserDevfileId(), + userDevfilePermission.getUserId(), + userDevfilePermission.getActions()); + } + + @Override + public String getInstanceId() { + return userDevfileId; + } + + @Override + public String getDomainId() { + return UserDevfileDomain.DOMAIN_ID; + } + + @Override + public List getActions() { + return actions; + } + + @Override + public String getUserDevfileId() { + return userDevfileId; + } + + @Override + public String toString() { + return "UserDevfilePermissionImpl{" + + "userDevfileId='" + + userDevfileId + + '\'' + + ", userDevfile=" + + userDevfile + + ", actions=" + + actions + + "} " + + super.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + UserDevfilePermissionImpl that = (UserDevfilePermissionImpl) o; + return Objects.equals(userDevfileId, that.userDevfileId) + && Objects.equals(actions, that.actions); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), userDevfileId, actions); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java new file mode 100644 index 00000000000..402eae79e55 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi; + +import java.util.List; +import java.util.Optional; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; + +/** Defines data access object contract for {@link UserDevfilePermissionImpl}. */ +public interface UserDevfilePermissionDao { + + /** + * Stores (adds or updates) UserDevfilePermissions. + * + * @param userDevfilePermissions userDevfilePermissions to store + * @return optional with updated userDevfilePermissions, other way empty optional must be returned + * @throws NullPointerException when {@code userDevfilePermissions} is null + * @throws ServerException when any other error occurs during userDevfilePermissions storing + */ + Optional store(UserDevfilePermissionImpl userDevfilePermissions) + throws ServerException; + + /** + * Gets userDevfilePermissions by user and userDevfileId + * + * @param userDevfileId user devfile identifier + * @param userId user identifier + * @return userDevfilePermissions instance, never null + * @throws NullPointerException when {@code workspace} or {@code user} is null + * @throws NotFoundException when worker with given {@code workspace} and {@code user} was not + * found + * @throws ServerException when any other error occurs during worker fetching + */ + UserDevfilePermissionImpl getUserDevfilePermission(String userDevfileId, String userId) + throws ServerException, NotFoundException; + + /** + * Removes userDevfilePermissions + * + *

Doesn't throw an exception when userDevfilePermissions with given {@code UserDevfile} and + * {@code user} does not exist + * + * @param userDevfileId workspace identifier + * @param userId user identifier + * @throws NullPointerException when {@code UserDevfile} or {@code user} is null + * @throws ServerException when any other error occurs during userDevfilePermissions removing + */ + void removeUserDevfilePermission(String userDevfileId, String userId) throws ServerException; + + /** + * Gets userDevfilePermissions by user devfile id. + * + * @param userDevfileId user devfile identifier + * @param maxItems the maximum number of userDevfilePermissions to return + * @param skipCount the number of userDevfilePermissions to skip + * @return list of userDevfilePermissions instance + * @throws NullPointerException when {@code userDevfile} is null + * @throws ServerException when any other error occurs during userDevfilePermissions fetching + */ + Page getUserDevfilePermission( + String userDevfileId, int maxItems, long skipCount) throws ServerException; + + /** + * Gets UserDevfilePermissions by user + * + * @param userId user identifier + * @return list of UserDevfilePermissions instance + * @throws NullPointerException when {@code user} is null + * @throws ServerException when any other error occurs during UserDevfilePermissions fetching + */ + List getUserDevfilePermissionByUser(String userId) + throws ServerException; +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java new file mode 100644 index 00000000000..061e247feaa --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; + +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.persist.Transactional; +import java.util.List; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent; +import org.eclipse.che.api.user.server.event.BeforeUserRemovedEvent; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.core.db.cascade.CascadeEventSubscriber; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.api.permission.server.jpa.AbstractJpaPermissionsDao; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; + +/** JPA implementation of {@link UserDevfilePermissionDao}. */ +public class JpaUserDevfilePermissionDao + extends AbstractJpaPermissionsDao + implements UserDevfilePermissionDao { + + @Inject + public JpaUserDevfilePermissionDao( + AbstractPermissionsDomain supportedDomain) { + super(supportedDomain); + } + + @Override + public UserDevfilePermissionImpl get(String userId, String instanceId) + throws ServerException, NotFoundException { + + requireNonNull(instanceId, "User devfile identifier required"); + requireNonNull(userId, "User identifier required"); + try { + return new UserDevfilePermissionImpl(getEntity(wildcardToNull(userId), instanceId)); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public List getByUser(String userId) throws ServerException { + requireNonNull(userId, "User identifier required"); + return doGetByUser(wildcardToNull(userId)) + .stream() + .map(UserDevfilePermissionImpl::new) + .collect(toList()); + } + + @Override + @Transactional + public Page getByInstance( + String instanceId, int maxItems, long skipCount) throws ServerException { + requireNonNull(instanceId, "User devfile identifier required"); + checkArgument( + skipCount <= Integer.MAX_VALUE, + "The number of items to skip can't be greater than " + Integer.MAX_VALUE); + + try { + final EntityManager entityManager = managerProvider.get(); + final List permissions = + entityManager + .createNamedQuery( + "UserDevfilePermission.getByUserDevfileId", UserDevfilePermissionImpl.class) + .setParameter("userDevfileId", instanceId) + .setMaxResults(maxItems) + .setFirstResult((int) skipCount) + .getResultList() + .stream() + .map(UserDevfilePermissionImpl::new) + .collect(toList()); + final Long workersCount = + entityManager + .createNamedQuery("UserDevfilePermission.getCountByUserDevfileId", Long.class) + .setParameter("userDevfileId", instanceId) + .getSingleResult(); + return new Page<>(permissions, skipCount, maxItems, workersCount); + } catch (RuntimeException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } + + @Override + protected UserDevfilePermissionImpl getEntity(String userId, String instanceId) + throws NotFoundException, ServerException { + try { + return doGet(userId, instanceId); + } catch (NoResultException e) { + throw new NotFoundException( + format( + "User devfile permission for devfile '%s' with id '%s' was not found.", + instanceId, userId)); + } catch (RuntimeException e) { + throw new ServerException(e.getMessage(), e); + } + } + + @Override + public UserDevfilePermissionImpl getUserDevfilePermission(String userDevfileId, String userId) + throws ServerException, NotFoundException { + return new UserDevfilePermissionImpl(get(userId, userDevfileId)); + } + + @Override + public void removeUserDevfilePermission(String userDevfileId, String userId) + throws ServerException { + try { + super.remove(userId, userDevfileId); + } catch (NotFoundException e) { + throw new ServerException(e); + } + } + + @Override + public Page getUserDevfilePermission( + String userDevfileId, int maxItems, long skipCount) throws ServerException { + return getByInstance(userDevfileId, maxItems, skipCount); + } + + @Override + public List getUserDevfilePermissionByUser(String userId) + throws ServerException { + return getByUser(userId); + } + + @Transactional + protected UserDevfilePermissionImpl doGet(String userId, String instanceId) { + return managerProvider + .get() + .createNamedQuery( + "UserDevfilePermission.getByUserAndUserDevfileId", UserDevfilePermissionImpl.class) + .setParameter("userDevfileId", instanceId) + .setParameter("userId", userId) + .getSingleResult(); + } + + @Transactional + protected List doGetByUser(@Nullable String userId) + throws ServerException { + try { + return managerProvider + .get() + .createNamedQuery("UserDevfilePermission.getByUserId", UserDevfilePermissionImpl.class) + .setParameter("userId", userId) + .getResultList(); + } catch (RuntimeException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } + + @Singleton + public static class RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber + extends CascadeEventSubscriber { + private static final int PAGE_SIZE = 100; + + @Inject private EventService eventService; + @Inject private UserDevfilePermissionDao userDevfilePermissionDao; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this, BeforeDevfileRemovedEvent.class); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this, BeforeDevfileRemovedEvent.class); + } + + @Override + public void onCascadeEvent(BeforeDevfileRemovedEvent event) throws Exception { + removeUserDevfilePermissions(event.getUserDevfile().getId(), PAGE_SIZE); + } + + @VisibleForTesting + void removeUserDevfilePermissions(String userDevfileId, int pageSize) throws ServerException { + Page permissionsPage; + do { + // skip count always equals to 0 because elements will be shifted after removing previous + // items + permissionsPage = + userDevfilePermissionDao.getUserDevfilePermission(userDevfileId, pageSize, 0); + for (UserDevfilePermissionImpl permission : permissionsPage.getItems()) { + userDevfilePermissionDao.removeUserDevfilePermission( + permission.getInstanceId(), permission.getUserId()); + } + } while (permissionsPage.hasNextPage()); + } + } + + @Singleton + public static class RemoveUserDevfilePermissionsBeforeUserRemovedEventSubscriber + extends CascadeEventSubscriber { + @Inject private EventService eventService; + @Inject private UserDevfilePermissionDao userDevfilePermissionDao; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this, BeforeUserRemovedEvent.class); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this, BeforeUserRemovedEvent.class); + } + + @Override + public void onCascadeEvent(BeforeUserRemovedEvent event) throws Exception { + for (UserDevfilePermissionImpl permission : + userDevfilePermissionDao.getUserDevfilePermissionByUser(event.getUser().getId())) { + userDevfilePermissionDao.removeUserDevfilePermission( + permission.getInstanceId(), permission.getUserId()); + } + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java new file mode 100644 index 00000000000..6297db8caec --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.List; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.commons.subject.Subject; + +/** JPA based implementation of {@link UserDevfileDao}. */ +@Singleton +public class MultiuserJpaUserDevfileDao extends JpaUserDevfileDao { + + @Inject + public MultiuserJpaUserDevfileDao( + Provider managerProvider, AccountDao accountDao, EventService eventService) { + super(managerProvider, accountDao, eventService); + } + + @Override + public Page getDevfiles( + int maxItems, + int skipCount, + List> filter, + List> order) + throws ServerException { + checkArgument(maxItems > 0, "The number of items has to be positive."); + checkArgument( + skipCount >= 0, + "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); + + final Subject subject = EnvironmentContext.getCurrent().getSubject(); + if (subject.isAnonymous()) { + throw new ServerException("Unexpected state. Current user is not set."); + } + + return doGetDevfiles( + maxItems, + skipCount, + filter, + order, + () -> + MultiuserUserDevfileSearchQueryBuilder.newBuilder(managerProvider.get()) + .withUserId(subject.getUserId())); + } + + public static class MultiuserUserDevfileSearchQueryBuilder + extends JpaUserDevfileDao.UserDevfileSearchQueryBuilder { + + MultiuserUserDevfileSearchQueryBuilder(EntityManager entityManager) { + super(entityManager); + } + + public MultiuserUserDevfileSearchQueryBuilder withUserId(String userId) { + params.put("userId", userId); + return this; + } + + public static MultiuserUserDevfileSearchQueryBuilder newBuilder(EntityManager entityManager) { + return new MultiuserUserDevfileSearchQueryBuilder(entityManager); + } + + @Override + public JpaUserDevfileDao.UserDevfileSearchQueryBuilder withFilter( + List> filter) { + super.withFilter(filter); + if (this.filter.isEmpty()) { + this.filter = "WHERE permission.userId = :userId AND 'read' MEMBER OF permission.actions"; + } else { + this.filter += " AND permission.userId = :userId AND 'read' MEMBER OF permission.actions"; + } + return this; + } + + @Override + public TypedQuery buildCountQuery() { + StringBuilder query = + new StringBuilder() + .append("SELECT ") + .append(" COUNT(userdevfile) ") + .append("FROM UserDevfilePermission permission ") + .append("LEFT JOIN permission.userDevfile userdevfile ") + .append(filter); + TypedQuery typedQuery = entityManager.createQuery(query.toString(), Long.class); + params.forEach((k, v) -> typedQuery.setParameter(k, v)); + return typedQuery; + } + + @Override + public TypedQuery buildSelectItemsQuery() { + StringBuilder query = + new StringBuilder() + .append("SELECT ") + .append(" userdevfile ") + .append("FROM UserDevfilePermission permission ") + .append("LEFT JOIN permission.userDevfile userdevfile ") + .append(filter) + .append(order); + TypedQuery typedQuery = + entityManager + .createQuery(query.toString(), UserDevfileImpl.class) + .setFirstResult(skipCount) + .setMaxResults(maxItems); + params.forEach((k, v) -> typedQuery.setParameter(k, v)); + return typedQuery; + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/TestObjectGenerator.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/TestObjectGenerator.java new file mode 100644 index 00000000000..0e21b0bfb1c --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/TestObjectGenerator.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; + +import com.google.common.collect.ImmutableMap; +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.DtoConverter; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.commons.subject.SubjectImpl; + +public class TestObjectGenerator { + + public static final String TEST_CHE_NAMESPACE = "user"; + public static final String CURRENT_USER_ID = NameGenerator.generate("usrid", 6); + public static final Subject TEST_SUBJECT = + new SubjectImpl(TEST_CHE_NAMESPACE, CURRENT_USER_ID, "token", false); + public static final String USER_DEVFILE_ID = NameGenerator.generate("usrd", 16); + public static final AccountImpl TEST_ACCOUNT = + new AccountImpl("acc-id042u3ui3oi", TEST_CHE_NAMESPACE, "test"); + + public static UserDevfileDto createUserDevfileDto() { + return DtoConverter.asDto(createUserDevfile(NameGenerator.generate("name", 6))); + } + + public static UserDevfileImpl createUserDevfile() { + return createUserDevfile(NameGenerator.generate("name", 6)); + } + + public static UserDevfileImpl createUserDevfile(String name) { + return createUserDevfile(NameGenerator.generate("id", 6), name); + } + + public static UserDevfileImpl createUserDevfile(String id, String name) { + return new UserDevfileImpl(id, TEST_ACCOUNT, name, "devfile description", createDevfile(name)); + } + + public static UserDevfileImpl createUserDevfile(String id, Account account, String name) { + return new UserDevfileImpl(id, account, name, "devfile description", createDevfile(name)); + } + + public static DevfileImpl createDevfile(String name) { + return createDevfile(name, "rosetta-"); + } + + public static DevfileImpl createDevfile(String name, String generatedName) { + + SourceImpl source1 = + new SourceImpl( + "type1", + "http://location", + "branch1", + "point1", + "tag1", + "commit1", + "sparseCheckoutDir1"); + ProjectImpl project1 = new ProjectImpl("project1", source1, "path1"); + + SourceImpl source2 = + new SourceImpl( + "type2", + "http://location", + "branch2", + "point2", + "tag2", + "commit2", + "sparseCheckoutDir2"); + ProjectImpl project2 = new ProjectImpl("project2", source2, "path2"); + + ActionImpl action1 = + new ActionImpl("exec1", "component1", "run.sh", "/home/user/1", null, null); + ActionImpl action2 = + new ActionImpl("exec2", "component2", "run.sh", "/home/user/2", null, null); + + CommandImpl command1 = + new CommandImpl(name + "-1", singletonList(action1), singletonMap("attr1", "value1"), null); + CommandImpl command2 = + new CommandImpl(name + "-2", singletonList(action2), singletonMap("attr2", "value2"), null); + + EntrypointImpl entrypoint1 = + new EntrypointImpl( + "parentName1", + singletonMap("parent1", "selector1"), + "containerName1", + asList("command1", "command2"), + asList("arg1", "arg2")); + + EntrypointImpl entrypoint2 = + new EntrypointImpl( + "parentName2", + singletonMap("parent2", "selector2"), + "containerName2", + asList("command3", "command4"), + asList("arg3", "arg4")); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume1 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name1", "path1"); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume2 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name2", "path2"); + + EnvImpl env1 = new EnvImpl("name1", "value1"); + EnvImpl env2 = new EnvImpl("name2", "value2"); + + EndpointImpl endpoint1 = new EndpointImpl("name1", 1111, singletonMap("key1", "value1")); + EndpointImpl endpoint2 = new EndpointImpl("name2", 2222, singletonMap("key2", "value2")); + + ComponentImpl component1 = + new ComponentImpl( + "kubernetes", + "component1", + "eclipse/che-theia/0.0.1", + ImmutableMap.of("java.home", "/home/user/jdk11"), + "https://mysite.com/registry/somepath1", + "/dev.yaml", + "refcontent1", + ImmutableMap.of("app.kubernetes.io/component", "db"), + asList(entrypoint1, entrypoint2), + "image", + "256G", + "128M", + "2", + "130m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + component1.setSelector(singletonMap("key1", "value1")); + + ComponentImpl component2 = + new ComponentImpl( + "kubernetes", + "component2", + "eclipse/che-theia/0.0.1", + ImmutableMap.of( + "java.home", + "/home/user/jdk11aertwertert", + "java.boolean", + true, + "java.long", + 123444L), + "https://mysite.com/registry/somepath2", + "/dev.yaml", + "refcontent2", + ImmutableMap.of("app.kubernetes.io/component", "webapp"), + asList(entrypoint1, entrypoint2), + "image", + "256G", + "256M", + "3", + "180m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + component2.setSelector(singletonMap("key2", "value2")); + MetadataImpl metadata = new MetadataImpl(name); + metadata.setGenerateName(generatedName); + DevfileImpl devfile = + new DevfileImpl( + "0.0.1", + asList(project1, project2), + asList(component1, component2), + asList(command1, command2), + singletonMap("attribute1", "value1"), + metadata); + + return devfile; + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java new file mode 100644 index 00000000000..47c9fbf8374 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.filters; + +import static com.jayway.restassured.RestAssured.given; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; +import static org.everrest.assured.JettyHttpServer.SECURE_PATH; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.testng.Assert.assertEquals; + +import com.jayway.restassured.response.Response; +import java.util.Collections; +import java.util.HashSet; +import org.eclipse.che.api.core.BadRequestException; +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.rest.ApiExceptionMapper; +import org.eclipse.che.api.core.rest.CheJsonProvider; +import org.eclipse.che.api.devfile.server.DevfileService; +import org.eclipse.che.api.devfile.server.UserDevfileManager; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; +import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; +import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator; +import org.eclipse.che.api.workspace.server.devfile.validator.DevfileSchemaValidator; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.everrest.assured.EverrestJetty; +import org.everrest.core.Filter; +import org.everrest.core.GenericContainerRequest; +import org.everrest.core.RequestFilter; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +/** Tests for {@link UserDevfilePermissionsFilter}. */ +@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) +public class UserDevfilePermissionsFilterTest { + private static final String USERNAME = "userok"; + + ApiExceptionMapper mapper; + + CheJsonProvider jsonProvider = new CheJsonProvider(new HashSet<>()); + private DevfileEntityProvider devfileEntityProvider = + new DevfileEntityProvider( + new DevfileParser( + new DevfileSchemaValidator(new DevfileSchemaProvider()), + new DevfileIntegrityValidator(Collections.emptyMap()))); + + @SuppressWarnings("unused") + private static final EnvironmentFilter FILTER = new EnvironmentFilter(); + + @Mock private static Subject subject; + + @Mock private UserDevfileManager userDevfileManager; + + private UserDevfilePermissionsFilter permissionsFilter; + + @Mock private DevfileService devfileService; + private UserDevfileDto userDevfileDto = TestObjectGenerator.createUserDevfileDto(); + private UserDevfileImpl userDevfile = + new UserDevfileImpl(userDevfileDto, TestObjectGenerator.TEST_ACCOUNT); + // + @BeforeMethod + public void setUp() throws Exception { + lenient().when(subject.getUserName()).thenReturn(USERNAME); + lenient().when(userDevfileManager.getById(any())).thenReturn(userDevfile); + + permissionsFilter = spy(new UserDevfilePermissionsFilter(userDevfileManager)); + + lenient() + .doThrow(new ForbiddenException("")) + .when(subject) + .checkPermission(anyString(), anyString(), anyString()); + } + + @Test + public void shouldNotCheckAnyPermissionOnDevfileCreate() { + // given + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(userDevfileDto) + .when() + .post(SECURE_PATH + "/devfile/"); + // then + assertEquals(response.getStatusCode(), 204); + verifyZeroInteractions(subject); + } + + @Test + public void shouldNotCheckAnyPermissionOnDevfileSearch() + throws BadRequestException, ForbiddenException, NotFoundException, ServerException { + // given + Mockito.when(devfileService.getUserDevfiles(any(), any(), any())) + .thenReturn(javax.ws.rs.core.Response.ok().build()); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/devfile/list"); + // then + assertEquals(response.getStatusCode(), 200); + verifyZeroInteractions(subject); + } + + @Test + public void shouldNotCheckAnyPermissionOnDevfileSchema() + throws BadRequestException, ForbiddenException, NotFoundException, ServerException { + // given + Mockito.when(devfileService.getSchema()).thenReturn(javax.ws.rs.core.Response.ok().build()); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/devfile"); + // then + assertEquals(response.getStatusCode(), 200); + verifyZeroInteractions(subject); + } + + @Test + public void shouldCheckReadPermissionsOnFetchingUserDevfileById() throws Exception { + // given + Mockito.when(devfileService.getById(eq(userDevfileDto.getId()))).thenReturn(userDevfileDto); + doNothing() + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.READ)); + + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .when() + .get(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 200); + verify(devfileService).getById(eq(userDevfileDto.getId())); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.READ)); + } + + @Test + public void shouldBeAbleToFailOnCheckPermissionDevfileReadByID() throws ForbiddenException { + // given + doThrow(new ForbiddenException("forbidden")) + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.READ)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 403); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.READ)); + } + + @Test + public void shouldChecksPermissionDevfileUpdate() throws ForbiddenException { + // given + doNothing() + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.UPDATE)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(userDevfileDto) + .when() + .put(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 204); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.UPDATE)); + } + + @Test + public void shouldBeAbleToFailOnCheckPermissionDevfileUpdate() throws ForbiddenException { + // given + doThrow(new ForbiddenException("forbidden")) + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.UPDATE)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(userDevfileDto) + .when() + .put(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 403); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.UPDATE)); + } + + @Test + public void shouldChecksPermissionDevfileDelete() throws ForbiddenException { + // given + doNothing() + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.DELETE)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .delete(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 204); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.DELETE)); + } + + @Test + public void shouldBeAbleToFailOnCheckPermissionDevfileDelete() throws ForbiddenException { + // given + doThrow(new ForbiddenException("forbidden")) + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.DELETE)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .delete(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + + // then + assertEquals(response.getStatusCode(), 403); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.DELETE)); + } + + @Filter + public static class EnvironmentFilter implements RequestFilter { + public void doFilter(GenericContainerRequest request) { + EnvironmentContext.getCurrent().setSubject(subject); + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserJpaUserDevfileDaoTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserJpaUserDevfileDaoTest.java new file mode 100644 index 00000000000..099b8576ea0 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserJpaUserDevfileDaoTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.jpa; + +import static org.eclipse.che.commons.lang.NameGenerator.generate; +import static org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator.createUserDevfile; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DELETE; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.READ; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.UPDATE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.persistence.EntityManager; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.subject.SubjectImpl; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.MultiuserJpaUserDevfileDao; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class MultiuserJpaUserDevfileDaoTest { + private TckResourcesCleaner tckResourcesCleaner; + private EntityManager manager; + private MultiuserJpaUserDevfileDao dao; + + private List permissions; + private List users; + private List userDevfiles; + private List accounts; + + @BeforeClass + public void setupEntities() throws Exception { + permissions = + ImmutableList.of( + new UserDevfilePermissionImpl( + "devfile_id1", "user1", Arrays.asList(READ, DELETE, UPDATE)), + new UserDevfilePermissionImpl("devfile_id2", "user1", Arrays.asList(READ, UPDATE)), + new UserDevfilePermissionImpl("devfile_id3", "user1", Arrays.asList(DELETE, UPDATE)), + new UserDevfilePermissionImpl( + "devfile_id1", "user2", Arrays.asList(READ, DELETE, UPDATE))); + + users = + ImmutableList.of( + new UserImpl("user1", "user1@com.com", "usr1"), + new UserImpl("user2", "user2@com.com", "usr2")); + accounts = + ImmutableList.of( + new AccountImpl("acc-1", NameGenerator.generate("account", 6), "user"), + new AccountImpl("acc-2", NameGenerator.generate("account", 6), "user")); + userDevfiles = + ImmutableList.of( + createUserDevfile("devfile_id1", accounts.get(0), generate("name", 6)), + createUserDevfile("devfile_id2", accounts.get(0), generate("name", 6)), + createUserDevfile("devfile_id3", accounts.get(0), generate("name", 6))); + Injector injector = Guice.createInjector(new UserDevfileTckModule()); + manager = injector.getInstance(EntityManager.class); + dao = injector.getInstance(MultiuserJpaUserDevfileDao.class); + tckResourcesCleaner = injector.getInstance(TckResourcesCleaner.class); + } + + @BeforeMethod + public void setUp() throws Exception { + manager.getTransaction().begin(); + + users.stream().map(UserImpl::new).forEach(manager::persist); + accounts.stream().map(AccountImpl::new).forEach(manager::persist); + userDevfiles.stream().map(UserDevfileImpl::new).forEach(manager::persist); + permissions.stream().map(UserDevfilePermissionImpl::new).forEach(manager::persist); + manager.getTransaction().commit(); + manager.clear(); + } + + @AfterMethod + public void cleanup() { + manager.getTransaction().begin(); + + manager + .createQuery("SELECT e FROM UserDevfilePermission e", UserDevfilePermissionImpl.class) + .getResultList() + .forEach(manager::remove); + + manager + .createQuery("SELECT w FROM UserDevfile w", UserDevfileImpl.class) + .getResultList() + .forEach(manager::remove); + + manager + .createQuery("SELECT a FROM Account a", AccountImpl.class) + .getResultList() + .forEach(manager::remove); + manager + .createQuery("SELECT u FROM Usr u", UserImpl.class) + .getResultList() + .forEach(manager::remove); + + manager.getTransaction().commit(); + } + + @Test + public void shouldGetTotalWorkspaceCount() throws ServerException { + assertEquals(dao.getTotalCount(), 3); + } + + @AfterClass + public void shutdown() throws Exception { + tckResourcesCleaner.clean(); + EnvironmentContext.reset(); + } + + @Test + public void shouldFindDevfilesByByPermissions() throws Exception { + EnvironmentContext expected = EnvironmentContext.getCurrent(); + expected.setSubject(new SubjectImpl("user", users.get(0).getId(), "token", false)); + List results = + dao.getDevfiles(30, 0, Collections.emptyList(), Collections.emptyList()).getItems(); + assertEquals(results.size(), 2); + assertTrue(results.contains(userDevfiles.get(0))); + assertTrue(results.contains(userDevfiles.get(1))); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/UserDevfileTckModule.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/UserDevfileTckModule.java new file mode 100644 index 00000000000..447e57c4ba1 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/UserDevfileTckModule.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.jpa; + +import com.google.inject.TypeLiteral; +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.account.spi.jpa.JpaAccountDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.workspace.server.devfile.SerializableConverter; +import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; +import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl; +import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.commons.test.db.H2DBTestServer; +import org.eclipse.che.commons.test.db.H2JpaCleaner; +import org.eclipse.che.commons.test.db.PersistTestModuleBuilder; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.core.db.DBInitializer; +import org.eclipse.che.core.db.h2.jpa.eclipselink.H2ExceptionHandler; +import org.eclipse.che.core.db.schema.SchemaInitializer; +import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.tck.UserDevfilePermissionDaoTest; +import org.h2.Driver; + +public class UserDevfileTckModule extends TckModule { + + @Override + protected void configure() { + H2DBTestServer server = H2DBTestServer.startDefault(); + install( + new PersistTestModuleBuilder() + .setDriver(Driver.class) + .runningOn(server) + .addEntityClasses( + AccountImpl.class, + UserImpl.class, + WorkspaceImpl.class, + WorkspaceConfigImpl.class, + ProjectConfigImpl.class, + EnvironmentImpl.class, + MachineConfigImpl.class, + SourceStorageImpl.class, + ServerConfigImpl.class, + CommandImpl.class, + RecipeImpl.class, + VolumeImpl.class, + // devfile + UserDevfileImpl.class, + UserDevfilePermissionImpl.class, + ActionImpl.class, + org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl.class, + ComponentImpl.class, + DevfileImpl.class, + EndpointImpl.class, + EntrypointImpl.class, + EnvImpl.class, + ProjectImpl.class, + SourceImpl.class, + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl.class) + .addEntityClass( + "org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute") + .addClass(SerializableConverter.class) + .setExceptionHandler(H2ExceptionHandler.class) + .build()); + bind(DBInitializer.class).asEagerSingleton(); + bind(SchemaInitializer.class) + .toInstance(new FlywaySchemaInitializer(server.getDataSource(), "che-schema")); + bind(TckResourcesCleaner.class).toInstance(new H2JpaCleaner(server)); + + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfilePermissionImpl.class)); + + bind(new TypeLiteral>() {}) + .to(UserDevfilePermissionDaoTest.TestDomain.class); + + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(AccountDao.class).to(JpaAccountDao.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(AccountImpl.class)); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/EntityManagerExceptionInterceptor.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/EntityManagerExceptionInterceptor.java new file mode 100644 index 00000000000..1d239b804cf --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/EntityManagerExceptionInterceptor.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import javax.persistence.EntityManager; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +public class EntityManagerExceptionInterceptor implements MethodInterceptor { + @Inject Provider emf; + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + emf.get().getTransaction().setRollbackOnly(); + throw new RuntimeException("Database exception"); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java new file mode 100644 index 00000000000..d13046c20b6 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import com.google.inject.TypeLiteral; +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.account.spi.jpa.JpaAccountDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.workspace.server.devfile.SerializableConverter; +import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; +import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl; +import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.commons.test.db.H2DBTestServer; +import org.eclipse.che.commons.test.db.H2JpaCleaner; +import org.eclipse.che.commons.test.db.PersistTestModuleBuilder; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.core.db.DBInitializer; +import org.eclipse.che.core.db.h2.jpa.eclipselink.H2ExceptionHandler; +import org.eclipse.che.core.db.schema.SchemaInitializer; +import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.tck.UserDevfilePermissionDaoTest; +import org.h2.Driver; + +public class JpaTckModule extends TckModule { + + @Override + protected void configure() { + H2DBTestServer server = H2DBTestServer.startDefault(); + install( + new PersistTestModuleBuilder() + .setDriver(Driver.class) + .runningOn(server) + .addEntityClasses( + AccountImpl.class, + UserImpl.class, + WorkspaceImpl.class, + WorkspaceConfigImpl.class, + ProjectConfigImpl.class, + EnvironmentImpl.class, + UserDevfilePermissionImpl.class, + MachineConfigImpl.class, + SourceStorageImpl.class, + ServerConfigImpl.class, + CommandImpl.class, + RecipeImpl.class, + VolumeImpl.class, + // devfile + ActionImpl.class, + org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl.class, + ComponentImpl.class, + DevfileImpl.class, + EndpointImpl.class, + EntrypointImpl.class, + EnvImpl.class, + ProjectImpl.class, + SourceImpl.class, + UserDevfileImpl.class, + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl.class) + .addEntityClass( + "org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute") + .addClass(SerializableConverter.class) + .setExceptionHandler(H2ExceptionHandler.class) + .build()); + + bind(new TypeLiteral>() {}) + .to(UserDevfilePermissionDaoTest.TestDomain.class); + + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(AccountDao.class).to(JpaAccountDao.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfilePermission.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(AccountImpl.class)); + + bind(SchemaInitializer.class) + .toInstance(new FlywaySchemaInitializer(server.getDataSource(), "che-schema")); + bind(DBInitializer.class).asEagerSingleton(); + bind(TckResourcesCleaner.class).toInstance(new H2JpaCleaner(server)); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java new file mode 100644 index 00000000000..dce67bffdc6 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import static org.eclipse.che.commons.lang.NameGenerator.generate; +import static org.eclipse.che.inject.Matchers.names; +import static org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain.SET_PERMISSIONS; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.matcher.Matchers; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.persistence.EntityManager; +import org.aopalliance.intercept.MethodInterceptor; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class JpaUserDevfilePermissionDaoTest { + + private JpaUserDevfilePermissionDao userDevfilePermissionsDao; + private EntityManager manager; + private TckResourcesCleaner tckResourcesCleaner; + + @BeforeMethod + private void setUpManager() { + final Injector injector = + Guice.createInjector(new JpaTckModule(), new ExceptionEntityManagerModule()); + manager = injector.getInstance(EntityManager.class); + userDevfilePermissionsDao = injector.getInstance(JpaUserDevfilePermissionDao.class); + tckResourcesCleaner = injector.getInstance(TckResourcesCleaner.class); + } + + @AfterMethod + private void cleanup() { + manager.getTransaction().begin(); + final List entities = new ArrayList<>(); + entities.addAll(manager.createQuery("SELECT p FROM UserDevfilePermission p").getResultList()); + entities.addAll(manager.createQuery("SELECT d FROM UserDevfile d").getResultList()); + entities.addAll(manager.createQuery("SELECT a FROM Account a").getResultList()); + entities.addAll(manager.createQuery("SELECT u FROM Usr u").getResultList()); + for (Object entity : entities) { + manager.remove(entity); + } + manager.getTransaction().commit(); + tckResourcesCleaner.clean(); + } + + @Test( + expectedExceptions = ServerException.class, + expectedExceptionsMessageRegExp = "Database exception") + public void shouldThrowServerExceptionOnExistsWhenRuntimeExceptionOccursInDoGetMethod() + throws Exception { + + // Persist the account + manager.getTransaction().begin(); + manager.persist(TestObjectGenerator.TEST_ACCOUNT); + manager.getTransaction().commit(); + manager.clear(); + + final UserDevfileImpl userDevfile = TestObjectGenerator.createUserDevfile(); + // Persist the userdevfule + manager.getTransaction().begin(); + manager.persist(userDevfile); + manager.getTransaction().commit(); + manager.clear(); + + final UserImpl user = new UserImpl(generate("user", 6), "user0@com.com", "usr0"); + // Persist the user + manager.getTransaction().begin(); + manager.persist(user); + manager.getTransaction().commit(); + manager.clear(); + + // Persist the worker + UserDevfilePermissionImpl worker = + new UserDevfilePermissionImpl( + userDevfile.getId(), user.getId(), Collections.singletonList(SET_PERMISSIONS)); + manager.getTransaction().begin(); + manager.persist(worker); + manager.getTransaction().commit(); + manager.clear(); + + userDevfilePermissionsDao.exists(user.getId(), userDevfile.getId(), SET_PERMISSIONS); + } + + public class ExceptionEntityManagerModule extends TckModule { + + @Override + protected void configure() { + MethodInterceptor interceptor = new EntityManagerExceptionInterceptor(); + requestInjection(interceptor); + bindInterceptor( + Matchers.subclassesOf(JpaUserDevfilePermissionDao.class), names("doGet"), interceptor); + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java new file mode 100644 index 00000000000..d3f4b5b1ecb --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import static org.eclipse.che.commons.lang.NameGenerator.generate; +import static org.testng.AssertJUnit.assertEquals; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.util.Arrays; +import java.util.stream.Stream; +import javax.persistence.EntityManager; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Tests for {@link RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber} */ +public class RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest { + private TckResourcesCleaner tckResourcesCleaner; + private EntityManager manager; + private JpaUserDevfilePermissionDao userDevfilePermissionsDao; + private JpaUserDevfileDao userDevfileDao; + + private RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber subscriber; + + private UserDevfileImpl userDevfile; + private UserDevfilePermissionImpl[] userDevfilePermissions; + private UserImpl[] users; + + @BeforeClass + public void setupEntities() throws Exception { + + users = + new UserImpl[] { + new UserImpl("user1", "user1@com.com", "usr1"), + new UserImpl("user2", "user2@com.com", "usr2") + }; + + userDevfile = TestObjectGenerator.createUserDevfile("devfile_id1", generate("name", 6)); + + userDevfilePermissions = + new UserDevfilePermissionImpl[] { + new UserDevfilePermissionImpl( + userDevfile.getId(), "user1", Arrays.asList("read", "use", "run")), + new UserDevfilePermissionImpl(userDevfile.getId(), "user2", Arrays.asList("read", "use")) + }; + + Injector injector = Guice.createInjector(new JpaTckModule()); + + manager = injector.getInstance(EntityManager.class); + userDevfilePermissionsDao = injector.getInstance(JpaUserDevfilePermissionDao.class); + userDevfileDao = injector.getInstance(JpaUserDevfileDao.class); + subscriber = + injector.getInstance( + RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber.class); + subscriber.subscribe(); + tckResourcesCleaner = injector.getInstance(TckResourcesCleaner.class); + } + + @BeforeMethod + public void setUp() throws Exception { + manager.getTransaction().begin(); + manager.persist(userDevfile); + Stream.of(users).forEach(manager::persist); + manager.persist(TestObjectGenerator.TEST_ACCOUNT); + Stream.of(userDevfilePermissions).forEach(manager::persist); + manager.getTransaction().commit(); + manager.clear(); + } + + @AfterMethod + public void cleanup() { + manager.getTransaction().begin(); + + manager + .createQuery("SELECT e FROM UserDevfilePermission e", UserDevfilePermissionImpl.class) + .getResultList() + .forEach(manager::remove); + + manager + .createQuery("SELECT w FROM UserDevfile w", UserDevfileImpl.class) + .getResultList() + .forEach(manager::remove); + + manager + .createQuery("SELECT a FROM Account a", AccountImpl.class) + .getResultList() + .forEach(manager::remove); + manager + .createQuery("SELECT u FROM Usr u", UserImpl.class) + .getResultList() + .forEach(manager::remove); + + manager.getTransaction().commit(); + } + + @AfterClass + public void shutdown() throws Exception { + subscriber.unsubscribe(); + tckResourcesCleaner.clean(); + } + + @Test + public void shouldRemoveAllPermissionsWhenUserDevfileIsRemoved() throws Exception { + userDevfileDao.remove(userDevfile.getId()); + + assertEquals( + userDevfilePermissionsDao + .getUserDevfilePermission(userDevfile.getId(), 1, 0) + .getTotalItemsCount(), + 0); + } + + @Test + public void shouldRemoveAllPermissionsWhenPageSizeEqualsToOne() throws Exception { + subscriber.removeUserDevfilePermissions(userDevfile.getId(), 1); + + assertEquals( + userDevfilePermissionsDao + .getUserDevfilePermission(userDevfile.getId(), 1, 0) + .getTotalItemsCount(), + 0); + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/tck/UserDevfilePermissionDaoTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/tck/UserDevfilePermissionDaoTest.java new file mode 100644 index 00000000000..8c0da168a28 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/tck/UserDevfilePermissionDaoTest.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.tck; + +import static org.eclipse.che.commons.lang.NameGenerator.generate; +import static org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator.createUserDevfile; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DELETE; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.READ; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.UPDATE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.inject.Inject; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.TckListener; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +/** Compatibility test for {@link UserDevfilePermissionDao} */ +@Listeners(TckListener.class) +@Test(suiteName = "UserDevfilePermissionTck") +public class UserDevfilePermissionDaoTest { + + @Inject private UserDevfilePermissionDao permissionDao; + + @Inject private TckRepository permissionTckRepository; + + @Inject private TckRepository userRepository; + + @Inject private TckRepository devfileRepository; + + @Inject private TckRepository accountRepository; + + UserDevfilePermissionImpl[] permissions; + + @BeforeMethod + public void setUp() throws TckRepositoryException { + accountRepository.createAll(ImmutableList.of(TestObjectGenerator.TEST_ACCOUNT)); + permissions = + new UserDevfilePermissionImpl[] { + new UserDevfilePermissionImpl( + "devfile_id1", "user1", Arrays.asList(READ, DELETE, UPDATE)), + new UserDevfilePermissionImpl("devfile_id1", "user2", Arrays.asList(READ, DELETE)), + new UserDevfilePermissionImpl("devfile_id2", "user1", Arrays.asList(READ, UPDATE)), + new UserDevfilePermissionImpl( + "devfile_id2", "user2", Arrays.asList(READ, DELETE, UPDATE)), + new UserDevfilePermissionImpl("devfile_id2", "user0", Arrays.asList(READ, DELETE, UPDATE)) + }; + + final UserImpl[] users = + new UserImpl[] { + new UserImpl("user0", "user0@com.com", "usr0"), + new UserImpl("user1", "user1@com.com", "usr1"), + new UserImpl("user2", "user2@com.com", "usr2") + }; + userRepository.createAll(Arrays.asList(users)); + + devfileRepository.createAll( + ImmutableList.of( + createUserDevfile("devfile_id1", generate("name", 6)), + createUserDevfile("devfile_id2", generate("name", 6)), + createUserDevfile("devfile_id3", generate("name", 6)), + createUserDevfile("devfile_id4", generate("name", 6)), + createUserDevfile("devfile_id5", generate("name", 6)))); + + permissionTckRepository.createAll( + Stream.of(permissions).map(UserDevfilePermissionImpl::new).collect(Collectors.toList())); + } + + @AfterMethod + public void cleanUp() throws TckRepositoryException { + permissionTckRepository.removeAll(); + devfileRepository.removeAll(); + accountRepository.removeAll(); + userRepository.removeAll(); + } + + @Test + public void shouldStorePermissions() throws Exception { + UserDevfilePermissionImpl permission = + new UserDevfilePermissionImpl("devfile_id1", "user0", Arrays.asList(READ, DELETE, UPDATE)); + permissionDao.store(permission); + Assert.assertEquals( + permissionDao.getUserDevfilePermission("devfile_id1", "user0"), + new UserDevfilePermissionImpl(permission)); + } + + @Test + public void shouldReplaceExistingPermissionOnStoring() throws Exception { + UserDevfilePermissionImpl replace = + new UserDevfilePermissionImpl("devfile_id1", "user1", Collections.singletonList("READ")); + permissionDao.store(replace); + Assert.assertEquals(permissionDao.getUserDevfilePermission("devfile_id1", "user1"), replace); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenStoringArgumentIsNull() throws Exception { + permissionDao.store(null); + } + + @Test + public void shouldGetPermissionByWorkspaceIdAndUserId() throws Exception { + Assert.assertEquals( + permissionDao.getUserDevfilePermission("devfile_id1", "user1"), permissions[0]); + Assert.assertEquals( + permissionDao.getUserDevfilePermission("devfile_id2", "user2"), permissions[3]); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenGetPermissionDevfileIdArgumentIsNull() throws Exception { + permissionDao.getUserDevfilePermission(null, "user1"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenGetPermissionUserIdArgumentIsNull() throws Exception { + permissionDao.getUserDevfilePermission("devfile_id1", null); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionOnGetIfPermissionWithSuchDevfileIdOrUserIdDoesNotExist() + throws Exception { + permissionDao.getUserDevfilePermission("devfile_id9", "user1"); + } + + @Test + public void shouldGetPermissionsByDevfileId() throws Exception { + Page permissionsPage = + permissionDao.getUserDevfilePermission("devfile_id2", 1, 1); + + final List fetchedPermissions = permissionsPage.getItems(); + assertEquals(permissionsPage.getTotalItemsCount(), 3); + assertEquals(permissionsPage.getItemsCount(), 1); + assertTrue( + fetchedPermissions.contains(permissions[2]) + ^ fetchedPermissions.contains(permissions[3]) + ^ fetchedPermissions.contains(permissions[4])); + } + + @Test + public void shouldGetPermissionsByUserId() throws Exception { + List actual = permissionDao.getUserDevfilePermissionByUser("user1"); + List expected = Arrays.asList(permissions[0], permissions[2]); + assertEquals(actual.size(), expected.size()); + assertTrue(new HashSet<>(actual).equals(new HashSet<>(expected))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenGetPermissionsByDevfileArgumentIsNull() throws Exception { + permissionDao.getUserDevfilePermission(null, 1, 0); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenGetPermissionsByUserArgumentIsNull() throws Exception { + permissionDao.getUserDevfilePermissionByUser(null); + } + + @Test + public void shouldReturnEmptyListIfPermissionsWithSuchDevfileIdDoesNotFound() throws Exception { + assertEquals( + 0, permissionDao.getUserDevfilePermission("unexisted_devfile_id", 1, 0).getItemsCount()); + } + + @Test + public void shouldReturnEmptyListIfPermissionsWithSuchUserIdDoesNotFound() throws Exception { + assertEquals(0, permissionDao.getUserDevfilePermissionByUser("unexisted_user").size()); + } + + @Test + public void shouldRemovePermission() throws Exception { + permissionDao.removeUserDevfilePermission("devfile_id1", "user1"); + assertEquals(1, permissionDao.getUserDevfilePermissionByUser("user1").size()); + assertNull( + notFoundToNull(() -> permissionDao.getUserDevfilePermission("devfile_id1", "user1"))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenRemovePermissionDevfileIdArgumentIsNull() throws Exception { + permissionDao.removeUserDevfilePermission(null, "user1"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenRemovePermissionUserIdArgumentIsNull() throws Exception { + permissionDao.removeUserDevfilePermission("devfile_id1", null); + } + + @Test(expectedExceptions = ServerException.class) + public void shouldThrowNotFoundExceptionOnRemoveIfPermissionWithSuchDevfileIdDoesNotExist() + throws Exception { + permissionDao.removeUserDevfilePermission("unexisted_ws", "user1"); + } + + @Test(expectedExceptions = ServerException.class) + public void shouldThrowNotFoundExceptionOnRemoveIfPermissionWithSuchUserIdDoesNotExist() + throws Exception { + permissionDao.removeUserDevfilePermission("devfile_id1", "unexisted_user"); + } + + public static class TestDomain extends AbstractPermissionsDomain { + public TestDomain() { + super(UserDevfileDomain.DOMAIN_ID, Arrays.asList(READ, DELETE, UPDATE)); + } + + @Override + protected UserDevfilePermissionImpl doCreateInstance( + String userId, String instanceId, List allowedActions) { + return new UserDevfilePermissionImpl(userId, instanceId, allowedActions); + } + } + + private static T notFoundToNull(Callable action) throws Exception { + try { + return action.call(); + } catch (NotFoundException x) { + return null; + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permissions/devfile/DevfilePermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permissions/devfile/DevfilePermissionsFilterTest.java deleted file mode 100644 index 105eee59b47..00000000000 --- a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permissions/devfile/DevfilePermissionsFilterTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.multiuser.permissions.devfile; - -import static com.jayway.restassured.RestAssured.given; -import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; -import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; -import static org.everrest.assured.JettyHttpServer.SECURE_PATH; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import com.jayway.restassured.response.Response; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.eclipse.che.api.workspace.server.devfile.DevfileService; -import org.eclipse.che.commons.env.EnvironmentContext; -import org.eclipse.che.commons.subject.Subject; -import org.eclipse.che.multiuser.permission.devfile.DevfilePermissionsFilter; -import org.everrest.assured.EverrestJetty; -import org.everrest.core.Filter; -import org.everrest.core.GenericContainerRequest; -import org.everrest.core.RequestFilter; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) -public class DevfilePermissionsFilterTest { - - @SuppressWarnings("unused") - private static final EnvironmentFilter FILTER = new EnvironmentFilter(); - - @Mock private static Subject subject; - - @Mock private DevfileService service; - - @SuppressWarnings("unused") - @InjectMocks - private DevfilePermissionsFilter permissionsFilter; - - @Test - public void shouldTestThatAllPublicMethodsAreCoveredByPermissionsFilter() throws Exception { - // given - final List collect = - Stream.of(DevfileService.class.getDeclaredMethods()) - .filter(method -> Modifier.isPublic(method.getModifiers())) - .map(Method::getName) - .collect(Collectors.toList()); - - // then - assertEquals(collect.size(), 1); - assertTrue(collect.contains(DevfilePermissionsFilter.GET_SCHEMA_METHOD)); - } - - @Test - public void shouldNotCheckPermissionsOnExportingSchema() throws Exception { - final Response response = - given() - .auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .when() - .get(SECURE_PATH + "/devfile"); - - assertEquals(response.getStatusCode(), 204); - } - - @Filter - public static class EnvironmentFilter implements RequestFilter { - public void doFilter(GenericContainerRequest request) { - EnvironmentContext.getCurrent().setSubject(subject); - } - } -} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/multiuser/permission/che-multiuser-permission-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 00000000000..edd289622d5 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaTckModule diff --git a/multiuser/permission/che-multiuser-permission-factory/src/test/java/org/eclipse/che/multiuser/permission/factory/FactoryPermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-factory/src/test/java/org/eclipse/che/multiuser/permission/factory/FactoryPermissionsFilterTest.java index dbf64736e06..3c04ff38cad 100644 --- a/multiuser/permission/che-multiuser-permission-factory/src/test/java/org/eclipse/che/multiuser/permission/factory/FactoryPermissionsFilterTest.java +++ b/multiuser/permission/che-multiuser-permission-factory/src/test/java/org/eclipse/che/multiuser/permission/factory/FactoryPermissionsFilterTest.java @@ -11,36 +11,15 @@ */ package org.eclipse.che.multiuser.permission.factory; -import static com.jayway.restassured.RestAssured.given; -import static org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain.DOMAIN_ID; -import static org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain.READ; -import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; -import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; -import static org.everrest.assured.JettyHttpServer.SECURE_PATH; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.testng.Assert.assertEquals; -import com.jayway.restassured.response.Response; import java.util.Map; -import javax.ws.rs.core.UriInfo; import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.model.factory.Factory; import org.eclipse.che.api.factory.server.FactoryManager; import org.eclipse.che.api.factory.server.FactoryService; -import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; -import org.eclipse.che.dto.server.DtoFactory; import org.everrest.assured.EverrestJetty; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; @@ -85,7 +64,6 @@ public void shouldDoNothingWhenPublicMethodMethodIsCalled(String name, Class[] p @DataProvider(name = "publicMethods") public Object[][] publicMethods() { return new Object[][] { - {"resolveFactory", new Class[] {Map.class, Boolean.class}}, }; } diff --git a/multiuser/sql-schema/src/main/resources/che-schema/7.19.0/1.1__add_userdevfile_permissions.sql b/multiuser/sql-schema/src/main/resources/che-schema/7.19.0/1.1__add_userdevfile_permissions.sql new file mode 100644 index 00000000000..915b974305d --- /dev/null +++ b/multiuser/sql-schema/src/main/resources/che-schema/7.19.0/1.1__add_userdevfile_permissions.sql @@ -0,0 +1,40 @@ +-- +-- Copyright (c) 2012-2020 Red Hat, Inc. +-- This program and the accompanying materials are made +-- available under the terms of the Eclipse Public License 2.0 +-- which is available at https://www.eclipse.org/legal/epl-2.0/ +-- +-- SPDX-License-Identifier: EPL-2.0 +-- +-- Contributors: +-- Red Hat, Inc. - initial API and implementation +-- + +-- User devfile permissions ----------------------------------------------------------- +CREATE TABLE che_userdevfile_permissions ( + id VARCHAR(255) NOT NULL, + user_id VARCHAR(255), + userdevfile_id VARCHAR(255), + + PRIMARY KEY (id) +); +-- indexes +CREATE UNIQUE INDEX che_index_userdevfile_permissions_user_id_userdevfile_id ON che_userdevfile_permissions (user_id, userdevfile_id); +CREATE INDEX che_index_userdevfile_permissions_userdevfile_id ON che_userdevfile_permissions (userdevfile_id); +-- constraints +ALTER TABLE che_userdevfile_permissions ADD CONSTRAINT che_fk_userdevfile_permissions_user_id FOREIGN KEY (user_id) REFERENCES usr (id); +ALTER TABLE che_userdevfile_permissions ADD CONSTRAINT che_fk_userdevfile_permissions_workspace_id FOREIGN KEY (userdevfile_id) REFERENCES userdevfile (id); +-------------------------------------------------------------------------------- + + +-- User devfile permission actions -------------------------------------------------------------- +CREATE TABLE che_userdevfile_permissions_actions ( + userdevfile_permissions_id VARCHAR(255), + actions VARCHAR(255) +); +-- indexes +CREATE INDEX che_index_userdevfile_permissions_actions_actions ON che_userdevfile_permissions_actions (actions); +CREATE INDEX che_index_userdevfile_permissions_actions_userdevfile_id ON che_userdevfile_permissions_actions (userdevfile_permissions_id); +-- constraints +ALTER TABLE che_userdevfile_permissions_actions ADD CONSTRAINT che_fk_userdevfile_permissions_actions_id FOREIGN KEY (userdevfile_permissions_id) REFERENCES che_userdevfile_permissions(id); +-------------------------------------------------------------------------------- From 079e192eebf03f9bae9f5f395f90ebc4b18706d9 Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Thu, 17 Sep 2020 17:13:27 +0300 Subject: [PATCH 04/10] Devfile permission integration testing Signed-off-by: Sergii Kabashniuk --- .../che-multiuser-cascade-removal/pom.xml | 10 ++ .../JpaEntitiesCascadeRemovalTest.java | 48 +++++- .../cascaderemoval/TestObjectsFactory.java | 147 ++++++++++++++++++ .../test/resources/META-INF/persistence.xml | 2 + .../che-multiuser-mysql-tck/pom.xml | 25 +-- .../test/java/MultiuserMySqlTckModule.java | 15 ++ .../test/resources/META-INF/persistence.xml | 2 + .../che-multiuser-postgresql-tck/pom.xml | 11 ++ .../java/MultiuserPostgresqlTckModule.java | 15 ++ .../test/resources/META-INF/persistence.xml | 2 + 10 files changed, 266 insertions(+), 11 deletions(-) diff --git a/multiuser/integration-tests/che-multiuser-cascade-removal/pom.xml b/multiuser/integration-tests/che-multiuser-cascade-removal/pom.xml index 1a8ba41587c..4b7505e1ae0 100644 --- a/multiuser/integration-tests/che-multiuser-cascade-removal/pom.xml +++ b/multiuser/integration-tests/che-multiuser-cascade-removal/pom.xml @@ -83,6 +83,11 @@ che-core-api-core test + + org.eclipse.che.core + che-core-api-devfile + test + org.eclipse.che.core che-core-api-factory @@ -178,6 +183,11 @@ che-multiuser-machine-authentication test + + org.eclipse.che.multiuser + che-multiuser-permission-devfile + test + org.eclipse.che.multiuser che-multiuser-permission-workspace diff --git a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/JpaEntitiesCascadeRemovalTest.java b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/JpaEntitiesCascadeRemovalTest.java index ec557918e92..92a5a356f85 100644 --- a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/JpaEntitiesCascadeRemovalTest.java +++ b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/JpaEntitiesCascadeRemovalTest.java @@ -24,6 +24,9 @@ import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createUser; import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createWorker; import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createWorkspace; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DELETE; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.READ; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.UPDATE; import static org.eclipse.che.multiuser.resource.spi.jpa.JpaFreeResourcesLimitDao.RemoveFreeResourcesLimitSubscriber; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; @@ -33,6 +36,7 @@ import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; +import com.google.common.collect.ImmutableList; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; @@ -54,6 +58,8 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.api.factory.server.jpa.FactoryJpaModule; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.factory.server.spi.FactoryDao; @@ -106,6 +112,10 @@ import org.eclipse.che.multiuser.organization.spi.MemberDao; import org.eclipse.che.multiuser.organization.spi.impl.MemberImpl; import org.eclipse.che.multiuser.organization.spi.impl.OrganizationImpl; +import org.eclipse.che.multiuser.permission.devfile.server.jpa.MultiuserUserDevfileJpaModule; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserRemovedEventSubscriber; import org.eclipse.che.multiuser.permission.workspace.server.jpa.MultiuserWorkspaceJpaModule; import org.eclipse.che.multiuser.permission.workspace.server.spi.WorkerDao; import org.eclipse.che.multiuser.resource.api.AvailableResourcesProvider; @@ -143,6 +153,8 @@ public class JpaEntitiesCascadeRemovalTest { private SshDao sshDao; private FactoryDao factoryDao; private WorkerDao workerDao; + private UserDevfilePermissionDao userDevfilePermissionDao; + private UserDevfileDao userDevfileDao; private SignatureKeyDao signatureKeyDao; private FreeResourcesLimitDao freeResourcesLimitDao; private OrganizationManager organizationManager; @@ -195,6 +207,9 @@ public class JpaEntitiesCascadeRemovalTest { private FreeResourcesLimitImpl freeResourcesLimit2; + private UserDevfileImpl devfile; + private UserDevfilePermissionImpl devfilePermission; + private H2JpaCleaner h2JpaCleaner; @BeforeMethod @@ -221,6 +236,8 @@ protected void configure() { install(new MultiuserWorkspaceJpaModule()); install(new MachineAuthModule()); install(new DevfileModule()); + install(new MultiuserUserDevfileJpaModule()); + bind(ExecutorServiceWrapper.class).to(NoopExecutorServiceWrapper.class); bind(FreeResourcesLimitDao.class).to(JpaFreeResourcesLimitDao.class); @@ -305,6 +322,8 @@ protected void configure() { workspaceDao = injector.getInstance(WorkspaceDao.class); factoryDao = injector.getInstance(FactoryDao.class); workerDao = injector.getInstance(WorkerDao.class); + userDevfileDao = injector.getInstance(UserDevfileDao.class); + userDevfilePermissionDao = injector.getInstance(UserDevfilePermissionDao.class); signatureKeyDao = injector.getInstance(SignatureKeyDao.class); freeResourcesLimitDao = injector.getInstance(FreeResourcesLimitDao.class); organizationManager = injector.getInstance(OrganizationManager.class); @@ -334,10 +353,18 @@ public void shouldDeleteAllTheEntitiesWhenUserIsDeleted() throws Exception { assertTrue(preferenceDao.getPreferences(user.getId()).isEmpty()); assertTrue(sshDao.get(user.getId()).isEmpty()); assertTrue(workspaceDao.getByNamespace(account.getName(), 30, 0).isEmpty()); + assertTrue(userDevfileDao.getByNamespace(account.getName(), 30, 0).isEmpty()); assertTrue(factoryDao.getByUser(user.getId(), 30, 0).isEmpty()); // Check workers and parent entity is removed assertTrue(workspaceDao.getByNamespace(user2.getId(), 30, 0).isEmpty()); + assertTrue(userDevfileDao.getByNamespace(user2.getId(), 30, 0).isEmpty()); assertEquals(workerDao.getWorkers(workspace3.getId(), 1, 0).getTotalItemsCount(), 0); + assertNull( + notFoundToNull( + () -> + userDevfilePermissionDao.getUserDevfilePermission(devfile.getId(), user2.getId()))); + assertFalse(userDevfileDao.getById(devfile.getId()).isPresent()); + // Permissions are removed // Non-removed user permissions and stack are present // Check existence of organizations @@ -391,6 +418,11 @@ public void shouldRollbackTransactionWhenFailedToRemoveAnyOfEntries( assertNotNull(notFoundToNull(() -> organizationManager.getById(childOrganization.getId()))); assertNotNull(notFoundToNull(() -> organizationManager.getById(organization2.getId()))); assertNotNull(notFoundToNull(() -> signatureKeyDao.get(workspace2.getId()))); + assertTrue(userDevfileDao.getById(devfile.getId()).isPresent()); + assertNotNull( + notFoundToNull( + () -> + userDevfilePermissionDao.getUserDevfilePermission(devfile.getId(), user2.getId()))); assertFalse( organizationResourcesDistributor.getResourcesCaps(childOrganization.getId()).isEmpty()); wipeTestData(); @@ -399,7 +431,11 @@ public void shouldRollbackTransactionWhenFailedToRemoveAnyOfEntries( @DataProvider(name = "beforeRemoveRollbackActions") public Object[][] beforeRemoveActions() { return new Class[][] { - {RemoveOrganizationOnLastUserRemovedEventSubscriber.class, BeforeUserRemovedEvent.class} + {RemoveOrganizationOnLastUserRemovedEventSubscriber.class, BeforeUserRemovedEvent.class}, + { + RemoveUserDevfilePermissionsBeforeUserRemovedEventSubscriber.class, + BeforeUserRemovedEvent.class + } }; } @@ -453,6 +489,13 @@ private void createTestData() organizationResourcesDistributor.capResources( childOrganization.getId(), singletonList(new ResourceImpl(RamResourceType.ID, 1024, RamResourceType.UNIT))); + + userDevfileDao.create( + devfile = TestObjectsFactory.createUserDevfile("id-dev1", "devfile1", account)); + userDevfilePermissionDao.store( + devfilePermission = + new UserDevfilePermissionImpl( + devfile.getId(), user2.getId(), ImmutableList.of(READ, DELETE, UPDATE))); } private void prepareCreator(String userId) { @@ -477,6 +520,9 @@ private void wipeTestData() throws ConflictException, ServerException, NotFoundE workerDao.removeWorker(workspace3.getId(), user2.getId()); + userDevfilePermissionDao.removeUserDevfilePermission(devfile.getId(), user2.getId()); + userDevfileDao.remove(devfile.getId()); + factoryDao.remove(factory1.getId()); factoryDao.remove(factory2.getId()); diff --git a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/TestObjectsFactory.java b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/TestObjectsFactory.java index c2a2e6bb7a2..f12bfac0635 100644 --- a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/TestObjectsFactory.java +++ b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/TestObjectsFactory.java @@ -12,6 +12,8 @@ package org.eclipse.che.multiuser.integration.jpa.cascaderemoval; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import com.google.common.collect.ImmutableMap; import java.security.KeyPair; @@ -22,6 +24,7 @@ import java.util.Map; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; @@ -29,6 +32,16 @@ import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl; import org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl; import org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl; @@ -112,5 +125,139 @@ public static SignatureKeyPairImpl createSignatureKeyPair(String workspaceId) return new SignatureKeyPairImpl(workspaceId, pair.getPublic(), pair.getPrivate()); } + public static UserDevfileImpl createUserDevfile(String id, String name, Account account) { + return new UserDevfileImpl(id, account, name, "descr", createDevfile(name)); + } + + public static DevfileImpl createDevfile(String name) { + + SourceImpl source1 = + new SourceImpl( + "type1", + "http://location", + "branch1", + "point1", + "tag1", + "commit1", + "sparseCheckoutDir1"); + ProjectImpl project1 = new ProjectImpl("project1", source1, "path1"); + + SourceImpl source2 = + new SourceImpl( + "type2", + "http://location", + "branch2", + "point2", + "tag2", + "commit2", + "sparseCheckoutDir2"); + ProjectImpl project2 = new ProjectImpl("project2", source2, "path2"); + + ActionImpl action1 = + new ActionImpl("exec1", "component1", "run.sh", "/home/user/1", null, null); + ActionImpl action2 = + new ActionImpl("exec2", "component2", "run.sh", "/home/user/2", null, null); + + CommandImpl command1 = + new CommandImpl(name + "-1", singletonList(action1), singletonMap("attr1", "value1"), null); + CommandImpl command2 = + new CommandImpl(name + "-2", singletonList(action2), singletonMap("attr2", "value2"), null); + + EntrypointImpl entrypoint1 = + new EntrypointImpl( + "parentName1", + singletonMap("parent1", "selector1"), + "containerName1", + asList("command1", "command2"), + asList("arg1", "arg2")); + + EntrypointImpl entrypoint2 = + new EntrypointImpl( + "parentName2", + singletonMap("parent2", "selector2"), + "containerName2", + asList("command3", "command4"), + asList("arg3", "arg4")); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume1 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name1", "path1"); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume2 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name2", "path2"); + + EnvImpl env1 = new EnvImpl("name1", "value1"); + EnvImpl env2 = new EnvImpl("name2", "value2"); + + EndpointImpl endpoint1 = new EndpointImpl("name1", 1111, singletonMap("key1", "value1")); + EndpointImpl endpoint2 = new EndpointImpl("name2", 2222, singletonMap("key2", "value2")); + + ComponentImpl component1 = + new ComponentImpl( + "kubernetes", + "component1", + "eclipse/che-theia/0.0.1", + ImmutableMap.of("java.home", "/home/user/jdk11"), + "https://mysite.com/registry/somepath1", + "/dev.yaml", + "refcontent1", + ImmutableMap.of("app.kubernetes.io/component", "db"), + asList(entrypoint1, entrypoint2), + "image", + "256G", + "128M", + "2", + "130m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + component1.setSelector(singletonMap("key1", "value1")); + + ComponentImpl component2 = + new ComponentImpl( + "kubernetes", + "component2", + "eclipse/che-theia/0.0.1", + ImmutableMap.of( + "java.home", + "/home/user/jdk11aertwertert", + "java.boolean", + true, + "java.long", + 123444L), + "https://mysite.com/registry/somepath2", + "/dev.yaml", + "refcontent2", + ImmutableMap.of("app.kubernetes.io/component", "webapp"), + asList(entrypoint1, entrypoint2), + "image", + "256G", + "256M", + "3", + "180m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + component2.setSelector(singletonMap("key2", "value2")); + + DevfileImpl devfile = + new DevfileImpl( + "0.0.1", + asList(project1, project2), + asList(component1, component2), + asList(command1, command2), + singletonMap("attribute1", "value1"), + new MetadataImpl(name)); + + return devfile; + } + private TestObjectsFactory() {} } diff --git a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/resources/META-INF/persistence.xml b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/resources/META-INF/persistence.xml index d76a333e66c..ae8dc36ff38 100644 --- a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/resources/META-INF/persistence.xml +++ b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/resources/META-INF/persistence.xml @@ -74,6 +74,8 @@ org.eclipse.che.multiuser.resource.spi.impl.ResourceImpl org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl + org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl + org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl true diff --git a/multiuser/integration-tests/che-multiuser-mysql-tck/pom.xml b/multiuser/integration-tests/che-multiuser-mysql-tck/pom.xml index 3df4a9d5d71..a3605822f5d 100644 --- a/multiuser/integration-tests/che-multiuser-mysql-tck/pom.xml +++ b/multiuser/integration-tests/che-multiuser-mysql-tck/pom.xml @@ -47,6 +47,11 @@ che-multiuser-api-resource tests + + org.eclipse.che.multiuser + che-multiuser-permission-devfile + tests + org.eclipse.che.multiuser che-multiuser-permission-workspace @@ -112,6 +117,11 @@ che-multiuser-machine-authentication test + + org.eclipse.che.multiuser + che-multiuser-permission-devfile + test + org.eclipse.che.multiuser che-multiuser-permission-workspace @@ -165,12 +175,6 @@ org.apache.maven.plugins maven-dependency-plugin - - analyze - - true - - unpack-dependencies @@ -178,9 +182,10 @@ ${project.build.testOutputDirectory} che-multiuser-api-resource, - che-multiuser-api-organization, - che-multiuser-api-permission, - che-multiuser-permission-workspace + che-multiuser-api-organization, + che-multiuser-api-permission, + che-multiuser-permission-devfile, + che-multiuser-permission-workspace test @@ -192,7 +197,7 @@ che-core-sql-schema, - che-multiuser-sql-schema + che-multiuser-sql-schema che-schema/ ${project.build.directory} diff --git a/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/java/MultiuserMySqlTckModule.java b/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/java/MultiuserMySqlTckModule.java index b9ed9f510d8..d4f8d46d54f 100644 --- a/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/java/MultiuserMySqlTckModule.java +++ b/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/java/MultiuserMySqlTckModule.java @@ -38,6 +38,7 @@ import javax.persistence.NoResultException; import javax.persistence.spi.PersistenceUnitTransactionType; import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.commons.test.tck.JpaCleaner; @@ -68,6 +69,11 @@ import org.eclipse.che.multiuser.organization.spi.jpa.JpaMemberDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDistributedResourcesDao; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; import org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl; import org.eclipse.che.multiuser.permission.workspace.server.spi.WorkerDao; import org.eclipse.che.multiuser.permission.workspace.server.spi.jpa.JpaWorkerDao; @@ -159,6 +165,9 @@ protected void configure() { bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(SignatureKeyPairImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + // dao bind(OrganizationDao.class).to(JpaOrganizationDao.class); bind(OrganizationDistributedResourcesDao.class) @@ -171,6 +180,12 @@ protected void configure() { bind(new TypeLiteral>() {}).to(JpaMemberDao.class); bind(new TypeLiteral>() {}).to(OrganizationDomain.class); + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(new TypeLiteral>() {}) + .to(UserDevfileDomain.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfilePermission.class)); + // SHA-512 ecnryptor is faster than PBKDF2 so it is better for testing bind(PasswordEncryptor.class).to(SHA512PasswordEncryptor.class).in(Singleton.class); diff --git a/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/resources/META-INF/persistence.xml b/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/resources/META-INF/persistence.xml index b2867c60161..9ccbdb08e04 100644 --- a/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/resources/META-INF/persistence.xml +++ b/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/resources/META-INF/persistence.xml @@ -43,6 +43,8 @@ org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl org.eclipse.che.api.workspace.server.devfile.SerializableConverter + org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl + org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl org.eclipse.che.api.workspace.server.model.impl.CommandImpl org.eclipse.che.workspace.infrastructure.docker.snapshot.MachineSourceImpl diff --git a/multiuser/integration-tests/che-multiuser-postgresql-tck/pom.xml b/multiuser/integration-tests/che-multiuser-postgresql-tck/pom.xml index 556fd76a459..037c6caecab 100644 --- a/multiuser/integration-tests/che-multiuser-postgresql-tck/pom.xml +++ b/multiuser/integration-tests/che-multiuser-postgresql-tck/pom.xml @@ -47,6 +47,11 @@ che-multiuser-api-resource tests + + org.eclipse.che.multiuser + che-multiuser-permission-devfile + tests + org.eclipse.che.multiuser che-multiuser-permission-workspace @@ -107,6 +112,11 @@ che-multiuser-machine-authentication test + + org.eclipse.che.multiuser + che-multiuser-permission-devfile + test + org.eclipse.che.multiuser che-multiuser-permission-workspace @@ -174,6 +184,7 @@ che-multiuser-api-resource, che-multiuser-api-organization, che-multiuser-api-permission, + che-multiuser-permission-devfile, che-multiuser-permission-workspace test diff --git a/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/java/MultiuserPostgresqlTckModule.java b/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/java/MultiuserPostgresqlTckModule.java index 4f52b65f885..9e1437037c0 100644 --- a/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/java/MultiuserPostgresqlTckModule.java +++ b/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/java/MultiuserPostgresqlTckModule.java @@ -38,6 +38,7 @@ import javax.persistence.NoResultException; import javax.persistence.spi.PersistenceUnitTransactionType; import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.commons.test.tck.JpaCleaner; @@ -68,6 +69,11 @@ import org.eclipse.che.multiuser.organization.spi.jpa.JpaMemberDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDistributedResourcesDao; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; import org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl; import org.eclipse.che.multiuser.permission.workspace.server.spi.WorkerDao; import org.eclipse.che.multiuser.permission.workspace.server.spi.jpa.JpaWorkerDao; @@ -158,6 +164,9 @@ protected void configure() { bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(SignatureKeyPairImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + // dao bind(OrganizationDao.class).to(JpaOrganizationDao.class); bind(OrganizationDistributedResourcesDao.class) @@ -170,6 +179,12 @@ protected void configure() { bind(new TypeLiteral>() {}).to(JpaMemberDao.class); bind(new TypeLiteral>() {}).to(OrganizationDomain.class); + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(new TypeLiteral>() {}) + .to(UserDevfileDomain.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfilePermission.class)); + // SHA-512 ecnryptor is faster than PBKDF2 so it is better for testing bind(PasswordEncryptor.class).to(SHA512PasswordEncryptor.class).in(Singleton.class); diff --git a/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/resources/META-INF/persistence.xml b/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/resources/META-INF/persistence.xml index 12e02437e12..3df010aecc0 100644 --- a/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/resources/META-INF/persistence.xml +++ b/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/resources/META-INF/persistence.xml @@ -43,6 +43,8 @@ org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl org.eclipse.che.api.workspace.server.devfile.SerializableConverter + org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl + org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl org.eclipse.che.api.workspace.server.model.impl.CommandImpl org.eclipse.che.workspace.infrastructure.docker.snapshot.MachineSourceImpl From 0d1fbe641b37581d2a0fe2c9c4fd0ad7784276eb Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Thu, 17 Sep 2020 17:13:50 +0300 Subject: [PATCH 05/10] Devfile service assembly Signed-off-by: Sergii Kabashniuk --- assembly/assembly-wsmaster-war/pom.xml | 4 ++++ .../che/api/deploy/WsMasterModule.java | 9 +++++++- .../main/resources/META-INF/persistence.xml | 2 ++ pom.xml | 22 +++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/assembly/assembly-wsmaster-war/pom.xml b/assembly/assembly-wsmaster-war/pom.xml index ac000005499..6f6e820f5fe 100644 --- a/assembly/assembly-wsmaster-war/pom.xml +++ b/assembly/assembly-wsmaster-war/pom.xml @@ -123,6 +123,10 @@ org.eclipse.che.core che-core-api-core + + org.eclipse.che.core + che-core-api-devfile + org.eclipse.che.core che-core-api-factory diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index c2990208341..2a88929d85e 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -157,6 +157,8 @@ protected void configure() { bind(WorkspaceEntityProvider.class); bind(org.eclipse.che.api.workspace.server.TemporaryWorkspaceRemover.class); bind(org.eclipse.che.api.workspace.server.WorkspaceService.class); + bind(org.eclipse.che.api.devfile.server.DevfileService.class); + bind(org.eclipse.che.api.devfile.server.UserDevfileEntityProvider.class); install(new FactoryModuleBuilder().build(ServersCheckerFactory.class)); Multibinder internalEnvironmentProvisioners = @@ -288,6 +290,7 @@ private void configureSingleUserMode(Map persistenceProperties) install(new org.eclipse.che.api.user.server.jpa.UserJpaModule()); install(new org.eclipse.che.api.workspace.server.jpa.WorkspaceJpaModule()); + install(new org.eclipse.che.api.devfile.server.jpa.UserDevfileJpaModule()); bind(org.eclipse.che.api.user.server.CheUserCreator.class); @@ -344,6 +347,11 @@ private void configureMultiUserMode( new org.eclipse.che.multiuser.permission.workspace.server.jpa .MultiuserWorkspaceJpaModule()); install(new MultiUserWorkspaceActivityModule()); + install( + new org.eclipse.che.multiuser.permission.devfile.server.jpa + .MultiuserUserDevfileJpaModule()); + install( + new org.eclipse.che.multiuser.permission.devfile.server.UserDevfileApiPermissionsModule()); // Permission filters bind(org.eclipse.che.multiuser.permission.system.SystemServicePermissionsFilter.class); @@ -358,7 +366,6 @@ private void configureMultiUserMode( bind(org.eclipse.che.multiuser.permission.user.UserServicePermissionsFilter.class); bind(org.eclipse.che.multiuser.permission.logger.LoggerServicePermissionsFilter.class); - bind(org.eclipse.che.multiuser.permission.devfile.DevfilePermissionsFilter.class); bind(org.eclipse.che.multiuser.permission.workspace.activity.ActivityPermissionsFilter.class); bind(AdminPermissionInitializer.class).asEagerSingleton(); bind( diff --git a/assembly/assembly-wsmaster-war/src/main/resources/META-INF/persistence.xml b/assembly/assembly-wsmaster-war/src/main/resources/META-INF/persistence.xml index ad62ad0180d..f4ae661ee33 100644 --- a/assembly/assembly-wsmaster-war/src/main/resources/META-INF/persistence.xml +++ b/assembly/assembly-wsmaster-war/src/main/resources/META-INF/persistence.xml @@ -58,12 +58,14 @@ org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl org.eclipse.che.api.workspace.server.devfile.SerializableConverter + org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl org.eclipse.che.api.ssh.server.model.impl.SshPairImpl org.eclipse.che.multiuser.api.permission.server.model.impl.SystemPermissionsImpl org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl + org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl org.eclipse.che.multiuser.resource.spi.impl.ResourceImpl diff --git a/pom.xml b/pom.xml index b7127956e7f..8980a358322 100644 --- a/pom.xml +++ b/pom.xml @@ -701,6 +701,22 @@ che-core-api-core ${che.version} + + org.eclipse.che.core + che-core-api-devfile + ${che.version} + + + org.eclipse.che.core + che-core-api-devfile + ${che.version} + tests + + + org.eclipse.che.core + che-core-api-devfile-shared + ${che.version} + org.eclipse.che.core che-core-api-dto @@ -1113,6 +1129,12 @@ che-multiuser-permission-devfile ${che.version} + + org.eclipse.che.multiuser + che-multiuser-permission-devfile + ${che.version} + tests + org.eclipse.che.multiuser che-multiuser-permission-logger From d790930d3e02013ab4ad2596f4817b2d349cd4c1 Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Tue, 29 Sep 2020 17:44:38 +0300 Subject: [PATCH 06/10] Applying reviewers suggestions Signed-off-by: Sergii Kabashniuk --- .../model/workspace/devfile/UserDevfile.java | 2 +- .../jpa/MultiuserUserDevfileJpaModule.java | 6 +- .../server/spi/UserDevfilePermissionDao.java | 18 +++--- .../spi/jpa/JpaUserDevfilePermissionDao.java | 10 ++- .../UserDevfilePermissionsFilterTest.java | 2 +- .../devfile/server/spi/jpa/JpaTckModule.java | 22 ------- .../jpa/JpaUserDevfilePermissionDaoTest.java | 2 +- ...UserDevfileRemovedEventSubscriberTest.java | 8 +-- .../api/devfile/server/DevfileService.java | 2 +- ...leBeforeAccountRemovedEventSubscriber.java | 63 +++++++++++++++++++ .../server/UserDevfileEntityProvider.java | 4 +- .../devfile/server/jpa/JpaUserDevfileDao.java | 57 ++++------------- .../server/jpa/UserDevfileJpaModule.java | 4 +- .../server/model/impl/UserDevfileImpl.java | 2 +- .../devfile/server/spi/UserDevfileDao.java | 4 +- .../devfile/server/DevfileServiceTest.java | 14 ++--- .../server/spi/tck/UserDevfileDaoTest.java | 4 +- .../api/factory/server/jpa/JpaFactoryDao.java | 2 +- 18 files changed, 112 insertions(+), 114 deletions(-) create mode 100644 wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/RemoveUserDevfileBeforeAccountRemovedEventSubscriber.java diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java index b8586dc5fe8..67b3e233141 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java @@ -11,7 +11,7 @@ */ package org.eclipse.che.api.core.model.workspace.devfile; -/** Devfile that persisted in permanent storage. */ +/** Devfile that is persisted in permanent storage. */ public interface UserDevfile { /** Returns the identifier of this persisted devfile instance. It is mandatory and unique. */ String getId(); diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java index b56fe4a988f..2dad7dc7b01 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java @@ -14,7 +14,7 @@ import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; import com.google.inject.multibindings.Multibinder; -import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao.RemoveUserDevfileBeforeAccountRemovedEventSubscriber; +import org.eclipse.che.api.devfile.server.RemoveUserDevfileBeforeAccountRemovedEventSubscriber; import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions; @@ -23,7 +23,7 @@ import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; -import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriber; import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserRemovedEventSubscriber; import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.MultiuserJpaUserDevfileDao; @@ -35,7 +35,7 @@ protected void configure() { bind(UserDevfileDao.class).to(MultiuserJpaUserDevfileDao.class); bind(RemoveUserDevfileBeforeAccountRemovedEventSubscriber.class).asEagerSingleton(); - bind(RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber.class) + bind(RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriber.class) .asEagerSingleton(); bind(RemoveUserDevfilePermissionsBeforeUserRemovedEventSubscriber.class).asEagerSingleton(); diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java index 402eae79e55..673db3ab110 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java @@ -38,10 +38,10 @@ Optional store(UserDevfilePermissionImpl userDevfileP * @param userDevfileId user devfile identifier * @param userId user identifier * @return userDevfilePermissions instance, never null - * @throws NullPointerException when {@code workspace} or {@code user} is null - * @throws NotFoundException when worker with given {@code workspace} and {@code user} was not - * found - * @throws ServerException when any other error occurs during worker fetching + * @throws NullPointerException when {@code userDevfileId} or {@code userId} is null + * @throws NotFoundException when permission with given {@code userDevfileId} and {@code userId} + * was not found + * @throws ServerException when any other error occurs during permission fetching */ UserDevfilePermissionImpl getUserDevfilePermission(String userDevfileId, String userId) throws ServerException, NotFoundException; @@ -49,12 +49,12 @@ UserDevfilePermissionImpl getUserDevfilePermission(String userDevfileId, String /** * Removes userDevfilePermissions * - *

Doesn't throw an exception when userDevfilePermissions with given {@code UserDevfile} and - * {@code user} does not exist + *

Doesn't throw an exception when userDevfilePermissions with given {@code userDevfileId} and + * {@code userId} does not exist * * @param userDevfileId workspace identifier * @param userId user identifier - * @throws NullPointerException when {@code UserDevfile} or {@code user} is null + * @throws NullPointerException when {@code userDevfileId} or {@code userId} is null * @throws ServerException when any other error occurs during userDevfilePermissions removing */ void removeUserDevfilePermission(String userDevfileId, String userId) throws ServerException; @@ -66,7 +66,7 @@ UserDevfilePermissionImpl getUserDevfilePermission(String userDevfileId, String * @param maxItems the maximum number of userDevfilePermissions to return * @param skipCount the number of userDevfilePermissions to skip * @return list of userDevfilePermissions instance - * @throws NullPointerException when {@code userDevfile} is null + * @throws NullPointerException when {@code userDevfileId} is null * @throws ServerException when any other error occurs during userDevfilePermissions fetching */ Page getUserDevfilePermission( @@ -77,7 +77,7 @@ Page getUserDevfilePermission( * * @param userId user identifier * @return list of UserDevfilePermissions instance - * @throws NullPointerException when {@code user} is null + * @throws NullPointerException when {@code userId} is null * @throws ServerException when any other error occurs during UserDevfilePermissions fetching */ List getUserDevfilePermissionByUser(String userId) diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java index 061e247feaa..6acd4c249ce 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java @@ -93,12 +93,12 @@ public Page getByInstance( .stream() .map(UserDevfilePermissionImpl::new) .collect(toList()); - final Long workersCount = + final Long permissionsCount = entityManager .createNamedQuery("UserDevfilePermission.getCountByUserDevfileId", Long.class) .setParameter("userDevfileId", instanceId) .getSingleResult(); - return new Page<>(permissions, skipCount, maxItems, workersCount); + return new Page<>(permissions, skipCount, maxItems, permissionsCount); } catch (RuntimeException e) { throw new ServerException(e.getLocalizedMessage(), e); } @@ -111,9 +111,7 @@ protected UserDevfilePermissionImpl getEntity(String userId, String instanceId) return doGet(userId, instanceId); } catch (NoResultException e) { throw new NotFoundException( - format( - "User devfile permission for devfile '%s' with id '%s' was not found.", - instanceId, userId)); + format("User %s does not have permissions assigned to devfile %s.", instanceId, userId)); } catch (RuntimeException e) { throw new ServerException(e.getMessage(), e); } @@ -173,7 +171,7 @@ protected List doGetByUser(@Nullable String userId) } @Singleton - public static class RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber + public static class RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriber extends CascadeEventSubscriber { private static final int PAGE_SIZE = 100; diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java index 47c9fbf8374..8d62a00502e 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java @@ -129,7 +129,7 @@ public void shouldNotCheckAnyPermissionOnDevfileSearch() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() - .get(SECURE_PATH + "/devfile/list"); + .get(SECURE_PATH + "/devfile/search"); // then assertEquals(response.getStatusCode(), 200); verifyZeroInteractions(subject); diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java index d13046c20b6..953080e1e48 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java @@ -18,16 +18,6 @@ import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.devfile.SerializableConverter; -import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; -import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; -import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; -import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; -import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; -import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; -import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl; -import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; -import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; -import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; @@ -66,17 +56,7 @@ protected void configure() { .addEntityClasses( AccountImpl.class, UserImpl.class, - WorkspaceImpl.class, - WorkspaceConfigImpl.class, - ProjectConfigImpl.class, - EnvironmentImpl.class, UserDevfilePermissionImpl.class, - MachineConfigImpl.class, - SourceStorageImpl.class, - ServerConfigImpl.class, - CommandImpl.class, - RecipeImpl.class, - VolumeImpl.class, // devfile ActionImpl.class, org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl.class, @@ -89,8 +69,6 @@ protected void configure() { SourceImpl.class, UserDevfileImpl.class, org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl.class) - .addEntityClass( - "org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute") .addClass(SerializableConverter.class) .setExceptionHandler(H2ExceptionHandler.class) .build()); diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java index dce67bffdc6..9fc62fa4757 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java @@ -77,7 +77,7 @@ public void shouldThrowServerExceptionOnExistsWhenRuntimeExceptionOccursInDoGetM manager.clear(); final UserDevfileImpl userDevfile = TestObjectGenerator.createUserDevfile(); - // Persist the userdevfule + // Persist the userdevfile manager.getTransaction().begin(); manager.persist(userDevfile); manager.getTransaction().commit(); diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java index d3f4b5b1ecb..3b1a227b6c2 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java @@ -26,21 +26,21 @@ import org.eclipse.che.commons.test.tck.TckResourcesCleaner; import org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator; import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; -import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriber; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -/** Tests for {@link RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber} */ +/** Tests for {@link RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriber} */ public class RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest { private TckResourcesCleaner tckResourcesCleaner; private EntityManager manager; private JpaUserDevfilePermissionDao userDevfilePermissionsDao; private JpaUserDevfileDao userDevfileDao; - private RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber subscriber; + private RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriber subscriber; private UserDevfileImpl userDevfile; private UserDevfilePermissionImpl[] userDevfilePermissions; @@ -71,7 +71,7 @@ public void setupEntities() throws Exception { userDevfileDao = injector.getInstance(JpaUserDevfileDao.class); subscriber = injector.getInstance( - RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber.class); + RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriber.class); subscriber.subscribe(); tckResourcesCleaner = injector.getInstance(TckResourcesCleaner.class); } diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileService.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileService.java index 7de22637947..2641e38d66c 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileService.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DevfileService.java @@ -179,7 +179,7 @@ public UserDevfileDto getById( } @GET - @Path("list") + @Path("search") @Produces(APPLICATION_JSON) @ApiOperation( value = "Get devfiles which user can read", diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/RemoveUserDevfileBeforeAccountRemovedEventSubscriber.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/RemoveUserDevfileBeforeAccountRemovedEventSubscriber.java new file mode 100644 index 00000000000..1d8e432d9ab --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/RemoveUserDevfileBeforeAccountRemovedEventSubscriber.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server; + +import static org.eclipse.che.api.core.Pages.iterate; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.account.event.BeforeAccountRemovedEvent; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.core.db.cascade.CascadeEventSubscriber; + +/** + * An event listener that is removing all {@link UserDevfile}s that belong to the account that is + * going to be removed. + */ +@Singleton +public class RemoveUserDevfileBeforeAccountRemovedEventSubscriber + extends CascadeEventSubscriber { + + private final EventService eventService; + private final UserDevfileManager userDevfileManager; + + @Inject + public RemoveUserDevfileBeforeAccountRemovedEventSubscriber( + EventService eventService, UserDevfileManager userDevfileManager) { + this.eventService = eventService; + this.userDevfileManager = userDevfileManager; + } + + @PostConstruct + public void subscribe() { + eventService.subscribe(this, BeforeAccountRemovedEvent.class); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this, BeforeAccountRemovedEvent.class); + } + + @Override + public void onCascadeEvent(BeforeAccountRemovedEvent event) throws Exception { + for (UserDevfile userDevfile : + iterate( + (maxItems, skipCount) -> + userDevfileManager.getByNamespace( + event.getAccount().getName(), maxItems, skipCount))) { + userDevfileManager.removeUserDevfile(userDevfile.getId()); + } + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java index 9cf9e0cb718..257bfe5107e 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java @@ -51,8 +51,8 @@ public class UserDevfileEntityProvider implements MessageBodyReader, MessageBodyWriter { - private DevfileParser devfileParser; - private ObjectMapper mapper = new ObjectMapper(); + private final DevfileParser devfileParser; + private final ObjectMapper mapper = new ObjectMapper(); @Inject public UserDevfileEntityProvider(DevfileParser devfileParser) { diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java index 2dacb895397..8587a1b6f9c 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java @@ -16,7 +16,6 @@ import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; -import static org.eclipse.che.api.core.Pages.iterate; import static org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao.UserDevfileSearchQueryBuilder.newBuilder; import com.google.common.annotations.Beta; @@ -29,14 +28,11 @@ import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; -import org.eclipse.che.account.event.BeforeAccountRemovedEvent; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountDao; import org.eclipse.che.api.core.ConflictException; @@ -45,22 +41,17 @@ import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.devfile.server.UserDevfileManager; import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent; import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.commons.lang.Pair; -import org.eclipse.che.core.db.cascade.CascadeEventSubscriber; import org.eclipse.che.core.db.jpa.DuplicateKeyException; import org.eclipse.che.core.db.jpa.IntegrityConstraintViolationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** JPA based implementation of {@link UserDevfileDao}. */ @Singleton @Beta public class JpaUserDevfileDao implements UserDevfileDao { - private static final Logger LOG = LoggerFactory.getLogger(JpaUserDevfileDao.class); protected final Provider managerProvider; protected final AccountDao accountDao; @@ -78,8 +69,7 @@ public JpaUserDevfileDao( } @Override - public UserDevfile create(UserDevfile userDevfile) - throws ConflictException, ServerException, NotFoundException { + public UserDevfile create(UserDevfile userDevfile) throws ConflictException, ServerException { requireNonNull(userDevfile); try { Account account = accountDao.getByName(userDevfile.getNamespace()); @@ -89,13 +79,18 @@ public UserDevfile create(UserDevfile userDevfile) } catch (DuplicateKeyException ex) { throw new ConflictException( format( - "Devfile with name '%s' already exists in current account '%s'", + "Devfile with name '%s' already exists in the specified account '%s'", userDevfile.getName(), userDevfile.getNamespace())); } catch (IntegrityConstraintViolationException ex) { throw new ConflictException( - "Could not create devfile with creator that refers on non-existent user"); + "Could not create devfile with creator that refers to a non-existent user"); } catch (RuntimeException ex) { throw new ServerException(ex.getMessage(), ex); + } catch (NotFoundException e) { + throw new ConflictException( + format( + "Not able to create devfile in requested namespace %s bacause it is not found", + userDevfile.getNamespace())); } } @@ -200,7 +195,7 @@ protected Page doGetDevfiles( filter.stream().filter(p -> !p.first.equalsIgnoreCase("name")).collect(toList()); if (!invalidFilter.isEmpty()) { throw new IllegalArgumentException( - "Filtering allowed only on `name`. But got: " + invalidFilter); + "Filtering allowed only by name but got: " + invalidFilter); } } List> effectiveOrder = DEFAULT_ORDER; @@ -211,8 +206,7 @@ protected Page doGetDevfiles( .filter(p -> !p.first.equalsIgnoreCase("name") && !p.first.equalsIgnoreCase("id")) .collect(toList()); if (!invalidOrder.isEmpty()) { - throw new IllegalArgumentException( - "Order allowed only on `name`. But got: " + invalidOrder); + throw new IllegalArgumentException("Order allowed only by name but got: " + invalidOrder); } List> invalidSortOrder = @@ -222,7 +216,7 @@ protected Page doGetDevfiles( .collect(Collectors.toList()); if (!invalidSortOrder.isEmpty()) { throw new IllegalArgumentException( - "Invalid sort order direction. Possible values 'asc' or 'desc'. But got: " + "Invalid sort order direction. Possible values are 'asc' or 'desc' but got: " + invalidSortOrder); } effectiveOrder = order; @@ -388,33 +382,4 @@ public TypedQuery buildSelectItemsQuery() { return typedQuery; } } - - @Singleton - public static class RemoveUserDevfileBeforeAccountRemovedEventSubscriber - extends CascadeEventSubscriber { - - @Inject private EventService eventService; - @Inject private UserDevfileManager userDevfileManager; - - @PostConstruct - public void subscribe() { - eventService.subscribe(this, BeforeAccountRemovedEvent.class); - } - - @PreDestroy - public void unsubscribe() { - eventService.unsubscribe(this, BeforeAccountRemovedEvent.class); - } - - @Override - public void onCascadeEvent(BeforeAccountRemovedEvent event) throws Exception { - for (UserDevfile userDevfile : - iterate( - (maxItems, skipCount) -> - userDevfileManager.getByNamespace( - event.getAccount().getName(), maxItems, skipCount))) { - userDevfileManager.removeUserDevfile(userDevfile.getId()); - } - } - } } diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java index f705408968a..e33ec933c9e 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java @@ -13,6 +13,7 @@ import com.google.common.annotations.Beta; import com.google.inject.AbstractModule; +import org.eclipse.che.api.devfile.server.RemoveUserDevfileBeforeAccountRemovedEventSubscriber; import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; @Beta @@ -20,7 +21,6 @@ public class UserDevfileJpaModule extends AbstractModule { @Override protected void configure() { bind(UserDevfileDao.class).to(JpaUserDevfileDao.class); - bind(JpaUserDevfileDao.RemoveUserDevfileBeforeAccountRemovedEventSubscriber.class) - .asEagerSingleton(); + bind(RemoveUserDevfileBeforeAccountRemovedEventSubscriber.class).asEagerSingleton(); } } diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java index 16212380d4f..e623e2356b0 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java @@ -38,7 +38,7 @@ query = "SELECT d FROM UserDevfile d WHERE d.account.name = :namespace"), @NamedQuery( name = "UserDevfile.getByNamespaceCount", - query = "SELECT COUNT(d) " + "FROM UserDevfile d " + "WHERE d.account.name = :namespace "), + query = "SELECT COUNT(d) FROM UserDevfile d WHERE d.account.name = :namespace "), @NamedQuery(name = "UserDevfile.getAll", query = "SELECT d FROM UserDevfile d ORDER BY d.id"), @NamedQuery(name = "UserDevfile.getTotalCount", query = "SELECT COUNT(d) FROM UserDevfile d"), }) diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java index 7de64f58086..13872581b90 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java @@ -32,9 +32,9 @@ public interface UserDevfileDao { * @return created devfile * @throws NullPointerException when {@code devfile} is null * @throws ServerException when any other error occurs + * @throws ConflictException when required namespace is not found. */ - UserDevfile create(UserDevfile devfile) - throws ServerException, ConflictException, NotFoundException; + UserDevfile create(UserDevfile devfile) throws ServerException, ConflictException; /** * Updates devfile to the new entity, using replacement strategy. diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java index c6229b6957e..a7536ec5640 100644 --- a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java @@ -168,8 +168,6 @@ public void shouldFailToCreateInvalidUserDevfileFromJson( .contentType("application/json") .body(DtoFactory.getInstance().toJson(userDevfileDto)) .when() - .log() - .all() .post(SECURE_PATH + "/devfile"); assertEquals(response.getStatusCode(), 400); @@ -257,8 +255,6 @@ public void shouldBeAbleToUpdateUserDevfile() throws Exception { .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType(APPLICATION_JSON) .body(DtoFactory.getInstance().toJson(devfileDto)) - .log() - .all() .when() .put(SECURE_PATH + "/devfile/" + devfileDto.getId()); // then @@ -282,8 +278,6 @@ public void shouldFailToUpdateWithInvalidUserDevfile( .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType(APPLICATION_JSON) .body(DtoFactory.getInstance().toJson(userDevfileDto)) - .log() - .all() .when() .put(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); // then @@ -374,7 +368,7 @@ public void shouldGetUserDevfilesAvailableToUser() throws Exception { .when() .expect() .statusCode(200) - .get(SECURE_PATH + "/devfile/list"); + .get(SECURE_PATH + "/devfile/search"); // then final List res = unwrapDtoList(response, UserDevfileDto.class); assertEquals(res.size(), 1); @@ -399,7 +393,7 @@ public void shouldBeAbleToSetLimitAndOffsetOnUserDevfileSearch() throws Exceptio .when() .expect() .statusCode(200) - .get(SECURE_PATH + "/devfile/list"); + .get(SECURE_PATH + "/devfile/search"); // then verify(userDevfileManager).getUserDevfiles(eq(5), eq(52), anyList(), anyList()); } @@ -423,7 +417,7 @@ public void shouldBeAbleToSetFiltertOnUserDevfileSearch() throws Exception { .when() .expect() .statusCode(200) - .get(SECURE_PATH + "/devfile/list"); + .get(SECURE_PATH + "/devfile/search"); // then Class>> listClass = (Class>>) (Class) ArrayList.class; @@ -450,7 +444,7 @@ public void shouldBeAbleToSetOrderOnUserDevfileSearch() throws Exception { .when() .expect() .statusCode(200) - .get(SECURE_PATH + "/devfile/list"); + .get(SECURE_PATH + "/devfile/search"); // then Class>> listClass = (Class>>) (Class) ArrayList.class; diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java index 69f4b2d6833..910206332f2 100644 --- a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java @@ -474,8 +474,8 @@ public void shouldNotAllowNegativeItemsToSkipToSearch() @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = - "Invalid sort order direction\\. Possible values 'asc' or 'desc'\\." - + " But got: \\[\\{first=name, second=ddd}, \\{first=id, second=bla}]") + "Invalid sort order direction\\. Possible values are 'asc' or 'desc'" + + " but got: \\[\\{first=name, second=ddd}, \\{first=id, second=bla}]") public void shouldFailOnInvalidSortOrder() throws ServerException, NotFoundException, ConflictException { // given diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java index b5ee0d066b9..d9c0ed08ecd 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java @@ -64,7 +64,7 @@ public FactoryImpl create(FactoryImpl factory) throws ConflictException, ServerE format("Factory with name '%s' already exists for current user", factory.getName())); } catch (IntegrityConstraintViolationException ex) { throw new ConflictException( - "Could not create factory with creator that refers on non-existent user"); + "Could not create factory with creator that refers to a non-existent user"); } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } From 50e9f0944af36947fdd80184ffa2a51b4616f362 Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Tue, 29 Sep 2020 18:16:24 +0300 Subject: [PATCH 07/10] Applying reviewers suggestions Signed-off-by: Sergii Kabashniuk --- .../devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java | 2 +- .../che/api/devfile/server/model/impl/UserDevfileImpl.java | 7 +++++++ .../main/resources/che-schema/7.20.0/1__userdevfile.sql | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java index 6297db8caec..8b99ace1fef 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java @@ -106,7 +106,7 @@ public TypedQuery buildCountQuery() { .append("LEFT JOIN permission.userDevfile userdevfile ") .append(filter); TypedQuery typedQuery = entityManager.createQuery(query.toString(), Long.class); - params.forEach((k, v) -> typedQuery.setParameter(k, v)); + params.forEach(typedQuery::setParameter); return typedQuery; } diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java index e623e2356b0..917bc8c9339 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java @@ -45,6 +45,13 @@ @Beta public class UserDevfileImpl implements UserDevfile { + /** + * In {@MetadataImpl} name is mandatory and generateName is transient. That is not suitable for + * UserDevfile because we need to handle situations when the name is not defined and generateName + * is defined. To workaround that original name and generateName stored in individual fields + * meta_name and meta_generated_name. But at the same time, we can't leave metadata filed null in + * devfile because of database hard constrain. To replace that FAKE_META is used. + */ private static final MetadataImpl FAKE_META = new MetadataImpl("name"); @Id diff --git a/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.20.0/1__userdevfile.sql b/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.20.0/1__userdevfile.sql index d30776e6975..2ea95dbc5e3 100644 --- a/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.20.0/1__userdevfile.sql +++ b/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.20.0/1__userdevfile.sql @@ -26,4 +26,4 @@ CREATE INDEX index_userdevfile_devfile_id ON userdevfile (devfile_id); CREATE INDEX index_userdevfile_name ON userdevfile(name); ALTER TABLE userdevfile ADD CONSTRAINT unq_userdevfile_0 UNIQUE (name, accountid); ALTER TABLE userdevfile ADD CONSTRAINT fx_userdevfile_accountid FOREIGN KEY (accountid) REFERENCES account (id); -ALTER TABLE userdevfile ADD CONSTRAINT fk_userdevfile_devfile_id FOREIGN KEY (devfile_id) REFERENCES devfile (id); \ No newline at end of file +ALTER TABLE userdevfile ADD CONSTRAINT fk_userdevfile_devfile_id FOREIGN KEY (devfile_id) REFERENCES devfile (id); From f8cb092e165d05d6da6502c03062f09f539dc479 Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Wed, 30 Sep 2020 12:23:49 +0300 Subject: [PATCH 08/10] Added explanation of what UserDevfile.namespace is Signed-off-by: Sergii Kabashniuk --- .../che/api/core/model/workspace/devfile/UserDevfile.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java index 67b3e233141..dc8ea33dfae 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java @@ -20,8 +20,9 @@ public interface UserDevfile { String getName(); /** - * Returns the namespace of the current devfile instance. Devfile name is unique for devfiles in - * the same namespace. + * Returns the namespace also known as the account name. This name can be the name of the + * organization or the name of the user to which this devfile belong to. Namespace and name + * uniquely identify devfile. */ String getNamespace(); From a7f9eb30fb93b9f82ba6a31faaf573023b9cc92c Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Thu, 1 Oct 2020 11:53:54 +0300 Subject: [PATCH 09/10] Cover posibility of using invalid search and order filed names Signed-off-by: Sergii Kabashniuk --- .../devfile/server/jpa/JpaUserDevfileDao.java | 37 +++++++--- .../server/spi/tck/UserDevfileDaoTest.java | 72 +++++++++++++++++++ 2 files changed, 100 insertions(+), 9 deletions(-) diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java index 8587a1b6f9c..e12492790f2 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java @@ -21,11 +21,13 @@ import com.google.common.annotations.Beta; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.inject.persist.Transactional; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.StringJoiner; import java.util.stream.Collectors; import javax.inject.Inject; @@ -56,9 +58,13 @@ public class JpaUserDevfileDao implements UserDevfileDao { protected final Provider managerProvider; protected final AccountDao accountDao; protected final EventService eventService; - + /** sorting order that would be used by default during search. */ public static final List> DEFAULT_ORDER = ImmutableList.of(new Pair<>("id", "ASC")); + /** Set of field that is eligible to use for search. */ + public static final Set VALID_SEARCH_FIELDS = ImmutableSet.of("name"); + /** Set of field that is eligible to use for sorting during search. */ + public static final Set VALID_ORDER_FIELDS = ImmutableSet.of("id", "name"); @Inject public JpaUserDevfileDao( @@ -192,10 +198,13 @@ protected Page doGetDevfiles( throws ServerException { if (filter != null && !filter.isEmpty()) { List> invalidFilter = - filter.stream().filter(p -> !p.first.equalsIgnoreCase("name")).collect(toList()); + filter + .stream() + .filter(p -> !VALID_SEARCH_FIELDS.contains(p.first.toLowerCase())) + .collect(toList()); if (!invalidFilter.isEmpty()) { throw new IllegalArgumentException( - "Filtering allowed only by name but got: " + invalidFilter); + "Filtering allowed only by " + VALID_SEARCH_FIELDS + " but got: " + invalidFilter); } } List> effectiveOrder = DEFAULT_ORDER; @@ -203,10 +212,11 @@ protected Page doGetDevfiles( List> invalidOrder = order .stream() - .filter(p -> !p.first.equalsIgnoreCase("name") && !p.first.equalsIgnoreCase("id")) + .filter(p -> !VALID_ORDER_FIELDS.contains(p.first.toLowerCase())) .collect(toList()); if (!invalidOrder.isEmpty()) { - throw new IllegalArgumentException("Order allowed only by name but got: " + invalidOrder); + throw new IllegalArgumentException( + "Order allowed only by " + VALID_ORDER_FIELDS + "¬ but got: " + invalidOrder); } List> invalidSortOrder = @@ -302,6 +312,8 @@ public static class UserDevfileSearchQueryBuilder { public UserDevfileSearchQueryBuilder(EntityManager entityManager) { this.entityManager = entityManager; this.params = new HashMap<>(); + this.filter = ""; + this.order = ""; } public static UserDevfileSearchQueryBuilder newBuilder(EntityManager entityManager) { @@ -320,13 +332,15 @@ public UserDevfileSearchQueryBuilder withSkipCount(int skipCount) { public UserDevfileSearchQueryBuilder withFilter(List> filter) { if (filter == null || filter.isEmpty()) { - this.filter = ""; return this; } final StringJoiner matcher = new StringJoiner(" AND ", " WHERE ", " "); int i = 0; for (Pair attribute : filter) { - + if (!VALID_SEARCH_FIELDS.contains(attribute.first.toLowerCase())) { + throw new IllegalArgumentException( + "Filtering allowed only by " + VALID_SEARCH_FIELDS + " but got: " + attribute.first); + } final String parameterName = "parameterName" + i++; if (attribute.second.startsWith("like:")) { params.put(parameterName, attribute.second.substring(5)); @@ -342,11 +356,16 @@ public UserDevfileSearchQueryBuilder withFilter(List> filte public UserDevfileSearchQueryBuilder withOrder(List> order) { if (order == null || order.isEmpty()) { - this.order = ""; return this; } final StringJoiner matcher = new StringJoiner(", ", " ORDER BY ", " "); - order.forEach(pair -> matcher.add("userdevfile." + pair.first + " " + pair.second)); + for (Pair pair : order) { + if (!VALID_ORDER_FIELDS.contains(pair.first.toLowerCase())) { + throw new IllegalArgumentException( + "Order allowed only by " + VALID_ORDER_FIELDS + " but got: " + pair.first); + } + matcher.add("userdevfile." + pair.first + " " + pair.second); + } this.order = matcher.toString(); return this; diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java index 910206332f2..486f944fa41 100644 --- a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java @@ -31,6 +31,8 @@ import java.util.Optional; import java.util.stream.Stream; import javax.inject.Inject; +import javax.inject.Provider; +import javax.persistence.EntityManager; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; @@ -40,6 +42,7 @@ import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.devfile.server.TestObjectGenerator; import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent; +import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao; import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.api.user.server.model.impl.UserImpl; @@ -54,6 +57,7 @@ import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.test.tck.TckListener; import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -81,6 +85,8 @@ public class UserDevfileDaoTest { @Inject private TckRepository accountRepo; + @Inject private Provider entityManagerProvider; + @BeforeMethod public void setUp() throws Exception { accounts = new AccountImpl[COUNT_OF_ACCOUNTS]; @@ -514,4 +520,70 @@ public void emptyListShouldBeReturnedWhenThereAreNoDevfilesInGivenNamespace() th public void shouldThrowNpeWhenGettingDevfilesByNullNamespace() throws Exception { userDevfileDaoDao.getByNamespace(null, 30, 0); } + + @Test(dataProvider = "validOrderFiled") + public void shouldTestValidOrderFileds(String filed) { + JpaUserDevfileDao.UserDevfileSearchQueryBuilder queryBuilder = + new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(null); + try { + queryBuilder.withOrder(ImmutableList.of(new Pair<>(filed, "blah"))); + } catch (IllegalArgumentException e) { + Assert.fail(filed + " is valid but failed"); + } + } + + @Test( + dataProvider = "invalidOrderField", + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Order allowed only by \\[id, name\\] but got: .*") + public void shouldTestInvalidOrderFileds(String filed) { + JpaUserDevfileDao.UserDevfileSearchQueryBuilder queryBuilder = + new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(null); + queryBuilder.withOrder(ImmutableList.of(new Pair<>(filed, "blah"))); + } + + @Test(dataProvider = "validSearchFiled") + public void shouldTestValidSearchFileds(String filed) { + JpaUserDevfileDao.UserDevfileSearchQueryBuilder queryBuilder = + new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(null); + try { + queryBuilder.withFilter(ImmutableList.of(new Pair<>(filed, "blah"))); + } catch (IllegalArgumentException e) { + Assert.fail(filed + " is valid but failed"); + } + } + + @Test( + dataProvider = "invalidSearchField", + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Filtering allowed only by \\[name\\] but got: .*") + public void shouldTestInvalidSearchFileds(String filed) { + JpaUserDevfileDao.UserDevfileSearchQueryBuilder queryBuilder = + new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(null); + queryBuilder.withFilter(ImmutableList.of(new Pair<>(filed, "blah"))); + } + + @DataProvider + public Object[][] validOrderFiled() { + return new Object[][] {{"id"}, {"Id"}, {"name"}, {"nAmE"}}; + } + + @DataProvider + public Object[][] invalidOrderField() { + return new Object[][] {{"devfile"}, {"meta_name"}, {"description"}, {"meta_generated_name"}}; + } + + @DataProvider + public Object[][] validSearchFiled() { + return new Object[][] { + {"name"}, {"NaMe"}, + }; + } + + @DataProvider + public Object[][] invalidSearchField() { + return new Object[][] { + {"id"}, {"devfile"}, {"ID"}, {"meta_name"}, {"description"}, {"meta_generated_name"} + }; + } } From b2580d034f227d5c0051dfa1dd016bae1fe4387a Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Mon, 5 Oct 2020 23:06:54 +0300 Subject: [PATCH 10/10] Fixup Signed-off-by: Sergii Kabashniuk --- .../devfile/server/UserDevfileCreatorPermissionsProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java index a9eea681b53..c9fa5a38c7d 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java +++ b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java @@ -49,7 +49,7 @@ void subscribe() { @PreDestroy void unsubscribe() { - eventService.subscribe(this); + eventService.unsubscribe(this); } @Override