WebNotificationsMigration.java
package org.exoplatform.commons.migration;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import org.exoplatform.commons.api.settings.SettingService;
import org.exoplatform.commons.api.settings.SettingValue;
import org.exoplatform.commons.api.settings.data.Context;
import org.exoplatform.commons.api.settings.data.Scope;
import org.exoplatform.commons.notification.impl.jpa.web.JPAWebNotificationStorage;
import org.exoplatform.commons.notification.impl.service.storage.WebNotificationStorageImpl;
import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.commons.utils.RDBMSMigrationUtils;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.component.RequestLifeCycle;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.User;
public class WebNotificationsMigration {
private static final Log LOG = ExoLogger.getLogger(WebNotificationsMigration.class);
//JPA Storage
private JPAWebNotificationStorage jpaWebNotificationStorage;
//JCR storage
private WebNotificationStorageImpl jcrWebNotificationStorage;
private SessionProvider sProvider;
private NodeHierarchyCreator nodeHierarchyCreator;
private OrganizationService organizationService;
private SettingService settingService;
private static List<String> allUsers = new LinkedList<String>();
private static List<String> nonRemovedWebNotifs = new LinkedList<String>();
private static List<String> nonMigratedWebNotifs = new LinkedList<String>();
//scope of user web notification migration
public static final String WEB_NOTIFICATION_MIGRATION_USER_KEY = "WEB_NOTIFICATION_MIGRATION_USER";
//scope of web notification migration status
public static final String WEB_NOTIFICATION_MIGRATION_DONE_KEY = "WEB_NOTIFICATION_MIGRATION_DONE";
//status of web notifications migration (true if migration completed successfully)
public static final String WEB_NOTIFICATION_RDBMS_MIGRATION_DONE = "WEB_NOTIFICATION_RDBMS_MIGRATION_DONE";
//status of web notifications cleanup from JCR (true if cleanup is completed successfully)
public static final String WEB_NOTIFICATION_RDBMS_CLEANUP_DONE = "WEB_NOTIFICATION_RDBMS_CLEANUP_DONE";
public WebNotificationsMigration(JPAWebNotificationStorage jpaWebNotificationStorage,
NodeHierarchyCreator nodeHierarchyCreator,
OrganizationService organizationService,
SettingService settingService,
WebNotificationStorageImpl jcrWebNotificationStorage) {
this.jpaWebNotificationStorage = jpaWebNotificationStorage;
this.jcrWebNotificationStorage = jcrWebNotificationStorage;
this.organizationService = organizationService;
this.settingService = settingService;
this.nodeHierarchyCreator = nodeHierarchyCreator;
}
public void migrate() {
//migration of web notifications data from JCR to RDBMS is done as a background task
RDBMSMigrationUtils.getExecutorService().submit(new Callable<Void>() {
@Override
public Void call() {
ExoContainerContext.setCurrentContainer(PortalContainer.getInstance());
if (!isWebNotifMigrationDone()) {
int pageSize = 20;
int current = 0;
RequestLifeCycle.begin(PortalContainer.getInstance());
try {
ListAccess<User> allUsersListAccess = organizationService.getUserHandler().findAllUsers();
int totalUsers = allUsersListAccess.getSize();
LOG.info(" Number of users = " + totalUsers);
User[] users;
LOG.info("=== Start migration of Web Notifications data from JCR");
try {
sProvider = SessionProvider.createSystemProvider();
} catch (Exception e) {
LOG.error("Error while getting Notification nodes for Notifications migration - Cause : " + e.getMessage(), e);
}
long startTime = System.currentTimeMillis();
do {
LOG.info(" Progression of users web notifications migration : " + current + "/" + totalUsers);
if (current + pageSize > totalUsers) {
pageSize = totalUsers - current;
}
users = allUsersListAccess.load(current, pageSize);
int migratedUsersCount = current + 1;
for (User user : users) {
RequestLifeCycle.end();
RequestLifeCycle.begin(PortalContainer.getInstance());
String userName = user.getUserName();
if (!hasWebNotifDataToMigrate(userName) || isWebNotifMigrated(userName)) {
int progression = (int)((migratedUsersCount++ * 100) / totalUsers);
LOG.info("Web notification migration - progression = ({}%), username={}, migrated Web Notifications Count = {}", progression, userName, 0);
continue;
}
try {
long notificationsCount = migrateWebNotifDataOfUser(nodeHierarchyCreator.getUserApplicationNode(sProvider, userName));
int progression = (int)((migratedUsersCount++ * 100) / totalUsers);
LOG.info("Web notification migration - progression = ({}%), username={}, migrated Web Notifications Count = {}", progression, userName, notificationsCount);
allUsers.add(userName);
settingService.set(Context.USER.id(userName), Scope.APPLICATION.id(WEB_NOTIFICATION_MIGRATION_USER_KEY), WEB_NOTIFICATION_RDBMS_MIGRATION_DONE, SettingValue.create("true"));
} catch (Exception e) {
settingService.set(Context.USER.id(userName), Scope.APPLICATION.id(WEB_NOTIFICATION_MIGRATION_USER_KEY), WEB_NOTIFICATION_RDBMS_MIGRATION_DONE, SettingValue.create("false"));
}
}
current += users.length;
} while (users != null && users.length > 0);
long endTime = System.currentTimeMillis();
LOG.info("=== Migration of Web Notification data done in " + (endTime - startTime) + " ms");
} catch (Exception e) {
LOG.error("Error while migrating Web Notification data from JCR to RDBMS - Cause : " + e.getMessage(), e);
} finally {
RequestLifeCycle.end();
if (sProvider != null) {
sProvider.close();
}
}
settingService.set(Context.GLOBAL, Scope.APPLICATION.id(WEB_NOTIFICATION_MIGRATION_DONE_KEY), WEB_NOTIFICATION_RDBMS_MIGRATION_DONE, SettingValue.create("true"));
} else {
LOG.info("No web notifications data to migrate from JCR to RDBMS");
}
cleanup();
return null;
}
});
}
public void cleanup() {
RDBMSMigrationUtils.getExecutorService().submit(new Callable<Void>() {
@Override
public Void call() {
PortalContainer currentContainer = PortalContainer.getInstance();
ExoContainerContext.setCurrentContainer(currentContainer);
RequestLifeCycle.begin(currentContainer);
try {
if (isWebNotifMigrationDone() && !isWebNotifCleanupDone()) {
deleteJcrWebNotifications();
}
} catch (Exception e) {
LOG.error("Error while cleaning Web Notifications data from JCR", e);
} finally {
RequestLifeCycle.end();
}
return null;
}
});
}
private boolean isWebNotifMigrated(String userName) {
SettingValue<?> setting = settingService.get(Context.USER.id(userName), Scope.APPLICATION.id(WEB_NOTIFICATION_MIGRATION_USER_KEY), WEB_NOTIFICATION_RDBMS_MIGRATION_DONE);
return (setting != null && setting.getValue().equals("true"));
}
private boolean isWebNotifMigrationDone() {
SettingValue<?> setting = settingService.get(Context.GLOBAL, Scope.APPLICATION.id(WEB_NOTIFICATION_MIGRATION_DONE_KEY), WEB_NOTIFICATION_RDBMS_MIGRATION_DONE);
return (setting != null && setting.getValue().equals("true"));
}
private boolean isWebNotifCleanupDone() {
SettingValue<?> setting = settingService.get(Context.GLOBAL, Scope.APPLICATION.id(WEB_NOTIFICATION_MIGRATION_DONE_KEY), WEB_NOTIFICATION_RDBMS_CLEANUP_DONE);
return (setting != null && setting.getValue().equals("true"));
}
private long migrateWebNotifDataOfUser(Node userAppNode) throws Exception {
NodeIterator dateIterator = userAppNode.getNode("notifications").getNode("web").getNodes();
long notificationsCount = dateIterator.getSize();
while (dateIterator.hasNext()) {
NodeIterator notifIterator = dateIterator.nextNode().getNodes();
while (notifIterator.hasNext()) {
migrateWebNotifNodeToRDBMS(notifIterator.nextNode());
}
}
return notificationsCount;
}
private void migrateWebNotifNodeToRDBMS(Node node) throws Exception {
jpaWebNotificationStorage.save(jcrWebNotificationStorage.fillModel(node));
}
private void deleteJcrWebNotifications() {
LOG.info("=== Start Cleaning Web Notifications data from JCR");
long startTime = System.currentTimeMillis();
int i = 0;
int totalSize = allUsers.size();
ExoContainer currentContainer = ExoContainerContext.getCurrentContainer();
for (String userId : allUsers) {
i++;
if (isWebNotifMigrated(userId)) {
try {
Node node = nodeHierarchyCreator.getUserApplicationNode(sProvider, userId).getNode("notifications").getNode("web");
node.remove();
node.getSession().save();
settingService.remove(Context.USER.id(userId), Scope.APPLICATION.id(WEB_NOTIFICATION_MIGRATION_USER_KEY));
} catch (Exception e) {
nonRemovedWebNotifs.add(userId);
LOG.error("Error while cleaning Web notifications JCR data to RDBMS of user: " + userId + " - Cause : "
+ e.getMessage(), e);
}
} else {
nonMigratedWebNotifs.add(userId);
}
if (i % 100 == 0) {
LOG.info("Web Notifications JCR cleanup - progression = {}/{}", i, totalSize);
RequestLifeCycle.end();
RequestLifeCycle.begin(currentContainer);
}
}
LOG.info(" === Web Notifications Migration & cleanup from JCR to RDBBMS report:");
LOG.info(" - " + i + " Web Notifications nodes are cleaned from JCR");
LOG.info(" - " + nonMigratedWebNotifs.size() + " Web Notifications nodes are not migrated to RDBMS");
LOG.info(" - " + nonRemovedWebNotifs.size() + " Web Notifications nodes are migrated but not removed from JCR");
long endTime = System.currentTimeMillis();
LOG.info("=== Web notifications JCR data cleaning due to RDBMS migration done in " + (endTime - startTime) + " ms");
settingService.set(Context.GLOBAL, Scope.APPLICATION.id(WEB_NOTIFICATION_MIGRATION_DONE_KEY), WEB_NOTIFICATION_RDBMS_CLEANUP_DONE, SettingValue.create("true"));
}
private boolean hasWebNotifDataToMigrate(String userName) {
try {
Node node = nodeHierarchyCreator.getUserApplicationNode(sProvider, userName).getNode("notifications");
if (node.hasNode("web")) {
return true;
}
} catch (PathNotFoundException e) {
return false;
} catch (Exception e) {
LOG.error("Error while verifying if web notification nodes exist in JCR - Cause : " + e.getMessage(), e);
return true;
}
return false;
}
}