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

Fix named beans detection and route builder class hierarchy. #947

Merged
merged 1 commit into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -49,10 +49,10 @@ public abstract class AbstractCamelInspection extends LocalInspectionTool {

private boolean forceEnabled;

public AbstractCamelInspection() {
protected AbstractCamelInspection() {
}

public AbstractCamelInspection(boolean forceEnabled) {
protected AbstractCamelInspection(boolean forceEnabled) {
this.forceEnabled = forceEnabled;
}

Expand Down Expand Up @@ -119,7 +119,7 @@ private void validateSimple(@NotNull PsiElement element, final @NotNull Problems
CamelService camelService = element.getProject().getService(CamelService.class);

IElementType type = element.getNode().getElementType();
LOG.trace("Element " + element + " of type: " + type + " to inspect simple: " + text);
LOG.trace("Element %s of type: %s to inspect simple: %s".formatted(element, type, text));

try {
// need to use the classloader that can load classes from the camel-core
Expand All @@ -146,8 +146,8 @@ private void validateSimple(@NotNull PsiElement element, final @NotNull Problems
holder.registerProblem(element, msg);
}
}
} catch (Throwable e) {
LOG.warn("Error inspection Camel simple: " + text, e);
} catch (Exception e) {
LOG.warn("Error inspection Camel simple: %s".formatted(text), e);
}
}

Expand Down Expand Up @@ -189,8 +189,8 @@ private void validateJSonPath(@NotNull PsiElement element, final @NotNull Proble
holder.registerProblem(element, msg);
}
}
} catch (Throwable e) {
LOG.warn("Error inspection Camel jsonpath: " + text, e);
} catch (Exception e) {
LOG.warn("Error inspection Camel jsonpath: %s".formatted(text), e);
}
}

Expand All @@ -209,7 +209,7 @@ private void validateEndpoint(@NotNull PsiElement element, final @NotNull Proble

// camel catalog expects & as & when it parses so replace all & as &
String camelQuery = text;
camelQuery = camelQuery.replaceAll("&", "&");
camelQuery = camelQuery.replace("&", "&");

// strip up ending incomplete parameter
if (camelQuery.endsWith("&") || camelQuery.endsWith("?")) {
Expand All @@ -219,9 +219,7 @@ private void validateEndpoint(@NotNull PsiElement element, final @NotNull Proble
boolean stringFormat = camelIdeaUtils.isFromStringFormatEndpoint(element);
if (stringFormat) {
// if the node is fromF or toF, then replace all %X with {{%X}} as we cannot parse that value
camelQuery = camelQuery.replaceAll("%s", "\\{\\{\\%s\\}\\}");
camelQuery = camelQuery.replaceAll("%d", "\\{\\{\\%d\\}\\}");
camelQuery = camelQuery.replaceAll("%b", "\\{\\{\\%b\\}\\}");
camelQuery = camelQuery.replaceAll("(%[bds])", "{{$1}}");
}

boolean consumerOnly = camelIdeaUtils.isConsumerEndpoint(element);
Expand All @@ -238,8 +236,8 @@ private void validateEndpoint(@NotNull PsiElement element, final @NotNull Proble
extractSetValue(result, result.getUnknown(), text, element, holder, isOnTheFly, new AbstractCamelInspection.UnknownErrorMsg());
extractSetValue(result, result.getNotConsumerOnly(), text, element, holder, isOnTheFly, new AbstractCamelInspection.NotConsumerOnlyErrorMsg());
extractSetValue(result, result.getNotProducerOnly(), text, element, holder, isOnTheFly, new AbstractCamelInspection.NotProducerOnlyErrorMsg());
} catch (Throwable e) {
LOG.warn("Error inspecting Camel endpoint: " + text, e);
} catch (Exception e) {
LOG.warn("Error inspecting Camel endpoint: %s".formatted(text), e);
}
}

Expand Down Expand Up @@ -271,7 +269,7 @@ private static class BooleanErrorMsg implements CamelAnnotatorEndpointMessage<Ma
@Override
public String getErrorMessage(EndpointValidationResult result, Map.Entry<String, String> entry) {
String name = entry.getKey();
boolean empty = entry.getValue() == null || entry.getValue().length() == 0;
boolean empty = entry.getValue() == null || entry.getValue().isEmpty();
if (empty) {
return name + " has empty boolean value";
} else {
Expand Down Expand Up @@ -381,7 +379,6 @@ public boolean isWarnLevel() {
*
* @return the summary, or <tt>empty</tt> if no validation errors
*/
@SuppressWarnings("unchecked")
private <T> String summaryErrorMessage(EndpointValidationResult result, T entry, CamelAnnotatorEndpointMessage<T> msg) {
if (result.getIncapable() != null) {
return "Incapable of parsing uri: " + result.getIncapable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,14 @@
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

public class CamelDebuggerEvaluationDialog extends DialogWrapper {
public static final DataKey<CamelDebuggerEvaluationDialog> KEY = DataKey.create("CAMEL_DEBUGGER_EVALUATION_DIALOG");

//can not use new SHIFT_DOWN_MASK etc because in this case ActionEvent modifiers do not match
//cannot use new SHIFT_DOWN_MASK etc. because in this case ActionEvent modifiers do not match
private static final int ADD_WATCH_MODIFIERS = (SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK) | InputEvent.SHIFT_MASK;
static KeyStroke addWatchKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, ADD_WATCH_MODIFIERS);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.ClassUtil;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.testFramework.LightVirtualFile;
Expand Down Expand Up @@ -94,7 +93,7 @@ public class JavaCamelIdeaUtils extends CamelIdeaUtils implements CamelIdeaUtils
"kamelet", "step", "transacted", "saga", "route", "resequence", "policy", "onException", "onCompletion",
"from", "rest", "restConfiguration");
/**
* Name of the methods corresponding to root element of sub DSL.
* Name of the methods corresponding to the root element of sub DSL.
*/
private static final Set<String> SUB_DSL_ROOTS = Set.of("expression", "dataFormat");
/**
Expand Down Expand Up @@ -122,6 +121,23 @@ public class JavaCamelIdeaUtils extends CamelIdeaUtils implements CamelIdeaUtils
"org.apache.camel.spring.SpringRouteBuilder",
"org.apache.camel.builder.endpoint.EndpointRouteBuilder"
);
private static final List<String> BEAN_ANNOTATIONS = Arrays.asList(
"org.springframework.stereotype.Component",
"org.springframework.stereotype.Service",
"org.springframework.stereotype.Repository",
"javax.inject.Named",
"javax.inject.Singleton",
"javax.enterprise.context.ApplicationScoped",
"javax.enterprise.context.SessionScoped",
"javax.enterprise.context.ConversationScoped",
"javax.enterprise.context.RequestScoped",
"jakarta.inject.Named",
"jakarta.inject.Singleton",
"jakarta.enterprise.context.ApplicationScoped",
"jakarta.enterprise.context.SessionScoped",
"jakarta.enterprise.context.ConversationScoped",
"jakarta.enterprise.context.RequestScoped"
);

@Override
public boolean isCamelFile(PsiFile file) {
Expand Down Expand Up @@ -216,10 +232,10 @@ public boolean isCamelExpressionUsedAsPredicate(PsiElement element, String langu

// okay dive into the psi and find out which EIP are using the simple
PsiElement child = call.getFirstChild();
if (child instanceof PsiReferenceExpression) {
if (child instanceof PsiReferenceExpression psiReferenceExpression) {
// this code is needed as it may be used as a method call as a parameter and this requires
// a bit of psi code to unwrap the right elements.
PsiExpression exp = ((PsiReferenceExpression) child).getQualifierExpression();
PsiExpression exp = psiReferenceExpression.getQualifierExpression();
if (exp == null) {
// okay it was not a direct method call, so see if it was passed in as a parameter instead (expression list)
element = element.getParent();
Expand All @@ -230,8 +246,8 @@ public boolean isCamelExpressionUsedAsPredicate(PsiElement element, String langu
exp = PsiTreeUtil.getParentOfType(element.getParent(), PsiMethodCallExpression.class);
}
}
if (exp instanceof PsiMethodCallExpression) {
PsiMethod method = ((PsiMethodCallExpression) exp).resolveMethod();
if (exp instanceof PsiMethodCallExpression psiMethodCallExpression) {
PsiMethod method = psiMethodCallExpression.resolveMethod();
if (method != null) {
String name = method.getName();
return Arrays.asList(PREDICATE_EIPS).contains(name);
Expand Down Expand Up @@ -299,7 +315,7 @@ public boolean isFromStringFormatEndpoint(PsiElement element) {
public boolean acceptForAnnotatorOrInspection(PsiElement element) {
// skip XML limit on siblings
if (!IdeaUtils.getService().isFromFileType(element, "xml")) {
// for programming languages you can have complex structures with concat which we don't support yet
// for programming languages you can have complex structures with concat which we don't support it yet.
// we currently only support oneliner, so check how many siblings the element has (it has 1 with ending parenthesis which is okay)
return countSiblings(element) <= 1;
}
Expand All @@ -310,8 +326,8 @@ public boolean acceptForAnnotatorOrInspection(PsiElement element) {
public PsiClass getBeanClass(PsiElement element) {
final PsiElement beanPsiElement = getPsiElementForCamelBeanMethod(element);
if (beanPsiElement != null) {
if (beanPsiElement instanceof PsiClass) {
return (PsiClass) beanPsiElement;
if (beanPsiElement instanceof PsiClass psiClass) {
return psiClass;
}

PsiJavaCodeReferenceElement referenceElement = PsiTreeUtil.findChildOfType(beanPsiElement, PsiJavaCodeReferenceElement.class);
Expand Down Expand Up @@ -372,19 +388,22 @@ public boolean isPlaceForEndpointUri(PsiElement location) {
}

/**
* @return the {@link PsiClass} for the matching bean name by looking for classes annotated with spring Component, Service or Repository
* @return the {@link PsiClass} for the matching bean name by looking for classes annotated with
* Spring Component, Service or Repository or Quarkus javax or jakarta annotations.
*/
private Optional<PsiClass> searchForMatchingBeanClass(String beanName, Project project) {
final JavaClassUtils javaClassUtils = JavaClassUtils.getService();
return javaClassUtils.findBeanClassByName(beanName, "org.springframework.stereotype.Component", project)
.or(() -> javaClassUtils.findBeanClassByName(beanName, "org.springframework.stereotype.Service", project))
.or(() -> javaClassUtils.findBeanClassByName(beanName, "org.springframework.stereotype.Repository", project));

return BEAN_ANNOTATIONS
.stream()
.map(annotation -> javaClassUtils.findBeanClassByName(beanName, annotation, project))
.flatMap(Optional::stream)
.findFirst();
}

private List<PsiElement> findEndpoints(Module module, Predicate<String> uriCondition, Predicate<PsiLiteral> elementCondition) {
PsiManager manager = PsiManager.getInstance(module.getProject());
//TODO: use IdeaUtils.ROUTE_BUILDER_OR_EXPRESSION_CLASS_QUALIFIED_NAME somehow
PsiClass routeBuilderClass = ClassUtil.findPsiClass(manager, "org.apache.camel.builder.RouteBuilder");
PsiClass routeBuilderClass = IdeaUtils.findRouteBuilderClass(manager);

List<PsiElement> results = new ArrayList<>();
if (routeBuilderClass != null) {
Expand All @@ -394,8 +413,7 @@ private List<PsiElement> findEndpoints(Module module, Predicate<String> uriCondi
Collection<PsiLiteralExpression> literals = PsiTreeUtil.findChildrenOfType(routeBuilder, PsiLiteralExpression.class);
for (PsiLiteralExpression literal : literals) {
Object val = literal.getValue();
if (val instanceof String) {
String endpointUri = (String) val;
if (val instanceof String endpointUri) {
if (uriCondition.test(endpointUri) && elementCondition.test(literal)) {
results.add(literal);
}
Expand Down Expand Up @@ -436,7 +454,7 @@ private void format(PsiFile file, Document document, int startOffset, int endOff
final VirtualFile vFile = FileDocumentManager.getInstance().getFile(document);
if ((vFile == null || vFile instanceof LightVirtualFile) && !ApplicationManager.getApplication().isUnitTestMode()) {
// we assume that control flow reaches this place when the document is backed by a "virtual" file so any changes made by
// a formatter affect only PSI and it is out of sync with a document text
// a formatter affect only PSI, and it is out of sync with a document text
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.github.cameltooling.idea.reference.endpoint.CamelEndpoint;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiClass;
Expand All @@ -34,13 +35,13 @@
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* Utility methods to work with Camel related {@link com.intellij.psi.PsiElement} elements.
* <p/>
* This class is only for Camel related IDEA APIs. If you need only IDEA APIs then use {@link IdeaUtils} instead.
*/
@Service
public final class CamelIdeaUtils implements Disposable {

public static final String[] CAMEL_FILE_EXTENSIONS = {"java", "xml", "yaml", "yml"};
Expand Down Expand Up @@ -192,7 +193,7 @@ public boolean isCamelExpressionOrLanguage(PsiClass clazz) {
}

/**
* Certain elements should be skipped for endpoint validation such as ActiveMQ brokerURL property and others.
* Certain elements should be skipped for endpoint validation, such as ActiveMQ brokerURL property and others.
*/
public boolean skipEndpointValidation(PsiElement element) {
return enabledExtensions.stream()
Expand Down Expand Up @@ -264,10 +265,9 @@ public List<PsiElement> findEndpointDeclarations(Module module, Predicate<String
.toList();
}


@Override
public void dispose() {

//noop
}

/**
Expand Down
Loading
Loading