From 0731dac370c6695730628d283c0c06199b5a097f Mon Sep 17 00:00:00 2001 From: Alena Prokharchyk Date: Fri, 9 Dec 2011 12:20:06 -0800 Subject: [PATCH] bug 12399: introduced periodic thread that expires Project invitations status 12399: resolved fixed --- server/src/com/cloud/api/ApiServer.java | 20 ++-- .../cloud/projects/ProjectManagerImpl.java | 100 +++++++++++++----- .../projects/dao/ProjectInvitationDao.java | 8 +- .../dao/ProjectInvitationDaoImpl.java | 36 +++++-- utils/src/com/cloud/utils/DateUtil.java | 15 ++- 5 files changed, 123 insertions(+), 56 deletions(-) diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java index b49282c8923..a37dc953745 100755 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -970,14 +970,18 @@ public class ApiServer implements HttpRequestHandler { if (errorCode == BaseCmd.UNSUPPORTED_ACTION_ERROR || apiCommandParams == null || apiCommandParams.isEmpty()) { responseName = "errorresponse"; } else { - String cmdName = ((String[]) apiCommandParams.get("command"))[0]; - cmdClassName = _apiCommands.getProperty(cmdName); - if (cmdClassName != null) { - Class claz = Class.forName(cmdClassName); - responseName = ((BaseCmd) claz.newInstance()).getCommandName(); - } else { - responseName = "errorresponse"; - } + Object cmdObj = apiCommandParams.get("command"); + //cmd name can be null when "command" parameter is missing in the request + if (cmdObj != null) { + String cmdName = ((String[])cmdObj) [0]; + cmdClassName = _apiCommands.getProperty(cmdName); + if (cmdClassName != null) { + Class claz = Class.forName(cmdClassName); + responseName = ((BaseCmd) claz.newInstance()).getCommandName(); + } else { + responseName = "errorresponse"; + } + } } ExceptionResponse apiResponse = new ExceptionResponse(); diff --git a/server/src/com/cloud/projects/ProjectManagerImpl.java b/server/src/com/cloud/projects/ProjectManagerImpl.java index dd16d7840d8..d0e958c6340 100755 --- a/server/src/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/com/cloud/projects/ProjectManagerImpl.java @@ -18,11 +18,15 @@ package com.cloud.projects; import java.io.UnsupportedEncodingException; -import java.sql.Date; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; +import java.util.TimeZone; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.ejb.Local; import javax.mail.Authenticator; @@ -63,9 +67,11 @@ import com.cloud.user.DomainManager; import com.cloud.user.ResourceLimitService; import com.cloud.user.UserContext; import com.cloud.user.dao.AccountDao; +import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.Inject; import com.cloud.utils.component.Manager; +import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.JoinBuilder; @@ -106,8 +112,10 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ private ProjectInvitationDao _projectInvitationDao; protected boolean _invitationRequired = false; - protected long _invitationTimeOut = 86400; + protected long _invitationTimeOut = 86400000; protected boolean _allowUserToCreateProjet = true; + protected ScheduledExecutorService _executor; + protected int _projectCleanupExpInvInterval = 60; //Interval defining how often project invitation cleanup thread is running @Override @@ -116,7 +124,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ Map configs = _configDao.getConfiguration(params); _invitationRequired = Boolean.valueOf(configs.get(Config.ProjectInviteRequired.key())); - _invitationTimeOut = Long.valueOf(configs.get(Config.ProjectInvitationExpirationTime.key())); + _invitationTimeOut = Long.valueOf(configs.get(Config.ProjectInvitationExpirationTime.key()))*1000; _allowUserToCreateProjet = Boolean.valueOf(configs.get(Config.AllowUserToCreateProject.key())); @@ -136,12 +144,14 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } _emailInvite = new EmailInvite(smtpHost, smtpPort, useAuth, smtpUsername, smtpPassword, emailSender, smtpDebug); + _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Project-ExpireInvitations")); return true; } @Override public boolean start() { + _executor.scheduleWithFixedDelay(new ExpiredInvitationsCleanup(), _projectCleanupExpInvInterval, _projectCleanupExpInvInterval, TimeUnit.SECONDS); return true; } @@ -671,36 +681,54 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ return _projectAccountDao.search(sc, searchFilter); } - public ProjectInvitation createAccountInvitation(Project project, Long accountId) { - //verify if the invitation was already generated - ProjectInvitationVO invite = _projectInvitationDao.findPendingByAccountIdProjectId(accountId, project.getId()); + public ProjectInvitation createAccountInvitation(Project project, Long accountId) { + if (activeInviteExists(project, accountId, null)) { + throw new InvalidParameterValueException("There is already a pending invitation for account id=" + accountId + " to the project id=" + project); + } + ProjectInvitation invitation= _projectInvitationDao.persist(new ProjectInvitationVO(project.getId(), accountId, project.getDomainId(), null, null)); + + return invitation; + } + + @DB + public boolean activeInviteExists(Project project, Long accountId, String email) { + Transaction txn = Transaction.currentTxn(); + txn.start(); + //verify if the invitation was already generated + ProjectInvitationVO invite = null; + if (accountId != null) { + invite = _projectInvitationDao.findByAccountIdProjectId(accountId, project.getId()); + } else if (email != null) { + invite = _projectInvitationDao.findByEmailAndProjectId(email, project.getId()); + } + if (invite != null) { - if (_projectInvitationDao.isActive(invite.getId(), _invitationTimeOut)) { - throw new InvalidParameterValueException("There is already a pending invitation for account id=" + accountId + " to the project id=" + project); + if (invite.getState() == ProjectInvitation.State.Completed || _projectInvitationDao.isActive(invite.getId(), _invitationTimeOut)) { + return true; } else { if (invite.getState() == ProjectInvitation.State.Pending) { expireInvitation(invite); } + //remove the expired/declined invitation + if (accountId != null) { + s_logger.debug("Removing invitation in state " + invite.getState() + " for account id=" + accountId + " to project " + project); + } else if (email != null) { + s_logger.debug("Removing invitation in state " + invite.getState() + " for email " + email + " to project " + project); + } + + _projectInvitationDao.expunge(invite.getId()); } } - - return _projectInvitationDao.persist(new ProjectInvitationVO(project.getId(), accountId, project.getDomainId(), null, null)); - } + txn.commit(); + return false; + } public ProjectInvitation generateTokenBasedInvitation(Project project, String email, String token) { //verify if the invitation was already generated - ProjectInvitationVO invite = _projectInvitationDao.findPendingByEmailAndProjectId(email, project.getId()); - - if (invite != null) { - if (_projectInvitationDao.isActive(invite.getId(), _invitationTimeOut)) { - throw new InvalidParameterValueException("There is already a pending invitation for email=" + email + " to the project id=" + project); - } else { - if (invite.getState() == ProjectInvitation.State.Pending) { - expireInvitation(invite); - } - } - } + if (activeInviteExists(project, null, email)) { + throw new InvalidParameterValueException("There is already a pending invitation for email " + email + " to the project id=" + project); + } ProjectInvitation projectInvitation = _projectInvitationDao.persist(new ProjectInvitationVO(project.getId(), null, project.getDomainId(), email, token)); try { @@ -794,7 +822,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ if (activeOnly) { sc.setParameters("state", ProjectInvitation.State.Pending); - sc.setParameters("created", new Date((System.currentTimeMillis() >> 10) - _invitationTimeOut)); + sc.setParameters("created", new Date((DateUtil.currentGMTTime().getTime()) - _invitationTimeOut)); } return _projectInvitationDao.search(sc, searchFilter); @@ -837,9 +865,9 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ //check that invitation exists ProjectInvitationVO invite = null; if (token == null) { - invite = _projectInvitationDao.findPendingByAccountIdProjectId(accountId, projectId); + invite = _projectInvitationDao.findByAccountIdProjectId(accountId, projectId, ProjectInvitation.State.Pending); } else { - invite = _projectInvitationDao.findPendingByTokenAndProjectId(token, projectId); + invite = _projectInvitationDao.findPendingByTokenAndProjectId(token, projectId, ProjectInvitation.State.Pending); } if (invite != null) { @@ -1034,7 +1062,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ msg.setFrom(new InternetAddress(_emailSender, _emailSender)); msg.addRecipient(RecipientType.TO, address); msg.setSubject("You are invited to join the cloud stack project id=" + projectId); - msg.setSentDate(new Date(System.currentTimeMillis() >> 10)); + msg.setSentDate(new Date(DateUtil.currentGMTTime().getTime() >> 10)); msg.setContent(content, "text/plain"); msg.saveChanges(); @@ -1076,4 +1104,24 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ return false; } } + + public class ExpiredInvitationsCleanup implements Runnable { + @Override + public void run() { + try { + TimeZone.getDefault(); + List invitationsToExpire = _projectInvitationDao.listInvitationsToExpire(_invitationTimeOut); + if (!invitationsToExpire.isEmpty()) { + s_logger.debug("Found " + invitationsToExpire.size() + " projects to expire"); + for (ProjectInvitationVO invitationToExpire : invitationsToExpire) { + invitationToExpire.setState(ProjectInvitation.State.Expired); + _projectInvitationDao.update(invitationToExpire.getId(), invitationToExpire); + s_logger.trace("Expired project invitation id=" + invitationToExpire.getId()); + } + } + } catch (Exception ex) { + s_logger.warn("Exception while running expired invitations cleanup", ex); + } + } + } } diff --git a/server/src/com/cloud/projects/dao/ProjectInvitationDao.java b/server/src/com/cloud/projects/dao/ProjectInvitationDao.java index a3796277fda..2c3218d77d4 100644 --- a/server/src/com/cloud/projects/dao/ProjectInvitationDao.java +++ b/server/src/com/cloud/projects/dao/ProjectInvitationDao.java @@ -19,16 +19,18 @@ package com.cloud.projects.dao; import java.util.List; +import com.cloud.projects.ProjectInvitation.State; import com.cloud.projects.ProjectInvitationVO; import com.cloud.utils.db.GenericDao; public interface ProjectInvitationDao extends GenericDao{ - ProjectInvitationVO findPendingByAccountIdProjectId(long accountId, long projectId); + ProjectInvitationVO findByAccountIdProjectId(long accountId, long projectId, State... inviteState); List listExpiredInvitations(); boolean expirePendingInvitations(long timeOut); boolean isActive(long id, long timeout); - ProjectInvitationVO findPendingByEmailAndProjectId(String email, long projectId); - ProjectInvitationVO findPendingByTokenAndProjectId(String token, long projectId); + ProjectInvitationVO findByEmailAndProjectId(String email, long projectId, State... inviteState); + ProjectInvitationVO findPendingByTokenAndProjectId(String token, long projectId, State... inviteState); void cleanupInvitations(long projectId); ProjectInvitationVO findPendingById(long id); + List listInvitationsToExpire(long timeOut); } diff --git a/server/src/com/cloud/projects/dao/ProjectInvitationDaoImpl.java b/server/src/com/cloud/projects/dao/ProjectInvitationDaoImpl.java index e0941b5606e..343e544871c 100644 --- a/server/src/com/cloud/projects/dao/ProjectInvitationDaoImpl.java +++ b/server/src/com/cloud/projects/dao/ProjectInvitationDaoImpl.java @@ -9,6 +9,7 @@ import org.apache.log4j.Logger; import com.cloud.projects.ProjectInvitation.State; import com.cloud.projects.ProjectInvitationVO; +import com.cloud.utils.DateUtil; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -25,7 +26,7 @@ public class ProjectInvitationDaoImpl extends GenericDaoBase sc = AllFieldsSearch.create(); sc.setParameters("accountId", accountId); sc.setParameters("projectId", projectId); - sc.setParameters("state", State.Pending); - + if (inviteState != null && inviteState.length > 0) { + sc.setParameters("state", (Object[])inviteState); + } + return findOneBy(sc); } @@ -64,7 +67,7 @@ public class ProjectInvitationDaoImpl extends GenericDaoBase sc = InactiveSearch.create(); - sc.setParameters("created", new Date((System.currentTimeMillis() >> 10) - timeout)); + sc.setParameters("created", new Date((DateUtil.currentGMTTime().getTime() >> 10) - timeout)); sc.setParameters("state", State.Pending); List invitationsToExpire = listBy(sc); @@ -75,10 +78,17 @@ public class ProjectInvitationDaoImpl extends GenericDaoBase listInvitationsToExpire (long timeOut) { + SearchCriteria sc = InactiveSearch.create(); + sc.setParameters("created", new Date((DateUtil.currentGMTTime().getTime()) - timeOut)); + sc.setParameters("state", State.Pending); + return listBy(sc); + } + @Override public boolean isActive(long id, long timeout) { SearchCriteria sc = InactiveSearch.create(); @@ -90,7 +100,7 @@ public class ProjectInvitationDaoImpl extends GenericDaoBase> 10) - timeout)); + sc.setParameters("created", new Date((DateUtil.currentGMTTime().getTime()) - timeout)); if (findOneBy(sc) == null) { return true; @@ -100,21 +110,25 @@ public class ProjectInvitationDaoImpl extends GenericDaoBase sc = AllFieldsSearch.create(); sc.setParameters("email", email); sc.setParameters("projectId", projectId); - sc.setParameters("state", State.Pending); + if (inviteState != null && inviteState.length > 0) { + sc.setParameters("state", (Object[])inviteState); + } return findOneBy(sc); } @Override - public ProjectInvitationVO findPendingByTokenAndProjectId(String token, long projectId) { + public ProjectInvitationVO findPendingByTokenAndProjectId(String token, long projectId, State... inviteState) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("token", token); sc.setParameters("projectId", projectId); - sc.setParameters("state", State.Pending); + if (inviteState != null && inviteState.length > 0) { + sc.setParameters("state", (Object[])inviteState); + } return findOneBy(sc); } diff --git a/utils/src/com/cloud/utils/DateUtil.java b/utils/src/com/cloud/utils/DateUtil.java index 66e0bd507f3..46bbff2c41f 100644 --- a/utils/src/com/cloud/utils/DateUtil.java +++ b/utils/src/com/cloud/utils/DateUtil.java @@ -18,14 +18,13 @@ package com.cloud.utils; -import java.net.URI; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + import com.cloud.utils.exception.CloudRuntimeException; public class DateUtil {