diff --git a/data-upgrade-move-folders/pom.xml b/data-upgrade-move-folders/pom.xml
new file mode 100644
index 000000000..be82a8f93
--- /dev/null
+++ b/data-upgrade-move-folders/pom.xml
@@ -0,0 +1,72 @@
+
+
+
+ 4.0.0
+
+ org.exoplatform.addons.upgrade
+ upgrade
+ 6.4.x-SNAPSHOT
+
+
+ data-upgrade-move-folders
+ jar
+ eXo Add-on:: Data Upgrade Add-on - Move Folders
+
+
+ 0.66
+
+
+
+
+ org.exoplatform.commons
+ commons-component-upgrade
+ provided
+
+
+ org.exoplatform.jcr
+ exo.jcr.component.core
+ provided
+
+
+ org.exoplatform.jcr
+ exo.jcr.component.ext
+ provided
+
+
+ org.exoplatform.ecms
+ ecms-core-services
+ provided
+
+
+ junit
+ junit
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.powermock
+ powermock-api-mockito2
+ test
+
+
+ org.powermock
+ powermock-module-junit4
+ test
+
+
+
diff --git a/data-upgrade-move-folders/src/main/java/org/exoplatform/jcr/upgrade/MoveNodesUpgradePlugin.java b/data-upgrade-move-folders/src/main/java/org/exoplatform/jcr/upgrade/MoveNodesUpgradePlugin.java
new file mode 100644
index 000000000..5f800d018
--- /dev/null
+++ b/data-upgrade-move-folders/src/main/java/org/exoplatform/jcr/upgrade/MoveNodesUpgradePlugin.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2003-2023 eXo Platform SAS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.exoplatform.jcr.upgrade;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.lang3.StringUtils;
+import org.exoplatform.commons.upgrade.UpgradeProductPlugin;
+import org.exoplatform.commons.utils.ListAccess;
+import org.exoplatform.container.PortalContainer;
+import org.exoplatform.container.component.RequestLifeCycle;
+import org.exoplatform.container.xml.InitParams;
+import org.exoplatform.services.jcr.RepositoryService;
+import org.exoplatform.services.jcr.ext.app.SessionProviderService;
+import org.exoplatform.services.jcr.ext.common.SessionProvider;
+import org.exoplatform.services.log.ExoLogger;
+import org.exoplatform.services.log.Log;
+import org.exoplatform.social.core.space.model.Space;
+import org.exoplatform.social.core.space.spi.SpaceService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * plugin will be executed in order to move folders under spaces drives
+ * from an original path to a destination path as provided in the configuration
+ */
+public class MoveNodesUpgradePlugin extends UpgradeProductPlugin {
+
+ private static final Log log = ExoLogger.getLogger(MoveNodesUpgradePlugin.class.getName());
+
+ private static final String ORIGIN_PATH = "origin-folder-path";
+
+ private static final String DESTINATION_PATH = "destination-folder-path";
+ private static final String FOLDERS_TO_REMOVE = "folders-to-remove";
+
+ private static final int SPACES_PAGE_SIZE = 2;
+
+ private final SpaceService spaceService;
+
+ private RepositoryService repositoryService;
+
+ private SessionProviderService sessionProviderService;
+
+ private String originPath;
+
+ private String destinationPath;
+
+ private List foldersToRemove = new ArrayList<>();
+
+
+ public MoveNodesUpgradePlugin(InitParams initParams,
+ SpaceService spaceService,
+ RepositoryService repositoryService,
+ SessionProviderService sessionProviderService) {
+ super(initParams);
+ if(initParams.getValueParam(ORIGIN_PATH) != null && StringUtils.isNotBlank(initParams.getValueParam(ORIGIN_PATH).getValue())) {
+ this.originPath = initParams.getValueParam(ORIGIN_PATH).getValue();
+ }
+ if(initParams.getValueParam(ORIGIN_PATH) != null && StringUtils.isNotBlank(initParams.getValueParam(ORIGIN_PATH).getValue())) {
+ this.destinationPath = initParams.getValueParam(DESTINATION_PATH).getValue();
+ }
+ if(initParams.getValuesParam(FOLDERS_TO_REMOVE) != null && !initParams.getValuesParam(FOLDERS_TO_REMOVE).getValues().isEmpty()) {
+ this.foldersToRemove = initParams.getValuesParam(FOLDERS_TO_REMOVE).getValues();
+ }
+
+ this.spaceService = spaceService;
+ this.repositoryService = repositoryService;
+ this.sessionProviderService = sessionProviderService;
+ }
+
+ @Override
+ public void processUpgrade(String oldVersion, String newVersion) {
+ if(StringUtils.isBlank(originPath) || StringUtils.isBlank(destinationPath)) {
+ log.warn("Invalid parameter was provided for {}, this upgrade plugin will be ignored", StringUtils.isBlank(originPath) ? "'Origin path'":"'Destination path'");
+ return;
+ }
+ long startupTime = System.currentTimeMillis();
+ int movedFoldersCount = 0;
+ log.info("Start upgrade : Moving of folder from {} to {}", originPath, destinationPath);
+
+ SessionProvider sessionProvider = null;
+ RequestLifeCycle.begin(PortalContainer.getInstance());
+ try {
+ sessionProvider = sessionProviderService.getSystemSessionProvider(null);
+ Session session = sessionProvider.getSession(
+ repositoryService.getCurrentRepository()
+ .getConfiguration()
+ .getDefaultWorkspaceName(),
+ repositoryService.getCurrentRepository());
+ ListAccess spaces = spaceService.getAllSpacesWithListAccess();
+ int index = 0;
+ while(index <= spaces.getSize()) {
+ Space[] spaceArray = spaces.load(index, SPACES_PAGE_SIZE);
+ for (Space space : spaceArray) {
+ String originFolderPath = "/Groups" + space.getGroupId() + originPath;
+ String destinationFolderPath = "/Groups" + space.getGroupId() + destinationPath;
+ try {
+ Item originFolderNode = session.getItem(originFolderPath);
+ if (originFolderNode != null) {
+ session.move(originFolderPath, destinationFolderPath);
+ movedFoldersCount++;
+ }
+ } catch(RepositoryException e) {
+ if (log.isDebugEnabled()) {
+ log.warn("Folder {} to move was not found, ignoring it", originFolderPath, e);
+ } else {
+ log.warn("Folder {} to move was not found, ignoring it", originFolderPath);
+ }
+ }
+ // remove unnecessary folders if defined in init params
+ if(!foldersToRemove.isEmpty()) {
+ for(String folderToRemove : foldersToRemove) {
+ folderToRemove = "/Groups" + space.getGroupId() + folderToRemove;
+ try {
+ Item folderToRemoveNode = session.getItem(folderToRemove);
+ if (folderToRemoveNode != null) {
+ folderToRemoveNode.remove();
+ }
+ } catch (RepositoryException re) {
+ if(log.isDebugEnabled()) {
+ log.warn("Folder {} to delete was not found, ignoring it", folderToRemove, re);
+ } else {
+ log.warn("Folder {} to delete was not found, ignoring it", folderToRemove);
+ }
+ }
+ }
+ }
+ }
+
+ session.save();
+ index = index + SPACES_PAGE_SIZE;
+ }
+
+ log.info("End Moving of '{}' folders. It took {} ms",
+ movedFoldersCount,
+ (System.currentTimeMillis() - startupTime));
+ } catch (Exception e) {
+ if (log.isErrorEnabled()) {
+ log.error("An unexpected error occurs when moving folders:", e);
+ }
+ } finally {
+ if (sessionProvider != null) {
+ sessionProvider.close();
+ }
+ RequestLifeCycle.end();
+ }
+ }
+
+}
diff --git a/data-upgrade-move-folders/src/main/resources/conf/portal/configuration.xml b/data-upgrade-move-folders/src/main/resources/conf/portal/configuration.xml
new file mode 100644
index 000000000..b36de3b8a
--- /dev/null
+++ b/data-upgrade-move-folders/src/main/resources/conf/portal/configuration.xml
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+ org.exoplatform.commons.upgrade.UpgradeProductService
+
+ MoveNewsFolderToSpaceRoot
+ addUpgradePlugin
+ org.exoplatform.jcr.upgrade.MoveNodesUpgradePlugin
+ Move images attached to News to a new location
+
+
+ product.group.id
+ The groupId of the product
+ org.exoplatform.news
+
+
+ plugin.execution.order
+ The plugin execution order
+ 1
+
+
+ plugin.upgrade.execute.once
+ Execute this upgrade plugin only once
+ true
+
+
+ plugin.upgrade.async.execution
+ Execute this upgrade asynchronously
+ true
+
+
+ plugin.upgrade.target.version
+ Target version of the plugin
+ 6.4.4
+
+
+
+ origin-folder-path
+ Origin folder path
+ /Documents/news/images
+
+
+ destination-folder-path
+ Destination parent folder path
+ /News/images
+
+
+ folders-to-remove
+ /Documents/news
+
+
+
+
+ MoveNotesFolderToSpaceRoot
+ addUpgradePlugin
+ org.exoplatform.jcr.upgrade.MoveNodesUpgradePlugin
+ Move images attached to notes to a new location
+
+
+ product.group.id
+ The groupId of the product
+ org.meeds-io.notes
+
+
+ plugin.execution.order
+ The plugin execution order
+ 1
+
+
+ plugin.upgrade.execute.once
+ Execute this upgrade plugin only once
+ true
+
+
+ plugin.upgrade.async.execution
+ Execute this upgrade asynchronously
+ true
+
+
+ plugin.upgrade.target.version
+ Target version of the plugin
+ 6.4.4
+
+
+
+ origin-folder-path
+ Origin folder path
+ /Documents/notes
+
+
+ destination-folder-path
+ Destination parent folder path
+ /notes
+
+
+
+
+
+
+
diff --git a/data-upgrade-move-folders/src/test/java/org/exoplatform/jcr/upgrade/MoveNodesUpgradePluginTest.java b/data-upgrade-move-folders/src/test/java/org/exoplatform/jcr/upgrade/MoveNodesUpgradePluginTest.java
new file mode 100644
index 000000000..20a4c541e
--- /dev/null
+++ b/data-upgrade-move-folders/src/test/java/org/exoplatform/jcr/upgrade/MoveNodesUpgradePluginTest.java
@@ -0,0 +1,118 @@
+package org.exoplatform.jcr.upgrade;
+
+import org.exoplatform.commons.utils.ListAccess;
+import org.exoplatform.container.ExoContainerContext;
+import org.exoplatform.container.PortalContainer;
+import org.exoplatform.container.component.RequestLifeCycle;
+import org.exoplatform.container.xml.InitParams;
+import org.exoplatform.container.xml.ValueParam;
+import org.exoplatform.services.cms.impl.Utils;
+import org.exoplatform.services.jcr.RepositoryService;
+import org.exoplatform.services.jcr.config.RepositoryEntry;
+import org.exoplatform.services.jcr.core.ManageableRepository;
+import org.exoplatform.services.jcr.ext.app.SessionProviderService;
+import org.exoplatform.services.jcr.ext.common.SessionProvider;
+import org.exoplatform.social.core.space.SpaceListAccess;
+import org.exoplatform.social.core.space.model.Space;
+import org.exoplatform.social.core.space.spi.SpaceService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import javax.jcr.Item;
+import javax.jcr.Session;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ExoContainerContext.class, PortalContainer.class, RequestLifeCycle.class})
+@PowerMockIgnore({ "javax.management.*", "jdk.internal.reflect.*", "javax.naming.*", "javax.xml.*", "org.apache.xerces.*", "org.xml.*", "com.sun.*", "org.w3c.*" })
+public class MoveNodesUpgradePluginTest {
+ @Mock
+ private RepositoryService repositoryService;
+
+ @Mock
+ private SessionProviderService sessionProviderService;
+
+ @Mock
+ private ManageableRepository repository;
+
+ @Mock
+ private RepositoryEntry repositoryEntry;
+
+ @Mock
+ private SessionProvider sessionProvider;
+
+ @Mock
+ private Session session;
+
+ @Mock
+ private SpaceService spaceService;
+
+ @Before
+ public void setUp() throws Exception {
+ lenient().when(sessionProviderService.getSystemSessionProvider(any())).thenReturn(sessionProvider);
+ lenient().when(repositoryService.getCurrentRepository()).thenReturn(repository);
+ lenient().when(repository.getConfiguration()).thenReturn(repositoryEntry);
+ lenient().when(repositoryEntry.getDefaultWorkspaceName()).thenReturn("collaboration");
+ Item node = mock(Item.class);
+ when(session.getItem(anyString())).thenReturn(node);
+ lenient().when(sessionProvider.getSession(any(), any(ManageableRepository.class))).thenReturn(session);
+ PowerMockito.mockStatic(ExoContainerContext.class);
+ PowerMockito.mockStatic(PortalContainer.class);
+ PowerMockito.mockStatic(RequestLifeCycle.class);
+ }
+
+ @Test
+ public void testMoveFoldersUpgrade() throws Exception {
+ InitParams initParams = new InitParams();
+
+ ValueParam valueParam = new ValueParam();
+ valueParam.setName("product.group.id");
+ valueParam.setValue("org.exoplatform.platform");
+ initParams.addParameter(valueParam);
+
+ MoveNodesUpgradePlugin plugin = new MoveNodesUpgradePlugin(initParams,
+ spaceService,
+ repositoryService,
+ sessionProviderService);
+ plugin.processUpgrade(null,null);
+
+ verify(session, never()).move(anyString(), anyString());
+
+
+ ValueParam valueParam1 = new ValueParam();
+ valueParam1.setName("origin-folder-path");
+ valueParam1.setValue("/Documents/News");
+ initParams.addParameter(valueParam1);
+ ValueParam valueParam2 = new ValueParam();
+ valueParam2.setName("destination-folder-path");
+ valueParam2.setValue("/");
+ initParams.addParameter(valueParam2);
+ Space space1 = new Space();
+ space1.setGroupId("/spaces/spaceOne");
+
+ Space space2 = new Space();
+ space2.setGroupId("/spaces/spaceTw");
+
+ ListAccess spaces = mock(SpaceListAccess.class);
+ when(spaces.load(anyInt(), anyInt())).thenReturn(new Space[]{space1, space2});
+
+ when(spaceService.getAllSpacesWithListAccess()).thenReturn(spaces);
+
+ plugin = new MoveNodesUpgradePlugin(initParams,
+ spaceService,
+ repositoryService,
+ sessionProviderService);
+ plugin.processUpgrade(null,null);
+
+ verify(session, times(2)).move(anyString(), anyString());
+ }
+}
diff --git a/data-upgrade-packaging/pom.xml b/data-upgrade-packaging/pom.xml
index 88cde2645..b09d291d9 100644
--- a/data-upgrade-packaging/pom.xml
+++ b/data-upgrade-packaging/pom.xml
@@ -80,6 +80,10 @@
${project.groupId}
data-upgrade-es-reindex
+
+ ${project.groupId}
+ data-upgrade-move-folders
+
diff --git a/pom.xml b/pom.xml
index 55e124f7c..19d753248 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,6 +32,7 @@
data-upgrade-notifications
data-upgrade-processes-permissions
data-upgrade-es-reindex
+ data-upgrade-move-folders
data-upgrade-packaging
@@ -183,6 +184,11 @@
data-upgrade-es-reindex
${project.version}
+
+ ${project.groupId}
+ data-upgrade-move-folders
+ ${project.version}
+
${project.groupId}
data-upgrade-packaging