From f1f87a39f234848e0d5d0402a415a0c23cab7a3d Mon Sep 17 00:00:00 2001 From: "CMX\\tiburon" Date: Mon, 29 Jul 2019 13:07:40 +0300 Subject: [PATCH] =?UTF-8?q?URMS-36:=20=D0=9F=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20Confluence=20=D0=B3=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B2,=20=D0=BF=D1=80=D0=BE=D1=82=D0=B5=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=B8=20=D0=BF=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=B2=20=D0=B1=D0=BE?= =?UTF-8?q?=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + README.md | 340 ++++++++++++- pom.xml | 229 +++++++++ .../confluence/api/DataCleanUtil.java | 49 ++ .../confluence/api/DateTimeUtil.java | 7 + .../confluence/impl/DataCleanUtilImpl.java | 364 ++++++++++++++ .../confluence/impl/DateTimeUtilImpl.java | 21 + .../confluence/rest/AllTrashModel.java | 34 ++ .../confluence/rest/AllVersionsModel.java | 34 ++ .../confluence/rest/AttachmentModel.java | 79 +++ .../atlassian/confluence/rest/Cleaner.java | 456 ++++++++++++++++++ .../confluence/rest/DirectoryModel.java | 35 ++ .../confluence/rest/DirectoryService.java | 80 +++ .../atlassian/confluence/rest/ErrorModel.java | 52 ++ .../confluence/rest/MessageModel.java | 29 ++ .../confluence/rest/PageVersionsModel.java | 109 +++++ .../confluence/rest/ResponseModel.java | 103 ++++ .../confluence/rest/SpaceTrashModel.java | 53 ++ .../confluence/rest/SpaceVersionsModel.java | 70 +++ .../confluence/rest/TrashBaseParam.java | 71 +++ .../confluence/rest/VersionBaseParam.java | 99 ++++ .../META-INF/spring/plugin-context.xml | 10 + src/main/resources/atlassian-plugin.xml | 24 + src/main/resources/css/rest-extender.css | 0 .../resources/images/logo_octo_375_293.png | Bin 0 -> 48446 bytes src/main/resources/js/rest-extender.js | 0 src/main/resources/rest-extender.properties | 3 + 27 files changed, 2355 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/su/tiburon/atlassian/confluence/api/DataCleanUtil.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/api/DateTimeUtil.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/impl/DataCleanUtilImpl.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/impl/DateTimeUtilImpl.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/AllTrashModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/AllVersionsModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/AttachmentModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/Cleaner.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/DirectoryModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/DirectoryService.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/ErrorModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/MessageModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/PageVersionsModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/ResponseModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/SpaceTrashModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/SpaceVersionsModel.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/TrashBaseParam.java create mode 100644 src/main/java/su/tiburon/atlassian/confluence/rest/VersionBaseParam.java create mode 100644 src/main/resources/META-INF/spring/plugin-context.xml create mode 100644 src/main/resources/atlassian-plugin.xml create mode 100644 src/main/resources/css/rest-extender.css create mode 100644 src/main/resources/images/logo_octo_375_293.png create mode 100644 src/main/resources/js/rest-extender.js create mode 100644 src/main/resources/rest-extender.properties diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7278377 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.settings +.vscode +target +.classpath +.project diff --git a/README.md b/README.md index b54cc18..29d1ba7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,340 @@ -# confluence-rest-extender +[![OCTOPUS CodeWare](https://www.tiburon.su/files/logo_octo_text_600_153.png)](https://www.tiburon.su) +REST API Extender for Confluence +================ + +REST API for automated Confluence configuration with URMS + + +Resources +--------- + +All resources produce JSON (media type: `application/json`) results. + +### Page versions + +Get and delete outdated page versions + +* #### `GET /rest/extender/1/api/versions/all` + + **QueryString** + - type: all or page or attachment. (default:all) + - endDays: 1~ (0=today, 1=yesterday) (default:0) + - limit: 1~1000 (default:1000) + + Get all page versions in all instance. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + +* #### `DELETE /rest/extender/1/api/versions/all` + + **QueryString** + - type: all or page or attachment. (default:all) + - endDays: 1~ (0=today, 1=yesterday) (default:0) + - limit: 1~1000 (default:1000) + + Remove all page versions in all instance. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + + +* #### `GET /rest/extender/1/api/versions/space/{spaceKey}` + + **QueryString** + - spaceKey: Key of the target space. (required) + - type: all or page or attachment. (default:all) + - endDays: 1~ (0=today, 1=yesterday) (default:0) + - limit: 1~1000 (default:1000) + + Get all page versions in space with key=`spaceKey`. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + +* #### `DELETE /rest/extender/1/api/versions/space/{spaceKey}` + + **QueryString** + - spaceKey: Key of the target space. (required) + - type: all or page or attachment. (default:all) + - endDays: 1~ (0=today, 1=yesterday) (default:0) + - limit: 1~1000 (default:1000) + + Remove all page versions in space with key=`spaceKey`. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + + +* #### `GET /rest/extender/1/api/versions/page/{pageId}` + + **QueryString** + - pageId: Id of the target page. (required) + - type: all or page or attachment. (default:all) + - endDays: 1~ (0=today, 1=yesterday) (default:0) + - limit: 1~1000 (default:1000) + + Get versions of the page with id=`pageId`. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + +* #### `DELETE /rest/extender/1/api/versions/page/{pageId}` + + **QueryString** + - pageId: Id of the target page. (required) + - type: all or page or attachment. (default:all) + - endDays: 1~ (0=today, 1=yesterday) (default:0) + - limit: 1~1000 (default:1000) + + Remove versions of the page with id=`pageId`. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + +### Trashes + +Get and delete trash + +* #### `GET /rest/extender/1/api/trash/all` + + **QueryString** + - limit: 1~1000 (default:1000) + + Get all trash in all instance. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + +* #### `DELETE /rest/extender/1/api/trash/all` + + **QueryString** + - limit: 1~1000 (default:1000) + + Remove all trash in all instance. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + + +* #### `GET /rest/extender/1/api/trash/space/{spaceKey}` + + **QueryString** + - spaceKey: Key of the target space. (required) + - limit: 1~1000 (default:1000) + + Get trash from space with key=`spaceKey`. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + +* #### `DELETE /rest/extender/1/api/trash/space/{spaceKey}` + + **QueryString** + - spaceKey: Key of the target space. (required) + - limit: 1~1000 (default:1000) + + Remove trash from space with key=`spaceKey`. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + + + + + **QueryString** + - type: all or page or attachment. (default:all) + - endDays: 1~ (0=today, 1=yesterday) (default:0) + - limit: 1~1000 (default:1000) + + Remove versions of the page with id=`pageId`. + + __Responses__ + + ![Status 200][status-200] + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + + + + + + + + + + + +### Syncronise Crowd User Directory + +* #### `GET /rest/extender/1/directory` + + Get info about User Directories. + + __Responses__ + + ![Status 200][status-200] + + ```javascript + [ + { + "id": 262145, + "type": "INTERNAL", + "issynchronising": false + }, + { + "id": 1277953, + "type": "CROWD", + "issynchronising": false + } + ] + ``` + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + +* #### `PUT /rest/extender/1/directory` + + + **QueryString** + - id: User Directory ID. (required) + + Start sycronyse User Directory. + + __Responses__ + + ![Status 200][status-200] + + If OK, `issynchronising` of target directory returned as `true` + + ```javascript + [ + { + "id": 262145, + "type": "INTERNAL", + "issynchronising": false + }, + { + "id": 1277953, + "type": "CROWD", + "issynchronising": true + } + ] + ``` + + ![Status 401][status-401] + + Returned if the current user is not authenticated. + + ![Status 403][status-403] + + Returned if the current user is not an administrator. + + +[status-200]: https://img.shields.io/badge/status-200-brightgreen.svg +[status-400]: https://img.shields.io/badge/status-400-red.svg +[status-401]: https://img.shields.io/badge/status-401-red.svg +[status-403]: https://img.shields.io/badge/status-403-red.svg +[status-404]: https://img.shields.io/badge/status-404-red.svg diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0d6ed8c --- /dev/null +++ b/pom.xml @@ -0,0 +1,229 @@ + + + + + 4.0.0 + + su.tiburon.confluence + rest-extender + 1.1.1 + atlassian-plugin + + REST API extender for Confluence + REST API extender for Confluence. + + + + BSD 3-Clause License + https://opensource.org/licenses/BSD-3-Clause + + + + + Github + https://github.com/tiburon-777/confluence-rest-extender/issues + + + + scm:git:git://github.com/tiburon-777/confluence-rest-extender.git + scm:git:git@github.com:tiburon-777/confluence-rest-extender.git + https://github.com/tiburon-777/confluence-rest-extender + + + + OCTOPUS CodeWare + http://www.tiburon.su/ + + + + + Andrey Ivanov + ya@tiburon.su + OCTOPUS CodeWare + https://www.tiburon.su + + + + + + atlassian-public + https://packages.atlassian.com/maven/repository/public + + true + never + warn + + + true + warn + + + + + + atlassian-public + https://m2proxy.atlassian.com/repository/public/ + + true + + + true + + + + + + org.json + json + 20090211 + + + junit + junit + 4.10 + test + + + com.atlassian.confluence + confluence + ${confluence.version} + provided + + + com.atlassian.plugin + atlassian-spring-scanner-annotation + ${atlassian.spring.scanner.version} + provided + + + javax.inject + javax.inject + 1 + provided + + + + com.atlassian.plugins + atlassian-plugins-osgi-testrunner + ${plugin.testrunner.version} + test + + + javax.ws.rs + jsr311-api + 1.1.1 + provided + + + com.google.code.gson + gson + 2.2.2-atlassian-1 + + + javax.servlet + servlet-api + 2.4 + provided + + + javax.xml.bind + jaxb-api + 2.3.1 + provided + + + com.atlassian.plugins.rest + atlassian-rest-common + 1.0.2 + provided + + + com.atlassian.sal + sal-api + 2.10.10 + provided + + + org.apache.wink + wink-client + 1.4 + test + + + org.mockito + mockito-all + 1.8.5 + test + + + + + + com.atlassian.maven.plugins + confluence-maven-plugin + ${amps.version} + true + + ${confluence.version} + ${confluence.data.version} + true + + + + ${atlassian.plugin.key} + + sample.api + + + + + + com.atlassian.confluence.*;resolution:="optional", + + * + + + * + + true + + + + com.atlassian.plugin + atlassian-spring-scanner-maven-plugin + ${atlassian.spring.scanner.version} + + + + atlassian-spring-scanner + + process-classes + + + + + + com.atlassian.plugin + atlassian-spring-scanner-external-jar + + + false + + + + + + 6.14.0 + 6.14.0 + 8.0.0 + 2.0.1 + 2.1.8 + + ${project.groupId}.${project.artifactId} + UTF-8 + 1.8 + 1.8 + + diff --git a/src/main/java/su/tiburon/atlassian/confluence/api/DataCleanUtil.java b/src/main/java/su/tiburon/atlassian/confluence/api/DataCleanUtil.java new file mode 100644 index 0000000..d2d206f --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/api/DataCleanUtil.java @@ -0,0 +1,49 @@ +package su.tiburon.atlassian.confluence.api; + +import java.util.Date; +import java.util.List; + +import com.atlassian.confluence.pages.AbstractPage; +import com.atlassian.confluence.pages.Attachment; +import com.atlassian.confluence.pages.Page; +import com.atlassian.confluence.spaces.Space; + +import su.tiburon.atlassian.confluence.rest.PageVersionsModel; +import su.tiburon.atlassian.confluence.rest.SpaceTrashModel; +import su.tiburon.atlassian.confluence.rest.SpaceVersionsModel; + +public interface DataCleanUtil { + public abstract Date getCreatedOrUpdatedDate(int paramInt); + + public abstract List getAttachmentVersions(Attachment attachment, int endDays); + + public abstract List getPageVersions(Page page, int endDays, String type); + + public abstract long removePageVersions(Page paramPage, int paramInt, String paramString); + + public abstract long removeSpaceVersions(Space paramSpace, int paramInt, String paramString); + + public abstract long removeAllVersions(int paramInt, String paramString); + + public abstract int removeSpaceTrash(Space paramSpace); + + public abstract long removeAllTrash(); + + public abstract PageVersionsModel getPageVersionSummary(Page page, int endDays, String type); + + public abstract SpaceVersionsModel getSpaceVersionSummary(Space paramSpace, int paramInt, String paramString); + + public abstract List getAllVersionSummary(int paramInt, String paramString); + + public abstract SpaceTrashModel getSpaceTrashSummary(Space paramSpace); + + public abstract List getAllTrashSummary(); + + public abstract void setLimit(int paramInt); + + public abstract int getLimit(); + + public abstract void setCount(int paramInt); + + public abstract int getCount(); +} diff --git a/src/main/java/su/tiburon/atlassian/confluence/api/DateTimeUtil.java b/src/main/java/su/tiburon/atlassian/confluence/api/DateTimeUtil.java new file mode 100644 index 0000000..d6f5fdf --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/api/DateTimeUtil.java @@ -0,0 +1,7 @@ +package su.tiburon.atlassian.confluence.api; + +import java.util.Date; + +public interface DateTimeUtil { + public abstract Date getDate(); +} diff --git a/src/main/java/su/tiburon/atlassian/confluence/impl/DataCleanUtilImpl.java b/src/main/java/su/tiburon/atlassian/confluence/impl/DataCleanUtilImpl.java new file mode 100644 index 0000000..bb3c330 --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/impl/DataCleanUtilImpl.java @@ -0,0 +1,364 @@ +package su.tiburon.atlassian.confluence.impl; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.atlassian.confluence.core.VersionHistorySummary; +import com.atlassian.confluence.pages.AbstractPage; +import com.atlassian.confluence.pages.Attachment; +import com.atlassian.confluence.pages.AttachmentManager; +import com.atlassian.confluence.pages.Page; +import com.atlassian.confluence.pages.PageManager; +import com.atlassian.confluence.pages.TrashManager; +import com.atlassian.confluence.spaces.Space; +import com.atlassian.confluence.spaces.SpaceManager; +import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService; +import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; + +import su.tiburon.atlassian.confluence.api.DataCleanUtil; +import su.tiburon.atlassian.confluence.api.DateTimeUtil; +import su.tiburon.atlassian.confluence.rest.AttachmentModel; +import su.tiburon.atlassian.confluence.rest.PageVersionsModel; +import su.tiburon.atlassian.confluence.rest.SpaceTrashModel; +import su.tiburon.atlassian.confluence.rest.SpaceVersionsModel; + +@ExportAsService +@Named +public class DataCleanUtilImpl implements DataCleanUtil { + private static final Logger logger = LoggerFactory.getLogger(DataCleanUtil.class); + + @ComponentImport + private final PageManager pageManager; + @ComponentImport + private final AttachmentManager attachmentManager; + @ComponentImport + private final TrashManager trashManager; + @ComponentImport + private final SpaceManager spaceManager; + private DateTimeUtil dateTimeUtil; + private int limit; + private int count; + + @Inject + public DataCleanUtilImpl(PageManager pageManager, AttachmentManager attachmentManager, TrashManager trashManager, SpaceManager spaceManager, DateTimeUtil dateTimeUtil) { + this.pageManager = pageManager; + this.attachmentManager = attachmentManager; + this.trashManager = trashManager; + this.spaceManager = spaceManager; + this.limit = 1000; + this.count = 0; + this.dateTimeUtil = dateTimeUtil; + } + + @Override + public Date getCreatedOrUpdatedDate(int endDays) { + Date defaultDate = dateTimeUtil.getDate(); + try { + int lastCreatedOrUpdated = Integer.parseInt("-" + endDays); + Calendar cal = Calendar.getInstance(); + cal.setTime(defaultDate); + cal.add(Calendar.DAY_OF_MONTH, lastCreatedOrUpdated); + return cal.getTime(); + + } catch (NumberFormatException e) { + logger.error(e.getMessage(), e); + + } + + return defaultDate; + } + + @Override + public List getAttachmentVersions(Attachment attachment, int endDays) { + List resultAttachments = new ArrayList(); + Date lastUpdatedOrCreatedDate = getCreatedOrUpdatedDate(endDays); + List vAttachments = this.attachmentManager.getPreviousVersions(attachment); + + for (Attachment vAttachment : vAttachments) { + if (vAttachment.getLastModificationDate().before(lastUpdatedOrCreatedDate)) { + resultAttachments.add(vAttachment); + } + } + + return resultAttachments; + } + + @Override + public List getPageVersions(Page page, int endDays, String type) { + List resultVersions = new ArrayList(); + Date lastUpdatedOrCreatedDate = getCreatedOrUpdatedDate(endDays); + List versions = this.pageManager.getVersionHistorySummaries(page); + + for (VersionHistorySummary vSummary : versions) { + if (page.getId() != vSummary.getId() && vSummary.getLastModificationDate().before(lastUpdatedOrCreatedDate)) { + AbstractPage vPage = this.pageManager.getPage(vSummary.getId()); + if (vPage == null) { + vPage = this.pageManager.getBlogPost(vSummary.getId()); + + } + if (vPage != null) { + resultVersions.add(vPage); + } + } + } + + return resultVersions; + } + + @Override + public long removePageVersions(Page page, int endDays, String type) { + long delete_index = 0; + if (this.count >= this.limit) { + return delete_index; + } + + if ("all".equals(type) || "page".equals(type)) { + List aPages = getPageVersions(page, endDays, type); + + for (AbstractPage aPage : aPages) { + this.pageManager.removeHistoricalVersion(aPage); + delete_index++; + } + + if (delete_index > 0) { + this.count++; + if (this.count >= this.limit) { + return delete_index; + } + } + } + + if ("all".equals(type) || "attachment".equals(type)) { + List attachments = attachmentManager.getLatestVersionsOfAttachments(page); + + for (Attachment attachment : attachments) { + List targetAttachments = getAttachmentVersions(attachment, endDays); + + for (Attachment targetAttachment : targetAttachments) { + this.attachmentManager.removeAttachmentVersionFromServer(targetAttachment); + delete_index++; + } + + if (targetAttachments.size() > 0) { + this.count++; + if (this.count >= this.limit) { + return delete_index; + } + } + } + } + + return delete_index; + } + + @Override + public long removeSpaceVersions(Space space, int endDays, String type) { + Date lastUpdatedOrCreatedDate = getCreatedOrUpdatedDate(endDays); + Collection pages = this.pageManager.getPages(space, true); + long delete_index = 0L; + + for (Page page : pages) { + if (page.getCreationDate().before(lastUpdatedOrCreatedDate)) { + delete_index += removePageVersions(page, endDays, type); + + if (this.count >= this.limit) { + return delete_index; + } + } + } + + return delete_index; + } + + @Override + public long removeAllVersions(int endDays, String type) { + long delete_index = 0L; + for (Space space : this.spaceManager.getAllSpaces()) { + delete_index += this.removeSpaceVersions(space, endDays, type); + + if (this.count >= this.limit) { + return delete_index; + } + } + return delete_index; + } + + @Override + public int removeSpaceTrash(Space space) { + int numberOfItems = this.trashManager.getNumberOfItemsInTrash(space); + this.trashManager.emptyTrash(space); + + return numberOfItems; + } + + @Override + public long removeAllTrash() { + long deleted = 0L; + for (Space space : this.spaceManager.getAllSpaces()) { + deleted += this.removeSpaceTrash(space); + + this.count++; + if (this.count >= this.limit) { + return deleted; + } + } + return deleted; + } + + @Override + public PageVersionsModel getPageVersionSummary(Page page, int endDays, String type) { + List resultVersions = new ArrayList(); + if ("all".equals(type) || "page".equals(type)) { + resultVersions = getPageVersions(page, endDays, type); + } + PageVersionsModel pageModel = new PageVersionsModel(); + String modifier = page.getLastModifier() != null ? page.getLastModifier().getName() : ""; + pageModel.setId(page.getId()); + pageModel.setTitle(page.getTitle()); + pageModel.setVersionCount(resultVersions.size()); + pageModel.setLastModifier(modifier); + pageModel.setLastModified(page.getLastModificationDate()); + + long totalPageVersionCount = pageModel.getVersionCount(); + if (pageModel.getVersionCount() > 0) { + this.count++; + if (this.count >= this.limit) { + pageModel.setTotalVersionCount(totalPageVersionCount); + return pageModel; + + } + } + + if ("all".equals(type) || "attachment".equals(type)) { + List attachments = attachmentManager.getLatestVersionsOfAttachments(page); + List attachmentVersions = new ArrayList(); + List attachmentModels = new ArrayList(); + + for (Attachment attachment : attachments) { + List tmpAttachmentVersions = getAttachmentVersions(attachment, endDays); + int tmpAttachmentVersionsCount = tmpAttachmentVersions.size(); + + if (tmpAttachmentVersionsCount > 0) { + attachmentVersions.addAll(tmpAttachmentVersions); + totalPageVersionCount += tmpAttachmentVersions.size(); + String last_modifier = attachment.getLastModifier() != null ? attachment.getLastModifier().getName() : ""; + AttachmentModel model = new AttachmentModel( + attachment.getId(), + attachment.getTitle(), + tmpAttachmentVersionsCount, + last_modifier, + attachment.getLastModificationDate() + ); + attachmentModels.add(model); + this.count++; + if (this.count >= this.limit) { + break; + + } + } + } + pageModel.setAttachments(attachmentModels); + + } + + pageModel.setTotalVersionCount(totalPageVersionCount); + return pageModel; + } + + @Override + public SpaceVersionsModel getSpaceVersionSummary(Space space, int endDays, String type) { + Collection pages = this.pageManager.getPages(space, true); + List pageModels = new ArrayList(); + + long totalSpaceVersionCount = 0; + for (Page page : pages) { + PageVersionsModel pageModel = getPageVersionSummary(page, endDays, type); + + if (pageModel.getTotalVersionCount() > 0) { + totalSpaceVersionCount += pageModel.getTotalVersionCount(); + pageModels.add(pageModel); + } + + if (this.count >= this.limit) { + break; + } + } + SpaceVersionsModel spaceModel = new SpaceVersionsModel(space.getKey(), space.getDisplayTitle(), totalSpaceVersionCount, pageModels); + + return spaceModel; + } + + @Override + public List getAllVersionSummary(int endDays, String type) { + List spaceModels = new ArrayList(); + for (Space space : this.spaceManager.getAllSpaces()) { + SpaceVersionsModel spaceModel = this.getSpaceVersionSummary(space, endDays, type); + + if(spaceModel.getTotalVersionCount() > 0) { + spaceModels.add(spaceModel); + } + + if (this.count >= this.limit) { + return spaceModels; + } + } + return spaceModels; + } + + @Override + public SpaceTrashModel getSpaceTrashSummary(Space space) { + int numberOfItems = this.trashManager.getNumberOfItemsInTrash(space); + SpaceTrashModel trashModel = new SpaceTrashModel(space.getKey(), space.getDisplayTitle(), numberOfItems); + + return trashModel; + } + + @Override + public List getAllTrashSummary() { + List trashModels = new ArrayList(); + for (Space space : this.spaceManager.getAllSpaces()) { + SpaceTrashModel trashModel = this.getSpaceTrashSummary(space); + + if (trashModel.getNumberOfItemsInTrash() > 0) { + + trashModels.add(trashModel); + + this.count++; + if (this.count >= this.limit) { + return trashModels; + } + } + } + return trashModels; + } + + @Override + public void setLimit(int limit) { + this.limit = limit; + } + + @Override + public int getLimit() { + return this.limit; + } + + @Override + public void setCount(int count) { + this.count = count; + } + + @Override + public int getCount() { + return this.count; + } + +} diff --git a/src/main/java/su/tiburon/atlassian/confluence/impl/DateTimeUtilImpl.java b/src/main/java/su/tiburon/atlassian/confluence/impl/DateTimeUtilImpl.java new file mode 100644 index 0000000..8150975 --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/impl/DateTimeUtilImpl.java @@ -0,0 +1,21 @@ +package su.tiburon.atlassian.confluence.impl; + +import java.util.Date; + +import javax.inject.Named; + +import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService; + +import su.tiburon.atlassian.confluence.api.DateTimeUtil; + +@ExportAsService +@Named +public class DateTimeUtilImpl implements DateTimeUtil { + + @Override + public Date getDate() { + return new Date(); + } + + +} diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/AllTrashModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/AllTrashModel.java new file mode 100644 index 0000000..af8169e --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/AllTrashModel.java @@ -0,0 +1,34 @@ +package su.tiburon.atlassian.confluence.rest; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class AllTrashModel { + + @XmlElementWrapper + @XmlElement(name="trash") + public List trashList; + + public AllTrashModel() { + this.trashList = new ArrayList(); + } + + public AllTrashModel(List trashList) { + this.trashList = trashList; + } + + public List getTrashList() { + return trashList; + } + + public void setTrashList(List trashList) { + this.trashList = trashList; + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/AllVersionsModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/AllVersionsModel.java new file mode 100644 index 0000000..42ae298 --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/AllVersionsModel.java @@ -0,0 +1,34 @@ +package su.tiburon.atlassian.confluence.rest; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class AllVersionsModel { + + @XmlElementWrapper + @XmlElement(name="space") + public List spaces; + + public AllVersionsModel() { + this.spaces = new ArrayList(); + } + + public AllVersionsModel(List spaces) { + this.spaces = spaces; + } + + public List getSpaces() { + return spaces; + } + + public void setSpaces(List spaces) { + this.spaces = spaces; + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/AttachmentModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/AttachmentModel.java new file mode 100644 index 0000000..87b262a --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/AttachmentModel.java @@ -0,0 +1,79 @@ +package su.tiburon.atlassian.confluence.rest; + +import java.util.Date; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class AttachmentModel { + + @XmlElement + public String id; + @XmlElement + public String title; + @XmlElement + public int versionCount; + @XmlElement + public String lastModifier; + @XmlElement + public String lastModified; + + public AttachmentModel() { + this.id = ""; + this.title = ""; + this.versionCount = -1; + this.lastModifier = ""; + this.lastModified = ""; + } + + public AttachmentModel(Long id, String title, int versionCount, String lastModifier, Date lastModified) { + this.id = String.valueOf(id); + this.title = title; + this.versionCount = versionCount; + this.lastModifier = lastModifier; + this.lastModified = String.valueOf(lastModified); + } + + public String getId() { + return id; + } + + public void setId(Long id) { + this.id = String.valueOf(id); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getVersionCount() { + return versionCount; + } + + public void setVersionCount(int versionCount) { + this.versionCount = versionCount; + } + + public String getLastModifier() { + return lastModifier; + } + + public void setLastModifier(String lastModifier) { + this.lastModifier = lastModifier; + } + + public String getLastModified() { + return lastModified; + } + + public void setLastModified(Date lastModified) { + this.lastModified = String.valueOf(lastModified); + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/Cleaner.java b/src/main/java/su/tiburon/atlassian/confluence/rest/Cleaner.java new file mode 100644 index 0000000..a4e22fb --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/Cleaner.java @@ -0,0 +1,456 @@ +package su.tiburon.atlassian.confluence.rest; + +import java.text.MessageFormat; +import java.util.List; + +import javax.inject.Inject; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import com.atlassian.confluence.pages.Page; +import com.atlassian.confluence.pages.PageManager; +import com.atlassian.confluence.security.Permission; +import com.atlassian.confluence.security.PermissionManager; +import com.atlassian.confluence.spaces.Space; +import com.atlassian.confluence.spaces.SpaceManager; +import com.atlassian.confluence.user.AuthenticatedUserThreadLocal; +import com.atlassian.confluence.user.ConfluenceUser; +import com.atlassian.plugin.spring.scanner.annotation.imports.ConfluenceImport; +import com.atlassian.plugins.rest.common.security.AnonymousAllowed; + +import su.tiburon.atlassian.confluence.api.DataCleanUtil; + +@Path("/api") +public class Cleaner { + private static final Logger logger = LoggerFactory.getLogger(Cleaner.class); + + private final DataCleanUtil dataCleanUtil; + @ConfluenceImport + private final PermissionManager permissionManager; + @ConfluenceImport + private final PageManager pageManager; + @ConfluenceImport + private final SpaceManager spaceManager; + private final static int MAX_LIMIT_FOR_VERSIONS = 1000; + private final static int MAX_LIMIT_FOR_TRASH = 100; + private final static long NOT_DELETED = 0; + private final static String ITEM_TYPE_FOR_SPACE_TRASH = "SpaceTrash"; + private final static String END_DAYS_FOR_SPACE_TRASH = "Endless"; + + @Inject + public Cleaner(DataCleanUtil dataCleanUtil, PermissionManager permissionManager, PageManager pageManager, SpaceManager spaceManager) { + this.dataCleanUtil = dataCleanUtil; + this.permissionManager = permissionManager; + this.pageManager = pageManager; + this.spaceManager = spaceManager; + } + + @GET + @Produces({MediaType.APPLICATION_JSON}) + @Path("/versions/all") + @AnonymousAllowed + public Response getAllVersions(@QueryParam("type") String type, @QueryParam("endDays") String endDaysStr, @QueryParam("limit") String limitStr) { + // Authentication + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.isConfluenceAdministrator(loggedInAppUser)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Confluence Administrator Permission required!")).build(); + } + + // Validate Params + VersionBaseParam baseParam = new VersionBaseParam(type, endDaysStr, limitStr, MAX_LIMIT_FOR_VERSIONS); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + List spaceModels = this.dataCleanUtil.getAllVersionSummary(baseParam.getEndDays(), baseParam.getType()); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + NOT_DELETED, + NOT_DELETED, + spaceModels); + return Response.ok(responseModel).build(); + } + + @DELETE + @Produces({MediaType.APPLICATION_JSON}) + @Path("/versions/all") + @AnonymousAllowed + public Response deleteAllVersions(@QueryParam("type") String type, @QueryParam("endDays") String endDaysStr, @QueryParam("limit") String limitStr) { + // Authentication + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.isConfluenceAdministrator(loggedInAppUser)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Confluence Administrator Permission required!")).build(); + } + + // Validate Params + VersionBaseParam baseParam = new VersionBaseParam(type, endDaysStr, limitStr, MAX_LIMIT_FOR_VERSIONS); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + logger.info(MessageFormat.format("User({0}) deleting versions for all spaces..", new Object[] {loggedInAppUser.getName() })); + long deleted = this.dataCleanUtil.removeAllVersions(baseParam.getEndDays(), baseParam.getType()); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + this.dataCleanUtil.getCount(), + deleted, + new MessageModel("Deleted Versions Count - " + deleted)); + return Response.ok(responseModel).build(); + } + + @GET + @Produces({MediaType.APPLICATION_JSON}) + @Path("/versions/space/{spaceKey}") + @AnonymousAllowed + public Response getSpaceVersionsBySpaceKey(@QueryParam("type") String type, @QueryParam("endDays") String endDaysStr, @QueryParam("limit") String limitStr, @PathParam("spaceKey") String spaceKey) { + // Authentication + if (StringUtils.isEmpty(spaceKey)) { + return Response.status(Response.Status.BAD_REQUEST).entity(new ErrorModel("400 Bad Request", "Required fields are missing. : spaceKey")).build(); + } + Space space = this.spaceManager.getSpace(spaceKey); + if (space == null) { + return Response.status(Response.Status.NOT_FOUND).entity(new ErrorModel("404 Not Found", "Space is not exists!")).build(); + } + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.hasPermission(loggedInAppUser, Permission.VIEW, space)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "View Permission denied!")).build(); + } + + // Validate Params + VersionBaseParam baseParam = new VersionBaseParam(type, endDaysStr, limitStr, MAX_LIMIT_FOR_VERSIONS); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + SpaceVersionsModel spaceVersionModel = this.dataCleanUtil.getSpaceVersionSummary(space, baseParam.getEndDays(), baseParam.getType()); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + NOT_DELETED, + NOT_DELETED, + spaceVersionModel); + return Response.ok(responseModel).build(); + } + + @DELETE + @Produces({MediaType.APPLICATION_JSON}) + @Path("/versions/space/{spaceKey}") + @AnonymousAllowed + public Response deleteSpaceVersionsBySpaceKey(@QueryParam("type") String type, @QueryParam("endDays") String endDaysStr, @QueryParam("limit") String limitStr, @PathParam("spaceKey") String spaceKey) { + // Authentication + if (StringUtils.isEmpty(spaceKey)) { + return Response.status(Response.Status.BAD_REQUEST).entity(new ErrorModel("400 Bad Request", "Required fields are missing. : spaceKey")).build(); + } + Space space = this.spaceManager.getSpace(spaceKey); + if (space == null) { + return Response.status(Response.Status.NOT_FOUND).entity(new ErrorModel("404 Not Found", "Space is not exists!")).build(); + } + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.hasPermission(loggedInAppUser, Permission.ADMINISTER, space)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Admin Permission denied!")).build(); + } + + // Validate Params + VersionBaseParam baseParam = new VersionBaseParam(type, endDaysStr, limitStr, MAX_LIMIT_FOR_VERSIONS); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + logger.info(MessageFormat.format("User({0}) deleting versions for spaceKey {1}", new Object[] {loggedInAppUser.getName(), spaceKey })); + long deleted = this.dataCleanUtil.removeSpaceVersions(space, baseParam.getEndDays(), baseParam.getType()); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + this.dataCleanUtil.getCount(), + deleted, + new MessageModel("Deleted Versions Count - " + deleted)); + return Response.ok(responseModel).build(); + } + + @GET + @Produces({MediaType.APPLICATION_JSON}) + @Path("/versions/page/{pageId}") + @AnonymousAllowed + public Response getPageVersionsByPageId(@QueryParam("type") String type, @QueryParam("endDays") String endDaysStr, @QueryParam("limit") String limitStr, @PathParam("pageId") long pageId) { + // Authentication + if (pageId <= 0L) { + return Response.status(Response.Status.BAD_REQUEST).entity(new ErrorModel("400 Bad Request", "Required fields are missing. : spaceKey")).build(); + } + Page page = this.pageManager.getPage(pageId); + if (page == null) { + return Response.status(Response.Status.NOT_FOUND).entity(new ErrorModel("404 Not Found", "Space is not exists!")).build(); + } + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.hasPermission(loggedInAppUser, Permission.VIEW, page)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "View Permission denied!")).build(); + } + + // Validate Params + VersionBaseParam baseParam = new VersionBaseParam(type, endDaysStr, limitStr, MAX_LIMIT_FOR_VERSIONS); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + PageVersionsModel pageVersionsModel = this.dataCleanUtil.getPageVersionSummary(page, baseParam.getEndDays(), baseParam.getType()); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + NOT_DELETED, + NOT_DELETED, + pageVersionsModel); + return Response.ok(responseModel).build(); + } + + @DELETE + @Produces({MediaType.APPLICATION_JSON}) + @Path("/versions/page/{pageId}") + @AnonymousAllowed + public Response deletePageVersionsByPageId(@QueryParam("type") String type, @QueryParam("endDays") String endDaysStr, @QueryParam("limit") String limitStr, @PathParam("pageId") long pageId) { + // Authentication + if (pageId <= 0L) { + return Response.status(Response.Status.BAD_REQUEST).entity(new ErrorModel("400 Bad Request", "Required fields are missing. : spaceKey")).build(); + } + Page page = this.pageManager.getPage(pageId); + if (page == null) { + return Response.status(Response.Status.NOT_FOUND).entity(new ErrorModel("404 Not Found", "Space is not exists!")).build(); + } + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.hasPermission(loggedInAppUser, Permission.ADMINISTER, page)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Admin Permission denied!")).build(); + } + + // Validate Params + VersionBaseParam baseParam = new VersionBaseParam(type, endDaysStr, limitStr, MAX_LIMIT_FOR_VERSIONS); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + logger.info(MessageFormat.format("User({0}) deleting page versions for pageId {1}", new Object[] {loggedInAppUser.getName(), Long.valueOf(pageId) })); + long deleted = this.dataCleanUtil.removePageVersions(page, baseParam.getEndDays(), baseParam.getType()); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + this.dataCleanUtil.getCount(), + deleted, + new MessageModel("Deleted Versions Count - " + deleted)); + return Response.ok(responseModel).build(); + } + + @GET + @Produces({MediaType.APPLICATION_JSON}) + @Path("/trash/all") + @AnonymousAllowed + public Response getAllGarbages(@QueryParam("limit") String limitStr) { + // Authentication + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.isConfluenceAdministrator(loggedInAppUser)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Confluence Administrator Permission required!")).build(); + } + + // Validate Params + TrashBaseParam baseParam = new TrashBaseParam(ITEM_TYPE_FOR_SPACE_TRASH, END_DAYS_FOR_SPACE_TRASH, limitStr, MAX_LIMIT_FOR_TRASH); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + List trashModels = this.dataCleanUtil.getAllTrashSummary(); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + NOT_DELETED, + NOT_DELETED, + trashModels); + return Response.ok(responseModel).build(); + } + + @DELETE + @Produces({MediaType.APPLICATION_JSON}) + @Path("/trash/all") + @AnonymousAllowed + public Response deleteAllGarbages(@QueryParam("limit") String limitStr) { + // Authentication + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.isConfluenceAdministrator(loggedInAppUser)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Confluence Administrator Permission required!")).build(); + } + + // Validate Params + TrashBaseParam baseParam = new TrashBaseParam(ITEM_TYPE_FOR_SPACE_TRASH, END_DAYS_FOR_SPACE_TRASH, limitStr, MAX_LIMIT_FOR_TRASH); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + logger.info(MessageFormat.format("User({0}) deleting space trash for all", new Object[] {loggedInAppUser.getName() })); + long deleted = this.dataCleanUtil.removeAllTrash(); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + this.dataCleanUtil.getCount(), + deleted, + new MessageModel("Deleted Trash Items Count - " + deleted)); + return Response.ok(responseModel).build(); + } + + @GET + @Produces({MediaType.APPLICATION_JSON}) + @Path("/trash/space/{spaceKey}") + @AnonymousAllowed + public Response getSpaceGarbagesBySpaceKey(@QueryParam("limit") String limitStr, @PathParam("spaceKey") String spaceKey) { + // Authentication + if (StringUtils.isEmpty(spaceKey)) { + return Response.status(Response.Status.BAD_REQUEST).entity(new ErrorModel("400 Bad Request", "Required fields are missing. : spaceKey")).build(); + } + Space space = this.spaceManager.getSpace(spaceKey); + if (space == null) { + return Response.status(Response.Status.NOT_FOUND).entity(new ErrorModel("404 Not Found", "Space is not exists!")).build(); + } + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.hasPermission(loggedInAppUser, Permission.VIEW, space)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "View Permission denied!")).build(); + } + + // Validate Params + TrashBaseParam baseParam = new TrashBaseParam(ITEM_TYPE_FOR_SPACE_TRASH, END_DAYS_FOR_SPACE_TRASH, limitStr, MAX_LIMIT_FOR_TRASH); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + SpaceTrashModel spaceTrashModel = this.dataCleanUtil.getSpaceTrashSummary(space); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + NOT_DELETED, + NOT_DELETED, + spaceTrashModel); + return Response.ok(responseModel).build(); + } + + @DELETE + @Produces({MediaType.APPLICATION_JSON}) + @Path("/trash/space/{spaceKey}") + @AnonymousAllowed + public Response deleteSpaceGarbagesBySpaceKey(@QueryParam("limit") String limitStr, @PathParam("spaceKey") String spaceKey) { + // Authentication + if (StringUtils.isEmpty(spaceKey)) { + return Response.status(Response.Status.BAD_REQUEST).entity(new ErrorModel("400 Bad Request", "Required fields are missing. : spaceKey")).build(); + } + Space space = this.spaceManager.getSpace(spaceKey); + if (space == null) { + return Response.status(Response.Status.NOT_FOUND).entity(new ErrorModel("404 Not Found", "Space is not exists!")).build(); + } + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.hasPermission(loggedInAppUser, Permission.ADMINISTER, space)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Admin Permission denied!")).build(); + } + + // Validate Params + TrashBaseParam baseParam = new TrashBaseParam(ITEM_TYPE_FOR_SPACE_TRASH, END_DAYS_FOR_SPACE_TRASH, limitStr, MAX_LIMIT_FOR_TRASH); + if (baseParam.getErrorModel().getMessages().size() > 0) { + return Response.status(Response.Status.BAD_REQUEST).entity(baseParam.getErrorModel()).build(); + } + + // Set up + this.dataCleanUtil.setLimit(baseParam.getLimit()); + this.dataCleanUtil.setCount(0); + + // Main process + logger.info(MessageFormat.format("User({0}) deleting space trash for spaceKey {1}", new Object[] {loggedInAppUser.getName(), Long.valueOf(spaceKey) })); + int deleted = 0; + deleted = this.dataCleanUtil.removeSpaceTrash(space); + + ResponseModel responseModel = new ResponseModel( + baseParam.getType(), + baseParam.getEndDays(), + baseParam.getLimit(), + this.dataCleanUtil.getCount(), + this.dataCleanUtil.getCount(), + deleted, + new MessageModel("Deleted Trash Items Count - " + deleted)); + return Response.ok(responseModel).build(); + } + +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/DirectoryModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/DirectoryModel.java new file mode 100644 index 0000000..197cf39 --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/DirectoryModel.java @@ -0,0 +1,35 @@ +package su.tiburon.atlassian.confluence.rest; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import com.atlassian.crowd.embedded.api.DirectoryType; + + +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class DirectoryModel { + + @XmlElement + private Long id; + + @XmlElement + private DirectoryType type; + + @XmlElement + private Boolean issynchronising; + + public DirectoryModel() { + this.id = null; + this.type = null; + this.issynchronising = false; + } + + public DirectoryModel(Long id, DirectoryType type, Boolean issynchronising) { + this.id = id; + this.type = type; + this.issynchronising = issynchronising; + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/DirectoryService.java b/src/main/java/su/tiburon/atlassian/confluence/rest/DirectoryService.java new file mode 100644 index 0000000..f124d9c --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/DirectoryService.java @@ -0,0 +1,80 @@ +package su.tiburon.atlassian.confluence.rest; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.QueryParam; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.atlassian.confluence.security.PermissionManager; +import com.atlassian.confluence.user.AuthenticatedUserThreadLocal; +import com.atlassian.confluence.user.ConfluenceUser; +import com.atlassian.plugin.spring.scanner.annotation.imports.ConfluenceImport; +import com.atlassian.plugins.rest.common.security.AnonymousAllowed; +import com.atlassian.crowd.embedded.api.CrowdDirectoryService; +import com.atlassian.crowd.embedded.api.Directory; + +@Path("/directory") +@AnonymousAllowed +@Produces(MediaType.APPLICATION_JSON) +public class DirectoryService { + private static final Logger logger = LoggerFactory.getLogger(DirectoryService.class); + + @ConfluenceImport + private final PermissionManager permissionManager; + + @ConfluenceImport + private final CrowdDirectoryService crowdDirectoryService; + + @Inject + public DirectoryService(PermissionManager permissionManager, CrowdDirectoryService crowdDirectoryService) { + this.permissionManager = permissionManager; + this.crowdDirectoryService = crowdDirectoryService; + } + + @GET + public Response getDir() { + // Authentication + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.isConfluenceAdministrator(loggedInAppUser)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Confluence Administrator Permission required!")).build(); + } + logger.info(MessageFormat.format("User({0}) requesting infodmation about CUDs", new Object[] {loggedInAppUser.getName() })); + + Collection coll = new ArrayList(); + + for (Directory dsa : crowdDirectoryService.findAllDirectories()) { + coll.add(new DirectoryModel(dsa.getId(),dsa.getType(),crowdDirectoryService.isDirectorySynchronising(dsa.getId()))); + } + return Response.ok(coll).build(); + } + + @PUT + public Response syncDir(@QueryParam("id") Long dirId) { + + // Authentication + ConfluenceUser loggedInAppUser = AuthenticatedUserThreadLocal.get(); + if (!this.permissionManager.isConfluenceAdministrator(loggedInAppUser)) { + return Response.status(Response.Status.UNAUTHORIZED).entity(new ErrorModel("401 Unauthorized", "Confluence Administrator Permission required!")).build(); + } + + crowdDirectoryService.synchroniseDirectory(dirId, true); + + Collection coll = new ArrayList(); + + for (Directory dsa : crowdDirectoryService.findAllDirectories()) { + coll.add(new DirectoryModel(dsa.getId(),dsa.getType(),crowdDirectoryService.isDirectorySynchronising(dsa.getId()))); + } + return Response.ok(coll).build(); + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/ErrorModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/ErrorModel.java new file mode 100644 index 0000000..bb58bcb --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/ErrorModel.java @@ -0,0 +1,52 @@ +package su.tiburon.atlassian.confluence.rest; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class ErrorModel { + + @XmlElement(name = "statusCode") + private String statusCode; + + @XmlElementWrapper + @XmlElement(name="message") + public List messages; + + public ErrorModel() { + this.statusCode = ""; + this.messages = new ArrayList(); + } + + public ErrorModel(String statusCode, String messages) { + this.statusCode = statusCode; + this.messages = new ArrayList(); + this.addMessage(messages); + } + + public String getStatusCode() { + return statusCode; + } + + public void setStatusCode(String statusCode) { + this.statusCode = statusCode; + } + + public List getMessages() { + return messages; + } + + public void setMessages(List messages) { + this.messages = messages; + } + + public void addMessage(String message) { + this.messages.add(new MessageModel(message)); + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/MessageModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/MessageModel.java new file mode 100644 index 0000000..26cb49a --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/MessageModel.java @@ -0,0 +1,29 @@ +package su.tiburon.atlassian.confluence.rest; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class MessageModel { + + @XmlElement(name = "message") + private String message; + + public MessageModel() { + this.message = ""; + } + + public MessageModel(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/PageVersionsModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/PageVersionsModel.java new file mode 100644 index 0000000..447855e --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/PageVersionsModel.java @@ -0,0 +1,109 @@ +package su.tiburon.atlassian.confluence.rest; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class PageVersionsModel { + + @XmlElement + public String id; + @XmlElement + public String title; + @XmlElement + public long totalVersionCount; + @XmlElement + public int versionCount; + @XmlElement + public String lastModifier; + @XmlElement + public String lastModified; + @XmlElementWrapper + @XmlElement(name="attachment") + public List attachments; + + public PageVersionsModel() { + this.id = ""; + this.title = ""; + this.versionCount = -1; + this.totalVersionCount = -1; + this.lastModifier = ""; + this.lastModified = ""; + this.attachments = new ArrayList(); + } + + public PageVersionsModel(Long id, String title, long totalVersionCount, int versionCount, String lastModifier, Date lastModified, List attachments) { + this.id = String.valueOf(id); + this.title = title; + this.totalVersionCount = totalVersionCount; + this.versionCount = versionCount; + this.lastModifier = lastModifier; + this.lastModified = String.valueOf(lastModified); + this.attachments = attachments; + } + + public String getId() { + return id; + } + + public void setId(Long id) { + this.id = String.valueOf(id); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getVersionCount() { + return versionCount; + } + + public void setVersionCount(int versionCount) { + this.versionCount = versionCount; + } + + public String getLastModifier() { + return lastModifier; + } + + public void setLastModifier(String lastModifier) { + this.lastModifier = lastModifier; + } + + public String getLastModified() { + return lastModified; + } + + public void setLastModified(Date lastModified) { + this.lastModified = String.valueOf(lastModified); + } + + public List getAttachments() { + return attachments; + } + + public void setAttachments(List attachments) { + this.attachments = attachments; + } + + public long getTotalVersionCount() { + return this.totalVersionCount; + + } + + public void setTotalVersionCount(long totalVersionCount) { + this.totalVersionCount = totalVersionCount; + + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/ResponseModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/ResponseModel.java new file mode 100644 index 0000000..5b48893 --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/ResponseModel.java @@ -0,0 +1,103 @@ +package su.tiburon.atlassian.confluence.rest; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class ResponseModel { + + @XmlElement + public String itemType; + @XmlElement + public String endDays; + @XmlElement + public int itemCountLimit; + @XmlElement + public int targetItemCount; + @XmlElement + public long deletedItemCount; + @XmlElement + public long deletedVersionCount; + @XmlElement + public Object result; + + public ResponseModel(String itemType, int endDays, int itemCountLimit, int targetItemCount, long deletedItemCount, long deletedVersionCount, Object result) { + this.itemType = itemType; + this.endDays = String.valueOf(endDays); + this.itemCountLimit = itemCountLimit; + this.targetItemCount = targetItemCount; + this.deletedItemCount = deletedItemCount; + this.deletedVersionCount = deletedVersionCount; + this.result = result; + } + + public ResponseModel(String itemType, String endDaysStr, int itemCountLimit, int targetItemCount, long deletedItemCount, long deletedVersionCount, Object result) { + this.itemType = itemType; + this.endDays = endDaysStr; + this.itemCountLimit = itemCountLimit; + this.targetItemCount = targetItemCount; + this.deletedItemCount = deletedItemCount; + this.deletedVersionCount = deletedVersionCount; + this.result = result; + } + + public String getItemType() { + return itemType; + } + + public void setItemType(String itemType) { + this.itemType = itemType; + } + + public String getEndDays() { + return endDays; + } + + public void setEndDays(String endDays) { + this.endDays = endDays; + } + + public int getItemCountLimit() { + return itemCountLimit; + } + + public void setItemCountLimit(int itemCountLimit) { + this.itemCountLimit = itemCountLimit; + } + + public long getTargetItemCount() { + return targetItemCount; + } + + public void setTargetItemCount(int targetItemCount) { + this.targetItemCount = targetItemCount; + } + + public long getDeletedItemCount() { + return deletedItemCount; + } + + public void setDeletedItemCount(long deletedItemCount) { + this.deletedItemCount = deletedItemCount; + } + + public long getDeletedVersionCount() { + return deletedVersionCount; + } + + public void setDeletedVersionCount(long deletedVersionCount) { + this.deletedVersionCount = deletedVersionCount; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + +} diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/SpaceTrashModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/SpaceTrashModel.java new file mode 100644 index 0000000..2993160 --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/SpaceTrashModel.java @@ -0,0 +1,53 @@ +package su.tiburon.atlassian.confluence.rest; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class SpaceTrashModel { + + @XmlElement + public String spaceKey; + @XmlElement + public String title; + @XmlElement + public int numberOfItemsInTrash; + + public SpaceTrashModel() { + this.spaceKey = ""; + this.title = ""; + this.numberOfItemsInTrash = 0; + } + + public SpaceTrashModel(String spaceKey, String title, int numberOfItemsInTrash) { + this.spaceKey = spaceKey; + this.title = title; + this.numberOfItemsInTrash = numberOfItemsInTrash; + } + + public String getSpaceKey() { + return spaceKey; + } + + public void setSpaceKey(String spaceKey) { + this.spaceKey = spaceKey; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getNumberOfItemsInTrash() { + return numberOfItemsInTrash; + } + + public void setNumberOfItemsInTrash(int numberOfItemsInTrash) { + this.numberOfItemsInTrash = numberOfItemsInTrash; + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/SpaceVersionsModel.java b/src/main/java/su/tiburon/atlassian/confluence/rest/SpaceVersionsModel.java new file mode 100644 index 0000000..a4157f9 --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/SpaceVersionsModel.java @@ -0,0 +1,70 @@ +package su.tiburon.atlassian.confluence.rest; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class SpaceVersionsModel { + + @XmlElement + public String spaceKey; + @XmlElement + public String title; + @XmlElement + public long totalVersionCount; + @XmlElementWrapper + @XmlElement(name="page") + public List pages; + + public SpaceVersionsModel() { + this.spaceKey = ""; + this.title = ""; + this.totalVersionCount = -1; + this.pages = new ArrayList(); + } + + public SpaceVersionsModel(String spaceKey, String title, long totalVersionCount, List pages) { + this.spaceKey = spaceKey; + this.title = title; + this.totalVersionCount = totalVersionCount; + this.pages = pages; + } + + public String getSpaceKey() { + return spaceKey; + } + + public void setSpaceKey(String spaceKey) { + this.spaceKey = spaceKey; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public long getTotalVersionCount() { + return totalVersionCount; + } + + public void setTotalVersionCount(long totalVersionCount) { + this.totalVersionCount = totalVersionCount; + } + + public List getPages() { + return pages; + } + + public void setPages(List pages) { + this.pages = pages; + } +} \ No newline at end of file diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/TrashBaseParam.java b/src/main/java/su/tiburon/atlassian/confluence/rest/TrashBaseParam.java new file mode 100644 index 0000000..84d0b2d --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/TrashBaseParam.java @@ -0,0 +1,71 @@ +package su.tiburon.atlassian.confluence.rest; + +import org.springframework.util.StringUtils; + +public class TrashBaseParam { + + private String type; + private String endDays; + private int limit; + private ErrorModel errorModel; + + + public TrashBaseParam(String type, String endDaysStr, String limitStr, int maxLimit) { + this.errorModel = new ErrorModel(); + this.type = type; + this.endDays = endDaysStr; + validateLimit(limitStr, maxLimit); + + } + + public String getType() { + return type; + } + + + public void setType(String type) { + this.type = type; + } + + + public String getEndDays() { + return endDays; + } + + + public void setEndDays(String endDays) { + this.endDays = endDays; + } + + + public int getLimit() { + return limit; + } + + + public void setLimit(int limit) { + this.limit = limit; + } + + + public ErrorModel getErrorModel() { + return errorModel; + } + + private void validateLimit(String limitStr, int maxLimit) { + if(StringUtils.isEmpty(limitStr)) { + limitStr = String.valueOf(maxLimit); + } + try { + this.limit = Integer.parseInt(limitStr); + if (limit < 1 || limit > maxLimit) { + this.errorModel.setStatusCode("400 Bad Request"); + this.errorModel.addMessage("limit is invalid. : limit=1~" + String.valueOf(maxLimit)); + } + } catch (NumberFormatException e) { + this.errorModel.setStatusCode("400 Bad Request"); + this.errorModel.addMessage("limit is invalid. : limit=1~" + String.valueOf(maxLimit)); + } + } + +} diff --git a/src/main/java/su/tiburon/atlassian/confluence/rest/VersionBaseParam.java b/src/main/java/su/tiburon/atlassian/confluence/rest/VersionBaseParam.java new file mode 100644 index 0000000..77132d9 --- /dev/null +++ b/src/main/java/su/tiburon/atlassian/confluence/rest/VersionBaseParam.java @@ -0,0 +1,99 @@ +package su.tiburon.atlassian.confluence.rest; + +import org.springframework.util.StringUtils; + +public class VersionBaseParam { + + private String type; + private int endDays; + private int limit; + private ErrorModel errorModel; + + + public VersionBaseParam(String type, String endDaysStr, String limitStr, int maxLimit) { + this.errorModel = new ErrorModel(); + validateType(type); + validateEndDays(endDaysStr); + validateLimit(limitStr, maxLimit); + + } + + public String getType() { + return type; + } + + + public void setType(String type) { + this.type = type; + } + + + public int getEndDays() { + return endDays; + } + + + public void setEndDays(int endDays) { + this.endDays = endDays; + } + + + public int getLimit() { + return limit; + } + + + public void setLimit(int limit) { + this.limit = limit; + } + + + public ErrorModel getErrorModel() { + return errorModel; + } + + private void validateType(String type) { + if(StringUtils.isEmpty(type)) { + type = "all"; + } + if ("all".equals(type) || "page".equals(type) || "attachment".equals(type)) { + this.type = type; + } else { + this.errorModel.setStatusCode("400 Bad Request"); + this.errorModel.addMessage("type is invalid. : type=all or type=page or type=attachment"); + } + } + + private void validateEndDays(String endDaysStr) { + if(StringUtils.isEmpty(endDaysStr)) { + endDaysStr = "0"; + } + try { + this.endDays = Integer.parseInt(endDaysStr); + if (endDays < 0 ) { + this.errorModel.setStatusCode("400 Bad Request"); + this.errorModel.addMessage("endDays is invalid. : endDays=0~ "); + } + } catch (NumberFormatException e) { + this.errorModel.setStatusCode("400 Bad Request"); + this.errorModel.addMessage("endDays is invalid. : endDays=0~ "); + } + } + + private void validateLimit(String limitStr, int maxLimit) { + if(StringUtils.isEmpty(limitStr)) { + limitStr = String.valueOf(maxLimit); + } + try { + this.limit = Integer.parseInt(limitStr); + if (limit < 1 || limit > maxLimit) { + this.errorModel.setStatusCode("400 Bad Request"); + this.errorModel.addMessage("limit is invalid. : limit=1~" + String.valueOf(maxLimit)); + } + } catch (NumberFormatException e) { + this.errorModel.setStatusCode("400 Bad Request"); + this.errorModel.addMessage("limit is invalid. : limit=1~" + String.valueOf(maxLimit)); + } + } + +} diff --git a/src/main/resources/META-INF/spring/plugin-context.xml b/src/main/resources/META-INF/spring/plugin-context.xml new file mode 100644 index 0000000..82bc606 --- /dev/null +++ b/src/main/resources/META-INF/spring/plugin-context.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/atlassian-plugin.xml b/src/main/resources/atlassian-plugin.xml new file mode 100644 index 0000000..c1f5da2 --- /dev/null +++ b/src/main/resources/atlassian-plugin.xml @@ -0,0 +1,24 @@ + + + + + ${project.description} + ${project.version} + + images/logo_octo_375_293.png + images/logo_octo_375_293.png + + + + + + com.atlassian.auiplugin:ajs + + + + rest-extender + + + Rest Extender Plugin + + diff --git a/src/main/resources/css/rest-extender.css b/src/main/resources/css/rest-extender.css new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/images/logo_octo_375_293.png b/src/main/resources/images/logo_octo_375_293.png new file mode 100644 index 0000000000000000000000000000000000000000..543fe87bc1280c6a92e40a73af8e6f41fe4221f3 GIT binary patch literal 48446 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/js/rest-extender.js b/src/main/resources/js/rest-extender.js new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/rest-extender.properties b/src/main/resources/rest-extender.properties new file mode 100644 index 0000000..5a6bf7f --- /dev/null +++ b/src/main/resources/rest-extender.properties @@ -0,0 +1,3 @@ +#put any key/value pairs here +rest-extender.name=REST Extender +rest-extender.description=Confluence REST API extender