diff --git a/bundles/org.openhab.core.config.discovery.addon.process/src/main/java/org/openhab/core/config/discovery/addon/process/ProcessAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.process/src/main/java/org/openhab/core/config/discovery/addon/process/ProcessAddonFinder.java index ca10d42aa16..c46b0d42320 100644 --- a/bundles/org.openhab.core.config.discovery.addon.process/src/main/java/org/openhab/core/config/discovery/addon/process/ProcessAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.process/src/main/java/org/openhab/core/config/discovery/addon/process/ProcessAddonFinder.java @@ -15,13 +15,13 @@ import static org.openhab.core.config.discovery.addon.AddonFinderConstants.ADDON_SUGGESTION_FINDER; import java.util.HashSet; -import java.util.List; -import java.util.Optional; +import java.util.Map; import java.util.Set; -import java.util.function.Predicate; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonMatchProperty; @@ -46,35 +46,51 @@ public class ProcessAddonFinder extends BaseAddonFinder { public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; private static final String COMMAND = "command"; + private static final String COMMAND_LINE = "commandLine"; + private static final Set SUPPORTED_PROPERTIES = Set.of(COMMAND, COMMAND_LINE); private final Logger logger = LoggerFactory.getLogger(ProcessAddonFinder.class); - // get list of running processes visible to openHAB, - // also tries to mitigate differences on different operating systems - String getProcessCommandProcess(ProcessHandle h) { - Optional command = h.info().command(); - if (command.isPresent()) { - return command.get(); - } - Optional args = h.info().arguments(); - if (args.isEmpty()) { - return ""; - } - String[] argsArray = args.get(); - if (argsArray.length < 1) { - return ""; + /** + * Private record to extract match property parameters from a {@link ProcessHandle.Info} object. + * Tries to mitigate differences on different operating systems. + */ + protected static record ProcessInfo(@Nullable String command, @Nullable String commandLine) { + + /** + * Initializes the command and commandLine fields. + * If the command field is not present, it parses the first token in the command line. + */ + protected static ProcessInfo from(ProcessHandle.Info info) { + String commandLine = info.commandLine().orElse(null); + String cmd = info.command().orElse(null); + if ((cmd == null || cmd.isEmpty()) && commandLine != null) { + cmd = commandLine; + String[] args = info.arguments().orElse(null); + if (args != null) { + for (int i = args.length - 1; i >= 0; i--) { + int index = cmd.lastIndexOf(args[i]); + if (index >= 0) { + cmd = cmd.substring(0, index); + } + } + } + cmd = cmd.stripTrailing(); + } + return new ProcessInfo(cmd, commandLine); } - return argsArray[0]; } @Override public Set getSuggestedAddons() { logger.trace("ProcessAddonFinder::getSuggestedAddons"); Set result = new HashSet<>(); - Set processList; + Set processInfos; + try { - processList = ProcessHandle.allProcesses().map(this::getProcessCommandProcess) - .filter(Predicate.not(String::isEmpty)).collect(Collectors.toUnmodifiableSet()); + processInfos = ProcessHandle.allProcesses().map(process -> ProcessInfo.from(process.info())) + .filter(info -> (info.command != null) || (info.commandLine != null)) + .collect(Collectors.toUnmodifiableSet()); } catch (SecurityException | UnsupportedOperationException unused) { logger.info("Cannot obtain process list, suggesting add-ons based on running processes is not possible"); return result; @@ -84,28 +100,30 @@ public Set getSuggestedAddons() { for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream() .filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) { - List matchProperties = method.getMatchProperties(); - List commands = matchProperties.stream() - .filter(amp -> COMMAND.equals(amp.getName())).toList(); - - if (matchProperties.size() != commands.size()) { - logger.warn("Add-on '{}' addon.xml file contains unsupported 'match-property'", candidate.getUID()); - } + Map matchProperties = method.getMatchProperties().stream() + .collect(Collectors.toMap(AddonMatchProperty::getName, AddonMatchProperty::getPattern)); - if (commands.isEmpty()) { - logger.warn("Add-on '{}' addon.xml file does not specify match property \"{}\"", candidate.getUID(), - COMMAND); + if (matchProperties.isEmpty()) { + logger.warn("Add-on info for '{}' contains no 'match-property'", candidate.getUID()); break; } - // now check if a process matches the pattern defined in addon.xml - logger.trace("Checking candidate: {}", candidate.getUID()); + Set propertyNames = new HashSet<>(matchProperties.keySet()); + boolean noSupportedProperty = !propertyNames.removeAll(SUPPORTED_PROPERTIES); - for (AddonMatchProperty command : commands) { - logger.trace("Candidate {}, pattern \"{}\"", candidate.getUID(), command.getRegex()); - boolean match = processList.stream().anyMatch(c -> command.getPattern().matcher(c).matches()); + if (!propertyNames.isEmpty()) { + logger.warn("Add-on info for '{}' contains unsupported 'match-property' [{}]", candidate.getUID(), + String.join(",", propertyNames)); - if (match) { + if (noSupportedProperty) { + break; + } + } + + logger.trace("Checking candidate: {}", candidate.getUID()); + for (ProcessInfo processInfo : processInfos) { + if (propertyMatches(matchProperties, COMMAND, processInfo.command) + && propertyMatches(matchProperties, COMMAND_LINE, processInfo.commandLine)) { result.add(candidate); logger.debug("Suggested add-on found: {}", candidate.getUID()); break;