001 /*
002 * file QueryCommand.java
003 *
004 * Licensed Materials - Property of IBM
005 * Restricted Materials of IBM - you are allowed to copy, modify and
006 * redistribute this file as part of any program that interfaces with
007 * IBM Rational CM API.
008 *
009 * com.ibm.rational.stp.client.samples.QueryCommand
010 *
011 * (C) Copyright IBM Corporation 2004, 2008. All Rights Reserved.
012 * Note to U.S. Government Users Restricted Rights: Use, duplication or
013 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
014 */
015
016 package com.ibm.rational.stp.client.samples;
017
018 import java.util.ArrayList;
019 import java.util.Arrays;
020 import java.util.List;
021 import java.util.ListIterator;
022
023 import javax.wvcm.PropertyNameList;
024 import javax.wvcm.WvcmException;
025 import javax.wvcm.PropertyRequestItem.PropertyRequest;
026
027 import com.ibm.rational.wvcm.stp.StpLocation;
028 import com.ibm.rational.wvcm.stp.StpLocation.Namespace;
029 import com.ibm.rational.wvcm.stp.StpProvider.Domain;
030 import com.ibm.rational.wvcm.stp.cq.CqProvider;
031 import com.ibm.rational.wvcm.stp.cq.CqQuery;
032 import com.ibm.rational.wvcm.stp.cq.CqRecordType;
033 import com.ibm.rational.wvcm.stp.cq.CqResultSet;
034 import com.ibm.rational.wvcm.stp.cq.CqRowData;
035 import com.ibm.rational.wvcm.stp.cq.CqQuery.DisplayField;
036 import com.ibm.rational.wvcm.stp.cq.CqQuery.Filter.Operation;
037
038 /**
039 * A sample CM API application for defining and executing ClearQuest queries
040 * using a command-line interface. Command line arguments are
041 * <dl>
042 * <dt> <b>-name</b> <i>existing-query-location</i>
043 * <dd>Specifies an existing query to execute and possibly modify and save.</dd>
044 * <dt> <b>-save</b> [<i>new-query-location</i>]
045 * <dd>Specifies that the query is to be saved after definition or modification
046 * and before attempting execution. If no location is specified here it must be
047 * specified by a <b>-name</b> clause</dd>
048 * <dt> <b>-select</b> <i>display-field-specification</i>+
049 * <dd>Specifies the (new) display fields for the query. Requires either
050 * <b>-from</b> or <b>-name</b>
051 * <dd>
052 * <dt> <b>-from</b> <i>primary-record-type-location</i>
053 * <dd>Specifies the primary record type for the query. Requires either
054 * <b>-select</b> or <b>-name</b>. The <b>-save</b> location, if specified
055 * with <b>-name</b>, must be different from the <b>-name</b> location for
056 * this to be valid, since the primary type of an existing query cannot be
057 * changed.</dd>
058 * <dt> <b>-where</b> <i>filtering-expression-term</i>*
059 * <dd>Specifies the (new) filtering expression for the query. A <b>-where</b>
060 * clause with no filtering-expression-terms forces the query to be run without
061 * a filtering expression.</dd>
062 * <dt> <b>-min</b> <i>first-row-to-show</i>
063 * <dd>The first row of the result set to be displayed; defaults to 1, the
064 * first row. Must be positive.</dd>
065 * <dt> <b>-max</b> <i>last-row-to-show</i>
066 * <dd>the last row of the result set to be displayed; defaults to the last
067 * row. If zero, the number of rows found by the query will be reported.
068 * <dt> <b>-with</b> <i>filter-leaf-expression</i>+
069 * <dd>Specifies a value (and, optionally an operation) to fill in the dynamic
070 * filters of the query.</dd>
071 * <dt>-count
072 * <dd>Request a count of the number of rows matched by the query, even if not
073 * all rows are returned (as specified by the <b>-max</b> parameter)</dd>
074 * </dl>
075 *
076 * </pre>
077 *
078 * The implementation is divided into four methods: collectArgs, buildQuery,
079 * executeQuery, and printResults. As a sample CM API application, the
080 * buildQuery and executeQuery methods are the most instructive. To make the
081 * example realistic, a syntax for specifying display fields and filters on the
082 * command line was contrived. The parsing of this representation is implemented
083 * in the QueryUtilities class.
084 */
085 public class QueryCommand extends QueryUtilities
086 {
087 /** The CqProvider instance used by the class and its instances */
088 private static CqProvider g_provider = null;
089
090 /** The target ClearQuest user database, derived from command-line input */
091 private static String g_repo = "";
092
093 /** -name operand: The name of an existing query */
094 StpLocation m_oldName = null;
095 /** -save operand: the name under which the query is to be saved */
096 StpLocation m_saveName = null;
097 /** -from operand: the CqRecordType defining the type of records to query */
098 StpLocation m_primaryType = null;
099 /** -select operand: Used to build the DisplayField[] for the query */
100 List<String> m_select = null;
101 /** -where operand: Used to build the filtering expression for the query */
102 List<String> m_where = null;
103 /** -with operand: Used to build the FilterLeaf[] for query parameters */
104 List<String> m_with = null;
105 /** -min operand: The first row of the result set to return */
106 long m_min = 1;
107 /** -max operand: The last row of the result set to return */
108 long m_max = Long.MAX_VALUE;
109 /** Whether or not to count all rows: set by presence of -count */
110 CqQuery.ListOptions m_countRows = null;
111
112 /**
113 * Collects the command-line arguments into object fields. Variables for
114 * unspecified arguments are left at their initial value. As the arguments
115 * are collected, the resource names are converted to a complete CM API
116 * StpLocation and from that the targeted ClearQuest database is determined.
117 *
118 * @param args A String array containing the command-line arguments
119 * @throws WvcmException if StpLocation objects cannot be constructed from
120 * the resource names on the command line.
121 */
122 void collectArgs(String[] args)
123 throws WvcmException
124 {
125 ListIterator<String> argList = Arrays.asList(args).listIterator(0);
126
127 while (argList.hasNext()) {
128 String arg = argList.next();
129
130 if (arg.equals("-url"))
131 g_provider.setServerUrl(argList.next());
132 else if (arg.equals("-name"))
133 m_oldName = toSelector(Namespace.QUERY, argList.next());
134 else if (arg.equals("-save")) {
135 int next = argList.nextIndex();
136
137 if (next < args.length && !args[next].startsWith("-")) {
138 m_saveName = toSelector(Namespace.QUERY, args[next]);
139 argList.next();
140 } else
141 m_saveName = m_oldName;
142 } else if (arg.equals("-from"))
143 m_primaryType = toSelector(Namespace.RECORD, argList.next());
144 else if (arg.equals("-min"))
145 m_min = Long.parseLong(argList.next());
146 else if (arg.equals("-max"))
147 m_max = Long.parseLong(argList.next());
148 else if (arg.equals("-select"))
149 m_select = getArgList(argList);
150 else if (arg.equals("-where"))
151 m_where = getArgList(argList);
152 else if (arg.equals("-with"))
153 m_with = getArgList(argList);
154 else if (arg.equals("-count"))
155 m_countRows = CqQuery.COUNT_ROWS;
156 else if (arg.equals("-user"))
157 Utilities.g_user = argList.next();
158 else if (arg.equals("-password"))
159 Utilities.g_pass = argList.next();
160 }
161 }
162
163 /**
164 * Constructs a CqQuery proxy in which the query specification data
165 * presented on the command line is stored. If -save was specified, the
166 * proxy will use that location since the query will be executed from that
167 * location. If -save was not specified but -name was, then that location is
168 * used since either the results will come directly from the named query or
169 * the query will be done anonymously; Otherwise, a dummy location is used.
170 */
171 CqQuery buildQuery()
172 throws WvcmException
173 {
174 CqQuery query = null;
175
176 StpLocation queryLoc = m_saveName != null? m_saveName: g_provider
177 .userFriendlySelector(Domain.CLEAR_QUEST,
178 Namespace.QUERY,
179 "anonymous",
180 g_repo);
181
182 query = g_provider.cqQuery(queryLoc);
183
184 // If an existing query was specified, read it's specification from
185 // the database and use that data to initialize the query proxy.
186 if (m_oldName != null) {
187 CqQuery oldQuery = (CqQuery) g_provider.cqQuery(m_oldName)
188 .doReadProperties(QUERY_PROPERTIES);
189
190 if (m_saveName == null || m_saveName.equals(m_oldName)) {
191 query = oldQuery;
192 } else {
193 query.setPrimaryRecordType(oldQuery.getPrimaryRecordType());
194 query.setDisplayFields(oldQuery.getDisplayFields());
195 query.setFiltering(oldQuery.getFiltering());
196 }
197 } else
198 query.setFiltering(query.cqProvider().buildFilterNode(Operation.CONJUNCTION,
199 new CqQuery.Filter[0]));
200
201 // Now populate the query with modifications specified on the command
202 // line.
203
204 // If a primary record type was specified, read the required information
205 // about it from the database so that field and filter specifications
206 // can be converted properly.
207 if (m_primaryType != null) {
208 CqRecordType queryRecordType =
209 (CqRecordType) g_provider.cqRecordType(m_primaryType)
210 .doReadProperties(RECORD_TYPE_WITH_FIELDS);
211
212 query.setPrimaryRecordType(queryRecordType);
213 }
214
215 // If a -select clause was specified, parse it into a DisplayField
216 // array and add it to the query proxy.
217 if (m_select != null) {
218 DisplayField[] fields = new DisplayField[m_select.size()];
219
220 for (int i=0; i < fields.length; ++i)
221 fields[i] = parseDisplayField(query, m_select.get(i));
222
223 query.setDisplayFields(fields);
224 }
225
226 // If a -where clause was specified, parse it into a Filter object
227 // and add it to the query proxy.
228 if (m_where != null) {
229 query.setFiltering(parseFilter(query, m_where.iterator()));
230 }
231
232 return query;
233 }
234
235 /**
236 * Executes the constructed query.
237 * <p>
238 * At this point, if the user followed the rules, we should be able to
239 * execute the query. If -save was specified, we need to define the query in
240 * the database and then use doExecute to get the results. If an existing
241 * query is being executed unmodified, then we use doExecute as well;
242 * Otherwise we use CqRecordType.doQuery() passing the data from the command
243 * line to ClearQuest.
244 */
245 CqResultSet executeQuery(CqQuery input)
246 throws WvcmException
247 {
248 CqQuery query = input;
249
250 // Create the query if so directed by the input.
251 if (m_saveName != null && !m_saveName.equals(m_oldName)) {
252 System.out.println("Creating " + query + " as " + describe(query));
253 query = query.doCreateQuery(QUERY_PROPERTIES);
254
255 // Copy new name to caller's proxy
256 input.setProperty(CqQuery.USER_FRIENDLY_LOCATION,
257 query.getUserFriendlyLocation());
258 }
259
260 CqResultSet results;
261
262 // Use doExecute if the query is completely defined in the database (or
263 // will be once dirty properties in the proxy have been written). There
264 // will be dirty properties only if the input has directed that
265 // modifications to an existing query are to be written back to the same
266 // location.
267 if (query.updatedPropertyNameList().getPropertyNames().length == 0
268 || m_saveName != null) {
269 CqQuery.FilterLeaf[] params = null;
270
271 // If a -with clause was specified, parse the given
272 // parameter values into a FilterLeaf array for use in doExecute;
273 // otherwise leave the dynamic parameters null.
274
275 if (m_with != null) {
276 params = new CqQuery.FilterLeaf[query.getDynamicFilters().length];
277 String sep = "With parameter(s) ";
278
279 for (int i=0; i < params.length; ++i) {
280 String param = i < m_with.size()? m_with.get(i): "";
281
282 params[i] = parseDynamicFilter(query, i, param);
283
284 System.out.print(sep + params[i]); sep = ", ";
285 }
286
287 System.out.println("...");
288 }
289
290 // Note: If the display field and/or filter has been modified, it
291 // will be written to the definition before the query executes as a
292 // side-effect of either of these do-methods.
293 results = query.doExecute(m_min, m_max-m_min+1, m_countRows, params);
294 } else
295 results = query.getPrimaryRecordType()
296 .doQuery(query.getDisplayFields(),
297 query.getFiltering(),
298 m_min, m_max-m_min+1,
299 m_countRows);
300
301 return results;
302 }
303
304 /**
305 * Display the rows returned by the query on the console
306 * @param query The query that was executed.
307 * @param results The CqResultSet containing the rows of the result
308 * set.
309 * @throws WvcmException If interactions with the server fail or have failed
310 * to obtained the necessary information.
311 */
312 void printResults(CqQuery query, CqResultSet results)
313 throws WvcmException
314 {
315 System.out.print("Query '" + describe(query) + "' found ");
316
317 // Display the results
318 if (m_countRows != null)
319 System.out.print(results.getRowCount() + " rows ");
320
321 System.out.println("...");
322
323 if (results.hasNext()) {
324 // Print the column headings
325 DisplayField[] fields = query.getDisplayFields();
326 System.out.print("Row");
327
328 for (int i=0; i < fields.length; ++i)
329 if (fields[i].getIsVisible())
330 System.out.print("\t" + fields[i].getLabel());
331
332 System.out.println();
333
334 // Print the rows
335 while (results.hasNext()) {
336 CqRowData data = (CqRowData)results.next();
337 Object[] row = data.getValues();
338
339 System.out.print(data.getRowNumber());
340
341 for (int i=0; i < row.length; ++i)
342 System.out.print("\t" + row[i]);
343
344 System.out.println();
345 }
346 }
347 }
348
349 /**
350 * Assembles a String containing information about the query executed.
351 * @param query A CqQuery proxy for the query to be described. Must define
352 * the DISPLAY_FIELDS, PRIMARY_RECORD_TYPE, and FILTERING properties
353 * if the query has not been saved; USER_FRIENDLY_LOCATION, otherwise.
354 * @return A String containing the name of the query if saved or a stylized
355 * SQL select statement describing the query if not.
356 */
357 private String describe(CqQuery query)
358 throws WvcmException
359 {
360 PropertyNameList updated = query.updatedPropertyNameList();
361
362 if (updated.getPropertyNames().length == 0)
363 return query.getUserFriendlyLocation().toString();
364
365 StringBuffer sb = new StringBuffer();
366 DisplayField[] fields = query.getDisplayFields();
367
368 sb.append("select");
369
370 for (int i=0; i < fields.length; ++i)
371 sb.append (" " + fields[i]);
372
373 sb.append(" from ");
374 sb.append(((CqRecordType)query.getPrimaryRecordType()).getDisplayName());
375
376 String filter = query.getFiltering().toString();
377
378 if (!filter.equals(""))
379 sb.append(" where " + filter);
380
381 return sb.toString();
382 }
383
384 void run(String[] args)
385 throws WvcmException
386 {
387 collectArgs(args);
388 CqQuery query = buildQuery();
389 CqResultSet results = executeQuery(query);
390 printResults(query, results);
391 }
392
393 /**
394 * @param args
395 * @throws Exception
396 */
397 public static void main(String[] args) throws Exception
398 {
399 if (g_provider == null)
400 g_provider = Utilities.getStaticProvider();
401
402 g_repo = "";
403
404 new QueryCommand().run(args);
405 System.exit(0);
406 }
407
408 /**
409 * Collects the arguments in the command line upto, but not including, the
410 * first argument that begins with "-"
411 *
412 * @param args The command line argument iterator positioned on the argument
413 * that precedes the arguments to be collected. Must not be
414 * <b>null</b>.
415 * @return A List of the collected arguments. Will not be <b>null</b>, but
416 * may be empty.
417 */
418 private static List<String> getArgList(ListIterator<String> args)
419 {
420 List<String> list = new ArrayList<String>();
421
422 while (args.hasNext()) {
423 String arg = args.next();
424
425 if (arg.startsWith("-")) {
426 args.previous();
427 break;
428 }
429
430 list.add(arg);
431 }
432
433 return list;
434 }
435
436 /**
437 * Converts command-line input specifying a resource into an StpLocation,
438 * adding domain and namespace if omitted by the user
439 *
440 * @param namespace The expected namespace for the location. Must not be
441 * <b>null</b>.
442 * @param str The location as input by the user. Expect simple name and
443 * possibly repository information. Must not be <b>null</b>.
444 * @return An StpLocation specifying the same resource as the given string
445 * argument based on context.
446 * @throws WvcmException
447 */
448 private static StpLocation toSelector(Namespace namespace, String str)
449 throws WvcmException
450 {
451 StpLocation loc = g_provider.stpLocation(str);
452
453 if (loc.getDomain() == Domain.NONE)
454 loc = loc.recomposeWithDomain(Domain.CLEAR_QUEST);
455
456 if (loc.getNamespace() == Namespace.NONE)
457 loc = loc.recomposeWithNamespace(namespace);
458
459 if (g_repo.equals(""))
460 g_repo = loc.getRepo();
461 else if (loc.getRepo().equals(""))
462 loc = loc.recomposeWithRepo(g_repo);
463
464 return loc;
465 }
466
467 private static final PropertyRequest QUERY_PROPERTIES =
468 new PropertyRequest(CqQuery.PRIMARY_RECORD_TYPE
469 .nest(RECORD_TYPE_WITH_FIELDS),
470 CqQuery.DISPLAY_FIELDS,
471 CqQuery.DYNAMIC_FILTERS,
472 CqQuery.FILTERING,
473 CqQuery.USER_FRIENDLY_LOCATION
474 );
475 }