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 * © 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 */
015 package com.ibm.rational.stp.client.samples;
016
017 import java.awt.BorderLayout;
018 import java.awt.FlowLayout;
019 import java.awt.event.ActionEvent;
020 import java.awt.event.ActionListener;
021 import java.util.Hashtable;
022
023 import javax.swing.JButton;
024 import javax.swing.JComboBox;
025 import javax.swing.JComponent;
026 import javax.swing.JFileChooser;
027 import javax.swing.JFrame;
028 import javax.swing.JOptionPane;
029 import javax.swing.JPanel;
030 import javax.swing.JScrollPane;
031 import javax.swing.JTable;
032 import javax.swing.ListSelectionModel;
033 import javax.swing.event.ListSelectionEvent;
034 import javax.swing.event.ListSelectionListener;
035 import javax.swing.table.AbstractTableModel;
036 import javax.swing.table.TableModel;
037 import javax.wvcm.Folder;
038 import javax.wvcm.PropertyRequestItem;
039 import javax.wvcm.WvcmException;
040 import javax.wvcm.PropertyNameList.PropertyName;
041 import javax.wvcm.PropertyRequestItem.PropertyRequest;
042
043 import com.ibm.rational.wvcm.stp.StpException;
044 import com.ibm.rational.wvcm.stp.StpLocation;
045 import com.ibm.rational.wvcm.stp.StpProperty;
046 import com.ibm.rational.wvcm.stp.StpProperty.MetaPropertyName;
047 import com.ibm.rational.wvcm.stp.cq.CqAction;
048 import com.ibm.rational.wvcm.stp.cq.CqAttachment;
049 import com.ibm.rational.wvcm.stp.cq.CqFieldDefinition;
050 import com.ibm.rational.wvcm.stp.cq.CqFieldValue;
051 import com.ibm.rational.wvcm.stp.cq.CqProvider;
052 import 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 */
060 public 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 }