/** * 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.github.cameltooling.idea.service.extension.camel; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import com.github.cameltooling.idea.Constants; import com.github.cameltooling.idea.extension.CamelIdeaUtilsExtension; import com.github.cameltooling.idea.util.IdeaUtils; import com.github.cameltooling.idea.util.StringUtils; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.module.Module; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlToken; public class XmlCamelIdeaUtils extends CamelIdeaUtils implements CamelIdeaUtilsExtension { @Override public boolean isCamelRouteStart(PsiElement element) { if (element instanceof XmlTag) { return isCamelRouteStartTag((XmlTag) element); } else if (element.getText().equals("from") || element.getText().equals("rest")) { XmlTag xml = PsiTreeUtil.getParentOfType(element, XmlTag.class); boolean xmlEndTag = element.getPrevSibling().getText().equals("</"); if (xml != null && !xmlEndTag) { return isCamelRouteStartTag(xml); } } return false; } @Override public boolean isCamelRouteStartExpression(PsiElement element) { boolean textualXmlToken = element instanceof XmlToken && !element.getText().equals("<") && !element.getText().equals("</") && !element.getText().equals(">") && !element.getText().equals("/>"); return textualXmlToken && isCamelRouteStart(element); } private boolean isCamelRouteStartTag(XmlTag tag) { String name = tag.getLocalName(); XmlTag parentTag = tag.getParentTag(); if (parentTag != null) { //TODO: unsure about this, <rest> cannot be a child of <routes> according to blueprint xsd, see issue #475 return "routes".equals(parentTag.getLocalName()) && "rest".equals(name) || "route".equals(parentTag.getLocalName()) && "from".equals(name); } return false; } @Override public boolean isInsideCamelRoute(PsiElement element, boolean excludeRouteStart) { XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); if (tag == null || (excludeRouteStart && isCamelRouteStartTag(tag))) { return false; } PsiElement routeTag = getIdeaUtils().findFirstParent(tag, false, this::isCamelRouteTag, e -> e instanceof PsiFile); return routeTag != null; } @Override public List<PsiElement> findEndpointUsages(Module module, Predicate<String> uriCondition) { return findEndpoints(module, uriCondition, e -> !isCamelRouteStart(e)); } @Override public List<PsiElement> findEndpointDeclarations(Module module, Predicate<String> uriCondition) { return findEndpoints(module, uriCondition, this::isCamelRouteStart); } private List<PsiElement> findEndpoints(Module module, Predicate<String> uriCondition, Predicate<XmlTag> tagCondition) { Predicate<XmlAttributeValue> endpointMatcher = ((Predicate<XmlAttributeValue>)this::isEndpointUriValue) .and(e -> parentTagMatches(e, tagCondition)) .and(e -> uriCondition.test(e.getValue())); List<PsiElement> endpointDeclarations = new ArrayList<>(); IdeaUtils.getService().iterateXmlDocumentRoots(module, root -> { if (isAcceptedNamespace(root.getNamespace())) { IdeaUtils.getService().iterateXmlNodes(root, XmlAttributeValue.class, value -> { if (endpointMatcher.test(value)) { endpointDeclarations.add(value); } return true; }); } }); return endpointDeclarations; } private boolean parentTagMatches(PsiElement element, Predicate<XmlTag> parentTagCondition) { XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); return tag != null && parentTagCondition.test(tag); } private boolean isEndpointUriValue(XmlAttributeValue endpointUriValue) { XmlAttribute attribute = PsiTreeUtil.getParentOfType(endpointUriValue, XmlAttribute.class); return attribute != null && attribute.getLocalName().equals("uri"); } private boolean isCamelRouteTag(PsiElement element) { if (element instanceof XmlTag) { XmlTag routeTag = (XmlTag) element; return routeTag.getLocalName().equals("route"); } else { return false; } } @Override public boolean isCamelExpression(PsiElement element, String language) { // xml XmlTag xml; if (element instanceof XmlTag) { xml = (XmlTag) element; } else { xml = PsiTreeUtil.getParentOfType(element, XmlTag.class); } if (xml != null) { String name = xml.getLocalName(); // extra check for simple language if ("simple".equals(language) && "log".equals(name)) { return true; } return language.equals(name); } return false; } @Override public boolean isCamelExpressionUsedAsPredicate(PsiElement element, String language) { // xml XmlTag xml = PsiTreeUtil.getParentOfType(element, XmlTag.class); if (xml != null) { // if its coming from the log EIP then its not a predicate if ("simple".equals(language) && xml.getLocalName().equals("log")) { return false; } // special for loop which can be both expression or predicate if (getIdeaUtils().hasParentXmlTag(xml, "loop")) { XmlTag parent = PsiTreeUtil.getParentOfType(xml, XmlTag.class); if (parent != null) { String doWhile = parent.getAttributeValue("doWhile"); return "true".equalsIgnoreCase(doWhile); } } return Arrays.stream(PREDICATE_EIPS).anyMatch(n -> getIdeaUtils().hasParentXmlTag(xml, n)); } return false; } @Override public boolean isConsumerEndpoint(PsiElement element) { // xml XmlTag xml = PsiTreeUtil.getParentOfType(element, XmlTag.class); if (xml != null) { return getIdeaUtils().hasParentXmlTag(xml, "pollEnrich") || getIdeaUtils().isFromXmlTag(xml, "from", "interceptFrom"); } return false; } @Override public boolean isProducerEndpoint(PsiElement element) { XmlTag xml = PsiTreeUtil.getParentOfType(element, XmlTag.class); if (xml != null) { return getIdeaUtils().hasParentXmlTag(xml, "enrich") || getIdeaUtils().isFromXmlTag(xml, "to", "interceptSendToEndpoint", "wireTap", "deadLetterChannel"); } return false; } @Override public boolean skipEndpointValidation(PsiElement element) { // only accept xml tags from namespaces we support XmlTag xml = PsiTreeUtil.getParentOfType(element, XmlTag.class); if (xml != null) { String ns = xml.getNamespace(); // accept empty namespace which can be from testing boolean accepted = StringUtils.isEmpty(ns) || isAcceptedNamespace(ns); LOG.trace("XmlTag " + xml.getName() + " with namespace: " + ns + " is accepted namespace: " + accepted); return !accepted; // skip is the opposite } return false; } private boolean isAcceptedNamespace(String ns) { return Arrays.stream(Constants.ACCEPTED_NAMESPACES).anyMatch(ns::contains); } @Override public boolean isFromStringFormatEndpoint(PsiElement element) { return false; } @Override public boolean acceptForAnnotatorOrInspection(PsiElement element) { return false; } @Override public PsiClass getBeanClass(PsiElement element) { if (element instanceof XmlToken) { } return null; } @Override public PsiElement getPsiElementForCamelBeanMethod(PsiElement element) { return null; } @Override public boolean isPlaceForEndpointUri(PsiElement location) { XmlFile file = PsiTreeUtil.getParentOfType(location, XmlFile.class); if (file == null || file.getRootTag() == null || !isAcceptedNamespace(file.getRootTag().getNamespace())) { return false; } XmlAttributeValue value = PsiTreeUtil.getParentOfType(location, XmlAttributeValue.class, false); if (value == null) { return false; } XmlAttribute attr = PsiTreeUtil.getParentOfType(location, XmlAttribute.class); if (attr == null) { return false; } return attr.getLocalName().equals("uri") && isInsideCamelRoute(location, false); } @Override public boolean isExtensionEnabled() { return true; } private IdeaUtils getIdeaUtils() { return ServiceManager.getService(IdeaUtils.class); } }