Refactor ListProjectsCmd and ProjectResponse.

This commit is contained in:
Min Chen 2012-12-11 12:29:00 -08:00 committed by Rohit Yadav
parent c83323fea7
commit 8735716ba0
13 changed files with 942 additions and 304 deletions

View File

@ -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<List<? extends Project>, Integer> listProjects(Long id, String name, String displayText, String state, String accountName,
Pair<List<ProjectJoinVO>, 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<String, String> tags);
ProjectAccount assignAccountToProject(Project project, long accountId, Role accountRole);

View File

@ -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<ProjectResponse> createProjectResponse(ProjectJoinVO... projects);
List<TemplateResponse> createIsoResponses(VirtualMachineTemplate iso, long zoneId, boolean readyOnly);
List<TemplateResponse> createTemplateResponses(long templateId, Long vmId);

View File

@ -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<List<? extends Project>, Integer> projects = _projectService.listProjects(id, name, displayText, state,
Pair<List<ProjectJoinVO>, Integer> projects = _projectService.listProjects(id, name, displayText, state,
this.getAccountName(), this.getDomainId(), this.getKeyword(), this.getStartIndex(), this.getPageSizeVal(),
this.listAll(), this.isRecursive(), getTags());
ListResponse<ProjectResponse> response = new ListResponse<ProjectResponse>();
List<ProjectResponse> projectResponses = new ArrayList<ProjectResponse>();
for (Project project : projects.first()) {
ProjectResponse projectResponse = _responseGenerator.createProjectResponse(project);
projectResponses.add(projectResponse);
}
List<ProjectResponse> projectResponses = _responseGenerator.createProjectResponse(projects.first().toArray(new ProjectJoinVO[projects.first().size()]));
response.setResponses(projectResponses, projects.second());
response.setResponseName(getCommandName());

View File

@ -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<ResourceTagResponse> tags;
private List<ResourceTagResponse> tags = new ArrayList<ResourceTagResponse>();
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<ResourceTagResponse> tags) {
this.tags = tags;
}
public void addTag(ResourceTagResponse tag){
this.tags.add(tag);
}
}

View File

@ -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;
}
}

View File

@ -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<ProjectJoinVO> newProjectView(Project proj){
return _projectJoinDao.newProjectView(proj);
}
}

View File

@ -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<? extends ResourceTag> tags = ApiDBUtils.listByResourceTypeAndId(TaggedResourceType.Project, project.getId());
List<ResourceTagResponse> tagResponses = new ArrayList<ResourceTagResponse>();
for (ResourceTag tag : tags) {
ResourceTagResponse tagResponse = createResourceTagResponse(tag, true);
tagResponses.add(tagResponse);
}
response.setTags(tagResponses);
response.setObjectName("project");
return response;
List<ProjectJoinVO> viewPrjs = ApiDBUtils.newProjectView(project);
List<ProjectResponse> 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<ProjectResponse> createProjectResponse(ProjectJoinVO... projects) {
Hashtable<Long, ProjectResponse> prjDataList = new Hashtable<Long, ProjectResponse>();
// 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<ProjectResponse>(prjDataList.values());
}
@Override
public FirewallResponse createFirewallResponse(FirewallRule fwRule) {
FirewallResponse response = new FirewallResponse();

View File

@ -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<? extends GenericDao<?, ? extends Serializable>> info = addDao("ServiceOfferingDao", ServiceOfferingDaoImpl.class);
info.addParameter("cache.size", "50");
info.addParameter("cache.time.to.live", "600");

File diff suppressed because it is too large Load Diff

View File

@ -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<ProjectJoinVO, Long> {
ProjectResponse newProjectResponse(ProjectJoinVO proj);
ProjectResponse setProjectResponse(ProjectResponse rsp, ProjectJoinVO proj);
List<ProjectJoinVO> newProjectView(Project proj);
List<ProjectJoinVO> searchByIds(Long... ids);
}

View File

@ -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<ProjectJoinVO, Long> implements ProjectJoinDao {
public static final Logger s_logger = Logger.getLogger(ProjectJoinDaoImpl.class);
private SearchBuilder<ProjectJoinVO> vrSearch;
private SearchBuilder<ProjectJoinVO> 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<ProjectJoinVO> newProjectView(Project proj) {
SearchCriteria<ProjectJoinVO> sc = vrIdSearch.create();
sc.setParameters("id", proj.getId());
return searchIncludingRemoved(sc, null, null, false);
}
@Override
public List<ProjectJoinVO> searchByIds(Long... ids) {
SearchCriteria<ProjectJoinVO> sc = vrSearch.create();
sc.setParameters("idIN", ids);
return searchIncludingRemoved(sc, null, null, false);
}
}

View File

@ -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<List<? extends Project>, Integer> listProjects(Long id, String name, String displayText, String state, String accountName, Long domainId, String keyword, Long startIndex, Long pageSize, boolean listAll,
public Pair<List<ProjectJoinVO>, 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<String, String> tags) {
// TODO Auto-generated method stub
return null;
}
@Override
public Project findByProjectAccountIdIncludingRemoved(long projectAccountId) {
return null;

View File

@ -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;