Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
87ed99e
feat: add copyProject method to ProjectService thrift definition
sujalkalauni Mar 15, 2026
2c421ac
trigger ECA recheck
sujalkalauni Mar 15, 2026
d2cb37e
feat: implement copyProject in ProjectDatabaseHandler
sujalkalauni Mar 16, 2026
9c23783
feat: implement copyProject with field selection, overrides, and Clea…
sujalkalauni Mar 23, 2026
9ac8d4f
feat: expose copyProject in ProjectHandler service layer
sujalkalauni Mar 23, 2026
8b7ff0c
feat: add REST POST /projects/{id}/copy endpoint
sujalkalauni Mar 23, 2026
1034536
feat: add CopyProjectRequest DTO for REST copy endpoint
sujalkalauni Mar 23, 2026
384ea4e
fix: use explicit thrift path for Windows/CI compatibility
sujalkalauni Mar 24, 2026
7528a14
Merge branch 'main' into feat/customize-copy-project
sujalkalauni Mar 24, 2026
d108e4e
fix(PR#3915): address code review
sujalkalauni Mar 24, 2026
4fbe622
Merge branch 'feat/customize-copy-project' of https://github.com/suja…
sujalkalauni Mar 24, 2026
e207b57
gitignore: ignore node_modules
sujalkalauni Mar 24, 2026
94e3519
feat: implement selective field copy for ProjectDatabaseHandler
sujalkalauni Mar 24, 2026
96f8be9
fix: restore license header and use system thrift executable in datah…
sujalkalauni Apr 28, 2026
446f71b
fix: remove admin.json, package.json and package-lock.json from repo
sujalkalauni Apr 28, 2026
6f99045
fix: remove invalid PostMapping comment line in ProjectController
sujalkalauni Apr 28, 2026
cf330b7
fix: add local config files to gitignore
sujalkalauni Apr 28, 2026
fe7b661
fix: clean up test - remove GSoC markers, try/catch and sysout
sujalkalauni Apr 28, 2026
337324b
Merge branch 'main' into feat/customize-copy-project
sujalkalauni Apr 28, 2026
05f2695
Merge branch 'main' into feat/customize-copy-project
sujalkalauni Apr 28, 2026
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
Binary file modified .gitignore
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Committing a binary file is a big no no!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still here.

Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import org.eclipse.sw360.spdx.SpdxBOMImporterSink;
import org.jetbrains.annotations.NotNull;

import java.beans.Visibility;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid import. Please remove.

import java.io.*;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
Expand All @@ -73,6 +74,9 @@
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;


Comment on lines +78 to +79
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad code styling. Please remove irrelevant changes.


import static com.google.common.base.Strings.isNullOrEmpty;
import static org.eclipse.sw360.datahandler.common.CommonUtils.*;
Expand All @@ -87,7 +91,8 @@
import static org.eclipse.sw360.datahandler.common.WrappedException.wrapTException;
import static org.eclipse.sw360.datahandler.permissions.PermissionUtils.makePermission;
import org.eclipse.sw360.exporter.ProjectExporter;
import java.nio.ByteBuffer;
import java.nio.ByteBuffer;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad code styling. Please remove irrelevant changes.


/**
* Class for accessing the CouchDB database
Expand Down Expand Up @@ -261,7 +266,7 @@ public AddDocumentRequestSummary createClearingRequest(ClearingRequest clearingR
.setId(project.getClearingRequestId()).setMessage("Clearing request already present for project");
}

if (!(ProjectClearingState.CLOSED.equals(project.getClearingState()) || Visibility.PRIVATE.equals(project.getVisbility()))) {
if (!(ProjectClearingState.CLOSED.equals(project.getClearingState()) || org.eclipse.sw360.datahandler.thrift.Visibility.PRIVATE.equals(project.getVisbility()))) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Irrelevant change. Please revert.

clearingRequest.setProjectBU(project.getBusinessUnit());
String crId = moderator.createClearingRequest(clearingRequest, user);
if (CommonUtils.isNotNullEmptyOrWhitespace(crId)) {
Expand Down Expand Up @@ -478,7 +483,7 @@ public RequestStatus updateProject(Project project, User user, boolean forceUpda
Collections.emptySet())))) {
return RequestStatus.INVALID_INPUT;
} else if (isWriteActionAllowedOnProject(actual, user) || forceUpdate) {
copyImmutableFields(project,actual);

setRequestedDateAndTrimComment(project, actual, user);
setRequestedDateAndTrimCommentForPackages(project, actual, user);
project.setAttachments( getAllAttachmentsToKeep(toSource(actual), actual.getAttachments(), project.getAttachments()) );
Expand Down Expand Up @@ -568,6 +573,18 @@ private List<AttachmentUsage> parseAttachmentUsages(List<String> projectPaths, S
}
String releaseId = usage.getOwner().getReleaseId();
String attachmentContentId = usage.getAttachmentContentId();
AttachmentUsage newUsage = new AttachmentUsage(Source.releaseId(releaseId), attachmentContentId, org.eclipse.sw360.datahandler.thrift.Source.projectId(projectId));
final UsageData usageData;
LicenseInfoUsage licenseInfoUsage = new LicenseInfoUsage(Collections.emptySet());
licenseInfoUsage.setProjectPath(projectPath);
usageData = UsageData.licenseInfo(licenseInfoUsage);
newUsage.setUsageData(usageData);
result.add(newUsage);
}
}
}
Comment on lines +583 to +585
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ummm... What?

String releaseId = usage.getOwner().getReleaseId();
String attachmentContentId = usage.getAttachmentContentId();
AttachmentUsage newUsage = new AttachmentUsage(
Source.releaseId(releaseId), attachmentContentId, Source.projectId(projectId));
LicenseInfoUsage licenseInfoUsage = new LicenseInfoUsage(Collections.emptySet());
Expand Down Expand Up @@ -908,7 +925,7 @@ private ObligationList deleteObligationsOfUnlinkedReleases(Project updated) {
}

private void updateProjectDependentLinkedFields(Project updated, Project actual) throws SW360Exception {
Source usedBy = Source.projectId(updated.getId());
org.eclipse.sw360.datahandler.thrift.Source usedBy =org.eclipse.sw360.datahandler.thrift.Source.projectId(updated.getId());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Irrelevant change. Please revert.

Set<String> updatedLinkedReleaseIds = nullToEmptyMap(updated.getReleaseIdToUsage()).keySet();
Set<String> actualLinkedReleaseIds = nullToEmptyMap(actual.getReleaseIdToUsage()).keySet();
deleteAttachmentUsagesOfUnlinkedReleases(usedBy, updatedLinkedReleaseIds, actualLinkedReleaseIds);
Expand Down Expand Up @@ -1028,12 +1045,67 @@ public RequestStatus updateProjectFromAdditionsAndDeletions(Project projectAddit
}

}
///////////////////////////////
// COPY PROJECT //
/////////////////////////////
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad code styling. Please fix indentation.


private void copyImmutableFields(Project destination, Project source) {
ThriftUtils.copyField(source, destination, Project._Fields.CREATED_ON);
ThriftUtils.copyField(source, destination, Project._Fields.CREATED_BY);
public AddDocumentRequestSummary copyProject(String projectId, Set<String> fieldsToCopy, Project overrideFields, User user) throws TException {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad code styling. Please fix indentation.

Project sourceProject = getProjectById(projectId, user);
if (sourceProject == null) {
return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT);
}

Project newProject = new Project();

// Default: copy ALL non-internal fields if empty (per GMishx feedback)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to mention names :-)

if (fieldsToCopy.isEmpty()) {
fieldsToCopy = Stream.of(Project._Fields.values())
.filter(f -> !"ID".equals(f.name()) &&
!"CREATED_ON".equals(f.name()) &&
!"CREATED_BY".equals(f.name()) &&
!"REVISION".equals(f.name()) &&
!"CLEARING_STATE".equals(f.name()) &&
!"CLEARING_REQUEST_ID".equals(f.name()))
.map(Enum::name)
.collect(Collectors.toSet());
}

// Always copy the name (required field)
newProject.setName(sourceProject.getName() + " (Copy)");

// Copy only the fields requested by the user
for (String fieldName : fieldsToCopy) {
try {
Project._Fields field = Project._Fields.findByName(fieldName);
if (field != null && sourceProject.isSet(field)) {
ThriftUtils.copyField(sourceProject, newProject, field);
}
} catch (Exception e) {
log.warn("Could not copy field: " + fieldName, e);
}
}

// Reset fields that must not be carried over
newProject.unsetId();
newProject.unsetRevision();
newProject.unsetCreatedBy();
newProject.unsetCreatedOn();
newProject.unsetClearingRequestId();
newProject.unsetClearingState();

// Apply user overrides (name, version, etc.)
if (overrideFields != null) {
if (overrideFields.isSetName()) {
newProject.setName(overrideFields.getName());
}
if (overrideFields.isSetVersion()) {
newProject.setVersion(overrideFields.getVersion());
}
}

return addProject(newProject, user);
}

///////////////////////////////
// DELETE INDIVIDUAL OBJECTS //
///////////////////////////////
Expand Down Expand Up @@ -1215,7 +1287,7 @@ public Project getProjectForEdit(String id, User user) throws SW360Exception {
List<ModerationRequest> moderationRequestsForDocumentId = moderator.getModerationRequestsForDocumentId(id);

Project project = getProjectById(id,user);
Visibility actualVisbility = project.getVisbility();
org.eclipse.sw360.datahandler.thrift.Visibility actualVisbility = project.getVisbility();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Irrelevant change. Please revert.

DocumentState documentState;
if (moderationRequestsForDocumentId.isEmpty()) {
documentState = CommonUtils.getOriginalDocumentState();
Expand Down Expand Up @@ -1455,7 +1527,7 @@ public List<Project> fillClearingStateSummaryIncludingSubprojects(List<Project>

projects.stream().forEach(project -> {
// build project tree, get all linked release ids and fetch the releases
// current decision is to not check any permissions for subproject visibility
// current decision is to not check any permissions for subprojectorg.eclipse.sw360.datahandler.thrift.Visibility
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What?

Set<String> releaseIdsOfProjectTree = getReleaseIdsOfProjectTree(project, Sets.newHashSet(),
allProjectsIdMap, user, null);
List<Release> releasesForClearingStateSummary = componentDatabaseHandler
Expand Down Expand Up @@ -1921,7 +1993,7 @@ private String extractProjectNamesForEmail(Collection<Project> projects) {
/**
* CAUTION!
* You should most probably use {@link #getAccessibleProjects(User)} instead of this method.
* This method reads all the projects without checking for visibility constraints. It is intended to provide
* This method reads all the projects without checking fororg.eclipse.sw360.datahandler.thrift.Visibility constraints. It is intended to provide
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😕

* all projects for export from a scheduled service, where a valid sw360 user is not available.
* This is a less than optimal situation, but that's the way it is at the moment.
* @return list of all projects in the database
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

import java.beans.Visibility;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import. Please remove.

import java.util.*;

import static org.eclipse.sw360.datahandler.TestUtils.assertTestString;
Expand All @@ -40,6 +41,10 @@
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;

import static org.junit.Assert.*;
import org.eclipse.sw360.datahandler.thrift.projects.Project;
import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary;

@RunWith(MockitoJUnitRunner.class)
public class ProjectDatabaseHandlerTest {

Expand Down Expand Up @@ -444,4 +449,18 @@ private ProjectWithReleaseRelationTuple createTuple(Project p) {
private ProjectReleaseRelationship newDefaultProjectReleaseRelationship() {
return new ProjectReleaseRelationship(ReleaseRelationship.CONTAINED, MainlineState.MAINLINE);
}



@Test
public void testCopyProjectWithEmptyFields() throws Exception {
Comment on lines +452 to +456
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad code styling. Please fix indentation.

Set<String> emptyFields = new HashSet<>();
Project dummyOverride = new Project();
dummyOverride.setId("targetId");

AddDocumentRequestSummary result = handler.copyProject(
"P1", emptyFields, dummyOverride, user1);

assertNotNull("Result should not be null", result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,10 @@ public AddDocumentRequestSummary addProject(Project project, User user) throws T
assertUser(user);
validateNoEmptyKeys(project);
return handler.addProject(project, user);
@Override
public AddDocumentRequestSummary copyProject(String projectId, Set<String> fieldsToCopy, Project overrideFields, User user) throws SW360Exception { return projectDatabaseHandler.copyProject(projectId, fieldsToCopy, overrideFields, user);
Comment on lines +357 to +358
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad code styling. Please fix indentation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this is not the style SW360 follows. There should be newline after the {.

Use the linting suggested!

}

}

///////////////////////////////
Expand Down
20 changes: 9 additions & 11 deletions libraries/datahandler/pom.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Irrelevant change. Please revert.

<!--
~ Copyright Siemens AG, 2013-2015, 2019. Part of the SW360 Portal Project.
~
Expand All @@ -8,6 +7,9 @@
~
~ SPDX-License-Identifier: EPL-2.0
-->


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
Expand All @@ -34,7 +36,7 @@
<artifactId>maven-thrift-plugin</artifactId>
<version>0.1.11</version>
<configuration>
<thriftExecutable>thrift</thriftExecutable>
<thriftExecutable>thrift</thriftExecutable>
<generator>java:generated_annotations=suppress</generator>
<checkStaleness>true</checkStaleness>
</configuration>
Expand Down Expand Up @@ -122,12 +124,10 @@
<configuration>
<artifactItems combine.children="append">
<artifactItem>
<groupId>
org.eclipse.sw360</groupId>
<groupId>org.eclipse.sw360</groupId>
<artifactId>build-configuration</artifactId>
<version>${project.version}</version>
<type>
jar</type>
<type>jar</type>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
</artifactItem>
</artifactItems>
Expand All @@ -142,12 +142,10 @@
<configuration>
<artifactItems combine.children="append">
<artifactItem>
<groupId>
org.eclipse.sw360</groupId>
<groupId>org.eclipse.sw360</groupId>
<artifactId>build-configuration</artifactId>
<version>${project.version}</version>
<type>
test-jar</type>
<type>test-jar</type>
<outputDirectory>${project.build.testOutputDirectory}</outputDirectory>
</artifactItem>
</artifactItems>
Expand Down Expand Up @@ -261,4 +259,4 @@
<scope>compile</scope>
</dependency>
</dependencies>
</project>
</project>
21 changes: 12 additions & 9 deletions libraries/datahandler/src/main/thrift/projects.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ struct ProjectData {
2: required list<Project> first250Projects,
3: optional list<string> projectIdsOfRemainingProject
}
struct Project {

struct Project {
// General information
1: optional string id,
2: optional string revision,
Expand All @@ -128,7 +128,6 @@ struct Project {
22: optional string projectResponsible,
23: optional string leadArchitect,
25: optional set<string> moderators = [],
// 26: optional set<string> comoderators, //deleted
27: optional set<string> contributors = [],
28: optional Visibility visbility = sw360.Visibility.EVERYONE,
29: optional map<string,set<string>> roles, //customized roles with set of mail addresses
Expand All @@ -142,7 +141,6 @@ struct Project {
30: optional map<string, ProjectProjectRelationship> linkedProjects,
31: optional map<string, ProjectReleaseRelationship> releaseIdToUsage,
32: optional map<string, ProjectPackageRelationship> packageIds,
//32: optional set<string> packageIds,

// Admin data
40: optional string clearingTeam;
Expand Down Expand Up @@ -173,7 +171,6 @@ struct Project {
80: optional string clearingRequestId,

// Optional fields for summaries!
// 100: optional set<string> releaseIds, //deleted
101: optional ReleaseClearingStateSummary releaseClearingStateSummary,

// linked release obligations
Expand All @@ -195,7 +192,6 @@ struct ProjectLink {
2: required string name,
3: optional ProjectRelationship relation,
4: optional string version,
// 5: optional string parentId,
6: optional string nodeId,
7: optional string parentNodeId,
8: optional ProjectType projectType,
Expand Down Expand Up @@ -298,7 +294,6 @@ struct ProjectDTO{
22: optional string projectResponsible,
23: optional string leadArchitect,
25: optional set<string> moderators = [],
// 26: optional set<string> comoderators, //deleted
27: optional set<string> contributors = [],
28: optional Visibility visbility = sw360.Visibility.BUISNESSUNIT_AND_MODERATORS,
29: optional map<string,set<string>> roles, //customized roles with set of mail addresses
Expand Down Expand Up @@ -335,7 +330,6 @@ struct ProjectDTO{
80: optional string clearingRequestId,

// Optional fields for summaries!
// 100: optional set<string> releaseIds, //deleted
101: optional ReleaseClearingStateSummary releaseClearingStateSummary,

// linked release obligations
Expand Down Expand Up @@ -702,14 +696,17 @@ service ProjectService {
* Send email to the user once spreadsheet export completed
*/
void sendExportSpreadsheetSuccessMail(1: string url, 2: string userEmail);

/*
* make excel export
*/
binary getReportDataStream(1: User user,2: bool extendedByReleases,3: string projectId) throws (1: SW360Exception exp);
/*

/*
* excel export - return the filepath
*/
string getReportInEmail(1: User user, 2: bool extendedByReleases, string projectId) throws (1: SW360Exception exp);

/*
* download excel
*/
Expand Down Expand Up @@ -763,4 +760,10 @@ service ProjectService {
* Get linked releases information in dependency network of a project
*/
list<ReleaseNode> getLinkedReleasesInDependencyNetworkOfProject(1: string projectId, 2: User sw360User) throws (1: SW360Exception exp);
}

/**
* Method to copy a project with specific fields
*/
AddDocumentRequestSummary copyProject(1: string projectId, 2: set<string> fieldsToCopy, 3: Project overrideFields, 4: User user) throws (1: SW360Exception exp);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.eclipse.sw360.rest.resourceserver.project;

import org.eclipse.sw360.datahandler.thrift.projects.Project;
import java.util.Set;

public class CopyProjectRequest {
private Set<String> fieldsToCopy;
private Project overrideFields;

public Set<String> getFieldsToCopy() { return fieldsToCopy; }
public void setFieldsToCopy(Set<String> fieldsToCopy) { this.fieldsToCopy = fieldsToCopy; }
public Project getOverrideFields() { return overrideFields; }
public void setOverrideFields(Project overrideFields) { this.overrideFields = overrideFields; }
}
Loading
Loading