ProjectService.java
package io.featureprobe.api.service;
import io.featureprobe.api.auth.TokenHelper;
import io.featureprobe.api.base.component.SpringBeanManager;
import io.featureprobe.api.base.db.Archived;
import io.featureprobe.api.base.db.IncludeDeleted;
import io.featureprobe.api.dao.exception.ResourceConflictException;
import io.featureprobe.api.dao.exception.ResourceNotFoundException;
import io.featureprobe.api.dao.exception.ResourceOverflowException;
import io.featureprobe.api.dto.ApprovalSettings;
import io.featureprobe.api.dto.ApprovalSettingsResponse;
import io.featureprobe.api.dto.PreferenceCreateRequest;
import io.featureprobe.api.dto.ProjectCreateRequest;
import io.featureprobe.api.dto.ProjectQueryRequest;
import io.featureprobe.api.dto.ProjectResponse;
import io.featureprobe.api.dto.ProjectUpdateRequest;
import io.featureprobe.api.dao.entity.Environment;
import io.featureprobe.api.dao.entity.Project;
import io.featureprobe.api.base.enums.ChangeLogType;
import io.featureprobe.api.base.enums.ResourceType;
import io.featureprobe.api.base.enums.SketchStatusEnum;
import io.featureprobe.api.base.enums.ValidateTypeEnum;
import io.featureprobe.api.mapper.EnvironmentMapper;
import io.featureprobe.api.mapper.ProjectMapper;
import io.featureprobe.api.dao.repository.EnvironmentRepository;
import io.featureprobe.api.dao.repository.ProjectRepository;
import io.featureprobe.api.dao.repository.TargetingSketchRepository;
import io.featureprobe.api.base.util.KeyGenerateUtil;
import com.featureprobe.sdk.server.FPUser;
import com.featureprobe.sdk.server.FeatureProbe;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Service
@AllArgsConstructor
public class ProjectService {
private ProjectRepository projectRepository;
private EnvironmentRepository environmentRepository;
private TargetingSketchRepository targetingSketchRepository;
private ChangeLogService changeLogService;
@PersistenceContext
public EntityManager entityManager;
private static final String LIMITER_TOGGLE_KEY = "FeatureProbe_project_limiter";
@Transactional(rollbackFor = Exception.class)
public ProjectResponse create(ProjectCreateRequest createRequest) {
validateLimit();
return ProjectMapper.INSTANCE.entityToResponse(createProject(createRequest));
}
@Transactional(rollbackFor = Exception.class)
public ProjectResponse update(String projectKey, ProjectUpdateRequest updateRequest) {
Project project = projectRepository.findByKey(projectKey)
.orElseThrow(() -> new ResourceNotFoundException(ResourceType.PROJECT, projectKey));
if (!StringUtils.equals(project.getName(), updateRequest.getName())) {
validateName(updateRequest.getName());
}
ProjectMapper.INSTANCE.mapEntity(updateRequest, project);
return ProjectMapper.INSTANCE.entityToResponse(projectRepository.save(project));
}
@Transactional(rollbackFor = Exception.class)
public ProjectResponse delete(String projectKey) {
Project project = projectRepository.findByKey(projectKey)
.orElseThrow(() -> new ResourceNotFoundException(ResourceType.PROJECT, projectKey));
project.setArchived(true);
archiveAllEnvironments(project.getEnvironments());
return ProjectMapper.INSTANCE.entityToResponse(projectRepository.save(project));
}
@Transactional(rollbackFor = Exception.class)
public List<ApprovalSettingsResponse> updateApprovalSettings(String projectKey,
PreferenceCreateRequest createRequest) {
Map<String, ApprovalSettings> approvalSettingsMap = createRequest.getApprovalSettings().stream()
.collect(Collectors.toMap(ApprovalSettings::getEnvironmentKey, Function.identity()));
if (CollectionUtils.isNotEmpty(createRequest.getApprovalSettings())) {
List<Environment> environments = environmentRepository.findAllByProjectKey(projectKey);
environments.stream().forEach(environment -> EnvironmentMapper.INSTANCE
.mapEntity(approvalSettingsMap.get(environment.getKey()), environment));
environmentRepository.saveAll(environments);
}
return approvalSettingsList(projectKey);
}
public List<ApprovalSettingsResponse> approvalSettingsList(String projectKey) {
List<Environment> environments = environmentRepository.findAllByProjectKey(projectKey);
List<ApprovalSettingsResponse> approvalSettingsList = environments.stream().map(environment ->
EnvironmentMapper.INSTANCE.entityToApprovalSettingsResponse(environment)).collect(Collectors.toList());
for (ApprovalSettingsResponse response : approvalSettingsList) {
boolean locked = targetingSketchRepository.existsByProjectKeyAndEnvironmentKeyAndStatus(projectKey,
response.getEnvironmentKey(), SketchStatusEnum.PENDING);
response.setLocked(locked);
}
return approvalSettingsList;
}
private void archiveAllEnvironments(List<Environment> environments) {
for (Environment environment : environments) {
environment.setArchived(true);
changeLogService.create(environment, ChangeLogType.DELETE);
}
environmentRepository.saveAll(environments);
}
private void validateLimit() {
long total = projectRepository.count();
FPUser user = new FPUser(String.valueOf(TokenHelper.getUserId()));
user.with("account", TokenHelper.getAccount());
double limitNum = SpringBeanManager.getBeanByType(FeatureProbe.class).numberValue(LIMITER_TOGGLE_KEY, user, -1);
if (limitNum > 0 && total >= limitNum) {
throw new ResourceOverflowException(ResourceType.PROJECT);
}
}
private Project createProject(ProjectCreateRequest createRequest) {
validateKey(createRequest.getKey());
validateName(createRequest.getName());
Project createProject = ProjectMapper.INSTANCE.requestToEntity(createRequest);
createProject.setDeleted(false);
List<Environment> defaultEnvs = createDefaultEnv(createProject);
createProject.setEnvironments(defaultEnvs);
Project saved = projectRepository.save(createProject);
for (Environment environment : saved.getEnvironments()) {
changeLogService.create(environment, ChangeLogType.ADD);
}
return saved;
}
public List<ProjectResponse> list(ProjectQueryRequest queryRequest) {
List<Project> projects;
if (StringUtils.isNotBlank(queryRequest.getKeyword())) {
projects = projectRepository.findAllByNameContainsIgnoreCaseOrDescriptionContainsIgnoreCase(
queryRequest.getKeyword(), queryRequest.getKeyword());
} else {
projects = projectRepository.findAllByOrderByCreatedTimeDesc();
}
return projects.stream().map(project ->
ProjectMapper.INSTANCE.entityToResponse(project)).collect(Collectors.toList());
}
public ProjectResponse queryByKey(String key) {
Project project = projectRepository.findByKey(key)
.orElseThrow(() -> new ResourceNotFoundException(ResourceType.PROJECT, key));
return ProjectMapper.INSTANCE.entityToResponse(project);
}
private List<Environment> createDefaultEnv(Project project) {
List<Environment> environments = new ArrayList<>();
environments.add(createOnlineEnv(project));
return environments;
}
private Environment createOnlineEnv(Project project) {
Environment onlineEnv = new Environment();
onlineEnv.setName("online");
onlineEnv.setKey("online");
onlineEnv.setProject(project);
onlineEnv.setClientSdkKey(KeyGenerateUtil.getClientSdkKey());
onlineEnv.setServerSdkKey(KeyGenerateUtil.getServerSdkKey());
return onlineEnv;
}
@Archived
@IncludeDeleted
public void validateExists(ValidateTypeEnum type, String value) {
switch (type) {
case KEY:
validateKey(value);
break;
case NAME:
validateName(value);
break;
default:
break;
}
}
private void validateKey(String key) {
if (projectRepository.existsByKey(key)) {
throw new ResourceConflictException(ResourceType.PROJECT);
}
}
private void validateName(String name) {
if (projectRepository.existsByName(name)) {
throw new ResourceConflictException(ResourceType.PROJECT);
}
}
}