Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rest): create new enpoint to upload component csv file. #2308

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rest/resource-server/src/docs/asciidoc/api-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,4 @@ include::schedule.adoc[]
include::ecc.adoc[]
include::attachmentCleanUp.adoc[]
include::databaseSanitation.adoc[]
include::importExport.adoc[]
70 changes: 70 additions & 0 deletions rest/resource-server/src/docs/asciidoc/importExport.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright Siemens AG, 2024. Part of the SW360 Portal Project.
//
// 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
//

[[resources-importExport]]
=== ImportExport

The ImportExport resource is used to get and upload request.

[[upload-component]]
==== Upload component csv file.

A `POST` request help to upload the component csv file.

[red]#Request parameter#
|===
|Parameter |Description

|componentFile
|Upload the component csv file.
|===

===== Example request
include::{snippets}/should_document_upload_component_file/curl-request.adoc[]

===== Example response
include::{snippets}/should_document_upload_component_file/http-response.adoc[]

[[upload-release]]
==== Upload release link csv file.

A `POST` request help to upload the release csv file.

[red]#Request parameter#
|===
|Parameter |Description

|releaseFile
|Upload the release link csv file.
|===

===== Example request
include::{snippets}/should_document_upload_release_link_file/curl-request.adoc[]

===== Example response
include::{snippets}/should_document_upload_release_link_file/http-response.adoc[]

[[upload-component-attachment]]
==== Upload component attachment file.

A `POST` request help to upload the component attachment file.

[red]#Request parameter#
|===
|Parameter |Description

|component
|Upload the component attachment file.
|===

===== Example request
include::{snippets}/should_document_upload_component_attachment_file/curl-request.adoc[]

===== Example response
include::{snippets}/should_document_upload_component_attachment_file/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright Siemens AG, 2024-2025.
* Part of the SW360 Portal Project.
*
* 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
*/

package org.eclipse.sw360.rest.resourceserver.importexport;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;

import java.io.IOException;

import org.apache.thrift.TException;
import org.eclipse.sw360.datahandler.common.CommonUtils;
import org.eclipse.sw360.datahandler.thrift.RequestStatus;
import org.eclipse.sw360.datahandler.thrift.RequestSummary;
import org.eclipse.sw360.datahandler.thrift.users.User;
import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper;
import org.eclipse.sw360.rest.resourceserver.project.ProjectController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.data.rest.webmvc.RepositoryLinksResource;
import org.springframework.hateoas.server.RepresentationModelProcessor;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@BasePathAwareController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@RestController
@SecurityRequirement(name = "tokenAuth")
public class ImportExportController implements RepresentationModelProcessor<RepositoryLinksResource> {
public static final String IMPORTEXPORT_URL = "/importExport";

private static final MediaType form = null;

@NonNull
private final RestControllerHelper restControllerHelper;

@NonNull
private final Sw360ImportExportService importExportService;

@Override
public RepositoryLinksResource process(RepositoryLinksResource resource) {
resource.add(linkTo(ImportExportController.class).slash("api/importExport").withRel("importExport"));
return resource;
}

@Operation(
summary = "Upload component csv file.",
description = "Upload component csv file.",
tags = {"Component"}
)
@RequestMapping(value = IMPORTEXPORT_URL + "/uploadComponent", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_MIXED_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<RequestSummary> uploadComponentCsv(
@Parameter(description = "The component csv file to be uploaded.")
@RequestParam("componentFile") MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws TException, IOException, ServletException{

User sw360User = restControllerHelper.getSw360UserFromAuthentication();
RequestSummary requestSummary =importExportService.uploadComponent(sw360User, file, request, response);
return ResponseEntity.ok(requestSummary);
}

@Operation(
summary = "release link file.",
description = "release link file.",
tags = {"Release"}
)
@RequestMapping(value = IMPORTEXPORT_URL + "/uploadRelease", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_MIXED_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<RequestSummary> uploadReleaseCsv(
@Parameter(description = "The release csv file to be uploaded.")
@RequestParam("releaseFile") MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws TException, IOException, ServletException{

User sw360User = restControllerHelper.getSw360UserFromAuthentication();
RequestSummary requestSummary =importExportService.uploadReleaseLink(sw360User, file, request);
return ResponseEntity.ok(requestSummary);
}

@Operation(
summary = "component attachment file.",
description = "component attachment file.",
tags = {"Component"}
)
@RequestMapping(value = IMPORTEXPORT_URL + "/componentAttachment", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_MIXED_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<RequestSummary> uploadComponentAttachment(
@Parameter(description = "The component attachment csv file to be uploaded.")
@RequestParam("attachmentFile") MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws TException, IOException, ServletException {

User sw360User = restControllerHelper.getSw360UserFromAuthentication();
RequestSummary requestSummary =importExportService.uploadComponentAttachment(sw360User, file, request);
return ResponseEntity.ok(requestSummary);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright Siemens AG, 2024-2025.
* Part of the SW360 Portal Project.
*
* 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
*/

package org.eclipse.sw360.rest.resourceserver.importexport;

import static org.eclipse.sw360.datahandler.common.ImportCSV.readAsCSVRecords;
import static org.eclipse.sw360.importer.ComponentImportUtils.convertCSVRecordsToCompCSVRecords;
import static org.eclipse.sw360.importer.ComponentImportUtils.convertCSVRecordsToComponentAttachmentCSVRecords;
import static org.eclipse.sw360.importer.ComponentImportUtils.convertCSVRecordsToReleaseLinkCSVRecords;
import static org.eclipse.sw360.importer.ComponentImportUtils.writeAttachmentsToDatabase;
import static org.eclipse.sw360.importer.ComponentImportUtils.writeReleaseLinksToDatabase;
import static org.eclipse.sw360.importer.ComponentImportUtils.writeToDatabase;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.RequestContext;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.eclipse.sw360.datahandler.permissions.PermissionUtils;
import org.eclipse.sw360.datahandler.thrift.RequestSummary;
import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentService;
import org.eclipse.sw360.datahandler.thrift.components.ComponentService;
import org.eclipse.sw360.datahandler.thrift.users.User;
import org.eclipse.sw360.datahandler.thrift.users.UserGroup;
import org.eclipse.sw360.datahandler.thrift.vendors.VendorService;
import org.eclipse.sw360.importer.ComponentAttachmentCSVRecord;
import org.eclipse.sw360.importer.ComponentCSVRecord;
import org.eclipse.sw360.importer.ReleaseLinkCSVRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import lombok.RequiredArgsConstructor;

@SuppressWarnings("serial")
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Sw360ImportExportService {
@Value("${sw360.thrift-server-url:http://localhost:8080}")
private String thriftServerUrl;

@JsonInclude
public RequestSummary uploadComponent(User sw360User, MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws IOException, TException, ServletException {
if (!PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) {
throw new RuntimeException("Unable to upload component csv file. User is not admin");
}
List<CSVRecord> releaseRecords = getCSVFromRequest(request, "file");
FluentIterable<ComponentCSVRecord> compCSVRecords = convertCSVRecordsToCompCSVRecords(releaseRecords);
ComponentService.Iface sw360ComponentClient = getThriftComponentClient();
VendorService.Iface sw360VendorClient = getThriftVendorClient();
AttachmentService.Iface sw360AttachmentClient = getThriftAttachmentClient();
RequestSummary requestSummary = writeToDatabase(compCSVRecords, sw360ComponentClient, sw360VendorClient, sw360AttachmentClient, sw360User);
return requestSummary;
}

private List<CSVRecord> getCSVFromRequest(HttpServletRequest request, String fileUploadFormId) throws IOException, TException,ServletException {
final InputStream stream = getInputStreamFromRequest(request, fileUploadFormId);
return readAsCSVRecords(stream);
}

private InputStream getInputStreamFromRequest(HttpServletRequest request, String fileUploadFormId)
throws IOException, ServletException{
Collection<Part> parts = request.getParts();

for (Part part : parts) {
if (!part.getName().equals(fileUploadFormId)) {
return part.getInputStream();
}
}
throw new IOException("File not found in the request with the specified field name.");
}

private ComponentService.Iface getThriftComponentClient() throws TTransportException {
THttpClient thriftClient = new THttpClient(thriftServerUrl + "/components/thrift");
TProtocol protocol = new TCompactProtocol(thriftClient);
return new ComponentService.Client(protocol);
}

private VendorService.Iface getThriftVendorClient() throws TTransportException {
THttpClient thriftClient = new THttpClient(thriftServerUrl + "/vendors/thrift");
TProtocol protocol = new TCompactProtocol(thriftClient);
return new VendorService.Client(protocol);
}

private AttachmentService.Iface getThriftAttachmentClient() throws TTransportException {
THttpClient thriftClient = new THttpClient(thriftServerUrl + "/attachments/thrift");
TProtocol protocol = new TCompactProtocol(thriftClient);
return new AttachmentService.Client(protocol);
}

public RequestSummary uploadReleaseLink(User sw360User, MultipartFile file, HttpServletRequest request) throws IOException, TException, ServletException {
if (!PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) {
throw new RuntimeException("Unable to upload component csv file. User is not admin");
}
List<CSVRecord> releaseLinkRecords = getCSVFromRequest(request, "file");
FluentIterable<ReleaseLinkCSVRecord> csvRecords = convertCSVRecordsToReleaseLinkCSVRecords(releaseLinkRecords);
ComponentService.Iface sw360ComponentClient = getThriftComponentClient();
final RequestSummary requestSummary = writeReleaseLinksToDatabase(csvRecords, sw360ComponentClient, sw360User);
return requestSummary;
}

public RequestSummary uploadComponentAttachment(User sw360User, MultipartFile file, HttpServletRequest request) throws IOException, TException, ServletException {
if (!PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) {
throw new RuntimeException("Unable to upload component attachment csv file. User is not admin");
}
List<CSVRecord> attachmentRecords = getCSVFromRequest(request, "file");
FluentIterable<ComponentAttachmentCSVRecord> compCSVRecords = convertCSVRecordsToComponentAttachmentCSVRecords(attachmentRecords);
ComponentService.Iface sw360ComponentClient = getThriftComponentClient();
AttachmentService.Iface sw360AttachmentClient = getThriftAttachmentClient();
final RequestSummary requestSummary = writeAttachmentsToDatabase(compCSVRecords, sw360User, sw360ComponentClient, sw360AttachmentClient);
return requestSummary;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ public void should_document_index() throws Exception {
linkWithRel("sw360:schedule").description("The <<resources-schedule,Schedule resource>>"),
linkWithRel("sw360:ecc").description("The <<resources-ecc,Ecc resource>>"),
linkWithRel("sw360:attachmentCleanUp").description("The <<resources-attachmentCleanUp,attachmentCleanUp resource>>"),
linkWithRel("sw360:importExport").description("The <<resources-importExport,ImportExport resource>>"),
linkWithRel("curies").description("The Curies for documentation"),
linkWithRel("profile").description("The profiles of the REST resources")
),
Expand Down
Loading