From dace61d70935f40db3e72dea25efdf3e9360974a Mon Sep 17 00:00:00 2001 From: Patrick Hobusch Date: Tue, 29 Sep 2020 17:34:15 +0800 Subject: [PATCH] Implement applications endpoint --- index.adoc | 302 ++++++++++++++++++ .../confapi/crowd/model/ApplicationBean.java | 43 +++ .../confapi/crowd/model/ApplicationsBean.java | 21 ++ .../crowd/model/util/ApplicationBeanUtil.java | 93 ++++++ .../crowd/rest/ApplicationsResourceImpl.java | 47 +++ .../crowd/rest/api/ApplicationsResource.java | 32 ++ .../service/ApplicationsServiceImpl.java | 109 +++++++ .../service/api/ApplicationsService.java | 15 + 8 files changed, 662 insertions(+) create mode 100644 src/main/java/de/aservo/confapi/crowd/model/ApplicationBean.java create mode 100644 src/main/java/de/aservo/confapi/crowd/model/ApplicationsBean.java create mode 100644 src/main/java/de/aservo/confapi/crowd/model/util/ApplicationBeanUtil.java create mode 100644 src/main/java/de/aservo/confapi/crowd/rest/ApplicationsResourceImpl.java create mode 100644 src/main/java/de/aservo/confapi/crowd/rest/api/ApplicationsResource.java create mode 100644 src/main/java/de/aservo/confapi/crowd/service/ApplicationsServiceImpl.java create mode 100644 src/main/java/de/aservo/confapi/crowd/service/api/ApplicationsService.java diff --git a/index.adoc b/index.adoc index ec6f17b..bed18ec 100644 --- a/index.adoc +++ b/index.adoc @@ -309,6 +309,246 @@ ifdef::internal-generation[] endif::internal-generation[] +[.Default] +=== Default + + +[.addApplication] +==== addApplication + +`POST /applications` + + + +===== Description + + + + +// markup not found, no include::{specDir}applications/POST/spec.adoc[opts=optional] + + + +===== Parameters + + +===== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| ApplicationBean +| <> +| - +| +| + +|=== + + + + + +===== Return Type + + + +- + +===== Content Type + +* application/json + +===== Responses + +.http response codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 0 +| default response +| <<>> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}applications/POST/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}applications/POST/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :applications/POST/POST.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}applications/POST/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getApplications] +==== getApplications + +`GET /applications` + + + +===== Description + + + + +// markup not found, no include::{specDir}applications/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + + + +- + +===== Content Type + +* application/json + +===== Responses + +.http response codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 0 +| default response +| <<>> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}applications/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}applications/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :applications/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}applications/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.setApplications] +==== setApplications + +`PUT /applications` + + + +===== Description + + + + +// markup not found, no include::{specDir}applications/PUT/spec.adoc[opts=optional] + + + +===== Parameters + + +===== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| ApplicationsBean +| <> +| - +| +| + +|=== + + + + + +===== Return Type + + + +- + +===== Content Type + +* application/json + +===== Responses + +.http response codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 0 +| default response +| <<>> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}applications/PUT/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}applications/PUT/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :applications/PUT/PUT.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}applications/PUT/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + [.Directories] === Directories @@ -955,6 +1195,49 @@ endif::internal-generation[] |=== +[#ApplicationBean] +=== _ApplicationBean_ + + + +[.fields-ApplicationBean] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| name +| +| String +| +| + +| description +| +| String +| +| + +| active +| +| Boolean +| +| + +| type +| +| String +| +| _Enum:_ GENERIC, PLUGIN, CROWD, JIRA, CONFLUENCE, BITBUCKET, FISHEYE, CRUCIBLE, BAMBOO, + +| password +| +| String +| +| + +|=== + + [#ApplicationLinkBean] === _ApplicationLinkBean_ @@ -1041,6 +1324,25 @@ endif::internal-generation[] |=== +[#ApplicationsBean] +=== _ApplicationsBean_ + + + +[.fields-ApplicationsBean] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| applications +| +| List of <> +| +| + +|=== + + [#DirectoriesBean] === _DirectoriesBean_ diff --git a/src/main/java/de/aservo/confapi/crowd/model/ApplicationBean.java b/src/main/java/de/aservo/confapi/crowd/model/ApplicationBean.java new file mode 100644 index 0000000..5d9c32b --- /dev/null +++ b/src/main/java/de/aservo/confapi/crowd/model/ApplicationBean.java @@ -0,0 +1,43 @@ +package de.aservo.confapi.crowd.model; + +import de.aservo.confapi.commons.constants.ConfAPI; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@Data +@NoArgsConstructor +@XmlRootElement(name = ConfAPI.APPLICATION) +public class ApplicationBean { + + public enum ApplicationType { + GENERIC, + PLUGIN, + CROWD, + JIRA, + CONFLUENCE, + BITBUCKET, + FISHEYE, + CRUCIBLE, + BAMBOO, + ; + } + + @XmlElement + private String name; + + @XmlElement + private String description; + + @XmlElement + private Boolean active; + + @XmlElement + private ApplicationType type; + + @XmlElement + private String password; + +} diff --git a/src/main/java/de/aservo/confapi/crowd/model/ApplicationsBean.java b/src/main/java/de/aservo/confapi/crowd/model/ApplicationsBean.java new file mode 100644 index 0000000..0c9af41 --- /dev/null +++ b/src/main/java/de/aservo/confapi/crowd/model/ApplicationsBean.java @@ -0,0 +1,21 @@ +package de.aservo.confapi.crowd.model; + +import de.aservo.confapi.commons.constants.ConfAPI; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.Collection; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@XmlRootElement(name = ConfAPI.APPLICATIONS) +public class ApplicationsBean { + + @XmlElement + private Collection applications; + +} diff --git a/src/main/java/de/aservo/confapi/crowd/model/util/ApplicationBeanUtil.java b/src/main/java/de/aservo/confapi/crowd/model/util/ApplicationBeanUtil.java new file mode 100644 index 0000000..e6bbaf4 --- /dev/null +++ b/src/main/java/de/aservo/confapi/crowd/model/util/ApplicationBeanUtil.java @@ -0,0 +1,93 @@ +package de.aservo.confapi.crowd.model.util; + +import com.atlassian.crowd.embedded.api.PasswordCredential; +import com.atlassian.crowd.model.application.Application; +import com.atlassian.crowd.model.application.ApplicationType; +import com.atlassian.crowd.model.application.ImmutableApplication; +import de.aservo.confapi.crowd.model.ApplicationBean; + +import javax.annotation.Nullable; + +public class ApplicationBeanUtil { + + public static ApplicationBean toApplicationBean( + final Application application) { + + final ApplicationBean applicationBean = new ApplicationBean(); + + applicationBean.setName(application.getName()); + applicationBean.setDescription(application.getDescription()); + applicationBean.setActive(application.isActive()); + applicationBean.setType(toApplicationBeanType(application.getType())); + + return applicationBean; + } + + public static Application toApplication( + final ApplicationBean applicationBean) { + + return ImmutableApplication.builder(applicationBean.getName(), toApplicationType(applicationBean.getType())) + .setDescription(applicationBean.getDescription()) + .setActive(applicationBean.getActive()) + .setPasswordCredential(PasswordCredential.unencrypted(applicationBean.getPassword())) + .build(); + } + + @Nullable + public static ApplicationBean.ApplicationType toApplicationBeanType( + final ApplicationType applicationType) { + + if (ApplicationType.GENERIC_APPLICATION.equals(applicationType)) { + return ApplicationBean.ApplicationType.GENERIC; + } else if (ApplicationType.PLUGIN.equals(applicationType)) { + return ApplicationBean.ApplicationType.PLUGIN; + } else if (ApplicationType.CROWD.equals(applicationType)) { + return ApplicationBean.ApplicationType.CROWD; + } else if (ApplicationType.JIRA.equals(applicationType)) { + return ApplicationBean.ApplicationType.JIRA; + } else if (ApplicationType.CONFLUENCE.equals(applicationType)) { + return ApplicationBean.ApplicationType.CONFLUENCE; + } else if (ApplicationType.STASH.equals(applicationType)) { + return ApplicationBean.ApplicationType.BITBUCKET; + } else if (ApplicationType.FISHEYE.equals(applicationType)) { + return ApplicationBean.ApplicationType.FISHEYE; + } else if (ApplicationType.CRUCIBLE.equals(applicationType)) { + return ApplicationBean.ApplicationType.CRUCIBLE; + } else if (ApplicationType.BAMBOO.equals(applicationType)) { + return ApplicationBean.ApplicationType.BAMBOO; + } + + return null; + } + + @Nullable + public static ApplicationType toApplicationType( + final ApplicationBean.ApplicationType applicationBeanType) { + + if (ApplicationBean.ApplicationType.GENERIC.equals(applicationBeanType)) { + return ApplicationType.GENERIC_APPLICATION; + } else if (ApplicationBean.ApplicationType.PLUGIN.equals(applicationBeanType)) { + return ApplicationType.PLUGIN; + } else if (ApplicationBean.ApplicationType.CROWD.equals(applicationBeanType)) { + return ApplicationType.CROWD; + } else if (ApplicationBean.ApplicationType.JIRA.equals(applicationBeanType)) { + return ApplicationType.JIRA; + } else if (ApplicationBean.ApplicationType.CONFLUENCE.equals(applicationBeanType)) { + return ApplicationType.CONFLUENCE; + } else if (ApplicationBean.ApplicationType.BITBUCKET.equals(applicationBeanType)) { + return ApplicationType.STASH; + } else if (ApplicationBean.ApplicationType.FISHEYE.equals(applicationBeanType)) { + return ApplicationType.FISHEYE; + } else if (ApplicationBean.ApplicationType.CRUCIBLE.equals(applicationBeanType)) { + return ApplicationType.CRUCIBLE; + } else if (ApplicationBean.ApplicationType.BAMBOO.equals(applicationBeanType)) { + return ApplicationType.BAMBOO; + } + + return null; + } + + private ApplicationBeanUtil() { + } + +} diff --git a/src/main/java/de/aservo/confapi/crowd/rest/ApplicationsResourceImpl.java b/src/main/java/de/aservo/confapi/crowd/rest/ApplicationsResourceImpl.java new file mode 100644 index 0000000..73a76c8 --- /dev/null +++ b/src/main/java/de/aservo/confapi/crowd/rest/ApplicationsResourceImpl.java @@ -0,0 +1,47 @@ +package de.aservo.confapi.crowd.rest; + +import com.sun.jersey.spi.container.ResourceFilters; +import de.aservo.confapi.commons.constants.ConfAPI; +import de.aservo.confapi.crowd.filter.SysadminOnlyResourceFilter; +import de.aservo.confapi.crowd.model.ApplicationBean; +import de.aservo.confapi.crowd.model.ApplicationsBean; +import de.aservo.confapi.crowd.rest.api.ApplicationsResource; +import de.aservo.confapi.crowd.service.api.ApplicationsService; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +@Path(ConfAPI.APPLICATIONS) +@ResourceFilters(SysadminOnlyResourceFilter.class) +@Component +public class ApplicationsResourceImpl implements ApplicationsResource { + + private final ApplicationsService applicationsService; + + @Inject + public ApplicationsResourceImpl( + final ApplicationsService applicationsService) { + + this.applicationsService = applicationsService; + } + + @Override + public Response getApplications() { + return Response.ok(applicationsService.getApplications()).build(); + } + + @Override + public Response setApplications( + final ApplicationsBean applicationsBean) { + return Response.ok(applicationsService.setApplications(applicationsBean)).build(); + } + + @Override + public Response addApplication( + final ApplicationBean applicationBean) { + return Response.ok(applicationsService.addApplication(applicationBean)).build(); + } + +} diff --git a/src/main/java/de/aservo/confapi/crowd/rest/api/ApplicationsResource.java b/src/main/java/de/aservo/confapi/crowd/rest/api/ApplicationsResource.java new file mode 100644 index 0000000..8b805b0 --- /dev/null +++ b/src/main/java/de/aservo/confapi/crowd/rest/api/ApplicationsResource.java @@ -0,0 +1,32 @@ +package de.aservo.confapi.crowd.rest.api; + +import de.aservo.confapi.crowd.model.ApplicationBean; +import de.aservo.confapi.crowd.model.ApplicationsBean; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +public interface ApplicationsResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + Response getApplications(); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + Response setApplications( + ApplicationsBean applicationsBean); + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + Response addApplication( + ApplicationBean applicationBean); + +} diff --git a/src/main/java/de/aservo/confapi/crowd/service/ApplicationsServiceImpl.java b/src/main/java/de/aservo/confapi/crowd/service/ApplicationsServiceImpl.java new file mode 100644 index 0000000..049c6a6 --- /dev/null +++ b/src/main/java/de/aservo/confapi/crowd/service/ApplicationsServiceImpl.java @@ -0,0 +1,109 @@ +package de.aservo.confapi.crowd.service; + +import com.atlassian.crowd.embedded.api.PasswordCredential; +import com.atlassian.crowd.exception.ApplicationAlreadyExistsException; +import com.atlassian.crowd.exception.ApplicationNotFoundException; +import com.atlassian.crowd.exception.InvalidCredentialException; +import com.atlassian.crowd.manager.application.ApplicationManager; +import com.atlassian.crowd.manager.application.ApplicationManagerException; +import com.atlassian.crowd.model.application.Application; +import com.atlassian.crowd.model.application.ImmutableApplication; +import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService; +import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; +import de.aservo.confapi.commons.exception.BadRequestException; +import de.aservo.confapi.commons.exception.InternalServerErrorException; +import de.aservo.confapi.crowd.model.ApplicationBean; +import de.aservo.confapi.crowd.model.ApplicationsBean; +import de.aservo.confapi.crowd.model.util.ApplicationBeanUtil; +import de.aservo.confapi.crowd.service.api.ApplicationsService; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@ExportAsService(ApplicationsService.class) +public class ApplicationsServiceImpl implements ApplicationsService { + + private final ApplicationManager applicationManager; + + @Inject + public ApplicationsServiceImpl( + @ComponentImport final ApplicationManager applicationManager) { + + this.applicationManager = applicationManager; + } + + @Override + public ApplicationsBean getApplications() { + return new ApplicationsBean(applicationManager.findAll().stream() + .map(ApplicationBeanUtil::toApplicationBean) + .collect(Collectors.toList())); + } + + @Override + public ApplicationsBean setApplications( + final ApplicationsBean applicationsBean) { + + final List applicationBeans = new ArrayList<>(); + + for (ApplicationBean applicationBean : applicationsBean.getApplications()) { + try { + final Application application = applicationManager.findByName(applicationBean.getName()); + applicationBeans.add(setApplication(application.getId(), applicationBean)); + } catch (ApplicationNotFoundException ignored) { + applicationBeans.add(addApplication(applicationBean)); + } + } + + return new ApplicationsBean(applicationBeans); + } + + public ApplicationBean setApplication( + final long id, + final ApplicationBean applicationBean) { + + try { + final ImmutableApplication.Builder applicationBuilder = new ImmutableApplication.Builder(applicationManager.findById(id)); + + if (applicationBean.getName() != null) { + applicationBuilder.setName(applicationBean.getName()); + } + if (applicationBean.getType() != null) { + applicationBuilder.setType(ApplicationBeanUtil.toApplicationType(applicationBean.getType())); + } + if (applicationBean.getDescription() != null) { + applicationBuilder.setDescription(applicationBean.getDescription()); + } + if (applicationBean.getActive() != null) { + applicationBuilder.setActive(applicationBean.getActive()); + } + if (applicationBean.getPassword() != null) { + applicationBuilder.setPasswordCredential(PasswordCredential.unencrypted(applicationBean.getPassword())); + } + + try { + return ApplicationBeanUtil.toApplicationBean(applicationManager.update(applicationBuilder.build())); + } catch (ApplicationManagerException e) { + throw new InternalServerErrorException(e); + } + } catch (ApplicationNotFoundException e) { + throw new BadRequestException(e); + } + } + + @Override + public ApplicationBean addApplication( + final ApplicationBean applicationBean) { + + try { + final Application createdApplication = applicationManager.add(ApplicationBeanUtil.toApplication(applicationBean)); + return ApplicationBeanUtil.toApplicationBean(createdApplication); + } catch (InvalidCredentialException | ApplicationAlreadyExistsException e) { + throw new BadRequestException(e); + } + } + +} diff --git a/src/main/java/de/aservo/confapi/crowd/service/api/ApplicationsService.java b/src/main/java/de/aservo/confapi/crowd/service/api/ApplicationsService.java new file mode 100644 index 0000000..da3182d --- /dev/null +++ b/src/main/java/de/aservo/confapi/crowd/service/api/ApplicationsService.java @@ -0,0 +1,15 @@ +package de.aservo.confapi.crowd.service.api; + +import de.aservo.confapi.crowd.model.ApplicationBean; +import de.aservo.confapi.crowd.model.ApplicationsBean; + +public interface ApplicationsService { + + ApplicationsBean getApplications(); + + ApplicationsBean setApplications( + ApplicationsBean applicationsBean); + + ApplicationBean addApplication( + ApplicationBean applicationBean); +}