From 720aab005106015646b2d13da6428656c0cda178 Mon Sep 17 00:00:00 2001 From: Max Shaposhnik Date: Tue, 8 Oct 2019 22:29:20 +0300 Subject: [PATCH] Revert "Devfile validation via message entity provider" (#14812) This reverts commit fc531ea0153dfbb211c3fb2ed996a93708cb5a23. --- .../che/api/deploy/WsMasterModule.java | 2 - .../che/api/core/rest/CoreRestModule.java | 1 - .../rest/WebApplicationExceptionMapper.java | 89 ----------- .../WorkspacePermissionsFilterTest.java | 2 +- .../server/WorkspaceEntityProvider.java | 138 ----------------- .../workspace/server/WorkspaceService.java | 34 ++++- .../server/devfile/DevfileEntityProvider.java | 140 ------------------ .../server/devfile/DevfileModule.java | 1 - .../server/WorkspaceEntityProviderTest.java | 59 -------- .../server/WorkspaceServiceTest.java | 53 ++++++- .../devfile/DevfileEntityProviderTest.java | 81 ---------- .../src/test/resources/devfile/devfile.json | 84 ----------- 12 files changed, 80 insertions(+), 604 deletions(-) delete mode 100644 core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/WebApplicationExceptionMapper.java delete mode 100644 wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java delete mode 100644 wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java delete mode 100644 wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java delete mode 100644 wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java delete mode 100644 wsmaster/che-core-api-workspace/src/test/resources/devfile/devfile.json 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 515cc29265..cbe7fa9c3d 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 @@ -42,7 +42,6 @@ import org.eclipse.che.api.user.server.jpa.JpaPreferenceDao; import org.eclipse.che.api.user.server.jpa.JpaUserDao; import org.eclipse.che.api.user.server.spi.PreferenceDao; import org.eclipse.che.api.user.server.spi.UserDao; -import org.eclipse.che.api.workspace.server.WorkspaceEntityProvider; import org.eclipse.che.api.workspace.server.WorkspaceLockService; import org.eclipse.che.api.workspace.server.WorkspaceStatusCache; import org.eclipse.che.api.workspace.server.devfile.DevfileModule; @@ -149,7 +148,6 @@ public class WsMasterModule extends AbstractModule { install(new DevfileModule()); - bind(WorkspaceEntityProvider.class); bind(org.eclipse.che.api.workspace.server.TemporaryWorkspaceRemover.class); bind(org.eclipse.che.api.workspace.server.WorkspaceService.class); install(new FactoryModuleBuilder().build(ServersCheckerFactory.class)); diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/CoreRestModule.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/CoreRestModule.java index 9af3634e2d..134d6c70fe 100644 --- a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/CoreRestModule.java +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/CoreRestModule.java @@ -25,6 +25,5 @@ public class CoreRestModule extends AbstractModule { bind(RuntimeExceptionMapper.class); bind(ApiInfo.class).toProvider(ApiInfoProvider.class); Multibinder.newSetBinder(binder(), Class.class, Names.named("che.json.ignored_classes")); - bind(WebApplicationExceptionMapper.class); } } diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/WebApplicationExceptionMapper.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/WebApplicationExceptionMapper.java deleted file mode 100644 index 80b1ab81e4..0000000000 --- a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/WebApplicationExceptionMapper.java +++ /dev/null @@ -1,89 +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.core.rest; - -import static org.eclipse.che.dto.server.DtoFactory.newDto; - -import javax.inject.Singleton; -import javax.ws.rs.BadRequestException; -import javax.ws.rs.ForbiddenException; -import javax.ws.rs.NotAcceptableException; -import javax.ws.rs.NotAllowedException; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.NotSupportedException; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; -import javax.ws.rs.ext.ExceptionMapper; -import javax.ws.rs.ext.Provider; -import org.eclipse.che.api.core.rest.shared.dto.ServiceError; -import org.eclipse.che.dto.server.DtoFactory; - -/** - * Mapper for the {@link WebApplicationException} exceptions. - * - * @author Max Shaposhnyk - */ -@Provider -@Singleton -public class WebApplicationExceptionMapper implements ExceptionMapper { - - @Override - public Response toResponse(WebApplicationException exception) { - - ServiceError error = newDto(ServiceError.class).withMessage(exception.getMessage()); - - if (exception instanceof BadRequestException) { - return Response.status(Response.Status.BAD_REQUEST) - .entity(DtoFactory.getInstance().toJson(error)) - .type(MediaType.APPLICATION_JSON) - .build(); - } else if (exception instanceof ForbiddenException) { - return Response.status(Response.Status.FORBIDDEN) - .entity(DtoFactory.getInstance().toJson(error)) - .type(MediaType.APPLICATION_JSON) - .build(); - } else if (exception instanceof NotFoundException) { - return Response.status(Response.Status.NOT_FOUND) - .entity(DtoFactory.getInstance().toJson(error)) - .type(MediaType.APPLICATION_JSON) - .build(); - } else if (exception instanceof NotAuthorizedException) { - return Response.status(Response.Status.UNAUTHORIZED) - .entity(DtoFactory.getInstance().toJson(error)) - .type(MediaType.APPLICATION_JSON) - .build(); - } else if (exception instanceof NotAcceptableException) { - return Response.status(Status.NOT_ACCEPTABLE) - .entity(DtoFactory.getInstance().toJson(error)) - .type(MediaType.APPLICATION_JSON) - .build(); - } else if (exception instanceof NotAllowedException) { - return Response.status(Status.METHOD_NOT_ALLOWED) - .entity(DtoFactory.getInstance().toJson(error)) - .type(MediaType.APPLICATION_JSON) - .build(); - } else if (exception instanceof NotSupportedException) { - return Response.status(Status.UNSUPPORTED_MEDIA_TYPE) - .entity(DtoFactory.getInstance().toJson(error)) - .type(MediaType.APPLICATION_JSON) - .build(); - } else { - return Response.serverError() - .entity(DtoFactory.getInstance().toJson(error)) - .type(MediaType.APPLICATION_JSON) - .build(); - } - } -} diff --git a/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/filters/WorkspacePermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/filters/WorkspacePermissionsFilterTest.java index 6d82197ac4..726a8aa5d8 100644 --- a/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/filters/WorkspacePermissionsFilterTest.java +++ b/multiuser/permission/che-multiuser-permission-workspace/src/test/java/org/eclipse/che/multiuser/permission/workspace/server/filters/WorkspacePermissionsFilterTest.java @@ -158,7 +158,7 @@ public class WorkspacePermissionsFilterTest { .post(SECURE_PATH + "/workspace/devfile?namespace=userok"); assertEquals(response.getStatusCode(), 204); - verify(workspaceService).create(any(DevfileDto.class), any(), any(), eq("userok"), any()); + verify(workspaceService).create(anyString(), any(), any(), eq("userok"), any()); verify(permissionsFilter).checkAccountPermissions("userok", AccountOperation.CREATE_WORKSPACE); verifyZeroInteractions(subject); } 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 deleted file mode 100644 index 15480e5462..0000000000 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java +++ /dev/null @@ -1,138 +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; - -import static javax.ws.rs.core.MediaType.APPLICATION_JSON; -import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -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.workspace.server.devfile.DevfileManager; -import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; -import org.eclipse.che.api.workspace.server.dto.DtoServerImpls.WorkspaceDtoImpl; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.dto.server.DtoFactory; - -/** - * Entity provider for {@link WorkspaceDto}. Performs schema validation of devfile part of the - * workspace before actual {@link DevfileDto} creation. - * - * @author Max Shaposhnyk - */ -@Singleton -@Provider -@Produces({APPLICATION_JSON}) -@Consumes({APPLICATION_JSON}) -public class WorkspaceEntityProvider - implements MessageBodyReader, MessageBodyWriter { - - private DevfileManager devfileManager; - private ObjectMapper mapper = new ObjectMapper(); - - @Inject - public WorkspaceEntityProvider(DevfileManager devfileManager) { - this.devfileManager = devfileManager; - SimpleModule module = new SimpleModule(); - module.addDeserializer(DevfileDto.class, new DevfileDtoDeserializer()); - mapper.registerModule(module); - } - - @Override - public boolean isReadable( - Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return type == WorkspaceDto.class; - } - - @Override - public WorkspaceDto readFrom( - Class type, - Type genericType, - Annotation[] annotations, - MediaType mediaType, - MultivaluedMap httpHeaders, - InputStream entityStream) - throws IOException, WebApplicationException { - return mapper - .readerFor(WorkspaceDtoImpl.class) - .without(DeserializationFeature.WRAP_EXCEPTIONS) - .readValue(entityStream); - } - - @Override - public boolean isWriteable( - Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return WorkspaceDto.class.isAssignableFrom(type); - } - - @Override - public long getSize( - WorkspaceDto workspaceDto, - Class type, - Type genericType, - Annotation[] annotations, - MediaType mediaType) { - return -1; - } - - @Override - public void writeTo( - WorkspaceDto workspaceDto, - 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(workspaceDto)); - w.flush(); - } - } - - class DevfileDtoDeserializer extends JsonDeserializer { - @Override - public DevfileDto deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - try { - return asDto(devfileManager.parseJson(p.readValueAsTree().toString())); - } catch (DevfileFormatException e) { - throw new BadRequestException(e.getMessage()); - } - } - } -} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java index 7375bafbc5..ed19cdc286 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java @@ -17,12 +17,14 @@ import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.toList; import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; import static org.eclipse.che.api.workspace.server.WorkspaceKeyValidator.validateKey; import static org.eclipse.che.api.workspace.shared.Constants.CHE_WORKSPACE_AUTO_START; import static org.eclipse.che.api.workspace.shared.Constants.CHE_WORKSPACE_DEVFILE_REGISTRY_URL_PROPERTY; import static org.eclipse.che.api.workspace.shared.Constants.CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY; +import com.google.common.annotations.Beta; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; @@ -64,13 +66,16 @@ import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.rest.Service; +import org.eclipse.che.api.workspace.server.devfile.DevfileManager; 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.URLFileContentProvider; +import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; 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.ProjectConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.token.MachineAccessForbidden; import org.eclipse.che.api.workspace.server.token.MachineTokenException; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; @@ -84,7 +89,6 @@ import org.eclipse.che.api.workspace.shared.dto.RuntimeDto; import org.eclipse.che.api.workspace.shared.dto.ServerDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; @@ -106,6 +110,7 @@ public class WorkspaceService extends Service { private final String apiEndpoint; private final boolean cheWorkspaceAutoStart; private final FileContentProvider devfileContentProvider; + private final DevfileManager devfileManager; @Inject public WorkspaceService( @@ -116,7 +121,8 @@ public class WorkspaceService extends Service { WorkspaceLinksGenerator linksGenerator, @Named(CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY) @Nullable String pluginRegistryUrl, @Named(CHE_WORKSPACE_DEVFILE_REGISTRY_URL_PROPERTY) @Nullable String devfileRegistryUrl, - URLFetcher urlFetcher) { + URLFetcher urlFetcher, + DevfileManager devfileManager) { this.apiEndpoint = apiEndpoint; this.cheWorkspaceAutoStart = cheWorkspaceAutoStart; this.workspaceManager = workspaceManager; @@ -125,6 +131,7 @@ public class WorkspaceService extends Service { this.pluginRegistryUrl = pluginRegistryUrl; this.devfileRegistryUrl = devfileRegistryUrl; this.devfileContentProvider = new URLFileContentProvider(null, urlFetcher); + this.devfileManager = devfileManager; } @POST @@ -189,12 +196,16 @@ public class WorkspaceService extends Service { return Response.status(201).entity(asDtoWithLinksAndToken(workspace)).build(); } + @Beta @Path("/devfile") @POST @Consumes({APPLICATION_JSON, "text/yaml", "text/x-yaml"}) @Produces(APPLICATION_JSON) @ApiOperation( value = "Creates a new workspace based on the Devfile.", + notes = + "This method is in beta phase. It's strongly recommended to use `POST /devfile` instead" + + " to get a workspace from Devfile. Workspaces created with this method are not stable yet.", consumes = "application/json, text/yaml, text/x-yaml", produces = APPLICATION_JSON, nickname = "createFromDevfile", @@ -211,8 +222,7 @@ public class WorkspaceService extends Service { @ApiResponse(code = 500, message = "Internal server error occurred") }) public Response create( - @ApiParam(value = "The devfile of the workspace to create", required = true) - DevfileDto devfile, + @ApiParam(value = "The devfile of the workspace to create", required = true) String devfile, @ApiParam( value = "Workspace attribute defined in 'attrName:attrValue' format. " @@ -232,15 +242,29 @@ public class WorkspaceService extends Service { throws ConflictException, BadRequestException, ForbiddenException, NotFoundException, ServerException { requiredNotNull(devfile, "Devfile"); + + DevfileImpl devfileModel; + try { + if (APPLICATION_JSON_TYPE.isCompatible(contentType)) { + devfileModel = devfileManager.parseJson(devfile); + } else { + devfileModel = devfileManager.parseYaml(devfile); + } + } catch (DevfileException e) { + throw new BadRequestException(e.getMessage()); + } + final Map attributes = parseAttrs(attrsList); + if (namespace == null) { namespace = EnvironmentContext.getCurrent().getSubject().getUserName(); } + WorkspaceImpl workspace; try { workspace = workspaceManager.createWorkspace( - devfile, + devfileModel, namespace, attributes, // create a new cache for each request so that we don't have to care about lifetime 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 deleted file mode 100644 index 282c376b8c..0000000000 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java +++ /dev/null @@ -1,140 +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.google.common.base.Strings.isNullOrEmpty; -import static javax.ws.rs.core.MediaType.APPLICATION_JSON; -import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; - -import com.google.common.io.CharStreams; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -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.NotSupportedException; -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.workspace.server.devfile.exception.DevfileFormatException; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.dto.server.DtoFactory; - -/** - * Parses {@link DevfileDto} either from Json or yaml content, and performs schema validation before - * the actual DTO created. - * - * @author Max Shaposhnyk - */ -@Singleton -@Provider -@Produces({APPLICATION_JSON}) -@Consumes({APPLICATION_JSON, "text/yaml", "text/x-yaml"}) -public class DevfileEntityProvider - implements MessageBodyReader, MessageBodyWriter { - - private DevfileManager devfileManager; - - @Inject - public DevfileEntityProvider(DevfileManager devfileManager) { - this.devfileManager = devfileManager; - } - - @Override - public boolean isReadable( - Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return type == DevfileDto.class; - } - - @Override - public DevfileDto readFrom( - Class type, - Type genericType, - Annotation[] annotations, - MediaType mediaType, - MultivaluedMap httpHeaders, - InputStream entityStream) - throws IOException, WebApplicationException { - - try { - if (mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) { - return asDto( - devfileManager.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( - CharStreams.toString( - new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType))))); - } - } catch (DevfileFormatException e) { - throw new BadRequestException(e.getMessage()); - } - throw new NotSupportedException("Unknown media type " + mediaType.toString()); - } - - @Override - public boolean isWriteable( - Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return DevfileDto.class.isAssignableFrom(type); - } - - @Override - public long getSize( - DevfileDto devfileDto, - Class type, - Type genericType, - Annotation[] annotations, - MediaType mediaType) { - return -1; - } - - @Override - public void writeTo( - DevfileDto devfileDto, - 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(devfileDto)); - w.flush(); - } - } - - private String getCharsetOrUtf8(MediaType mediaType) { - String charset = mediaType == null ? null : mediaType.getParameters().get("charset"); - if (isNullOrEmpty(charset)) { - charset = "UTF-8"; - } - return charset; - } -} 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 ab49aff0bc..2184f9d5bf 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 @@ -25,7 +25,6 @@ public class DevfileModule extends AbstractModule { @Override protected void configure() { bind(DevfileService.class); - bind(DevfileEntityProvider.class); DevfileBindings.onWorkspaceApplierBinder( binder(), 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 deleted file mode 100644 index 66dfdee584..0000000000 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java +++ /dev/null @@ -1,59 +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; - -import static org.eclipse.che.dto.server.DtoFactory.newDto; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.ByteArrayInputStream; -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.model.impl.devfile.DevfileImpl; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.dto.server.DtoFactory; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -@Listeners(MockitoTestNGListener.class) -public class WorkspaceEntityProviderTest { - - @Mock private DevfileManager devfileManager; - - @InjectMocks private WorkspaceEntityProvider workspaceEntityProvider; - - @Test - public void shouldBuildDtoFromValidJson() throws Exception { - - when(devfileManager.parseJson(anyString())).thenReturn(new DevfileImpl()); - - WorkspaceDto actual = newDto(WorkspaceDto.class).withDevfile(newDto(DevfileDto.class)); - - workspaceEntityProvider.readFrom( - WorkspaceDto.class, - WorkspaceDto.class, - null, - MediaType.APPLICATION_JSON_TYPE, - new MultivaluedHashMap<>(), - new ByteArrayInputStream( - DtoFactory.getInstance().toJson(actual).getBytes(StandardCharsets.UTF_8))); - - verify(devfileManager).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 93502b1af4..7b2b8e5bd5 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 @@ -51,7 +51,6 @@ import java.util.Map; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.Page; -import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.model.workspace.config.ProjectConfig; @@ -63,7 +62,9 @@ import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; 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.DevfileManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; 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; @@ -73,6 +74,7 @@ import org.eclipse.che.api.workspace.server.model.impl.RuntimeImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; 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.DevfileImpl; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.api.workspace.shared.dto.CommandDto; @@ -132,6 +134,7 @@ public class WorkspaceServiceTest { @Mock private WorkspaceManager wsManager; @Mock private MachineTokenProvider machineTokenProvider; @Mock private WorkspaceLinksGenerator linksGenerator; + @Mock private DevfileManager devfileManager; @Mock private URLFetcher urlFetcher; private WorkspaceService service; @@ -147,7 +150,8 @@ public class WorkspaceServiceTest { linksGenerator, CHE_WORKSPACE_PLUGIN_REGISTRY_ULR, CHE_WORKSPACE_DEVFILE_REGISTRY_ULR, - urlFetcher); + urlFetcher, + devfileManager); } @Test @@ -189,6 +193,8 @@ public class WorkspaceServiceTest { final DevfileDto devfileDto = createDevfileDto(); final WorkspaceImpl workspace = createWorkspace(devfileDto); + when(devfileManager.parseJson(any())).thenReturn(new DevfileImpl()); + when(wsManager.createWorkspace(any(Devfile.class), anyString(), any(), any())) .thenReturn(workspace); @@ -220,13 +226,52 @@ public class WorkspaceServiceTest { any()); } + @Test + public void shouldAcceptYamlDevfileWhenCreatingWorkspace() throws Exception { + final DevfileDto devfileDto = createDevfileDto(); + final WorkspaceImpl workspace = createWorkspace(devfileDto); + + when(devfileManager.parseYaml(any())).thenReturn(new DevfileImpl()); + + when(wsManager.createWorkspace(any(Devfile.class), anyString(), any(), any())) + .thenReturn(workspace); + + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("text/yaml") + .when() + .post( + SECURE_PATH + + "/workspace/devfile" + + "?namespace=test" + + "&attribute=factoryId:factory123" + + "&attribute=custom:custom:value"); + + assertEquals(response.getStatusCode(), 201); + assertEquals( + new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); + verify(wsManager) + .createWorkspace( + any(Devfile.class), + eq("test"), + eq( + ImmutableMap.of( + "factoryId", "factory123", + "custom", "custom:value")), + any()); + } + @Test public void shouldReturnBadRequestOnInvalidDevfile() throws Exception { final DevfileDto devfileDto = createDevfileDto(); final WorkspaceImpl workspace = createWorkspace(devfileDto); + when(devfileManager.parseJson(any())).thenThrow(new DevfileFormatException("boom")); + when(wsManager.createWorkspace(any(Devfile.class), anyString(), any(), any())) - .thenThrow(new ValidationException("boom")); + .thenReturn(workspace); final Response response = given() @@ -245,6 +290,8 @@ public class WorkspaceServiceTest { assertEquals(response.getStatusCode(), 400); String error = unwrapError(response); assertEquals(error, "boom"); + + verify(wsManager, never()).createWorkspace(any(Devfile.class), any(), any(), any()); } @Test 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 deleted file mode 100644 index d4fde146f3..0000000000 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java +++ /dev/null @@ -1,81 +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 org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import javax.ws.rs.NotSupportedException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedHashMap; -import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -@Listeners(MockitoTestNGListener.class) -public class DevfileEntityProviderTest { - - @Mock private DevfileManager devfileManager; - - @InjectMocks private DevfileEntityProvider devfileEntityProvider; - - @Test - public void shouldBuildDtoFromValidYaml() throws Exception { - - when(devfileManager.parseYaml(anyString())).thenReturn(new DevfileImpl()); - - devfileEntityProvider.readFrom( - DevfileDto.class, - DevfileDto.class, - null, - MediaType.valueOf("text/x-yaml"), - new MultivaluedHashMap<>(), - getClass().getClassLoader().getResourceAsStream("devfile/devfile.yaml")); - - verify(devfileManager).parseYaml(anyString()); - } - - @Test - public void shouldBuildDtoFromValidJson() throws Exception { - - when(devfileManager.parseJson(anyString())).thenReturn(new DevfileImpl()); - - devfileEntityProvider.readFrom( - DevfileDto.class, - DevfileDto.class, - null, - MediaType.APPLICATION_JSON_TYPE, - new MultivaluedHashMap<>(), - getClass().getClassLoader().getResourceAsStream("devfile/devfile.json")); - - verify(devfileManager).parseJson(anyString()); - } - - @Test( - expectedExceptions = NotSupportedException.class, - expectedExceptionsMessageRegExp = "Unknown media type text/plain") - public void shouldThrowErrorOnInvalidMediaType() throws Exception { - - devfileEntityProvider.readFrom( - DevfileDto.class, - DevfileDto.class, - null, - MediaType.TEXT_PLAIN_TYPE, - new MultivaluedHashMap<>(), - getClass().getClassLoader().getResourceAsStream("devfile/devfile.json")); - } -} diff --git a/wsmaster/che-core-api-workspace/src/test/resources/devfile/devfile.json b/wsmaster/che-core-api-workspace/src/test/resources/devfile/devfile.json deleted file mode 100644 index da300e6a32..0000000000 --- a/wsmaster/che-core-api-workspace/src/test/resources/devfile/devfile.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "apiVersion": "1.0.0", - "metadata": { - "name": "petclinic-dev-environment", - "generateName": "petclinic-" - }, - "projects": [ - { - "name": "petclinic", - "source": { - "type": "git", - "location": "git@github.com:spring-projects/spring-petclinic.git" - } - } - ], - "components": [ - { - "alias": "mvn-stack", - "type": "chePlugin", - "id": "eclipse/chemaven-jdk8/1.0.0" - }, - { - "type": "cheEditor", - "id": "eclipse/che-theia/0.0.3" - }, - { - "alias": "jdt.ls", - "type": "chePlugin", - "id": "org.eclipse.chetheia-jdtls:0.0.3", - "preferences": { - "java.home": "/home/user/jdk11", - "java.jdt.ls.vmargs": "-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication", - "java.jtg.memory": 12345, - "java.boolean": true - } - }, - { - "type": "openshift", - "reference": "petclinic.yaml", - "selector": { - "app.kubernetes.io/name": "mysql", - "app.kubernetes.io/component": "database", - "app.kubernetes.io/part-of": "petclinic" - } - } - ], - "commands": [ - { - "name": "build", - "actions": [ - { - "type": "exec", - "component": "mvn-stack", - "command": "mvn package", - "workdir": "/projects/spring-petclinic" - } - ] - }, - { - "name": "run", - "attributes": { - "runType": "sequential" - }, - "actions": [ - { - "type": "exec", - "component": "mvn-stack", - "command": "mvn spring-boot:run", - "workdir": "/projects/spring-petclinic" - } - ] - }, - { - "name": "other", - "actions": [ - { - "type": "exec", - "component": "jdt.ls", - "command": "run.sh" - } - ] - } - ] -}