All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.checks.spring.SpringCacheableWithCachePutCheck Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Java
 * Copyright (C) 2012-2025 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonar.java.checks.spring;

import org.sonar.check.Rule;
import org.sonar.java.model.declaration.ClassTreeImpl;
import org.sonar.java.model.declaration.MethodTreeImpl;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.SymbolMetadata;
import org.sonar.plugins.java.api.tree.Tree;

import java.util.List;
import java.util.stream.Collectors;


@Rule(key = "S7179")
public class SpringCacheableWithCachePutCheck extends IssuableSubscriptionVisitor {

  private static final String MESSAGE_FORMAT = "Remove the \"@CachePut\" annotation or the \"@Cacheable\" annotation located on the same %s.";
  private static final String CLASS_MESSAGE = String.format(MESSAGE_FORMAT, "class");
  private static final String METHOD_MESSAGE = String.format(MESSAGE_FORMAT, "method");
  private static final String WRONG_CACHE_PUT_METHOD_MESSAGE = "Methods of a @Cacheable class should not be annotated with \"@CachePut\".";
  private static final String WRONG_CACHEABLE_METHOD_MESSAGE = "Methods of a @CachePut class should not be annotated with \"@Cacheable\".";
  private static final String CACHE_PUT_FQN = "org.springframework.cache.annotation.CachePut";
  private static final String CACHEABLE_FQN = "org.springframework.cache.annotation.Cacheable";

  @Override
  public List nodesToVisit() {
    return List.of(Tree.Kind.METHOD, Tree.Kind.CLASS);
  }

  @Override
  public void visitNode(Tree tree) {
    if (tree instanceof MethodTreeImpl methodTree) {
      Symbol.MethodSymbol methodSymbol = methodTree.symbol();
      if (isSymbolAnnotatedWithCacheableAndCachePut(methodSymbol)) {
        reportIssue(methodTree.simpleName(), METHOD_MESSAGE, getSecondaryLocations(methodSymbol), null);
      } else if (isAnnotatedWithCachePut(methodSymbol) && isAnnotatedWithCacheable(methodSymbol.enclosingClass())) {
        var secondaryLocations = getSecondaryLocations(methodSymbol, methodSymbol.enclosingClass());
        reportIssue(methodTree.simpleName(), WRONG_CACHE_PUT_METHOD_MESSAGE, secondaryLocations, null);
      } else if (isAnnotatedWithCacheable(methodSymbol) && isAnnotatedWithCachePut(methodSymbol.enclosingClass())) {
        var secondaryLocations = getSecondaryLocations(methodSymbol, methodSymbol.enclosingClass());
        reportIssue(methodTree.simpleName(), WRONG_CACHEABLE_METHOD_MESSAGE, secondaryLocations, null);
      }
    } else {
      ClassTreeImpl classTree = (ClassTreeImpl) tree;
      Symbol.TypeSymbol classSymbol = classTree.symbol();
      if (isSymbolAnnotatedWithCacheableAndCachePut(classSymbol)) {
        // classTree.simpleName() can never be null, as annotations cannot target anonymous classes
        reportIssue(classTree.simpleName(), CLASS_MESSAGE, getSecondaryLocations(classSymbol), null);
      }
    }
  }

  private static boolean isAnnotatedWithCachePut(Symbol symbol) {
    return symbol.metadata().isAnnotatedWith(CACHE_PUT_FQN);
  }

  private static boolean isAnnotatedWithCacheable(Symbol symbol) {
    return symbol.metadata().isAnnotatedWith(CACHEABLE_FQN);
  }

  private static boolean isSymbolAnnotatedWithCacheableAndCachePut(Symbol symbol) {
    return isAnnotatedWithCachePut(symbol) && isAnnotatedWithCacheable(symbol);
  }

  private static List getSecondaryLocations(Symbol symbol) {
    SymbolMetadata symbolMetadata = symbol.metadata();
    return symbolMetadata.annotations().stream()
      .filter(annotation ->
        annotation.symbol().type().is(CACHEABLE_FQN) || annotation.symbol().type().is(CACHE_PUT_FQN))
      .map(annotation -> new JavaFileScannerContext.Location(annotation.symbol().name(), symbolMetadata.findAnnotationTree(annotation)))
      .collect(Collectors.toList());
  }

  private static List getSecondaryLocations(Symbol first, Symbol second) {
    var locations = getSecondaryLocations(first);
    locations.addAll(getSecondaryLocations(second));
    return locations;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy