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 0000000..543fe87 Binary files /dev/null and b/src/main/resources/images/logo_octo_375_293.png differ 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