View Javadoc
1   package org.metricshub.wbem.client;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WBEM Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 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.HashMap;
24  import java.util.LinkedHashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Optional;
28  import java.util.Set;
29  import java.util.regex.Matcher;
30  import java.util.regex.Pattern;
31  import java.util.stream.Collectors;
32  import java.util.stream.Stream;
33  import org.metricshub.wbem.client.exceptions.WqlQuerySyntaxException;
34  
35  /**
36   * Handler for WQL query.
37   *
38   */
39  public class WqlQuery {
40  
41  	private WqlQuery() {}
42  
43  	private static final String ID = "[^\\s]+";
44  	private static final String LIST_SEPARATOR = "\\s*,\\s*";
45  
46  	private static final Pattern CHECK_SELECT_PATTERN = Pattern.compile(
47  		"^\\s*SELECT\\s+(\\*|(?!SELECT|FROM|WHERE)\\w+|((?!SELECT|FROM|WHERE)\\w+\\s*,\\s*)+((?!SELECT|FROM|WHERE)\\w+))\\s+FROM\\s+((?!SELECT|WHERE|FROM)\\w+)\\s*?$",
48  		Pattern.CASE_INSENSITIVE
49  	);
50  
51  	private static final Pattern EXTRACT_SELECT_PATTERN = Pattern.compile(
52  		"^\\s*SELECT\\s+(.+)\\s+FROM\\s+(" + ID + ")\\s*$",
53  		Pattern.CASE_INSENSITIVE
54  	);
55  	private static final int SELECT_GROUP_CLASSNAME = 2;
56  	private static final int SELECT_GROUP_FIELDS = 1;
57  	private static final int SELECT_GROUP_COUNT = 2;
58  
59  	private String className;
60  	private Set<String> properties;
61  	private List<String> originalProperties;
62  
63  	/**
64  	 * Constructor for WQL query.
65  	 *
66  	 * @param query the WQL query.
67  	 * @throws WqlQuerySyntaxException On WQL Syntax exception.
68  	 */
69  	public static WqlQuery parseQuery(final String query) throws WqlQuerySyntaxException {
70  		Utils.checkNonNull(query, "query");
71  
72  		if (CHECK_SELECT_PATTERN.matcher(query).find()) {
73  			final WqlQuery wqlQuery = new WqlQuery();
74  			wqlQuery.parseSelect(query);
75  			return wqlQuery;
76  		}
77  		throw new WqlQuerySyntaxException(query);
78  	}
79  
80  	public String getClassName() {
81  		return className;
82  	}
83  
84  	public String[] getPropertiesArray() {
85  		return properties.isEmpty()
86  			? null
87  			: properties
88  				.stream()
89  				.filter(property -> !WbemCimDataHandler.PATH_PROPERTY.equalsIgnoreCase(property))
90  				.toArray(String[]::new);
91  	}
92  
93  	public Set<String> getProperties() {
94  		return properties;
95  	}
96  
97  	public List<String> getOriginalProperties() {
98  		return originalProperties;
99  	}
100 
101 	public boolean hasDuplicateProperties() {
102 		return properties != null && originalProperties != null && properties.size() != originalProperties.size();
103 	}
104 
105 	private void parseSelect(final String query) throws WqlQuerySyntaxException {
106 		final Matcher matcher = EXTRACT_SELECT_PATTERN.matcher(query);
107 		if (!matcher.find()) {
108 			throw new WqlQuerySyntaxException(query);
109 		}
110 		if (matcher.groupCount() < SELECT_GROUP_COUNT) {
111 			throw new WqlQuerySyntaxException(query);
112 		}
113 
114 		className = matcher.group(SELECT_GROUP_CLASSNAME);
115 
116 		final String fieldsList = matcher.group(SELECT_GROUP_FIELDS);
117 		final String[] fieldArray = Optional
118 			.ofNullable(fieldsList)
119 			.filter(s -> !"*".equals(s))
120 			.map(s -> s.split(LIST_SEPARATOR))
121 			.orElse(new String[0]);
122 
123 		originalProperties = Stream.of(fieldArray).map(String::trim).collect(Collectors.toList());
124 
125 		// using a map, so the properties will keep the case and the order of the query.
126 		final Map<String, String> originalMap = new HashMap<>();
127 		originalProperties
128 			.stream()
129 			.forEach(property -> originalMap.computeIfAbsent(property.toLowerCase(), prop -> property));
130 
131 		properties =
132 			originalProperties
133 				.stream()
134 				.map(property -> originalMap.get(property.toLowerCase()))
135 				.collect(Collectors.toCollection(LinkedHashSet::new));
136 	}
137 }