View Javadoc
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 }