diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java index b13a841e30..1749456ed3 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java @@ -193,7 +193,7 @@ public List convertReleaseToReleaseLink(String id,User user) throws ClearingReport clearingReport = new ClearingReport(); Set attachments = getAttachmentForClearingReport(release); - if (attachments.size() != 0 ) { + if (!attachments.equals(Collections.emptySet())) { Set attachmentsAccepted = getAttachmentsStatusAccept(attachments); if(attachmentsAccepted.size() != 0) { clearingReport.setClearingReportStatus(ClearingReportStatus.DOWNLOAD); @@ -217,6 +217,8 @@ public List convertReleaseToReleaseLink(String id,User user) throws private Set getAttachmentForClearingReport(Release release){ final Set attachments = release.getAttachments(); + if (CommonUtils.isNullOrEmptyCollection(attachments)) + return Collections.emptySet(); return attachments.stream().filter(attachment -> AttachmentType.COMPONENT_LICENSE_INFO_XML.equals(attachment.getAttachmentType()) || AttachmentType.CLEARING_REPORT.equals(attachment.getAttachmentType())) .collect(Collectors.toSet()); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index fc076ff47f..ee7b59975a 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -404,6 +404,8 @@ static abstract class ComponentMixin extends Component { "revision", "permissions", "moderators", + "subscribers", + "contributors", "clearingInformation", "setAttachments", "setCreatedOn", @@ -471,7 +473,6 @@ static abstract class ComponentMixin extends Component { "otherLicenseIdsSize", "setOtherLicenseIds", "setModifiedOn", - "modifiedOn", "setModifiedBy", "modifiedBy", "setComponentType" @@ -488,18 +489,12 @@ static abstract class ReleaseMixin extends Release { @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties({ - "vendor", - "longName", - "releaseRelationship", - "hasSubreleases", "nodeId", "parentNodeId", "componentType", - "licenseIds", "licenseNames", "comment", "otherLicenseIds", - "accessible", "attachmentsSize", "setName", "setVersion", @@ -997,11 +992,6 @@ public static abstract class VulnerabilityApiDTOMixin extends VulnerabilityApiDT @JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonIgnoreProperties({ - "assessorContactPerson", - "assessorDepartment", - "eccComment", - "materialIndexNumber", - "assessmentDate", "setEccComment", "setEccn", "setEccStatus", diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java index 034274f05b..71a11988f8 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java @@ -36,6 +36,7 @@ import org.eclipse.sw360.datahandler.thrift.components.Component; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.components.ReleaseLink; import org.eclipse.sw360.datahandler.thrift.licenses.License; import org.eclipse.sw360.datahandler.thrift.licenses.Obligation; import org.eclipse.sw360.datahandler.thrift.moderation.ModerationRequest; @@ -100,6 +101,7 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @@ -293,6 +295,56 @@ public void addEmbeddedContributors(HalResource halResource, Set contrib } } + public void addEmbeddedDataToHalResourceRelease(HalResource halResource, Release sw360Release) { + addEmbeddedContributorsToHalResourceRelease(halResource, sw360Release); + addEmbeddedCreatedByToHalResourceRelease(halResource, sw360Release.getCreatedBy()); + addEmbeddedModifiedByToHalResourceRelease(halResource, sw360Release.getModifiedBy()); + addEmbeddedSubcribeToHalResourceRelease(halResource, sw360Release); + } + + public void addEmbeddedContributorsToHalResourceRelease(HalResource halResource, Release sw360Release) { + if (!CommonUtils.isNullOrEmptyCollection(sw360Release.getContributors())) { + Set contributors = sw360Release.getContributors(); + for (String contributorEmail : contributors) { + User sw360User = getUserByEmail(contributorEmail); + if (null != sw360User) { + addEmbeddedUser(halResource, sw360User, "sw360:contributors"); + sw360Release.setContributors(null); + } + + } + } + } + + public void addEmbeddedSubcribeToHalResourceRelease(HalResource halResource, Release sw360Release) { + if (!CommonUtils.isNullOrEmptyCollection(sw360Release.getSubscribers())) { + Set subscribers = sw360Release.getSubscribers(); + for (String subscribersEmail : subscribers) { + User sw360User = getUserByEmail(subscribersEmail); + if (null != sw360User) { + addEmbeddedUser(halResource, sw360User, "sw360:subscribers"); + sw360Release.setSubscribers(null); + } + } + } + } + + public void addEmbeddedCreatedByToHalResourceRelease(HalResource halRelease, String createdBy) { + if (CommonUtils.isNotNullEmptyOrWhitespace(createdBy)) { + User releaseCreator = getUserByEmail(createdBy); + if (null != releaseCreator ) + addEmbeddedUser(halRelease, releaseCreator, "sw360:createdBy"); + } + } + + public void addEmbeddedModifiedByToHalResourceRelease(HalResource halRelease, String modifiedBy) { + if (CommonUtils.isNotNullEmptyOrWhitespace(modifiedBy)) { + User releaseModify = getUserByEmail(modifiedBy); + if (null != releaseModify) + addEmbeddedUser(halRelease, releaseModify, "sw360:modifiedBy"); + } + } + public void addEmbeddedLeadArchitect(HalResource halResource, String leadArchitect) { User sw360User = getUserByEmail(leadArchitect); addEmbeddedUser(halResource, sw360User, "leadArchitect"); @@ -317,6 +369,15 @@ public void addEmbeddedReleases( } } + public void addEmbeddedReleaseLinks( + HalResource halResource, + List releaseLinks) { + List releaseLinkInogreAttachments = releaseLinks.stream().map(releaseLink -> releaseLink.setAttachments(null)).collect(Collectors.toList()); + for (ReleaseLink releaseLink : releaseLinkInogreAttachments) { + addEmbeddedReleaseLink(halResource, releaseLink); + } + } + public void addEmbeddedUser(HalResource halResource, User user, String relation) { User embeddedUser = convertToEmbeddedUser(user); EntityModel embeddedUserResource = EntityModel.of(embeddedUser); @@ -353,6 +414,23 @@ public HalResource addEmbeddedVendor(String vendorFullName) { return null; } + public HalResource addEmbeddedVendor(Vendor vendor) { + Vendor embeddedVendor = convertToEmbeddedVendor(vendor); + HalResource halVendor = new HalResource<>(embeddedVendor); + try { + Vendor vendorByFullName = vendorService.getVendorByFullName(vendor.getFullname()); + if(vendorByFullName != null) { + Link vendorSelfLink = linkTo(UserController.class) + .slash("api" + VendorController.VENDORS_URL + "/" + vendorByFullName.getId()).withSelfRel(); + halVendor.add(vendorSelfLink); + } + return halVendor; + } catch (Exception e) { + LOGGER.error("cannot create self link for vendor with full name: " + vendor.getFullname()); + } + return null; + } + public void addEmbeddedLicenses(HalResource halComponent, Set licenseIds) { for (String licenseId : licenseIds) { HalResource licenseHalResource = addEmbeddedLicense(licenseId); @@ -395,6 +473,11 @@ public void addEmbeddedRelease(HalResource halResource, Release release) { halResource.addEmbeddedResource("sw360:releases", halRelease); } + public void addEmbeddedReleaseLink(HalResource halResource, ReleaseLink releaseLink) { + HalResource halRelease = new HalResource<>(releaseLink); + halResource.addEmbeddedResource("sw360:releaseLinks", halRelease); + } + public void addEmbeddedAttachments( HalResource halResource, Set attachments) { @@ -652,9 +735,10 @@ public Obligation convertToEmbeddedObligation(Obligation obligation) { } public Vendor convertToEmbeddedVendor(Vendor vendor) { - Vendor embeddedVendor = convertToEmbeddedVendor(vendor.getFullname()); + Vendor embeddedVendor = new Vendor(); embeddedVendor.setId(vendor.getId()); embeddedVendor.setShortname(vendor.getShortname()); + embeddedVendor.setFullname(vendor.getFullname()); embeddedVendor.setUrl(vendor.getUrl()); return embeddedVendor; } @@ -988,6 +1072,7 @@ public void addEmbeddedModerationRequest(HalResource halResource, ModerationRequ public void addEmbeddedDataToComponent(HalResource halResource, Component sw360Component) { addEmbeddedModifiedByToComponent(halResource,sw360Component); addEmbeddedComponentOwnerToComponent(halResource,sw360Component); + addEmbeddedSubcribeToHalResourceComponent(halResource,sw360Component); } public void addEmbeddedModifiedByToComponent(HalResource halResource, Component sw360Component) { @@ -1007,4 +1092,18 @@ public void addEmbeddedComponentOwnerToComponent(HalResource halResource, Compon } } } + + public void addEmbeddedSubcribeToHalResourceComponent(HalResource halResource, Component sw360Component) { + if (!CommonUtils.isNullOrEmptyCollection(sw360Component.getSubscribers())) { + Set subscribers = sw360Component.getSubscribers(); + for (String subscribersEmail : subscribers) { + User sw360User = getUserByEmail(subscribersEmail); + if (null != sw360User) { + addEmbeddedUser(halResource, sw360User, "sw360:subscribers"); + sw360Component.setSubscribers(null); + } + } + } + } + } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java index 474bdd6ec2..54633a4e0b 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java @@ -28,6 +28,7 @@ import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentDTO; import org.eclipse.sw360.datahandler.thrift.attachments.UsageAttachment; import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.components.ReleaseLink; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.components.Component; import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcess; @@ -173,13 +174,10 @@ public ResponseEntity getRelease( User sw360User = restControllerHelper.getSw360UserFromAuthentication(); Release sw360Release = releaseService.getReleaseForUserById(id, sw360User); HalResource halRelease = createHalReleaseResource(sw360Release, true); - Map releaseIdToRelationship = sw360Release.getReleaseIdToRelationship(); - if (releaseIdToRelationship != null) { - List listOfLinkedRelease = releaseIdToRelationship.keySet().stream() - .map(linkedReleaseId -> wrapTException( - () -> releaseService.getReleaseForUserById(linkedReleaseId, sw360User))) - .collect(Collectors.toList()); - restControllerHelper.addEmbeddedReleases(halRelease, listOfLinkedRelease); + restControllerHelper.addEmbeddedDataToHalResourceRelease(halRelease, sw360Release); + List linkedReleaseRelations = releaseService.getLinkedReleaseRelations(sw360Release, sw360User); + if (linkedReleaseRelations != null) { + restControllerHelper.addEmbeddedReleaseLinks(halRelease, linkedReleaseRelations); } return new ResponseEntity<>(halRelease, HttpStatus.OK); } @@ -607,7 +605,7 @@ private HalResource createHalReleaseResource(Release release, boolean v } if (release.getVendor() != null) { Vendor vendor = release.getVendor(); - HalResource vendorHalResource = restControllerHelper.addEmbeddedVendor(vendor.getFullname()); + HalResource vendorHalResource = restControllerHelper.addEmbeddedVendor(vendor); halRelease.addEmbeddedResource("sw360:vendors", vendorHalResource); release.setVendor(null); } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java index 70c04e3e51..478a05a698 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java @@ -29,14 +29,8 @@ import org.eclipse.sw360.datahandler.thrift.ThriftClients; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType; -import org.eclipse.sw360.datahandler.thrift.components.Component; +import org.eclipse.sw360.datahandler.thrift.components.*; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; -import org.eclipse.sw360.datahandler.thrift.components.ComponentType; -import org.eclipse.sw360.datahandler.thrift.components.ExternalTool; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcess; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStatus; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStep; -import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.fossology.FossologyService; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.users.User; @@ -57,16 +51,13 @@ import org.springframework.util.FileCopyUtils; import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; +import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptyString; import static org.eclipse.sw360.datahandler.common.WrappedException.wrapTException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; @@ -118,6 +109,22 @@ public Release getReleaseForUserById(String releaseId, User sw360User) throws TE return releaseById; } + public List getLinkedReleaseRelations(Release release, User user) throws TException { + List linkedReleaseRelations = getLinkedReleaseRelationsWithAccessibility(release, user); + linkedReleaseRelations = linkedReleaseRelations.stream().filter(Objects::nonNull).sorted(Comparator.comparing( + rl -> rl.isAccessible() ? SW360Utils.getVersionedName(nullToEmptyString(rl.getName()), rl.getVersion()) : "~", String.CASE_INSENSITIVE_ORDER) + ).collect(Collectors.toList()); + return linkedReleaseRelations; + } + + public List getLinkedReleaseRelationsWithAccessibility(Release release, User user) throws TException { + if (release != null && release.getReleaseIdToRelationship() != null) { + ComponentService.Iface componentClient = getThriftComponentClient(); + return componentClient.getLinkedReleaseRelationsWithAccessibility(release.getReleaseIdToRelationship(), user); + } + return Collections.emptyList(); + } + public Release setComponentDependentFieldsInRelease(Release releaseById, User sw360User) { String componentId = releaseById.getComponentId(); if (CommonUtils.isNullEmptyOrWhitespace(componentId)) { diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java index b87979bc29..69fb4eda6e 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java @@ -15,17 +15,9 @@ import org.eclipse.sw360.datahandler.thrift.*; import org.eclipse.sw360.datahandler.thrift.attachments.*; import org.eclipse.sw360.datahandler.thrift.vulnerabilities.*; +import org.eclipse.sw360.datahandler.thrift.components.*; +import org.eclipse.sw360.datahandler.thrift.vulnerabilities.*; import org.eclipse.sw360.rest.resourceserver.attachment.AttachmentInfo; -import org.eclipse.sw360.datahandler.thrift.components.COTSDetails; -import org.eclipse.sw360.datahandler.thrift.components.ClearingInformation; -import org.eclipse.sw360.datahandler.thrift.components.ClearingState; -import org.eclipse.sw360.datahandler.thrift.components.Component; -import org.eclipse.sw360.datahandler.thrift.components.ComponentType; -import org.eclipse.sw360.datahandler.thrift.components.ExternalTool; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcess; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStatus; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStep; -import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.licenses.License; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectType; @@ -177,6 +169,17 @@ public void before() throws TException, IOException { releaseExternalIds.put("mainline-id-component", "1432"); releaseExternalIds.put("ws-component-id", "[\"2365\",\"5487923\"]"); + EccInformation eccInformation = new EccInformation(); + eccInformation.setAl("AL"); + eccInformation.setEccn("ECCN"); + eccInformation.setAssessorContactPerson("admin@sw360.org"); + eccInformation.setAssessorDepartment("DEPARTMENT"); + eccInformation.setEccComment("Set ECC"); + eccInformation.setMaterialIndexNumber("12"); + eccInformation.setAssessmentDate("2023-06-27"); + eccInformation.setEccStatus(ECCStatus.OPEN); + + release.setId(releaseId); owner.setReleaseId(release.getId()); release.setName("Spring Core 4.3.4"); @@ -186,7 +189,10 @@ public void before() throws TException, IOException { release.setCreatedOn("2016-12-18"); release.setCreatedBy("admin@sw360.org"); release.setModerators(new HashSet<>(Arrays.asList("admin@sw360.org", "jane@sw360.org"))); + release.setSubscribers(new HashSet<>(Arrays.asList("admin@sw360.org", "jane@sw360.org"))); + release.setContributors(new HashSet<>(Arrays.asList("admin@sw360.org", "jane@sw360.org"))); release.setCreatedBy("admin@sw360.org"); + release.setModifiedBy("admin@sw360.org"); release.setSourceCodeDownloadurl("http://www.google.com"); release.setBinaryDownloadurl("http://www.google.com/binaries"); release.setComponentId(component.getId()); @@ -201,6 +207,7 @@ public void before() throws TException, IOException { release.setOtherLicenseIds(new HashSet<>(Arrays.asList("MIT", "BSD-3-Clause"))); release.setOperatingSystems(ImmutableSet.of("Windows", "Linux")); release.setSoftwarePlatforms(new HashSet<>(Arrays.asList("Java SE", ".NET"))); + release.setEccInformation(eccInformation); releaseList.add(release); Release release2 = new Release(); @@ -230,6 +237,7 @@ public void before() throws TException, IOException { release2.setReleaseIdToRelationship(releaseIdToRelationship); release2.setClearingInformation(clearingInfo); release2.setCotsDetails(cotsDetails); + release2.setEccInformation(eccInformation); releaseList.add(release2); release3 = new Release(); @@ -479,9 +487,9 @@ public void should_document_get_release_all_details() throws Exception { subsectionWithPath("_embedded.sw360:releases.[]sourceCodeDownloadurl").description("the source code download url of the release"), subsectionWithPath("_embedded.sw360:releases.[]binaryDownloadurl").description("the binary download url of the release"), subsectionWithPath("_embedded.sw360:releases.[]externalIds").description("When releases are imported from other tools, the external ids can be stored here. Store as 'Single String' when single value, or 'Array of String' when multi-values"), + subsectionWithPath("_embedded.sw360:releases.[]eccInformation").description("The eccInformation of this release"), subsectionWithPath("_embedded.sw360:releases.[]additionalData").description("A place to store additional data used by external tools").optional(), subsectionWithPath("_embedded.sw360:releases.[]languages").description("The language of the component"), - subsectionWithPath("_embedded.sw360:releases.[]contributors").description("An array of all project contributors with email").optional(), subsectionWithPath("_embedded.sw360:releases.[]mainLicenseIds").description("An array of all main licenses").optional(), subsectionWithPath("_embedded.sw360:releases.[]otherLicenseIds").description("An array of all other licenses associated with the release").optional(), subsectionWithPath("_embedded.sw360:releases.[]operatingSystems").description("The OS on which the release operates"), @@ -577,6 +585,7 @@ public void should_document_get_release() throws Exception { fieldWithPath("createdOn").description("The creation date of the internal sw360 release"), fieldWithPath("componentType").description("The componentType of the release, possible values are " + Arrays.asList(ComponentType.values())), fieldWithPath("mainlineState").description("the mainline state of the release, possible values are: " + Arrays.asList(MainlineState.values())), + subsectionWithPath("eccInformation").description("The eccInformation of this release"), fieldWithPath("sourceCodeDownloadurl").description("the source code download url of the release"), fieldWithPath("binaryDownloadurl").description("the binary download url of the release"), fieldWithPath("otherLicenseIds").description("An array of all other licenses associated with the release"), @@ -587,6 +596,10 @@ public void should_document_get_release() throws Exception { fieldWithPath("operatingSystems").description("The OS on which the release operates"), fieldWithPath("softwarePlatforms").description("The software platforms of the component"), subsectionWithPath("_embedded.sw360:moderators").description("An array of all release moderators with email and link to their <>"), + subsectionWithPath("_embedded.sw360:subscribers").description("An array of all release subscribers with email and link to their <>"), + subsectionWithPath("_embedded.sw360:contributors").description("An array of all release contributors with email and link to their <>"), + subsectionWithPath("_embedded.sw360:modifiedBy").description("A release modifiedBy with email and link to their <>"), + subsectionWithPath("_embedded.sw360:createdBy").description("A release createdBy with email and link to their <>"), subsectionWithPath("_embedded.sw360:attachments").description("An array of all release attachments and link to their <>"), subsectionWithPath("_links").description("<> to other resources") ))); @@ -883,6 +896,7 @@ private RestDocumentationResultHandler documentReleaseProperties() { fieldWithPath("createdOn").description("The creation date of the internal sw360 release"), fieldWithPath("mainlineState").description("the mainline state of the release, possible values are: " + Arrays.asList(MainlineState.values())), fieldWithPath("sourceCodeDownloadurl").description("the source code download url of the release"), + subsectionWithPath("eccInformation").description("The eccInformation of this release"), fieldWithPath("binaryDownloadurl").description("the binary download url of the release"), fieldWithPath("otherLicenseIds").description("An array of all other licenses associated with the release"), subsectionWithPath("externalIds").description("When releases are imported from other tools, the external ids can be stored here. Store as 'Single String' when single value, or 'Array of String' when multi-values"),