1 package org.metricshub.wmi;
2
3 /*-
4 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5 * WMI Java Client
6 * ჻჻჻჻჻჻
7 * Copyright (C) 2023 - 2025 MetricsHub
8 * ჻჻჻჻჻჻
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
21 */
22
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.regex.Pattern;
27 import java.util.stream.Collectors;
28 import org.metricshub.wmi.exceptions.WqlQuerySyntaxException;
29
30 public abstract class WmiHelper {
31
32 /**
33 * Private constructor, as this class cannot be instantiated (it's pure static)
34 */
35 private WmiHelper() {}
36
37 public static final String DEFAULT_NAMESPACE = "ROOT\\CIMV2";
38
39 /**
40 * Pattern to detect a simple WQL select query.
41 */
42 private static final Pattern WQL_SIMPLE_SELECT_PATTERN = Pattern.compile(
43 "^\\s*SELECT\\s+(\\*|(?!SELECT|FROM|WHERE)[a-z0-9._]+" +
44 "|((?!SELECT|FROM|WHERE)[a-z0-9._]+" +
45 "\\s*,\\s*)+((?!SELECT|FROM|WHERE)[a-z0-9._]+))\\s+" +
46 "FROM\\s+((?!WHERE|FROM)\\w+)\\s*(WHERE\\s+.*)?$",
47 Pattern.CASE_INSENSITIVE
48 );
49
50 /**
51 * Check if the WQL Query respect the simple syntax in the form of
52 * <code>Select * from (where)</code> or <code>Select a,b,c from (where)</code>
53 * is valid.
54 *
55 * @param wqlQuery
56 * @return whether specified WQL query's syntax is valid or not
57 */
58 public static boolean isValidWql(final String wqlQuery) {
59 return WQL_SIMPLE_SELECT_PATTERN.matcher(wqlQuery).find();
60 }
61
62 /**
63 * The "network resource" is either just the namespace (for localhost), or \\hostname\\namespace.
64 *
65 * @param hostname Host to connect to.
66 * @param namespace The Namespace.
67 * @return A string representing the Network resource.
68 */
69 public static String createNetworkResource(final String hostname, final String namespace) {
70 Utils.checkNonNull(namespace, "namespace");
71 return hostname == null || hostname.isEmpty() ? namespace : String.format("\\\\%s\\%s", hostname, namespace);
72 }
73
74 /**
75 * @param networkResource Network resource string to test
76 * @return whether specified networkResource is local or not
77 */
78 public static boolean isLocalNetworkResource(final String networkResource) {
79 Utils.checkNonNull(networkResource, "networkResource");
80 // @formatter:off
81 // CHECKSTYLE:OFF
82 return (
83 !networkResource.startsWith("\\\\") ||
84 networkResource.startsWith("\\\\localhost\\") ||
85 networkResource.startsWith("\\\\127.0.0.1\\") ||
86 networkResource.startsWith("\\\\0:0:0:0:0:0:0:1\\") ||
87 networkResource.startsWith("\\\\::1\\") ||
88 networkResource.startsWith("\\\\0000:0000:0000:0000:0000:0000:0000:0001\\") ||
89 networkResource.toLowerCase().startsWith("\\\\" + Utils.getComputerName().toLowerCase() + "\\")
90 );
91 // CHECKSTYLE:ON
92 // @formatter:on
93 }
94
95 /**
96 * Extract the exact name of the properties from a WMI result.
97 * The interest is to retrieve the exact case of the property names, instead of
98 * the lowercase that we have at this stage.
99 * @param resultRows The result whose first row will be parsed
100 * @param wql The WQL query that was used (so we make sure to return the properties in the same order)
101 * @return a list of property names
102 * @throws IllegalStateException if the specified WQL is invalid
103 */
104 public static List<String> extractPropertiesFromResult(final List<Map<String, Object>> resultRows, final String wql) {
105 try {
106 return extractPropertiesFromResult(resultRows, WqlQuery.newInstance(wql));
107 } catch (WqlQuerySyntaxException e) {
108 throw new IllegalStateException(e);
109 }
110 }
111
112 /**
113 * Extract the exact name of the properties from a WMI result.
114 * The interest is to retrieve the exact case of the property names, instead of
115 * the lowercase that we have at this stage.
116 * Note: The exact case cannot be retrieved if result is empty, in which case all
117 * names are reported in lower case
118 * @param resultRows The result whose first row will be parsed
119 * @param wqlQuery The WQL query that was used (so we make sure to return the properties in the same order)
120 * @return a list of property names
121 */
122 public static List<String> extractPropertiesFromResult(
123 final List<Map<String, Object>> resultRows,
124 final WqlQuery wqlQuery
125 ) {
126 // If resultRows is empty, we won't be able to retrieve the actual property names
127 // with the correct case. So, we simply return the list of specified properties in the
128 // WQL query
129 if (resultRows.isEmpty()) {
130 return wqlQuery.getSelectedProperties();
131 }
132
133 // Extract the actual property names
134 final String[] resultPropertyArray = resultRows.get(0).keySet().toArray(new String[0]);
135
136 // First case: we don't have any specified properties in the WQL Query, so we just
137 // return the properties from the result set in alphabetical order
138 if (wqlQuery.getSelectedProperties().isEmpty()) {
139 Arrays.sort(resultPropertyArray, String.CASE_INSENSITIVE_ORDER);
140 return Arrays.asList(resultPropertyArray);
141 }
142
143 // Create a new list based on queryPropertyArray (with its order), but with the values
144 // from resultPropertyArray
145 final List<String> queryProperties = wqlQuery.getSelectedProperties();
146 final Map<String, String> resultProperties = Arrays
147 .asList(resultPropertyArray)
148 .stream()
149 .collect(Collectors.toMap(String::toLowerCase, property -> property));
150 return queryProperties
151 .stream()
152 .map(property -> resultProperties.getOrDefault(property.toLowerCase(), property))
153 .collect(Collectors.toList());
154 }
155 }