Skip to content

Commit

Permalink
[3279] Restore support for project download and upload
Browse files Browse the repository at this point in the history
Bug: #3279
Signed-off-by: Stéphane Bégaudeau <[email protected]>
Signed-off-by: Jessy Mallet <[email protected]>
  • Loading branch information
sbegaudeau committed Jul 1, 2024
1 parent e9a515e commit 19cc39d
Show file tree
Hide file tree
Showing 30 changed files with 2,411 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
*
* @author sbegaudeau
*/
public record UploadDocumentSuccessPayload(@NotNull UUID id, String report) implements IPayload {
public record UploadDocumentSuccessPayload(@NotNull UUID id, @NotNull DocumentDTO document, String report) implements IPayload {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.application.document.services;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Optional;

import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.sirius.components.emf.services.EObjectIDManager;
import org.eclipse.sirius.emfjson.resource.JsonResource;
import org.eclipse.sirius.web.application.document.services.api.IDocumentExporter;
import org.eclipse.sirius.web.application.editingcontext.services.JsonResourceSerializationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

/**
* Used to export documents as Json resources.
*
* @author sbegaudeau
*/
@Service
public class JsonDocumentExporter implements IDocumentExporter {

private final Logger logger = LoggerFactory.getLogger(JsonDocumentExporter.class);

@Override
public boolean canHandle(Resource resource, String mediaType) {
return MediaType.APPLICATION_JSON.toString().equals(mediaType);
}

@Override
public Optional<byte[]> getBytes(Resource resource, String mediaType) {
Optional<byte[]> optionalBytes = Optional.empty();

if (resource instanceof JsonResource jsonResource) {
var serializationListener = new JsonResourceSerializationListener();

HashMap<Object, Object> options = new HashMap<>();
options.put(JsonResource.OPTION_ID_MANAGER, new EObjectIDManager());
options.put(JsonResource.OPTION_SCHEMA_LOCATION, true);
options.put(JsonResource.OPTION_SERIALIZATION_LISTENER, serializationListener);

try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {
jsonResource.save(outputStream, options);
optionalBytes = Optional.of(outputStream.toByteArray());
} catch (IOException exception) {
this.logger.warn(exception.getMessage(), exception);
}
}

return optionalBytes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.eclipse.sirius.web.application.document.services;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -32,14 +33,18 @@
import org.eclipse.sirius.components.core.api.IEditingContextSearchService;
import org.eclipse.sirius.components.core.api.IInput;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.emf.ResourceMetadataAdapter;
import org.eclipse.sirius.components.emf.services.JSONResourceFactory;
import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext;
import org.eclipse.sirius.web.application.UUIDParser;
import org.eclipse.sirius.web.application.document.dto.DocumentDTO;
import org.eclipse.sirius.web.application.document.dto.UploadDocumentInput;
import org.eclipse.sirius.web.application.document.dto.UploadDocumentSuccessPayload;
import org.eclipse.sirius.web.application.document.services.api.IDocumentSanitizedJsonContentProvider;
import org.eclipse.sirius.web.application.document.services.api.IProxyValidator;
import org.eclipse.sirius.web.application.document.services.api.IUploadDocumentReportProvider;
import org.eclipse.sirius.web.application.editingcontext.services.api.IResourceLoader;
import org.eclipse.sirius.web.application.views.explorer.services.ExplorerDescriptionProvider;
import org.eclipse.sirius.web.domain.services.api.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -114,12 +119,26 @@ public void handle(Sinks.One<IPayload> payloadSink, Sinks.Many<ChangeDescription
this.logger.warn("The resource {} contains unresolvable proxies and will not be uploaded.", uploadDocumentInput.file().getName());
} else {
Optional<Resource> newResource = this.resourceLoader.toResource(emfEditingContext.getDomain().getResourceSet(), UUID.randomUUID().toString(), fileName, content);
String report = null;
if (newResource.isPresent()) {
report = this.getReport(newResource.get());

var optionalId = newResource.map(Resource::getURI)
.map(uri -> uri.path().substring(1))
.flatMap(new UUIDParser()::parse);

var optionalName = newResource.map(Resource::eAdapters).stream()
.flatMap(Collection::stream)
.filter(ResourceMetadataAdapter.class::isInstance)
.map(ResourceMetadataAdapter.class::cast)
.findFirst()
.map(ResourceMetadataAdapter::getName);
if (newResource.isPresent() && optionalId.isPresent() && optionalName.isPresent()) {
var uploadedResource = newResource.get();
var id = optionalId.get();
var name = optionalName.get();

String report = this.getReport(uploadedResource);
payload = new UploadDocumentSuccessPayload(input.id(), new DocumentDTO(id, name, ExplorerDescriptionProvider.DOCUMENT_KIND), report);
changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, editingContext.getId(), input);
}
payload = new UploadDocumentSuccessPayload(input.id(), report);
changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, editingContext.getId(), input);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.application.project.controllers;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

import org.eclipse.sirius.components.annotations.spring.graphql.MutationDataFetcher;
import org.eclipse.sirius.components.core.api.ErrorPayload;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates;
import org.eclipse.sirius.components.graphql.api.UploadFile;
import org.eclipse.sirius.web.application.UUIDParser;
import org.eclipse.sirius.web.application.project.services.api.IProjectImportService;
import org.eclipse.sirius.web.domain.services.api.IMessageService;

import graphql.schema.DataFetchingEnvironment;

/**
* Data fetcher for the field Mutation#uploadProject.
*
* @author gcoutable
*/
@MutationDataFetcher(type = "Mutation", field = "uploadProject")
public class MutationUploadProjectDataFetcher implements IDataFetcherWithFieldCoordinates<IPayload> {

private static final String INPUT_ARGUMENT = "input";

private static final String ID = "id";

private static final String FILE = "file";

private final IProjectImportService projectImportService;

private final IMessageService messageService;

public MutationUploadProjectDataFetcher(IProjectImportService projectImportService, IMessageService messageService) {
this.projectImportService = Objects.requireNonNull(projectImportService);
this.messageService = Objects.requireNonNull(messageService);
}

@Override
public IPayload get(DataFetchingEnvironment environment) throws Exception {
Map<Object, Object> input = environment.getArgument(INPUT_ARGUMENT);

var optionalId = Optional.ofNullable(input.get(ID))
.map(Object::toString)
.flatMap(new UUIDParser()::parse);

var optionalFile = Optional.ofNullable(input.get(FILE))
.filter(UploadFile.class::isInstance)
.map(UploadFile.class::cast);

if (optionalId.isPresent() && optionalFile.isPresent()) {
var id = optionalId.get();
var uploadFile = optionalFile.get();
return this.projectImportService.importProject(id, uploadFile);
}

return new ErrorPayload(optionalId.orElse(UUID.randomUUID()), this.messageService.unexpectedError());

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.application.project.controllers;

import java.util.Objects;
import java.util.UUID;

import org.eclipse.sirius.web.application.project.services.api.IProjectExportService;
import org.eclipse.sirius.web.domain.boundedcontexts.project.services.api.IProjectSearchService;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* The entry point of the HTTP API to download a project in zip.
* <p>
* This endpoint will be available on the API base path prefix with download segment and followed by the project Id used
* as a suffix. As such, users will be able to send project download request to the following URL:
* </p>
*
* <pre>
* PROTOCOL://DOMAIN.TLD(:PORT)/API_BASE_PATH/projects/PROJECT_ID
* </pre>
*
* @author gcoutable
*/
@Controller
@RequestMapping("/api/projects")
public class ProjectDownloadController {

private final IProjectSearchService projectSearchService;

private final IProjectExportService projectExportService;

public ProjectDownloadController(IProjectSearchService projectSearchService, IProjectExportService projectExportService) {
this.projectSearchService = Objects.requireNonNull(projectSearchService);
this.projectExportService = Objects.requireNonNull(projectExportService);
}

@ResponseBody
@GetMapping(path = "/{projectId}")
public ResponseEntity<Resource> downloadProject(@PathVariable UUID projectId) {
var optionalProject = this.projectSearchService.findById(projectId);
if (optionalProject.isPresent()) {
var project = optionalProject.get();
byte[] content = this.projectExportService.export(project);

ContentDisposition contentDisposition = ContentDisposition.builder("attachment")
.filename(project.getName() + ".zip")
.build();

HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(contentDisposition);
headers.setContentType(MediaType.parseMediaType("application/zip"));
headers.setContentLength(content.length);
var resource = new ByteArrayResource(content);

return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
return new ResponseEntity<>(null, new HttpHeaders(), HttpStatus.NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.application.project.dto;

import java.util.UUID;

import org.eclipse.sirius.components.core.api.IInput;
import org.eclipse.sirius.components.graphql.api.UploadFile;

import jakarta.validation.constraints.NotNull;

/**
* Input used to upload a new project.
*
* @author sbegaudeau
*/
public record UploadProjectInput(@NotNull UUID id, UploadFile file) implements IInput {
}
Loading

0 comments on commit 19cc39d

Please sign in to comment.