diff --git a/api/src/com/cloud/projects/ProjectService.java b/api/src/com/cloud/projects/ProjectService.java index c0ec91bd511..2ef367b8f75 100644 --- a/api/src/com/cloud/projects/ProjectService.java +++ b/api/src/com/cloud/projects/ProjectService.java @@ -19,6 +19,8 @@ package com.cloud.projects; import java.util.List; import java.util.Map; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; + import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; @@ -61,7 +63,7 @@ public interface ProjectService { */ Project getProject(long id); - Pair, Integer> listProjects(Long id, String name, String displayText, String state, String accountName, + Pair, Integer> listProjects(Long id, String name, String displayText, String state, String accountName, Long domainId, String keyword, Long startIndex, Long pageSize, boolean listAll, boolean isRecursive, Map tags); ProjectAccount assignAccountToProject(Project project, long accountId, Role accountRole); diff --git a/api/src/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/org/apache/cloudstack/api/ResponseGenerator.java index acb548ab0a7..383ecf81253 100644 --- a/api/src/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/org/apache/cloudstack/api/ResponseGenerator.java @@ -153,6 +153,7 @@ import com.cloud.vm.InstanceGroup; import org.apache.cloudstack.api.view.vo.DomainRouterJoinVO; import org.apache.cloudstack.api.view.vo.EventJoinVO; import org.apache.cloudstack.api.view.vo.InstanceGroupJoinVO; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; import org.apache.cloudstack.api.view.vo.ResourceTagJoinVO; import org.apache.cloudstack.api.view.vo.SecurityGroupJoinVO; import org.apache.cloudstack.api.view.vo.UserAccountJoinVO; @@ -291,6 +292,8 @@ public interface ResponseGenerator { ProjectResponse createProjectResponse(Project project); + List createProjectResponse(ProjectJoinVO... projects); + List createIsoResponses(VirtualMachineTemplate iso, long zoneId, boolean readyOnly); List createTemplateResponses(long templateId, Long vmId); diff --git a/api/src/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java b/api/src/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java index 2350a2c0af4..cc1ccdda8e6 100644 --- a/api/src/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java @@ -32,6 +32,8 @@ import org.apache.cloudstack.api.Implementation; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; + import com.cloud.exception.InvalidParameterValueException; import com.cloud.projects.Project; import com.cloud.utils.Pair; @@ -107,15 +109,11 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd { @Override public void execute(){ - Pair, Integer> projects = _projectService.listProjects(id, name, displayText, state, + Pair, Integer> projects = _projectService.listProjects(id, name, displayText, state, this.getAccountName(), this.getDomainId(), this.getKeyword(), this.getStartIndex(), this.getPageSizeVal(), this.listAll(), this.isRecursive(), getTags()); ListResponse response = new ListResponse(); - List projectResponses = new ArrayList(); - for (Project project : projects.first()) { - ProjectResponse projectResponse = _responseGenerator.createProjectResponse(project); - projectResponses.add(projectResponse); - } + List projectResponses = _responseGenerator.createProjectResponse(projects.first().toArray(new ProjectJoinVO[projects.first().size()])); response.setResponses(projectResponses, projects.second()); response.setResponseName(getCommandName()); diff --git a/api/src/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/org/apache/cloudstack/api/response/ProjectResponse.java index dea1a68cdbf..c47e7b6fc09 100644 --- a/api/src/org/apache/cloudstack/api/response/ProjectResponse.java +++ b/api/src/org/apache/cloudstack/api/response/ProjectResponse.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.response; +import java.util.ArrayList; import java.util.List; import org.apache.cloudstack.api.ApiConstants; @@ -28,7 +29,7 @@ import org.apache.cloudstack.api.BaseResponse; public class ProjectResponse extends BaseResponse { @SerializedName(ApiConstants.ID) @Param(description="the id of the project") - private IdentityProxy id = new IdentityProxy("projects"); + private String id; @SerializedName(ApiConstants.NAME) @Param(description="the name of the project") private String name; @@ -37,7 +38,7 @@ public class ProjectResponse extends BaseResponse { private String displaytext; @SerializedName(ApiConstants.DOMAIN_ID) @Param(description="the domain id the project belongs to") - private IdentityProxy domainId = new IdentityProxy("domain"); + private String domainId; @SerializedName(ApiConstants.DOMAIN) @Param(description="the domain name where the project belongs to") private String domain; @@ -49,11 +50,11 @@ public class ProjectResponse extends BaseResponse { private String state; @SerializedName(ApiConstants.TAGS) @Param(description="the list of resource tags associated with vm", responseObject = ResourceTagResponse.class) - private List tags; + private List tags = new ArrayList(); - public void setId(Long id) { - this.id.setValue(id); + public void setId(String id) { + this.id = id; } public void setName(String name) { @@ -64,8 +65,8 @@ public class ProjectResponse extends BaseResponse { this.displaytext = displaytext; } - public void setDomainId(Long domainId) { - this.domainId.setValue(domainId); + public void setDomainId(String domainId) { + this.domainId = domainId; } public void setDomain(String domain) { @@ -83,4 +84,8 @@ public class ProjectResponse extends BaseResponse { public void setTags(List tags) { this.tags = tags; } + + public void addTag(ResourceTagResponse tag){ + this.tags.add(tag); + } } diff --git a/api/src/org/apache/cloudstack/api/view/vo/ProjectJoinVO.java b/api/src/org/apache/cloudstack/api/view/vo/ProjectJoinVO.java new file mode 100644 index 00000000000..f4226d3bbea --- /dev/null +++ b/api/src/org/apache/cloudstack/api/view/vo/ProjectJoinVO.java @@ -0,0 +1,395 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.view.vo; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; + +import com.cloud.server.ResourceTag.TaggedResourceType; +import com.cloud.utils.db.GenericDao; +import com.cloud.vm.VirtualMachine.State; + +@Entity +@Table(name="project_view") +public class ProjectJoinVO extends BaseViewVO { + + @Column(name="uuid") + private String uuid; + + @Column(name="name") + private String name; + + @Column(name="display_text") + String displayText; + + + @Column(name="owner") + String owner; + + @Column(name="state") + @Enumerated(value=EnumType.STRING) + private State state; + + @Column(name=GenericDao.CREATED_COLUMN) + private Date created; + + @Column(name=GenericDao.REMOVED_COLUMN) + private Date removed; + + @Column(name="account_id") + private long accountId; + + @Column(name="domain_id") + private long domainId; + + @Column(name="domain_uuid") + private String domainUuid; + + @Column(name="domain_name") + private String domainName; + + @Column(name="domain_path") + private String domainPath; + + @Column(name="tag_id") + private long tagId; + + @Column(name="tag_uuid") + private String tagUuid; + + @Column(name="tag_key") + private String tagKey; + + @Column(name="tag_value") + private String tagValue; + + @Column(name="tag_domain_id") + private long tagDomainId; + + @Column(name="tag_account_id") + private long tagAccountId; + + @Column(name="tag_resource_id") + private long tagResourceId; + + @Column(name="tag_resource_uuid") + private String tagResourceUuid; + + @Column(name="tag_resource_type") + @Enumerated(value=EnumType.STRING) + private TaggedResourceType tagResourceType; + + @Column(name="tag_customer") + private String tagCustomer; + + + public ProjectJoinVO() { + } + + + + + public String getUuid() { + return uuid; + } + + + + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + + + public String getName() { + return name; + } + + + public void setName(String name) { + this.name = name; + } + + + + + + + public long getDomainId() { + return domainId; + } + + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + + public String getDomainUuid() { + return domainUuid; + } + + + + + public void setDomainUuid(String domainUuid) { + this.domainUuid = domainUuid; + } + + + + + public String getDomainName() { + return domainName; + } + + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + public String getDomainPath() { + return domainPath; + } + + + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + + + public State getState() { + return state; + } + + + public void setState(State state) { + this.state = state; + } + + + public Date getCreated() { + return created; + } + + + public void setCreated(Date created) { + this.created = created; + } + + + public Date getRemoved() { + return removed; + } + + + public void setRemoved(Date removed) { + this.removed = removed; + } + + + + + public String getDisplayText() { + return displayText; + } + + + + + public void setDisplayText(String displayText) { + this.displayText = displayText; + } + + + + + public String getOwner() { + return owner; + } + + + + + public void setOwner(String owner) { + this.owner = owner; + } + + + + + public long getTagId() { + return tagId; + } + + + + + public void setTagId(long tagId) { + this.tagId = tagId; + } + + + + + public String getTagUuid() { + return tagUuid; + } + + + + + public void setTagUuid(String tagUuid) { + this.tagUuid = tagUuid; + } + + + + + public String getTagKey() { + return tagKey; + } + + + + + public void setTagKey(String tagKey) { + this.tagKey = tagKey; + } + + + + + public String getTagValue() { + return tagValue; + } + + + + + public void setTagValue(String tagValue) { + this.tagValue = tagValue; + } + + + + + public long getTagDomainId() { + return tagDomainId; + } + + + + + public void setTagDomainId(long tagDomainId) { + this.tagDomainId = tagDomainId; + } + + + + + public long getTagAccountId() { + return tagAccountId; + } + + + + + public void setTagAccountId(long tagAccountId) { + this.tagAccountId = tagAccountId; + } + + + + + public long getTagResourceId() { + return tagResourceId; + } + + + + + public void setTagResourceId(long tagResourceId) { + this.tagResourceId = tagResourceId; + } + + + + + public String getTagResourceUuid() { + return tagResourceUuid; + } + + + + + public void setTagResourceUuid(String tagResourceUuid) { + this.tagResourceUuid = tagResourceUuid; + } + + + + + public TaggedResourceType getTagResourceType() { + return tagResourceType; + } + + + + + public void setTagResourceType(TaggedResourceType tagResourceType) { + this.tagResourceType = tagResourceType; + } + + + + + public String getTagCustomer() { + return tagCustomer; + } + + + + + public void setTagCustomer(String tagCustomer) { + this.tagCustomer = tagCustomer; + } + + + + + public long getAccountId() { + return accountId; + } + + + + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + + + +} diff --git a/server/src/com/cloud/api/ApiDBUtils.java b/server/src/com/cloud/api/ApiDBUtils.java index 3cebe462c55..a60f91b3676 100755 --- a/server/src/com/cloud/api/ApiDBUtils.java +++ b/server/src/com/cloud/api/ApiDBUtils.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.InstanceGroupResponse; +import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.UserResponse; @@ -33,6 +34,7 @@ import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.view.vo.DomainRouterJoinVO; import org.apache.cloudstack.api.view.vo.EventJoinVO; import org.apache.cloudstack.api.view.vo.InstanceGroupJoinVO; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; import org.apache.cloudstack.api.view.vo.ResourceTagJoinVO; import org.apache.cloudstack.api.view.vo.SecurityGroupJoinVO; import org.apache.cloudstack.api.view.vo.UserAccountJoinVO; @@ -114,6 +116,7 @@ import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.projects.Project; import com.cloud.projects.ProjectService; +import com.cloud.projects.dao.ProjectJoinDao; import com.cloud.resource.ResourceManager; import com.cloud.server.Criteria; import com.cloud.server.ManagementServer; @@ -261,6 +264,7 @@ public class ApiDBUtils { private static EventJoinDao _eventJoinDao; private static InstanceGroupJoinDao _vmGroupJoinDao; private static UserAccountJoinDao _userAccountJoinDao; + private static ProjectJoinDao _projectJoinDao; static { _ms = (ManagementServer) ComponentLocator.getComponent(ManagementServer.Name); @@ -332,6 +336,7 @@ public class ApiDBUtils { _counterDao = locator.getDao(CounterDao.class); _tagJoinDao = locator.getDao(ResourceTagJoinDao.class); _eventJoinDao = locator.getDao(EventJoinDao.class); + _projectJoinDao = locator.getDao(ProjectJoinDao.class); // Note: stats collector should already have been initialized by this time, otherwise a null instance is returned _statsCollector = StatsCollector.getInstance(); @@ -1013,4 +1018,16 @@ public class ApiDBUtils { public static UserAccountJoinVO newUserView(UserAccount usr){ return _userAccountJoinDao.newUserView(usr); } + + public static ProjectResponse newProjectResponse(ProjectJoinVO proj) { + return _projectJoinDao.newProjectResponse(proj); + } + + public static ProjectResponse fillProjectDetails(ProjectResponse rsp, ProjectJoinVO proj){ + return _projectJoinDao.setProjectResponse(rsp,proj); + } + + public static List newProjectView(Project proj){ + return _projectJoinDao.newProjectView(proj); + } } diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index c8c78d027fe..76fe58d43b5 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -116,6 +116,7 @@ import org.apache.cloudstack.api.view.vo.DomainRouterJoinVO; import org.apache.cloudstack.api.view.vo.ControlledViewEntity; import org.apache.cloudstack.api.view.vo.EventJoinVO; import org.apache.cloudstack.api.view.vo.InstanceGroupJoinVO; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; import org.apache.cloudstack.api.view.vo.ResourceTagJoinVO; import org.apache.cloudstack.api.view.vo.SecurityGroupJoinVO; import org.apache.cloudstack.api.view.vo.UserAccountJoinVO; @@ -2671,31 +2672,33 @@ public class ApiResponseHelper implements ResponseGenerator { @Override public ProjectResponse createProjectResponse(Project project) { - ProjectResponse response = new ProjectResponse(); - response.setId(project.getId()); - response.setName(project.getName()); - response.setDisplaytext(project.getDisplayText()); - response.setState(project.getState().toString()); - - Domain domain = ApiDBUtils.findDomainById(project.getDomainId()); - response.setDomainId(domain.getId()); - response.setDomain(domain.getName()); - - response.setOwner(ApiDBUtils.getProjectOwner(project.getId()).getAccountName()); - - //set tag information - List tags = ApiDBUtils.listByResourceTypeAndId(TaggedResourceType.Project, project.getId()); - List tagResponses = new ArrayList(); - for (ResourceTag tag : tags) { - ResourceTagResponse tagResponse = createResourceTagResponse(tag, true); - tagResponses.add(tagResponse); - } - response.setTags(tagResponses); - - response.setObjectName("project"); - return response; + List viewPrjs = ApiDBUtils.newProjectView(project); + List listPrjs = createProjectResponse(viewPrjs.toArray(new ProjectJoinVO[viewPrjs.size()])); + assert listPrjs != null && listPrjs.size() == 1 : "There should be one project returned"; + return listPrjs.get(0); } + + @Override + public List createProjectResponse(ProjectJoinVO... projects) { + Hashtable prjDataList = new Hashtable(); + // Initialise the prjdatalist with the input data + for (ProjectJoinVO p : projects) { + ProjectResponse pData = prjDataList.get(p.getId()); + if ( pData == null ){ + // first time encountering this vm + pData = ApiDBUtils.newProjectResponse(p); + } + else{ + // update those 1 to many mapping fields + pData = ApiDBUtils.fillProjectDetails(pData, p); + } + prjDataList.put(p.getId(), pData); + } + return new ArrayList(prjDataList.values()); + } + + @Override public FirewallResponse createFirewallResponse(FirewallRule fwRule) { FirewallResponse response = new FirewallResponse(); diff --git a/server/src/com/cloud/configuration/DefaultComponentLibrary.java b/server/src/com/cloud/configuration/DefaultComponentLibrary.java index c1703bc3729..af7a84dcfc6 100755 --- a/server/src/com/cloud/configuration/DefaultComponentLibrary.java +++ b/server/src/com/cloud/configuration/DefaultComponentLibrary.java @@ -139,6 +139,7 @@ import com.cloud.projects.ProjectManagerImpl; import com.cloud.projects.dao.ProjectAccountDaoImpl; import com.cloud.projects.dao.ProjectDaoImpl; import com.cloud.projects.dao.ProjectInvitationDaoImpl; +import com.cloud.projects.dao.ProjectJoinDaoImpl; import com.cloud.resource.ResourceManagerImpl; import com.cloud.resourcelimit.ResourceLimitManagerImpl; import com.cloud.service.dao.ServiceOfferingDaoImpl; @@ -229,6 +230,7 @@ public class DefaultComponentLibrary extends ComponentLibraryBase implements Com addDao("ResourceTagJoinDao", ResourceTagJoinDaoImpl.class); addDao("EventJoinDao", EventJoinDaoImpl.class); addDao("UserAccountJoinDao", UserAccountJoinDaoImpl.class); + addDao("ProjectJoinDao", ProjectJoinDaoImpl.class); ComponentInfo> info = addDao("ServiceOfferingDao", ServiceOfferingDaoImpl.class); info.addParameter("cache.size", "50"); info.addParameter("cache.time.to.live", "600"); diff --git a/server/src/com/cloud/projects/ProjectManagerImpl.java b/server/src/com/cloud/projects/ProjectManagerImpl.java index c922ddb2c12..d9ff0b34277 100755 --- a/server/src/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/com/cloud/projects/ProjectManagerImpl.java @@ -5,7 +5,7 @@ // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, @@ -38,6 +38,8 @@ import javax.mail.URLName; import javax.mail.internet.InternetAddress; import javax.naming.ConfigurationException; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; +import org.apache.cloudstack.api.view.vo.UserVmJoinVO; import org.apache.log4j.Logger; import com.cloud.acl.SecurityChecker.AccessType; @@ -60,6 +62,7 @@ import com.cloud.projects.ProjectAccount.Role; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; import com.cloud.projects.dao.ProjectInvitationDao; +import com.cloud.projects.dao.ProjectJoinDao; import com.cloud.server.ResourceTag.TaggedResourceType; import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; @@ -83,6 +86,7 @@ import com.cloud.utils.db.Filter; import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; @@ -95,17 +99,19 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ public static final Logger s_logger = Logger.getLogger(ProjectManagerImpl.class); private String _name; private EmailInvite _emailInvite; - + @Inject private DomainDao _domainDao; @Inject private ProjectDao _projectDao; @Inject + private ProjectJoinDao _projectJoinDao; + @Inject AccountManager _accountMgr; @Inject DomainManager _domainMgr; @Inject - ConfigurationManager _configMgr; + ConfigurationManager _configMgr; @Inject ResourceLimitService _resourceLimitMgr; @Inject @@ -118,24 +124,24 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ private ProjectInvitationDao _projectInvitationDao; @Inject protected ResourceTagDao _resourceTagDao; - + protected boolean _invitationRequired = false; protected long _invitationTimeOut = 86400000; protected boolean _allowUserToCreateProject = true; protected ScheduledExecutorService _executor; protected int _projectCleanupExpInvInterval = 60; //Interval defining how often project invitation cleanup thread is running - - + + @Override public boolean configure(final String name, final Map params) throws ConfigurationException { _name = name; - + Map configs = _configDao.getConfiguration(params); _invitationRequired = Boolean.valueOf(configs.get(Config.ProjectInviteRequired.key())); _invitationTimeOut = Long.valueOf(configs.get(Config.ProjectInvitationExpirationTime.key()))*1000; _allowUserToCreateProject = Boolean.valueOf(configs.get(Config.AllowUserToCreateProject.key())); - - + + // set up the email system for project invitations String smtpHost = configs.get("project.smtp.host"); @@ -153,13 +159,13 @@ 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); + _executor.scheduleWithFixedDelay(new ExpiredInvitationsCleanup(), _projectCleanupExpInvInterval, _projectCleanupExpInvInterval, TimeUnit.SECONDS); return true; } @@ -172,99 +178,99 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ public String getName() { return _name; } - + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_CREATE, eventDescription = "creating project", create=true) @DB public Project createProject(String name, String displayText, String accountName, Long domainId) throws ResourceAllocationException{ Account caller = UserContext.current().getCaller(); Account owner = caller; - + //check if the user authorized to create the project if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL && !_allowUserToCreateProject) { - throw new PermissionDeniedException("Regular user is not permitted to create a project"); + throw new PermissionDeniedException("Regular user is not permitted to create a project"); } - + //Verify request parameters if ((accountName != null && domainId == null) || (domainId != null && accountName == null)) { throw new InvalidParameterValueException("Account name and domain id must be specified together"); } - + if (accountName != null) { owner = _accountMgr.finalizeOwner(caller, accountName, domainId, null); } - + //don't allow 2 projects with the same name inside the same domain if (_projectDao.findByNameAndDomain(name, owner.getDomainId()) != null) { throw new InvalidParameterValueException("Project with name " + name + " already exists in domain id=" + owner.getDomainId()); } - + //do resource limit check _resourceLimitMgr.checkResourceLimit(owner, ResourceType.project); - + Transaction txn = Transaction.currentTxn(); txn.start(); - + //Create an account associated with the project StringBuilder acctNm = new StringBuilder("PrjAcct-"); acctNm.append(name).append("-").append(owner.getDomainId()); - + Account projectAccount = _accountMgr.createAccount(acctNm.toString(), Account.ACCOUNT_TYPE_PROJECT, domainId, null, null); - + Project project = _projectDao.persist(new ProjectVO(name, displayText, owner.getDomainId(), projectAccount.getId())); - + //assign owner to the project assignAccountToProject(project, owner.getId(), ProjectAccount.Role.Admin); - + if (project != null) { UserContext.current().setEventDetails("Project id=" + project.getId()); } - + //Increment resource count _resourceLimitMgr.incrementResourceCount(owner.getId(), ResourceType.project); - + txn.commit(); - + return project; } - - + + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_CREATE, eventDescription = "creating project", async=true) @DB public Project enableProject(long projectId){ Account caller = UserContext.current().getCaller(); - + ProjectVO project= getProject(projectId); //verify input parameters if (project == null) { throw new InvalidParameterValueException("Unable to find project by id " + projectId); } - + _accountMgr.checkAccess(caller,AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - + //at this point enabling project doesn't require anything, so just update the state project.setState(State.Active); _projectDao.update(projectId, project); - + return project; } - - + + @Override - @ActionEvent(eventType = EventTypes.EVENT_PROJECT_DELETE, eventDescription = "deleting project", async = true) + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_DELETE, eventDescription = "deleting project", async = true) public boolean deleteProject(long projectId) { UserContext ctx = UserContext.current(); - + ProjectVO project= getProject(projectId); //verify input parameters if (project == null) { throw new InvalidParameterValueException("Unable to find project by id " + projectId); } - + _accountMgr.checkAccess(ctx.getCaller(),AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - - return deleteProject(ctx.getCaller(), ctx.getCallerUserId(), project); + + return deleteProject(ctx.getCaller(), ctx.getCallerUserId(), project); } @DB @@ -280,10 +286,10 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ Account projectOwner = getProjectOwner(project.getId()); if (projectOwner != null) { _resourceLimitMgr.decrementResourceCount(projectOwner.getId(), ResourceType.project); - } - + } + txn.commit(); - + if (updateResult) { //pass system caller when clenaup projects account if (!cleanupProject(project, _accountDao.findById(Account.ACCOUNT_ID_SYSTEM), User.UID_SYSTEM)) { @@ -297,31 +303,31 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ return false; } } - + @DB private boolean cleanupProject(Project project, AccountVO caller, Long callerUserId) { - boolean result=true; + boolean result=true; //Delete project's account AccountVO account = _accountDao.findById(project.getProjectAccountId()); s_logger.debug("Deleting projects " + project + " internal account id=" + account.getId() + " as a part of project cleanup..."); - + result = result && _accountMgr.deleteAccount(account, callerUserId, caller); - + if (result) { //Unassign all users from the project - + Transaction txn = Transaction.currentTxn(); txn.start(); - + s_logger.debug("Unassigning all accounts from project " + project + " as a part of project cleanup..."); List projectAccounts = _projectAccountDao.listByProjectId(project.getId()); for (ProjectAccount projectAccount : projectAccounts) { result = result && unassignAccountFromProject(projectAccount.getProjectId(), projectAccount.getAccountId()); } - + s_logger.debug("Removing all invitations for the project " + project + " as a part of project cleanup..."); _projectInvitationDao.cleanupInvitations(project.getId()); - + txn.commit(); if (result) { s_logger.debug("Accounts are unassign successfully from project " + project + " as a part of project cleanup..."); @@ -329,10 +335,10 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } else { s_logger.warn("Failed to cleanup project's internal account"); } - + return result; } - + @Override public boolean unassignAccountFromProject(long projectId, long accountId) { ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, accountId); @@ -340,7 +346,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ s_logger.debug("Account id=" + accountId + " is not assigned to project id=" + projectId + " so no need to unassign"); return true; } - + if ( _projectAccountDao.remove(projectAccount.getId())) { return true; } else { @@ -348,23 +354,24 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ return false; } } - + @Override public ProjectVO getProject (long projectId) { return _projectDao.findById(projectId); } - + @Override - public Pair, Integer> listProjects(Long id, String name, String displayText, String state, - String accountName, Long domainId, String keyword, Long startIndex, Long pageSize, boolean listAll, + public Pair, Integer> listProjects(Long id, String name, String displayText, String state, + String accountName, Long domainId, String keyword, Long startIndex, Long pageSize, boolean listAll, boolean isRecursive, Map tags) { Account caller = UserContext.current().getCaller(); Long accountId = null; String path = null; - - Filter searchFilter = new Filter(ProjectVO.class, "id", false, startIndex, pageSize); - SearchBuilder sb = _projectDao.createSearchBuilder(); - + + Filter searchFilter = new Filter(ProjectJoinVO.class, "id", false, startIndex, pageSize); + SearchBuilder sb = _projectJoinDao.createSearchBuilder(); + sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct ids + if (_accountMgr.isAdmin(caller.getType())) { if (domainId != null) { DomainVO domain = _domainDao.findById(domainId); @@ -383,120 +390,125 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } } else { //domainId == null - if (accountName != null) { - throw new InvalidParameterValueException("could not find account " + accountName + " because domain is not specified"); - } - + if (accountName != null) { + throw new InvalidParameterValueException("could not find account " + accountName + " because domain is not specified"); + } + } } else { if (accountName != null && !accountName.equals(caller.getAccountName())) { throw new PermissionDeniedException("Can't list account " + accountName + " projects; unauthorized"); } - + if (domainId != null && domainId.equals(caller.getDomainId())) { throw new PermissionDeniedException("Can't list domain id= " + domainId + " projects; unauthorized"); } - + accountId = caller.getId(); } - - if (domainId == null && accountId == null && (caller.getType() == Account.ACCOUNT_TYPE_NORMAL || !listAll)) { - accountId = caller.getId(); - } else if (caller.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN || (isRecursive && !listAll)) { + + if (domainId == null && accountId == null && (caller.getType() == Account.ACCOUNT_TYPE_NORMAL || !listAll)) { + accountId = caller.getId(); + } else if (caller.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN || (isRecursive && !listAll)) { DomainVO domain = _domainDao.findById(caller.getDomainId()); path = domain.getPath(); } - + if (path != null) { - SearchBuilder domainSearch = _domainDao.createSearchBuilder(); - domainSearch.and("path", domainSearch.entity().getPath(), SearchCriteria.Op.LIKE); - sb.join("domainSearch", domainSearch, sb.entity().getDomainId(), domainSearch.entity().getId(), JoinBuilder.JoinType.INNER); + sb.and("domainPath", sb.entity().getDomainPath(), SearchCriteria.Op.LIKE); } - + if (accountId != null) { - SearchBuilder projectAccountSearch = _projectAccountDao.createSearchBuilder(); - projectAccountSearch.and("accountId", projectAccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ); - sb.join("projectAccountSearch", projectAccountSearch, sb.entity().getId(), projectAccountSearch.entity().getProjectId(), JoinBuilder.JoinType.INNER); + sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ); } - + if (tags != null && !tags.isEmpty()) { - SearchBuilder tagSearch = _resourceTagDao.createSearchBuilder(); - for (int count=0; count < tags.size(); count++) { - tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ); - tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ); - tagSearch.cp(); - } - tagSearch.and("resourceType", tagSearch.entity().getResourceType(), SearchCriteria.Op.EQ); - sb.groupBy(sb.entity().getId()); - sb.join("tagSearch", tagSearch, sb.entity().getId(), tagSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER); + for (int count=0; count < tags.size(); count++) { + sb.or().op("key" + String.valueOf(count), sb.entity().getTagKey(), SearchCriteria.Op.EQ); + sb.and("value" + String.valueOf(count), sb.entity().getTagValue(), SearchCriteria.Op.EQ); + sb.cp(); + } } - - SearchCriteria sc = sb.create(); - + + + SearchCriteria sc = sb.create(); + if (id != null) { sc.addAnd("id", Op.EQ, id); } - + if (domainId != null && !isRecursive) { sc.addAnd("domainId", Op.EQ, domainId); } - + if (name != null) { sc.addAnd("name", Op.EQ, name); } - + if (displayText != null) { sc.addAnd("displayText", Op.EQ, displayText); } - + if (accountId != null) { - sc.setJoinParameters("projectAccountSearch", "accountId", accountId); + sc.setParameters("accountId", accountId); } - + if (state != null) { sc.addAnd("state", Op.EQ, state); } - + if (keyword != null) { - SearchCriteria ssc = _projectDao.createSearchCriteria(); + SearchCriteria ssc = _projectJoinDao.createSearchCriteria(); ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); ssc.addOr("displayText", SearchCriteria.Op.LIKE, "%" + keyword + "%"); sc.addAnd("name", SearchCriteria.Op.SC, ssc); } - + if (path != null) { - sc.setJoinParameters("domainSearch", "path", path); + sc.setParameters("domainPath", path); } - + if (tags != null && !tags.isEmpty()) { int count = 0; - sc.setJoinParameters("tagSearch", "resourceType", TaggedResourceType.Project.toString()); - for (String key : tags.keySet()) { - sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key); - sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key)); + for (String key : tags.keySet()) { + sc.setParameters("key" + String.valueOf(count), key); + sc.setParameters("value" + String.valueOf(count), tags.get(key)); count++; } } - Pair, Integer> result = _projectDao.searchAndCount(sc, searchFilter); - return new Pair, Integer>(result.first(), result.second()); + // search distinct projects to get count + Pair, Integer> uniquePrjPair = _projectJoinDao.searchAndCount(sc, searchFilter); + Integer count = uniquePrjPair.second(); + if ( count.intValue() == 0 ){ + // handle empty result cases + return uniquePrjPair; + } + List uniquePrjs = uniquePrjPair.first(); + Long[] prjIds = new Long[uniquePrjs.size()]; + int i = 0; + for (ProjectJoinVO v : uniquePrjs ){ + prjIds[i++] = v.getId(); + } + List prjs = _projectJoinDao.searchByIds(prjIds); + return new Pair, Integer>(prjs, count); } - + @Override public ProjectAccount assignAccountToProject(Project project, long accountId, ProjectAccount.Role accountRole) { return _projectAccountDao.persist(new ProjectAccountVO(project, accountId, accountRole)); } - + @Override @DB public boolean deleteAccountFromProject(long projectId, long accountId) { boolean success = true; Transaction txn = Transaction.currentTxn(); txn.start(); - + //remove account ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, accountId); success = _projectAccountDao.remove(projectAccount.getId()); - + //remove all invitations for account if (success) { s_logger.debug("Removed account " + accountId + " from project " + projectId + " , cleaning up old invitations for account/project..."); @@ -505,36 +517,36 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ success = success && _projectInvitationDao.remove(invite.getId()); } } - + txn.commit(); return success; } - + @Override public Account getProjectOwner(long projectId) { ProjectAccount prAcct = _projectAccountDao.getProjectOwner(projectId); if (prAcct != null) { return _accountMgr.getAccount(prAcct.getAccountId()); } - + return null; } - + @Override public ProjectVO findByProjectAccountId(long projectAccountId) { return _projectDao.findByProjectAccountId(projectAccountId); } - + @Override public ProjectVO findByProjectAccountIdIncludingRemoved(long projectAccountId) { return _projectDao.findByProjectAccountIdIncludingRemoved(projectAccountId); } - + @Override public Project findByNameAndDomainId(String name, long domainId) { return _projectDao.findByNameAndDomain(name, domainId); } - + @Override public boolean canAccessProjectAccount(Account caller, long accountId) { //ROOT admin always can access the project @@ -545,10 +557,10 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ _accountMgr.checkAccess(caller, _domainDao.findById(owner.getDomainId())); return true; } - + return _projectAccountDao.canAccessProjectAccount(caller.getId(), accountId); } - + public boolean canModifyProjectAccount(Account caller, long accountId) { //ROOT admin always can access the project if (caller.getType() == Account.ACCOUNT_TYPE_ADMIN) { @@ -560,29 +572,29 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } return _projectAccountDao.canModifyProjectAccount(caller.getId(), accountId); } - + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_PROJECT_UPDATE, eventDescription = "updating project", async=true) public Project updateProject(long projectId, String displayText, String newOwnerName) throws ResourceAllocationException{ Account caller = UserContext.current().getCaller(); - + //check that the project exists ProjectVO project = getProject(projectId); - + if (project == null) { throw new InvalidParameterValueException("Unable to find the project id=" + projectId); } - + //verify permissions _accountMgr.checkAccess(caller,AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - + Transaction txn = Transaction.currentTxn(); txn.start(); if (displayText != null) { project.setDisplayText(displayText); _projectDao.update(projectId, project); } - + if (newOwnerName != null) { //check that the new owner exists Account futureOwnerAccount = _accountMgr.getActiveAccountByName(newOwnerName, project.getDomainId()); @@ -595,68 +607,68 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ if (futureOwner == null) { throw new InvalidParameterValueException("Account " + newOwnerName + " doesn't belong to the project. Add it to the project first and then change the project's ownership"); } - + //do resource limit check _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(futureOwnerAccount.getId()), ResourceType.project); - + //unset the role for the old owner ProjectAccountVO currentOwner = _projectAccountDao.findByProjectIdAccountId(projectId, currentOwnerAccount.getId()); currentOwner.setAccountRole(Role.Regular); _projectAccountDao.update(currentOwner.getId(), currentOwner); _resourceLimitMgr.decrementResourceCount(currentOwnerAccount.getId(), ResourceType.project); - + //set new owner futureOwner.setAccountRole(Role.Admin); _projectAccountDao.update(futureOwner.getId(), futureOwner); _resourceLimitMgr.incrementResourceCount(futureOwnerAccount.getId(), ResourceType.project); - + } else { s_logger.trace("Future owner " + newOwnerName + "is already the owner of the project id=" + projectId); } } - + txn.commit(); - + return _projectDao.findById(projectId); - + } - + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACCOUNT_ADD, eventDescription = "adding account to project", async=true) public boolean addAccountToProject(long projectId, String accountName, String email) { Account caller = UserContext.current().getCaller(); - + //check that the project exists Project project = getProject(projectId); - + if (project == null) { - InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); - ex.addProxyObject(project, projectId, "projectId"); + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } - + //User can be added to Active project only if (project.getState() != Project.State.Active) { - InvalidParameterValueException ex = new InvalidParameterValueException("Can't add account to the specified project id in state=" + project.getState() + " as it's no longer active"); - ex.addProxyObject(project, projectId, "projectId"); + InvalidParameterValueException ex = new InvalidParameterValueException("Can't add account to the specified project id in state=" + project.getState() + " as it's no longer active"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } - + //check that account-to-add exists Account account = null; if (accountName != null) { account = _accountMgr.getActiveAccountByName(accountName, project.getDomainId()); if (account == null) { - InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find account name=" + accountName + " in specified domain id"); - // We don't have a DomainVO object with us, so just pass the tablename "domain" manually. + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find account name=" + accountName + " in specified domain id"); + // We don't have a DomainVO object with us, so just pass the tablename "domain" manually. ex.addProxyObject("domain", project.getDomainId(), "domainId"); throw ex; } - + //verify permissions - only project owner can assign _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - + //Check if the account already added to the project ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, account.getId()); if (projectAccount != null) { @@ -664,7 +676,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ return true; } } - + if (_invitationRequired) { return inviteAccountToProject(project, account, email); } else { @@ -679,7 +691,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } } } - + private boolean inviteAccountToProject(Project project, Account account, String email) { if (account != null) { if (createAccountInvitation(project, account.getId()) != null) { @@ -687,9 +699,9 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } else { s_logger.warn("Failed to generate invitation for account " + account.getAccountName() + " to project id=" + project); return false; - } + } } - + if (email != null) { //generate the token String token = generateToken(10); @@ -698,93 +710,93 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } else { s_logger.warn("Failed to generate invitation for email " + email + " to project id=" + project); return false; - } + } } - + return false; } - + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACCOUNT_REMOVE, eventDescription = "removing account from project", async=true) public boolean deleteAccountFromProject(long projectId, String accountName) { Account caller = UserContext.current().getCaller(); - + //check that the project exists Project project = getProject(projectId); - + if (project == null) { - InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); - ex.addProxyObject(project, projectId, "projectId"); + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } - + //check that account-to-remove exists Account account = _accountMgr.getActiveAccountByName(accountName, project.getDomainId()); if (account == null) { - InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find account name=" + accountName + " in domain id=" + project.getDomainId()); + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find account name=" + accountName + " in domain id=" + project.getDomainId()); // Since we don't have a domainVO object, pass the table name manually. - ex.addProxyObject("domain", project.getDomainId(), "domainId"); + ex.addProxyObject("domain", project.getDomainId(), "domainId"); } - + //verify permissions _accountMgr.checkAccess(caller,AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - + //Check if the account exists in the project ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, account.getId()); if (projectAccount == null) { InvalidParameterValueException ex = new InvalidParameterValueException("Account " + accountName + " is not assigned to the project with specified id"); // Use the projectVO object and not the projectAccount object to inject the projectId. - ex.addProxyObject(project, projectId, "projectId"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } - + //can't remove the owner of the project if (projectAccount.getAccountRole() == Role.Admin) { - InvalidParameterValueException ex = new InvalidParameterValueException("Unable to delete account " + accountName + " from the project with specified id as the account is the owner of the project"); - ex.addProxyObject(project, projectId, "projectId"); + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to delete account " + accountName + " from the project with specified id as the account is the owner of the project"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } - + return deleteAccountFromProject(projectId, account.getId()); } - - + + @Override public Pair, Integer> listProjectAccounts(long projectId, String accountName, String role, Long startIndex, Long pageSizeVal) { Account caller = UserContext.current().getCaller(); - + //check that the project exists Project project = getProject(projectId); - + if (project == null) { throw new InvalidParameterValueException("Unable to find the project id=" + projectId); } - + //verify permissions - only accounts belonging to the project can list project's account if (!_accountMgr.isAdmin(caller.getType()) && _projectAccountDao.findByProjectIdAccountId(projectId, caller.getAccountId()) == null) { throw new PermissionDeniedException("Account " + caller + " is not authorized to list users of the project id=" + projectId); } - + Filter searchFilter = new Filter(ProjectAccountVO.class, "id", false, startIndex, pageSizeVal); SearchBuilder sb = _projectAccountDao.createSearchBuilder(); sb.and("accountRole", sb.entity().getAccountRole(), Op.EQ); sb.and("projectId", sb.entity().getProjectId(), Op.EQ); - + SearchBuilder accountSearch; if (accountName != null) { accountSearch = _accountDao.createSearchBuilder(); accountSearch.and("accountName", accountSearch.entity().getAccountName(), SearchCriteria.Op.EQ); sb.join("accountSearch", accountSearch, sb.entity().getAccountId(), accountSearch.entity().getId(), JoinBuilder.JoinType.INNER); } - + SearchCriteria sc = sb.create(); - + sc.setParameters("projectId", projectId); - + if (role != null) { sc.setParameters("accountRole", role); } - + if (accountName != null) { sc.setJoinParameters("accountSearch", "accountName", accountName); } @@ -792,14 +804,14 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ Pair, Integer> result = _projectAccountDao.searchAndCount(sc, searchFilter); return new Pair, Integer>(result.first(), result.second()); } - - public ProjectInvitation createAccountInvitation(Project project, Long accountId) { + + 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; } @@ -811,14 +823,14 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ ProjectInvitationVO invite = null; if (accountId != null) { invite = _projectInvitationDao.findByAccountIdProjectId(accountId, project.getId()); - } else if (email != null) { - invite = _projectInvitationDao.findByEmailAndProjectId(email, project.getId()); - } - + } else if (email != null) { + invite = _projectInvitationDao.findByEmailAndProjectId(email, project.getId()); + } + if (invite != null) { - if (invite.getState() == ProjectInvitation.State.Completed || + if (invite.getState() == ProjectInvitation.State.Completed || (invite.getState() == ProjectInvitation.State.Pending && _projectInvitationDao.isActive(invite.getId(), _invitationTimeOut))) { - return true; + return true; } else { if (invite.getState() == ProjectInvitation.State.Pending) { expireInvitation(invite); @@ -836,13 +848,13 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ txn.commit(); return false; } - + public ProjectInvitation generateTokenBasedInvitation(Project project, String email, String token) { //verify if the invitation was already generated - if (activeInviteExists(project, null, email)) { + 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 { _emailInvite.sendInvite(token, email, project.getId()); @@ -851,28 +863,28 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ _projectInvitationDao.remove(projectInvitation.getId()); return null; } - + return projectInvitation; } - + private boolean expireInvitation(ProjectInvitationVO invite) { s_logger.debug("Expiring invitation id=" + invite.getId()); invite.setState(ProjectInvitation.State.Expired); return _projectInvitationDao.update(invite.getId(), invite); } - + @Override public Pair, Integer> listProjectInvitations(Long id, Long projectId, String accountName, Long domainId, String state, boolean activeOnly, Long startIndex, Long pageSizeVal, boolean isRecursive, boolean listAll) { Account caller = UserContext.current().getCaller(); List permittedAccounts = new ArrayList(); - + Ternary domainIdRecursiveListProject = new Ternary(domainId, isRecursive, null); _accountMgr.buildACLSearchParameters(caller, id, accountName, projectId, permittedAccounts, domainIdRecursiveListProject, listAll, true); domainId = domainIdRecursiveListProject.first(); isRecursive = domainIdRecursiveListProject.second(); - ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); - + ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); + Filter searchFilter = new Filter(ProjectInvitationVO.class, "id", true, startIndex, pageSizeVal); SearchBuilder sb = _projectInvitationDao.createSearchBuilder(); _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); @@ -885,19 +897,19 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ SearchCriteria sc = sb.create(); _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); - + if (projectId != null){ sc.setParameters("projectId", projectId); } - + if (state != null) { sc.setParameters("state", state); } - + if (id != null) { sc.setParameters("id", id); } - + if (activeOnly) { sc.setParameters("state", ProjectInvitation.State.Pending); sc.setParameters("created", new Date((DateUtil.currentGMTTime().getTime()) - _invitationTimeOut)); @@ -906,41 +918,41 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ Pair, Integer> result = _projectInvitationDao.searchAndCount(sc, searchFilter); return new Pair, Integer>(result.first(), result.second()); } - + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_PROJECT_INVITATION_UPDATE, eventDescription = "updating project invitation", async=true) public boolean updateInvitation(long projectId, String accountName, String token, boolean accept) { Account caller = UserContext.current().getCaller(); Long accountId = null; boolean result = true; - + //if accountname and token are null, default accountname to caller's account name if (accountName == null && token == null) { accountName = caller.getAccountName(); } - + //check that the project exists Project project = getProject(projectId); - + if (project == null) { throw new InvalidParameterValueException("Unable to find the project id=" + projectId); } - + if (accountName != null) { //check that account-to-remove exists Account account = _accountMgr.getActiveAccountByName(accountName, project.getDomainId()); if (account == null) { throw new InvalidParameterValueException("Unable to find account name=" + accountName + " in domain id=" + project.getDomainId()); } - + //verify permissions _accountMgr.checkAccess(caller, null, true, account); - + accountId = account.getId(); } else { accountId = caller.getId(); } - + //check that invitation exists ProjectInvitationVO invite = null; if (token == null) { @@ -948,7 +960,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } else { invite = _projectInvitationDao.findPendingByTokenAndProjectId(token, projectId, ProjectInvitation.State.Pending); } - + if (invite != null) { if (!_projectInvitationDao.isActive(invite.getId(), _invitationTimeOut) && accept) { expireInvitation(invite); @@ -956,116 +968,116 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } else { Transaction txn = Transaction.currentTxn(); txn.start(); - + ProjectInvitation.State newState = accept ? ProjectInvitation.State.Completed : ProjectInvitation.State.Declined; - + //update invitation s_logger.debug("Marking invitation " + invite + " with state " + newState); invite.setState(newState); result = _projectInvitationDao.update(invite.getId(), invite); - + if (result && accept) { //check if account already exists for the project (was added before invitation got accepted) ProjectAccount projectAccount = _projectAccountDao.findByProjectIdAccountId(projectId, accountId); if (projectAccount != null) { s_logger.debug("Account " + accountName + " already added to the project id=" + projectId); } else { - assignAccountToProject(project, accountId, ProjectAccount.Role.Regular); + assignAccountToProject(project, accountId, ProjectAccount.Role.Regular); } } else { s_logger.warn("Failed to update project invitation " + invite + " with state " + newState); } - + txn.commit(); } } else { throw new InvalidParameterValueException("Unable to find invitation for account name=" + accountName + " to the project id=" + projectId); } - + return result; } - + @Override public List listPermittedProjectAccounts(long accountId) { return _projectAccountDao.listPermittedAccountIds(accountId); } - + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACTIVATE, eventDescription = "activating project") @DB public Project activateProject(long projectId) { Account caller = UserContext.current().getCaller(); - + //check that the project exists ProjectVO project = getProject(projectId); - + if (project == null) { - InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); - ex.addProxyObject(project, projectId, "projectId"); + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } - + //verify permissions _accountMgr.checkAccess(caller,AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - + //allow project activation only when it's in Suspended state Project.State currentState = project.getState(); - + if (currentState == State.Active) { s_logger.debug("The project id=" + projectId + " is already active, no need to activate it again"); return project; - } - + } + if (currentState != State.Suspended) { throw new InvalidParameterValueException("Can't activate the project in " + currentState + " state"); } - + Transaction txn = Transaction.currentTxn(); txn.start(); - + project.setState(Project.State.Active); _projectDao.update(projectId, project); - + _accountMgr.enableAccount(project.getProjectAccountId()); - + txn.commit(); - + return _projectDao.findById(projectId); } - - + + @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_SUSPEND, eventDescription = "suspending project", async = true) public Project suspendProject (long projectId) throws ConcurrentOperationException, ResourceUnavailableException { Account caller = UserContext.current().getCaller(); - + ProjectVO project= getProject(projectId); //verify input parameters if (project == null) { InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id"); - ex.addProxyObject(project, projectId, "projectId"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } - + _accountMgr.checkAccess(caller,AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - + if (suspendProject(project)) { s_logger.debug("Successfully suspended project id=" + projectId); return _projectDao.findById(projectId); } else { CloudRuntimeException ex = new CloudRuntimeException("Failed to suspend project with specified id"); - ex.addProxyObject(project, projectId, "projectId"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } - + } - + private boolean suspendProject(ProjectVO project) throws ConcurrentOperationException, ResourceUnavailableException { - + s_logger.debug("Marking project " + project + " with state " + State.Suspended + " as a part of project suspend..."); project.setState(State.Suspended); boolean updateResult = _projectDao.update(project.getId(), project); - + if (updateResult) { long projectAccountId = project.getProjectAccountId(); if (!_accountMgr.disableAccount(projectAccountId)) { @@ -1076,8 +1088,8 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } return true; } - - + + public static String generateToken(int length) { String charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random rand = new Random(System.currentTimeMillis()); @@ -1088,7 +1100,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } return sb.toString(); } - + class EmailInvite { private Session _smtpSession; private final String _smtpHost; @@ -1138,7 +1150,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } } - public void sendInvite(String token, String email, long projectId) throws MessagingException, UnsupportedEncodingException { + public void sendInvite(String token, String email, long projectId) throws MessagingException, UnsupportedEncodingException { if (_smtpSession != null) { InternetAddress address = null; if (email != null) { @@ -1148,9 +1160,9 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ s_logger.error("Exception creating address for: " + email, ex); } } - + String content = "You've been invited to join the CloudStack project id=" + projectId + ". Please use token " + token + " to complete registration"; - + SMTPMessage msg = new SMTPMessage(_smtpSession); msg.setSender(new InternetAddress(_emailSender, _emailSender)); msg.setFrom(new InternetAddress(_emailSender, _emailSender)); @@ -1174,36 +1186,36 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ } } } - - + + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_PROJECT_INVITATION_REMOVE, eventDescription = "removing project invitation", async=true) public boolean deleteProjectInvitation(long id) { Account caller = UserContext.current().getCaller(); - + ProjectInvitation invitation = _projectInvitationDao.findById(id); if (invitation == null) { throw new InvalidParameterValueException("Unable to find project invitation by id " + id); } - + //check that the project exists Project project = getProject(invitation.getProjectId()); - + //check permissions - only project owner can remove the invitations _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - + if (_projectInvitationDao.remove(id)) { s_logger.debug("Project Invitation id=" + id + " is removed"); return true; } else { s_logger.debug("Failed to remove project invitation id=" + id); - return false; + return false; } } - + public class ExpiredInvitationsCleanup implements Runnable { - @Override - public void run() { + @Override + public void run() { try { TimeZone.getDefault(); List invitationsToExpire = _projectInvitationDao.listInvitationsToExpire(_invitationTimeOut); @@ -1228,7 +1240,7 @@ public class ProjectManagerImpl implements ProjectManager, Manager{ @Override public boolean allowUserToCreateProject() { - return _allowUserToCreateProject; + return _allowUserToCreateProject; } - + } diff --git a/server/src/com/cloud/projects/dao/ProjectJoinDao.java b/server/src/com/cloud/projects/dao/ProjectJoinDao.java new file mode 100644 index 00000000000..c5cc4227eee --- /dev/null +++ b/server/src/com/cloud/projects/dao/ProjectJoinDao.java @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.projects.dao; + +import java.util.List; + +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; +import com.cloud.projects.Project; +import com.cloud.utils.db.GenericDao; + +public interface ProjectJoinDao extends GenericDao { + + ProjectResponse newProjectResponse(ProjectJoinVO proj); + + ProjectResponse setProjectResponse(ProjectResponse rsp, ProjectJoinVO proj); + + List newProjectView(Project proj); + + List searchByIds(Long... ids); + +} diff --git a/server/src/com/cloud/projects/dao/ProjectJoinDaoImpl.java b/server/src/com/cloud/projects/dao/ProjectJoinDaoImpl.java new file mode 100644 index 00000000000..dc6ba866d06 --- /dev/null +++ b/server/src/com/cloud/projects/dao/ProjectJoinDaoImpl.java @@ -0,0 +1,130 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.projects.dao; + +import java.util.List; + +import javax.ejb.Local; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiDBUtils; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; +import org.apache.cloudstack.api.view.vo.ResourceTagJoinVO; +import com.cloud.projects.Project; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + + +@Local(value={ProjectJoinDao.class}) +public class ProjectJoinDaoImpl extends GenericDaoBase implements ProjectJoinDao { + public static final Logger s_logger = Logger.getLogger(ProjectJoinDaoImpl.class); + + private SearchBuilder vrSearch; + + private SearchBuilder vrIdSearch; + + + protected ProjectJoinDaoImpl() { + + vrSearch = createSearchBuilder(); + vrSearch.and("idIN", vrSearch.entity().getId(), SearchCriteria.Op.IN); + vrSearch.done(); + + vrIdSearch = createSearchBuilder(); + vrIdSearch.and("id", vrIdSearch.entity().getId(), SearchCriteria.Op.EQ); + vrIdSearch.done(); + + this._count = "select count(distinct id) from project_view WHERE "; + } + + + + + + + @Override + public ProjectResponse newProjectResponse(ProjectJoinVO proj) { + ProjectResponse response = new ProjectResponse(); + response.setId(proj.getUuid()); + response.setName(proj.getName()); + response.setDisplaytext(proj.getDisplayText()); + response.setState(proj.getState().toString()); + + response.setDomainId(proj.getDomainUuid()); + response.setDomain(proj.getDomainName()); + + response.setOwner(proj.getOwner()); + + // update tag information + Long tag_id = proj.getTagId(); + if (tag_id != null && tag_id.longValue() > 0) { + ResourceTagJoinVO vtag = ApiDBUtils.findResourceTagViewById(tag_id); + if ( vtag != null ){ + response.addTag(ApiDBUtils.newResourceTagResponse(vtag, false)); + } + } + + response.setObjectName("project"); + return response; + } + + + + @Override + public ProjectResponse setProjectResponse(ProjectResponse rsp, ProjectJoinVO proj) { + // update tag information + Long tag_id = proj.getTagId(); + if (tag_id != null && tag_id.longValue() > 0) { + ResourceTagJoinVO vtag = ApiDBUtils.findResourceTagViewById(tag_id); + if ( vtag != null ){ + rsp.addTag(ApiDBUtils.newResourceTagResponse(vtag, false)); + } + } + return rsp; + } + + + + + + + @Override + public List newProjectView(Project proj) { + SearchCriteria sc = vrIdSearch.create(); + sc.setParameters("id", proj.getId()); + return searchIncludingRemoved(sc, null, null, false); + } + + + + + + + @Override + public List searchByIds(Long... ids) { + SearchCriteria sc = vrSearch.create(); + sc.setParameters("idIN", ids); + return searchIncludingRemoved(sc, null, null, false); + } + + + + +} diff --git a/server/test/com/cloud/projects/MockProjectManagerImpl.java b/server/test/com/cloud/projects/MockProjectManagerImpl.java index d8bce2fcf81..dad777a8b77 100644 --- a/server/test/com/cloud/projects/MockProjectManagerImpl.java +++ b/server/test/com/cloud/projects/MockProjectManagerImpl.java @@ -22,6 +22,8 @@ import java.util.Map; import javax.ejb.Local; import javax.naming.ConfigurationException; +import org.apache.cloudstack.api.view.vo.ProjectJoinVO; + import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; @@ -225,12 +227,12 @@ public class MockProjectManagerImpl implements ProjectManager, Manager { * @see com.cloud.projects.ProjectService#listProjects(java.lang.Long, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Long, java.lang.String, java.lang.Long, java.lang.Long, boolean, boolean, java.util.Map) */ @Override - public Pair, Integer> listProjects(Long id, String name, String displayText, String state, String accountName, Long domainId, String keyword, Long startIndex, Long pageSize, boolean listAll, + public Pair, Integer> listProjects(Long id, String name, String displayText, String state, String accountName, Long domainId, String keyword, Long startIndex, Long pageSize, boolean listAll, boolean isRecursive, Map tags) { // TODO Auto-generated method stub return null; } - + @Override public Project findByProjectAccountIdIncludingRemoved(long projectAccountId) { return null; diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index 722b31f440f..6699d766193 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -2890,3 +2890,36 @@ domain.path domain_path from user inner join account on user.account_id = account.id inner join domain on account.domain_id=domain.id; + +DROP VIEW IF EXISTS `cloud`.`project_view`; +CREATE VIEW project_view AS +select +projects.id, +projects.uuid, +projects.name, +projects.display_text, +projects.state, +projects.removed, +projects.created, +account.account_name owner, +pacct.account_id, +domain.id domain_id, +domain.uuid domain_uuid, +domain.name domain_name, +domain.path domain_path, +resource_tags.id tag_id, +resource_tags.uuid tag_uuid, +resource_tags.key tag_key, +resource_tags.value tag_value, +resource_tags.domain_id tag_domain_id, +resource_tags.account_id tag_account_id, +resource_tags.resource_id tag_resource_id, +resource_tags.resource_uuid tag_resource_uuid, +resource_tags.resource_type tag_resource_type, +resource_tags.customer tag_customer +from projects +inner join domain on projects.domain_id=domain.id +inner join project_account on projects.id = project_account.project_id and project_account.account_role = "Admin" +inner join account on account.id = project_account.account_id +left join resource_tags on resource_tags.resource_id = projects.id and resource_tags.resource_type = "Project" +left join project_account pacct on projects.id = pacct.project_id;