Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: removed some code smells from few classes #5284

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,8 @@ public class BizConfig extends RefreshableConfig {
new TypeToken<Map<String, Integer>>() {
}.getType();

private final BizDBPropertySource propertySource;

public BizConfig(final BizDBPropertySource propertySource) {
this.propertySource = propertySource;
}

@Override
protected List<RefreshablePropertySource> getRefreshablePropertySources() {
return Collections.singletonList(propertySource);
super(propertySource);
}

public List<String> eurekaServiceUrls() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,17 @@ public Long findReleaseIdFromGrayReleaseRule(String clientAppId, String clientIp
* load gray releases. Because gray release rules actually apply to one more dimension - cluster.
*/
public boolean hasGrayReleaseRule(String clientAppId, String clientIp, String namespaceName) {
return reversedGrayReleaseRuleCache.containsKey(assembleReversedGrayReleaseRuleKey(clientAppId,
namespaceName, clientIp)) || reversedGrayReleaseRuleCache.containsKey
(assembleReversedGrayReleaseRuleKey(clientAppId, namespaceName, GrayReleaseRuleItemDTO
.ALL_IP));

String ruleKeyViaClientIp = assembleReversedGrayReleaseRuleKey(clientAppId,
namespaceName, clientIp);
String ruleKeyViaItemDTO = assembleReversedGrayReleaseRuleKey(clientAppId, namespaceName, GrayReleaseRuleItemDTO
.ALL_IP);

boolean hasScheduleForThisKey = reversedGrayReleaseRuleCache.containsKey(ruleKeyViaClientIp);
boolean hasScheduleForThatKey = reversedGrayReleaseRuleCache.containsKey
(ruleKeyViaItemDTO);

return hasScheduleForThisKey || hasScheduleForThatKey;
}

private void scanGrayReleaseRules() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Map;
import java.util.Set;
Expand Down
2 changes: 1 addition & 1 deletion apollo-build-sql-converter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</dependencies>

<profiles>
<profile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Significant breaking changes detected in RefreshableConfig

The verification reveals that this is indeed a breaking change with significant impact:

  • 5 classes directly extend RefreshableConfig:
    • apollo-portal: EmailConfig, EnvConfig, SecurityConfig, ServerConfig
    • apollo-biz: BizConfig

The changes are breaking because:

  1. The abstract method getRefreshablePropertySources() has been removed
  2. A new constructor requiring RefreshablePropertySource is now mandatory
  3. The class is no longer abstract, changing its contract completely

All extending classes will need to be modified to:

  • Remove their implementation of getRefreshablePropertySources()
  • Add constructor to pass the required RefreshablePropertySource
🔗 Analysis chain

Verify impact of removing abstract modifier

The class has been changed from abstract to concrete, which is a breaking change. This could affect existing subclasses that previously implemented abstract methods.

Also applies to: 40-40

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for classes extending RefreshableConfig to assess impact
ast-grep --pattern 'class $_ extends RefreshableConfig {
  $$$
}'

Length of output: 67


Script:

#!/bin/bash
# Let's try a different approach using ripgrep to find references to RefreshableConfig
rg "extends\s+RefreshableConfig" -l

Length of output: 501


Script:

#!/bin/bash
# Also search for any usage of RefreshableConfig class
rg "RefreshableConfig" -A 3

Length of output: 8017


Script:

#!/bin/bash
# Check the file history to understand the context of changes
git diff HEAD~1 apollo-common/src/main/java/com/ctrip/framework/apollo/common/config/RefreshableConfig.java

Length of output: 2144

import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
Expand All @@ -36,7 +37,7 @@
import javax.annotation.PostConstruct;


public abstract class RefreshableConfig {
public class RefreshableConfig {

private static final Logger logger = LoggerFactory.getLogger(RefreshableConfig.class);

Expand All @@ -49,18 +50,24 @@ public abstract class RefreshableConfig {
@Autowired
private ConfigurableEnvironment environment;

private List<RefreshablePropertySource> propertySources;

/**
* register refreshable property source.
* Notice: The front property source has higher priority.
*/
protected abstract List<RefreshablePropertySource> getRefreshablePropertySources();
private List<RefreshablePropertySource> propertySources;

public RefreshableConfig(RefreshablePropertySource propertySources) {
this.propertySources = Collections.singletonList(propertySources);
}

private List<RefreshablePropertySource> getPropertySources() {
return propertySources;
}

@PostConstruct
public void setup() {

propertySources = getRefreshablePropertySources();
propertySources = getPropertySources();
if (CollectionUtils.isEmpty(propertySources)) {
throw new IllegalStateException("Property sources can not be empty.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ protected Release findLatestActiveRelease(String appId, String clusterName, Stri

ConfigCacheEntry cacheEntry = configCache.getUnchecked(cacheKey);

//cache is out-dated
if (clientMessages != null && clientMessages.has(messageKey) &&
clientMessages.get(messageKey) > cacheEntry.getNotificationId()) {
// Checking if the cache is out-dated
boolean isCacheOutdated = outdatedCacheCheck(clientMessages, messageKey, cacheEntry);
if (isCacheOutdated) {
//invalidate the cache and try to load from db again
invalidate(cacheKey);
cacheEntry = configCache.getUnchecked(cacheKey);
Expand All @@ -123,6 +123,12 @@ protected Release findLatestActiveRelease(String appId, String clusterName, Stri
return cacheEntry.getRelease();
}

private static boolean outdatedCacheCheck(ApolloNotificationMessages clientMessages, String messageKey, ConfigCacheEntry cacheEntry) {
boolean clientMessagesHasMessageKey = clientMessages != null && clientMessages.has(messageKey);
return clientMessagesHasMessageKey &&
clientMessages.get(messageKey) > cacheEntry.getNotificationId();
}

private void invalidate(String key) {
configCache.invalidate(key);
Tracer.logEvent(TRACER_EVENT_CACHE_INVALIDATE, key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,49 @@

import static com.ctrip.framework.apollo.portal.service.SystemRoleManagerService.SYSTEM_PERMISSION_TARGET_ID;

import com.ctrip.framework.apollo.openapi.entity.ConsumerRole;
import com.ctrip.framework.apollo.openapi.repository.ConsumerRoleRepository;
import com.ctrip.framework.apollo.openapi.service.ConsumerRolePermissionService;
import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
import com.ctrip.framework.apollo.portal.constant.PermissionType;
import com.ctrip.framework.apollo.portal.entity.po.Permission;
import com.ctrip.framework.apollo.portal.entity.po.RolePermission;
import com.ctrip.framework.apollo.portal.repository.PermissionRepository;
import com.ctrip.framework.apollo.portal.repository.RolePermissionRepository;
import com.ctrip.framework.apollo.portal.util.RoleUtils;
import com.nimbusds.oauth2.sdk.util.CollectionUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Component
public class ConsumerPermissionValidator {

private final ConsumerRolePermissionService permissionService;
private final ConsumerAuthUtil consumerAuthUtil;
private final RolePermissionRepository rolePermissionRepository;
private final ConsumerRoleRepository consumerRoleRepository;
private final PermissionRepository permissionRepository;

public ConsumerPermissionValidator(final ConsumerRolePermissionService permissionService,
final ConsumerAuthUtil consumerAuthUtil) {
this.permissionService = permissionService;
public ConsumerPermissionValidator(final ConsumerAuthUtil consumerAuthUtil,
final RolePermissionRepository rolePermissionRepository
, final ConsumerRoleRepository consumerRoleRepository,
final PermissionRepository permissionRepository) {
this.consumerAuthUtil = consumerAuthUtil;
this.rolePermissionRepository = rolePermissionRepository;
this.consumerRoleRepository = consumerRoleRepository;
this.permissionRepository = permissionRepository;
}

public boolean hasModifyNamespacePermission(HttpServletRequest request, String appId,
String namespaceName, String env) {
if (hasCreateNamespacePermission(request, appId)) {
return true;
}
return permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
return consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
PermissionType.MODIFY_NAMESPACE, RoleUtils.buildNamespaceTargetId(appId, namespaceName))
|| permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
|| consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
PermissionType.MODIFY_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName, env));

Expand All @@ -55,26 +71,54 @@ public boolean hasReleaseNamespacePermission(HttpServletRequest request, String
if (hasCreateNamespacePermission(request, appId)) {
return true;
}
return permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
return consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
PermissionType.RELEASE_NAMESPACE, RoleUtils.buildNamespaceTargetId(appId, namespaceName))
|| permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
|| consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
PermissionType.RELEASE_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName, env));

}

public boolean hasCreateNamespacePermission(HttpServletRequest request, String appId) {
return permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
return consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
PermissionType.CREATE_NAMESPACE, appId);
}

public boolean hasCreateClusterPermission(HttpServletRequest request, String appId) {
return permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
return consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
PermissionType.CREATE_CLUSTER, appId);
}

public boolean hasCreateApplicationPermission(HttpServletRequest request) {
long consumerId = consumerAuthUtil.retrieveConsumerId(request);
return permissionService.consumerHasPermission(consumerId, PermissionType.CREATE_APPLICATION, SYSTEM_PERMISSION_TARGET_ID);
return consumerHasPermission(consumerId, PermissionType.CREATE_APPLICATION, SYSTEM_PERMISSION_TARGET_ID);
}

public boolean consumerHasPermission(long consumerId, String permissionType, String targetId) {
Permission permission =
permissionRepository.findTopByPermissionTypeAndTargetId(permissionType, targetId);
if (permission == null) {
return false;
}

List<ConsumerRole> consumerRoles = consumerRoleRepository.findByConsumerId(consumerId);
if (CollectionUtils.isEmpty(consumerRoles)) {
return false;
}

Set<Long> roleIds =
consumerRoles.stream().map(ConsumerRole::getRoleId).collect(Collectors.toSet());
List<RolePermission> rolePermissions = rolePermissionRepository.findByRoleIdIn(roleIds);
if (CollectionUtils.isEmpty(rolePermissions)) {
return false;
}

for (RolePermission rolePermission : rolePermissions) {
if (rolePermission.getPermissionId() == permission.getId()) {
return true;
}
}

return false;
Comment on lines +97 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize Database Queries in consumerHasPermission Method

The consumerHasPermission method performs multiple sequential database queries, which may impact performance. You can optimize these queries by fetching the necessary data in a single query or reducing the number of database calls.

Consider refactoring the method as follows:

public boolean consumerHasPermission(long consumerId, String permissionType, String targetId) {
    Permission permission = permissionRepository.findTopByPermissionTypeAndTargetId(permissionType, targetId);
    if (permission == null) {
        return false;
    }

    Set<Long> roleIds = consumerRoleRepository.findByConsumerId(consumerId)
            .stream()
            .map(ConsumerRole::getRoleId)
            .collect(Collectors.toSet());

    if (CollectionUtils.isEmpty(roleIds)) {
        return false;
    }

    int count = rolePermissionRepository.countByRoleIdInAndPermissionId(roleIds, permission.getId());
    return count > 0;
}

This refactoring reduces the number of database queries by:

  • Combining the retrieval of RolePermission into a single count query.
  • Eliminating unnecessary iterations over collections.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ public OpenReleaseDTO createRelease(@PathVariable String appId, @PathVariable St
@PathVariable String namespaceName,
@RequestBody NamespaceReleaseDTO model,
HttpServletRequest request) {
RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(model.getReleasedBy(), model
.getReleaseTitle()),
"Params(releaseTitle and releasedBy) can not be empty");
boolean doesContainEmpty = StringUtils.isContainEmpty(model.getReleasedBy(), model.getReleaseTitle());
RequestPrecondition.checkArguments(!doesContainEmpty, "Params(releaseTitle and releasedBy) can not be empty");

if (userService.findByUserId(model.getReleasedBy()) == null) {
throw BadRequestException.userNotExists(model.getReleasedBy());
Expand All @@ -99,9 +98,8 @@ public OpenReleaseDTO merge(@PathVariable String appId, @PathVariable String env
@PathVariable String clusterName, @PathVariable String namespaceName,
@PathVariable String branchName, @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
@RequestBody NamespaceReleaseDTO model, HttpServletRequest request) {
RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(model.getReleasedBy(), model
.getReleaseTitle()),
"Params(releaseTitle and releasedBy) can not be empty");
boolean doesContainEmpty = StringUtils.isContainEmpty(model.getReleasedBy(), model.getReleaseTitle());
RequestPrecondition.checkArguments(!doesContainEmpty, "Params(releaseTitle and releasedBy) can not be empty");

if (userService.findByUserId(model.getReleasedBy()) == null) {
throw BadRequestException.userNotExists(model.getReleasedBy());
Expand All @@ -121,20 +119,15 @@ public OpenReleaseDTO createGrayRelease(@PathVariable String appId,
@PathVariable String namespaceName, @PathVariable String branchName,
@RequestBody NamespaceReleaseDTO model,
HttpServletRequest request) {
RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(model.getReleasedBy(), model
.getReleaseTitle()),
"Params(releaseTitle and releasedBy) can not be empty");
boolean doesContainEmpty = StringUtils.isContainEmpty(model.getReleasedBy(), model.getReleaseTitle());
RequestPrecondition.checkArguments(!doesContainEmpty, "Params(releaseTitle and releasedBy) can not be empty");

if (userService.findByUserId(model.getReleasedBy()) == null) {
throw BadRequestException.userNotExists(model.getReleasedBy());
}

NamespaceReleaseModel releaseModel = BeanUtils.transform(NamespaceReleaseModel.class, model);

releaseModel.setAppId(appId);
releaseModel.setEnv(Env.valueOf(env).toString());
releaseModel.setClusterName(branchName);
releaseModel.setNamespaceName(namespaceName);
populateReleaseModel(releaseModel, appId, Env.valueOf(env).toString(), branchName, namespaceName);

return OpenApiBeanUtils.transformFromReleaseDTO(releaseService.publish(releaseModel));
}
Expand All @@ -146,9 +139,9 @@ public OpenReleaseDTO createGrayDelRelease(@PathVariable String appId,
@PathVariable String namespaceName, @PathVariable String branchName,
@RequestBody NamespaceGrayDelReleaseDTO model,
HttpServletRequest request) {
RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(model.getReleasedBy(), model
.getReleaseTitle()),
"Params(releaseTitle and releasedBy) can not be empty");
boolean doesContainEmpty = StringUtils.isContainEmpty(model.getReleasedBy(), model.getReleaseTitle());
RequestPrecondition.checkArguments(!doesContainEmpty, "Params(releaseTitle and releasedBy) can not be empty");

RequestPrecondition.checkArguments(model.getGrayDelKeys() != null,
"Params(grayDelKeys) can not be null");

Expand All @@ -157,19 +150,17 @@ public OpenReleaseDTO createGrayDelRelease(@PathVariable String appId,
}

NamespaceGrayDelReleaseModel releaseModel = BeanUtils.transform(NamespaceGrayDelReleaseModel.class, model);
releaseModel.setAppId(appId);
releaseModel.setEnv(env.toUpperCase());
releaseModel.setClusterName(branchName);
releaseModel.setNamespaceName(namespaceName);
populateReleaseModel(releaseModel, appId, env.toUpperCase(), branchName, namespaceName);

return OpenApiBeanUtils.transformFromReleaseDTO(releaseService.publish(releaseModel, releaseModel.getReleasedBy()));
ReleaseDTO publish = releaseService.publish(releaseModel, releaseModel.getReleasedBy());
return OpenApiBeanUtils.transformFromReleaseDTO(publish);
}

@PutMapping(path = "/releases/{releaseId}/rollback")
public void rollback(@PathVariable String env,
@PathVariable long releaseId, @RequestParam String operator, HttpServletRequest request) {
RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(operator),
"Param operator can not be empty");
boolean doesContainEmpty = StringUtils.isContainEmpty(operator);
RequestPrecondition.checkArguments(!doesContainEmpty, "Param operator can not be empty");

if (userService.findByUserId(operator) == null) {
throw BadRequestException.userNotExists(operator);
Expand All @@ -188,4 +179,11 @@ public void rollback(@PathVariable String env,
this.releaseOpenApiService.rollbackRelease(env, releaseId, operator);
}

private void populateReleaseModel(NamespaceReleaseModel releaseModel, String appId, String env, String branchName, String namespaceName) {
releaseModel.setAppId(appId);
releaseModel.setEnv(env.toUpperCase());
releaseModel.setClusterName(branchName);
releaseModel.setNamespaceName(namespaceName);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.ctrip.framework.apollo.portal.component.config;

import com.ctrip.framework.apollo.common.config.RefreshableConfig;
import com.ctrip.framework.apollo.portal.service.PortalDBPropertySource;
import com.google.common.base.Strings;
import org.springframework.stereotype.Component;

/**
* This class handled email related configs for portalConfig class
*/
@Component
public class EmailConfig extends RefreshableConfig {

public EmailConfig(final PortalDBPropertySource portalDBPropertySource) {
super(portalDBPropertySource);
}

public String emailSender() {
String value = getValue(PortalConfigConstants.EMAIL_SENDER, "");
return Strings.isNullOrEmpty(value) ? emailConfigUser() : value;
}

public String emailConfigHost() {
return getValue(PortalConfigConstants.EMAIL_CONFIG_HOST, "");
}

public String emailConfigPassword() {
return getValue(PortalConfigConstants.EMAIL_CONFIG_PASSWORD, "");
}

public String emailTemplateFramework() {
return getValue(PortalConfigConstants.EMAIL_TEMPLATE_FRAMEWORK, "");
}

public String emailReleaseDiffModuleTemplate() {
return getValue(PortalConfigConstants.EMAIL_RELEASE_MODULE_DIFF, "");
}

public String emailRollbackDiffModuleTemplate() {
return getValue(PortalConfigConstants.EMAIL_ROLLBACK_MODULE_DIFF, "");
}

public String emailGrayRulesModuleTemplate() {
return getValue(PortalConfigConstants.EMAIL_RELEASE_MODULE_RULES, "");
}

public String emailConfigUser() {
return getValue(PortalConfigConstants.EMAIL_CONFIG_USER, "");
}
Comment on lines +23 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Based on the gathered information, I can now provide the final response:

Empty email configuration values could lead to SMTP connection failures

The DefaultEmailService uses these configurations for SMTP server connection, and empty values for host, user, and password would cause email delivery failures. The email templates are used for building notification emails for release, rollback, and gray deployment events.

  • Empty SMTP configuration values (host, user, password) should have meaningful defaults or proper validation
  • Email template paths should have default templates in the classpath or validate for empty values
  • Add JavaDoc to document the expected format and impact of these configurations
🔗 Analysis chain

Add documentation and consider non-empty defaults

The configuration methods should:

  1. Have JavaDoc explaining their purpose and default values
  2. Consider meaningful defaults instead of empty strings
  3. Verify the implications of empty configuration values in the system

Let me gather more information about the email configuration constants and their usage in the system.


Let me check one more thing about the email service implementation to understand how it handles these configurations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of these methods to understand the impact of empty values
rg -t java "emailConfigHost|emailConfigPassword|emailTemplateFramework|emailReleaseDiffModuleTemplate|emailRollbackDiffModuleTemplate|emailGrayRulesModuleTemplate|emailConfigUser" -A 2

Length of output: 11363


Script:

#!/bin/bash
# Search for the constants definitions and any validation/checks on empty values
rg -t java "EMAIL_CONFIG_HOST|EMAIL_CONFIG_PASSWORD|EMAIL_TEMPLATE_FRAMEWORK|EMAIL_RELEASE_MODULE_DIFF|EMAIL_ROLLBACK_MODULE_DIFF|EMAIL_RELEASE_MODULE_RULES|EMAIL_CONFIG_USER" -A 2

# Check DefaultEmailService implementation for empty value handling
ast-grep --pattern 'class DefaultEmailService {
  $$$
}'

Length of output: 4668


Script:

#!/bin/bash
# Check DefaultEmailService for SMTP configuration and error handling
rg -t java -A 10 "class DefaultEmailService"

# Check if there are any default email templates in resources
fd -t f -e html -e ftl -e vm "email" 

Length of output: 1513


public boolean isEmailEnabled() {
return getBooleanProperty(PortalConfigConstants.EMAIL_ENABLED, false);
}
}
Loading
Loading