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 * © 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 }