001/* 002 * file EditRecord.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.EditRecord 010 * 011 * (C) Copyright IBM Corporation 2005, 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 */ 015package com.ibm.rational.stp.client.samples; 016 017import java.awt.BorderLayout; 018import java.awt.FlowLayout; 019import java.awt.event.ActionEvent; 020import java.awt.event.ActionListener; 021import java.util.Hashtable; 022 023import javax.swing.JButton; 024import javax.swing.JComboBox; 025import javax.swing.JComponent; 026import javax.swing.JFileChooser; 027import javax.swing.JFrame; 028import javax.swing.JOptionPane; 029import javax.swing.JPanel; 030import javax.swing.JScrollPane; 031import javax.swing.JTable; 032import javax.swing.ListSelectionModel; 033import javax.swing.event.ListSelectionEvent; 034import javax.swing.event.ListSelectionListener; 035import javax.swing.table.AbstractTableModel; 036import javax.swing.table.TableModel; 037import javax.wvcm.Folder; 038import javax.wvcm.PropertyRequestItem; 039import javax.wvcm.WvcmException; 040import javax.wvcm.PropertyNameList.PropertyName; 041import javax.wvcm.PropertyRequestItem.PropertyRequest; 042 043import com.ibm.rational.wvcm.stp.StpException; 044import com.ibm.rational.wvcm.stp.StpLocation; 045import com.ibm.rational.wvcm.stp.StpProperty; 046import com.ibm.rational.wvcm.stp.StpProperty.MetaPropertyName; 047import com.ibm.rational.wvcm.stp.cq.CqAction; 048import com.ibm.rational.wvcm.stp.cq.CqAttachment; 049import com.ibm.rational.wvcm.stp.cq.CqFieldDefinition; 050import com.ibm.rational.wvcm.stp.cq.CqFieldValue; 051import com.ibm.rational.wvcm.stp.cq.CqProvider; 052import com.ibm.rational.wvcm.stp.cq.CqRecord; 053 054/** 055 * A sample CM API application that allows the user to browse to a record by 056 * executing a query and selecting a record from the result set. Within the 057 * record display the fields of the record can be modified under an applicable 058 * action. 059 */ 060public class EditRecord { 061 /** 062 * An extension of the ViewRecord.Viewer that adds the ability to start an 063 * action under which fields of the record can be modified. 064 */ 065 static class Viewer extends ViewRecord.Viewer { 066 Viewer(CqProvider provider) { super(provider); } 067 068 /** 069 * Reads the legal actions for the current state of a given record 070 * then displays the fields of the record 071 * (using {@link ViewRecord.Viewer#showRecord}) with a drop-down 072 * containing the actions and buttons the user can use to initiate an 073 * edit or add or remove an attachment. 074 * @see com.ibm.rational.stp.client.samples.ExecuteQuery.Viewer#view(com.ibm.rational.wvcm.stp.cq.CqRecord) 075 */ 076 public JFrame view(CqRecord selected) 077 { 078 try { 079 final CqRecord record = 080 (CqRecord)selected.doReadProperties(RECORD_PROPERTIES); 081 final JButton start = new JButton("Edit"); 082 final JComboBox choices = new JComboBox(); 083 084 for (CqAction a: record.getLegalActions()) { 085 if (a.getType() != CqAction.Type.IMPORT 086 && a.getType() != CqAction.Type.SUBMIT 087 && a.getType() != CqAction.Type.BASE 088 && a.getType() != CqAction.Type.RECORD_SCRIPT_ALIAS) 089 choices.addItem(a); 090 } 091 092 final JButton add = new JButton("Attach"); 093 final JButton remove = new JButton("Detach"); 094 final ViewRecord.RecordFrame frame = 095 showRecord("View: ", record, choices.getItemCount()==0? null: 096 new JComponent[]{choices, start, add, remove}); 097 098 add.addActionListener(new ActionListener(){ 099 /** 100 * Prompts the user for the name of a file to be attached to 101 * the selected field. If so provided, the content of the 102 * file is read into the ClearQuest database as an attachment. 103 */ 104 public void actionPerformed(ActionEvent arg0) 105 { 106 if (!ViewRecord.isAttachmentList(frame.m_fields, 107 frame.m_table.getSelectedRow())) 108 return; 109 110 JFileChooser chooser = new JFileChooser(); 111 112 if (chooser.showOpenDialog(frame) == 113 JFileChooser.APPROVE_OPTION) { 114 try { 115 String filename = 116 chooser.getSelectedFile().getAbsolutePath(); 117 CqFieldValue field = 118 frame.m_fields.get(frame.m_table.getSelectedRow()); 119 StpLocation aLoc = (StpLocation) 120 ((Folder)field.getValue()).location() 121 .child("new" + System.currentTimeMillis()); 122 CqAttachment attachment = 123 record.cqProvider().cqAttachment(aLoc); 124 125 attachment = attachment 126 .doCreateAttachment(filename, 127 null, 128 CqProvider.DELIVER_ALL); 129 130 JOptionPane.showMessageDialog(frame, 131 "Added '" + filename + "' as " + attachment); 132 133 frame.dispose(); 134 view(record); 135 } catch(Throwable t) 136 { Utilities.exception(frame, "Add Attachment", t);} 137 }}}); 138 139 remove.addActionListener(new ActionListener(){ 140 /** 141 * Allows the user to select an attachment associated with 142 * the selected field. If the user selects such an attachment 143 * it is deleted. 144 */ 145 public void actionPerformed(ActionEvent arg0) 146 { 147 if (!ViewRecord.isAttachmentList(frame.m_fields, frame.m_table.getSelectedRow())) 148 return; 149 150 try { 151 CqAttachment attachment = ViewRecord.selectAttachment 152 (frame, frame.m_fields, frame.m_table.getSelectedRow(), "Remove"); 153 154 if (attachment != null) { 155 attachment.doUnbindAll(null); 156 frame.dispose(); 157 view(record); 158 } 159 } catch(Throwable t) 160 { Utilities.exception(frame, "Remove Attachment", t);} 161 } 162 } 163 ); 164 165 start.addActionListener(new ActionListener(){ 166 /** 167 * Starts editing of the record using the action currently 168 * selected by the combo box on the view dialog. 169 */ 170 public void actionPerformed(ActionEvent arg0) 171 { 172 CqAction action = (CqAction)choices.getSelectedItem(); 173 174 try { 175 if (action.getType() == CqAction.Type.DUPLICATE) { 176 String id = JOptionPane.showInputDialog 177 (frame, "Enter ID of duplicated record"); 178 179 if (id == null) return; 180 181 action.argumentMap(new Hashtable<String, Object>()); 182 action.argumentMap() 183 .put("original", 184 record.cqProvider() 185 .cqRecord((StpLocation)record 186 .location().parent().child(id))); 187 } 188 189 edit(record, action); 190 frame.dispose(); 191 } catch (Exception ex) { 192 Utilities.exception(frame, "Duplicate Action", ex); 193 } 194 } 195 }); 196 197 return frame; 198 } catch (WvcmException ex){ 199 ex.printStackTrace(); 200 } 201 202 return null; 203 } 204 205 /** 206 * Constructs a new change context in which the given record can be 207 * edited, initiates the editing using the given action, and then 208 * displays the editable record in a new window for editing. 209 * @param selected A Record proxy for the record to be edited. Must not 210 * be null but needs no properties defined. 211 * @param action An Action proxy for the action to be used for editing. 212 * Must not be null. 213 */ 214 public void edit(CqRecord selected, CqAction action) 215 { 216 try { 217 CqProvider context = m_provider; 218 CqRecord record = context.cqRecord(selected.stpLocation()); 219 220 record = 221 (CqRecord) record.setAction(action) 222 .doWriteProperties(RECORD_PROPERTIES, 223 CqProvider.HOLD); 224 editRecord("Edit: ", record, selected); 225 } catch (WvcmException ex){ 226 Utilities.exception(null, "Start Action", ex); 227 } 228 } 229 230 /** 231 * Displays the fields of a record and allows the user to modify those 232 * fields (as permitted by the schema) until the changes are either 233 * committed back to the database or abandoned altogether by the user. 234 * @param title The title string for the window in which the record is 235 * displayed. 236 * @param record The Record proxy for the editable version of the record. 237 * @param selected The Record proxy for the original version of the 238 * record. Used to redisplay the record (in its original change context) 239 * after the editable version has either been committed or abandoned. 240 * @throws WvcmException If the properties required for the editable 241 * record are not defined by the editable record proxy. 242 */ 243 JFrame editRecord( 244 final String title, 245 final CqRecord record, 246 final CqRecord selected 247 ) throws WvcmException 248 { 249 final JFrame frame = 250 new JFrame(title + record.getUserFriendlyLocation().toString()); 251 JPanel panel = new JPanel(new BorderLayout()); 252 JPanel buttons = new JPanel(new FlowLayout()); 253 final JButton show = new JButton("View"); 254 final JButton add = new JButton("Add"); 255 final JButton remove = new JButton("Remove"); 256 final JButton cancel = new JButton("Cancel"); 257 final StpProperty.List<CqFieldValue<?>> fields = 258 record.getAllFieldValues(); 259 TableModel dataModel = new AbstractTableModel() { 260 public int getColumnCount() { return fieldMetaProperties.length; } 261 public int getRowCount() { return fields.size();} 262 public String getColumnName(int col) 263 { return fieldMetaProperties[col].getRoot().getName(); } 264 public Object getValueAt(int row, int col) 265 { 266 try { 267 return fields.get(row) 268 .getMetaProperty((MetaPropertyName<?>) 269 fieldMetaProperties[col].getRoot()); 270 } catch(Throwable ex) { 271 if (ex instanceof StpException) { 272 return ((StpException)ex).getStpReasonCode(); 273 } else { 274 String name = ex.getClass().getName(); 275 return name.substring(name.lastIndexOf(".")+1); 276 } 277 } 278 } 279 280 /** 281 * Determines whether or not the specified cell should be 282 * editable. A cell is editable only if it corresponds to the 283 * VALUE meta-property of a field whose REQUIREDNESS is not 284 * READ_ONLY 285 */ 286 public boolean isCellEditable(int row, int col) { 287 if (fieldMetaProperties[col].getRoot() 288 .equals(StpProperty.VALUE)) { 289 CqFieldValue field = (CqFieldValue)fields.get(row); 290 291 try { 292 return field.getRequiredness() 293 != CqFieldDefinition.Requiredness.READ_ONLY; 294 } catch (WvcmException ex) { 295 Utilities.exception(frame, "Field Requiredness", ex); 296 } 297 } 298 299 return false; 300 } 301 302 /** 303 * Sets a new value into the record field that corresponds to 304 * the specified row. Uses CqFieldValue.initialize() to interpret 305 * the value with respect to the type of the field and then 306 * sets the updated CqFieldValue into the proxy for writing to 307 * the repository. 308 */ 309 public void setValueAt(Object aValue, int row, int col) 310 { 311 if (fieldMetaProperties[col].getRoot() 312 .equals(StpProperty.VALUE)) { 313 CqFieldValue<Object> field = 314 (CqFieldValue<Object>)fields.get(row); 315 316 try { 317 field.initialize(aValue); 318 } catch (WvcmException e) { 319 Utilities.exception(frame, title, e); 320 } 321 record.setFieldInfo(field.getFieldName(), field); 322 } 323 } 324 private static final long serialVersionUID = 1L; 325 }; 326 final JTable table = new JTable(dataModel); 327 328 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 329 show.setEnabled(false); 330 add.setEnabled(false); 331 remove.setEnabled(false); 332 333 // Ask to be notified of selection changes. 334 ListSelectionModel rowSM = table.getSelectionModel(); 335 rowSM.addListSelectionListener(new ListSelectionListener() { 336 /** 337 * Enables the buttons in the dialog based on the type of 338 * field selected in the table. 339 */ 340 public void valueChanged(ListSelectionEvent e) { 341 if (!e.getValueIsAdjusting()){ 342 int[] selected = table.getSelectedRows(); 343 show.setEnabled(false); 344 add.setEnabled(false); 345 remove.setEnabled(false); 346 for (int i=0; i <selected.length; ++i) 347 if (ViewRecord.getRecordReferencedAt(fields, selected[i]) != null) { 348 show.setEnabled(true); 349 } else if (ViewRecord.isAttachmentList(fields, selected[i])) { 350 show.setEnabled(true); 351 add.setEnabled(true); 352 remove.setEnabled(true); 353 } 354 } 355 } 356 }); 357 358 buttons.add(show); 359 show.addActionListener(new ActionListener(){ 360 /** 361 * Invokes a view method on the value of the field selected 362 * in the table. 363 */ 364 public void actionPerformed(ActionEvent arg0) 365 { 366 int[] selected = table.getSelectedRows(); 367 368 for (int i =0; i < selected.length; ++i) { 369 int row = selected[i]; 370 CqRecord record = 371 ViewRecord.getRecordReferencedAt(fields, row); 372 373 if (record != null) { 374 view(record); 375 } else if (ViewRecord.isAttachmentList(fields, row)) { 376 view(ViewRecord.selectAttachment(frame, 377 fields, row, 378 "View")); 379 } 380 } 381 } 382 }); 383 384 JButton deliver = new JButton("Deliver"); 385 386 buttons.add(deliver); 387 deliver.addActionListener(new ActionListener(){ 388 /** 389 * Delivers the modified record to the repository, 390 * closes the (now empty) change context, deletes the 391 * current viewer, and opens a new viewer on the updated 392 * record (in its original change context). 393 */ 394 public void actionPerformed(ActionEvent arg0) 395 { 396 try { 397 int mode = frame.getDefaultCloseOperation(); 398 399 record.doWriteProperties(null, CqProvider.DELIVER); 400 frame.dispose(); 401 view(selected).setDefaultCloseOperation(mode); 402 } catch (WvcmException ex) { 403 Utilities.exception(frame, "Deliver failed", ex); 404 } 405 } 406 }); 407 408 buttons.add(cancel); 409 cancel.addActionListener(new ActionListener(){ 410 /** 411 * Discards all modifications to the record, 412 * closes the (now empty) change context, deletes the 413 * current viewer, and opens a new viewer on the original 414 * record (in its original change context). 415 */ 416 public void actionPerformed(ActionEvent arg0) 417 { 418 try { 419 int mode = frame.getDefaultCloseOperation(); 420 421 record.doRevert(null); 422 frame.dispose(); 423 424 if (mode == JFrame.EXIT_ON_CLOSE) 425 System.exit(0); 426 427 view(selected) 428 .setDefaultCloseOperation(mode); 429 } catch (WvcmException ex) { 430 Utilities.exception(frame, "Cancel failed", ex); 431 } 432 } 433 }); 434 435 buttons.add(add); 436 add.addActionListener(new ActionListener(){ 437 /** 438 * Adds an file specified by the user to the database as an 439 * attachment to the selected record field. 440 */ 441 public void actionPerformed(ActionEvent arg0) 442 { 443 JFileChooser chooser = new JFileChooser(); 444 int returnVal = chooser.showOpenDialog(frame); 445 446 if (returnVal == JFileChooser.APPROVE_OPTION) { 447 try { 448 String filename = 449 chooser.getSelectedFile().getAbsolutePath(); 450 CqFieldValue field = 451 (CqFieldValue)fields.get(table.getSelectedRow()); 452 StpLocation aLoc = (StpLocation) 453 ((Folder)field.getValue()).location() 454 .child("new" + System.currentTimeMillis()); 455 CqAttachment attachment = 456 ((CqProvider)record.provider()).cqAttachment(aLoc); 457 458 attachment = attachment 459 .doCreateAttachment(filename, 460 null, CqProvider.HOLD); 461 462 JOptionPane.showMessageDialog(frame, 463 "Added '" + filename + "' as " + attachment 464 + " (pending delivery)"); 465 } catch(Throwable t) 466 { Utilities.exception(frame, "Add Attachment", t);} 467 }}}); 468 469 buttons.add(remove); 470 remove.addActionListener(new ActionListener(){ 471 /** 472 * Removes a user-specified entry from the attachment folder 473 * of the selected attachment field 474 */ 475 public void actionPerformed(ActionEvent arg0) 476 { 477 try { 478 CqAttachment attachment = ViewRecord.selectAttachment 479 (frame, fields, table.getSelectedRow(), "Remove"); 480 481 if (attachment != null) 482 attachment.doUnbindAll(null); 483 } catch(Throwable t) 484 { Utilities.exception(frame, "Remove Attachment", t);} 485 } 486 } 487 ); 488 489 panel.add(new JScrollPane(table), BorderLayout.CENTER); 490 panel.add(buttons, BorderLayout.SOUTH); 491 frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 492 frame.setContentPane(panel); 493 frame.setBounds(300, 300, 600, 300); 494 frame.setVisible(true); 495 496 return frame; 497 } 498 } 499 500 /** The field meta-properties requested and displayed */ 501 static PropertyRequestItem.NestedPropertyName<?>[] fieldMetaProperties 502 = ViewRecord.fieldMetaProperties; 503 504 /** The record properties read prior to editing the record */ 505 final static PropertyRequest RECORD_PROPERTIES = 506 new PropertyRequest( 507 CqRecord.USER_FRIENDLY_LOCATION, 508 CqRecord.STABLE_LOCATION, 509 CqRecord.LEGAL_ACTIONS.nest( 510 CqAction.USER_FRIENDLY_LOCATION, 511 CqAction.DISPLAY_NAME, 512 CqAction.TYPE), 513 CqRecord.ALL_FIELD_VALUES.nest( 514 StpProperty.VALUE.nest(fieldMetaProperties)) 515 ); 516 517 /** 518 * Extends the ViewRecord example by adding buttons on the record view 519 * window to start an edit action, add an attachment, or remove an 520 * attachment 521 * @param args not used 522 */ 523 public static void main( 524 String[] args) 525 { 526 try { 527 CqProvider provider = Utilities.getProvider().cqProvider(); 528 ExecuteQuery.run("Edit Record", provider, new Viewer(provider)); 529 } catch(Throwable ex) { 530 Utilities.exception(null, "Edit Record", ex); 531 System.exit(0); 532 } 533 } 534 535 /** 536 * Determines if the given PropertyName's have the same root name and 537 * namespace (ignoring any nested properties they may have). 538 * @param n1 A PropertyName. Must not be null 539 * @param n2 Another PropertyName. Must not be null 540 * @return true if n1 and n2 have the same root property name. 541 */ 542 static boolean matches(PropertyName n1, PropertyName n2) 543 { 544 String ns = n1.getNamespace(); 545 546 return n1.getName().equals(n2.getName()) && 547 (ns == null? n2.getNamespace()==null: ns.equals(n2.getNamespace())); 548 } 549}