001/*
002 * file ViewRecord.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.ViewRecord
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 */
015
016package com.ibm.rational.stp.client.samples;
017
018import static com.ibm.rational.stp.client.samples.ExecuteQuery.setUserFriendlyLocation;
019import java.awt.BorderLayout;
020import java.awt.Component;
021import java.awt.FlowLayout;
022import java.awt.event.ActionEvent;
023import java.awt.event.ActionListener;
024import java.io.File;
025
026import javax.swing.JButton;
027import javax.swing.JComponent;
028import javax.swing.JFrame;
029import javax.swing.JOptionPane;
030import javax.swing.JPanel;
031import javax.swing.JScrollPane;
032import javax.swing.JTable;
033import javax.swing.ListSelectionModel;
034import javax.swing.event.ListSelectionEvent;
035import javax.swing.event.ListSelectionListener;
036import javax.swing.table.AbstractTableModel;
037import javax.swing.table.TableModel;
038import javax.wvcm.ResourceList;
039import javax.wvcm.WvcmException;
040import javax.wvcm.PropertyRequestItem.NestedPropertyName;
041import javax.wvcm.PropertyRequestItem.PropertyRequest;
042
043import com.ibm.rational.wvcm.stp.StpException;
044import com.ibm.rational.wvcm.stp.StpProperty;
045import com.ibm.rational.wvcm.stp.StpResource;
046import com.ibm.rational.wvcm.stp.StpProperty.MetaPropertyName;
047import com.ibm.rational.wvcm.stp.cq.CqAttachment;
048import com.ibm.rational.wvcm.stp.cq.CqAttachmentFolder;
049import com.ibm.rational.wvcm.stp.cq.CqFieldValue;
050import com.ibm.rational.wvcm.stp.cq.CqProvider;
051import com.ibm.rational.wvcm.stp.cq.CqRecord;
052import com.ibm.rational.wvcm.stp.cq.CqFieldValue.ValueType;
053
054/**
055 * View the current state of a record
056 */
057public class ViewRecord {
058    /**
059     * An instance of ExecuteQuery.Viewer that allows (read-only) viewing of a
060     * Record resource.
061     */
062    static class Viewer implements ExecuteQuery.Viewer {
063        Viewer(CqProvider provider) {  m_provider = provider;  }
064        
065        /**
066         * @see com.ibm.rational.stp.client.samples.ExecuteQuery.Viewer#view(com.ibm.rational.wvcm.stp.cq.CqRecord)
067         */
068        public JFrame view(CqRecord record)
069        {
070            if (record != null) try {
071                record = (CqRecord)record.doReadProperties(RECORD_PROPERTIES);
072                return showRecord("View: ", record, null);
073            } catch (WvcmException ex){
074                ex.printStackTrace();
075            }
076            
077            return null;
078        }
079        
080        /**
081         * Displays the content of an Attachment resource in a text window.
082         * 
083         * @param attachment An Attachment proxy for the attachment to be
084         *            displayed.
085         */
086        public void view(CqAttachment attachment)
087        {
088            if (attachment != null) try{
089                File file = File.createTempFile("attach", "tmp");
090                
091                attachment.doReadContent(file.getAbsolutePath(), null);
092                BrowserDataModel.showFile(attachment.getDisplayName(), file);
093            } catch(Throwable ex) {
094                Utilities.exception(null, "View Attachment", ex);
095            }
096        }
097        
098        /**
099         * Displays the ALL_FIELD_VALUES property of a record resource in a
100         * table. The columns of the table are determined by the content of the
101         * {@link #fieldMetaProperties} array. The display of most objects is
102         * implemented by the object's own toString() method.
103         * 
104         * @param title The title string for the window that contains the table
105         * @param record The Record proxy for the record to be displayed. Must
106         *            define the ALL_FIELD_VALUES property and the FieldValues
107         *            in that property must define the meta-properties listed in
108         *            the {@link #fieldMetaProperties} array.
109         * @param future Additional window components to be displayed along with
110         *            the property table. (Used by extensions to this example).
111         * @return A RecordFrame structure containing the GUI components created
112         *         by this method.
113         * @throws WvcmException
114         */
115        RecordFrame showRecord(String title, 
116                               CqRecord record, 
117                               JComponent[] future) throws WvcmException 
118        {
119            final StpProperty.List<CqFieldValue<?>> fields = 
120                record.getAllFieldValues();
121            
122            // Define a table model in which each row is a property of the
123            // record resource and each column is a meta-property of the
124            // property, such as its name, type, and value;
125            TableModel dataModel = new AbstractTableModel() {
126                private static final long serialVersionUID = 1L;
127                public int getColumnCount() { return fieldMetaProperties.length; }
128                public int getRowCount() { return fields.size();}
129                public Object getValueAt(int row, int col) 
130                    { 
131                        try {
132                            Object val = fields.get(row)
133                                .getMetaProperty((MetaPropertyName<?>)
134                                                 fieldMetaProperties[col].getRoot());
135                            
136                            if (val instanceof CqRecord)
137                                return ((CqRecord)val).getUserFriendlyLocation()
138                                    .getName();
139                            else if (val instanceof CqAttachmentFolder)
140                                return ((CqAttachmentFolder)val)
141                                    .getAttachmentList().size()
142                                    + " attachments";
143                            else
144                                return val;
145                                
146                        } catch(Throwable ex) {
147                            if (ex instanceof StpException) {
148                                return ((StpException)ex).getStpReasonCode();  
149                              } else {
150                                  String name = ex.getClass().getName();
151                                  return name.substring(name.lastIndexOf(".")+1);
152                              }
153                        }
154                    }
155                public String getColumnName(int col)
156                    { return fieldMetaProperties[col].getRoot().getName(); }
157            };
158            
159            // Define the display layout
160            final JTable table = new JTable(dataModel);
161            final JPanel panel = new JPanel(new BorderLayout());
162            final JPanel buttons = new JPanel(new FlowLayout());
163            final JButton button = new JButton("View");
164            final RecordFrame frame = 
165                new RecordFrame(title + record.getUserFriendlyLocation().toString(),
166                                table, fields);
167    
168            // Add a button for viewing a selected record or attachment field
169            buttons.add(button, BorderLayout.SOUTH);
170            button.setEnabled(false);
171            button.addActionListener(new ActionListener(){
172                    public void actionPerformed(ActionEvent arg0)
173                    {
174                        int[] selected = table.getSelectedRows();
175                        
176                        for (int i =0; i < selected.length; ++i) {
177                            int row = selected[i];
178                            if (isAttachmentList(fields, row)) {
179                                view(selectAttachment(frame, fields, row, "View"));
180                            } else {
181                                view(getRecordReferencedAt(fields, row));
182                            }
183                        }
184                    }
185                });
186            
187            // Add more buttons (used by later examples) 
188            if (future != null)
189                for(int i = 0; i < future.length; ++i) buttons.add(future[i]);
190    
191            table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
192    
193            // Ask to be notified of selection changes and enable the view button
194            // only if a record-valued field or attachment field is selected
195            ListSelectionModel rowSM = table.getSelectionModel();
196            rowSM.addListSelectionListener(new ListSelectionListener() {
197                public void valueChanged(ListSelectionEvent e) {
198                    if (!e.getValueIsAdjusting()){
199                        int[] selected = table.getSelectedRows();
200                        button.setEnabled(false);
201    
202                        for (int i=0; i <selected.length; ++i)
203                            if (getRecordReferencedAt(fields, selected[i]) != null
204                                || isAttachmentList(fields, selected[i])) {
205                                button.setEnabled(true);
206                                break;
207                            }
208                    }
209                }
210            });
211    
212            panel.add(new JScrollPane(table), BorderLayout.CENTER);
213            panel.add(buttons, BorderLayout.SOUTH);
214            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
215            frame.setContentPane(panel);
216            frame.setBounds(g_windowX += 10, g_windowY += 10, 600, 300);
217            frame.setVisible(true);
218            
219            return frame;
220        }
221    
222        protected CqProvider m_provider;
223    }
224    
225    /** 
226     * Properties to be requested from each record field valie, including
227     * specific additional information for attachments 
228     */
229    static final PropertyRequest VALUE_PROPERTIES =
230        new PropertyRequest(StpResource.USER_FRIENDLY_LOCATION,
231                            CqAttachmentFolder.ATTACHMENT_LIST
232                                .nest(CqAttachment.DISPLAY_NAME,
233                                      CqAttachment.FILE_NAME,
234                                      CqAttachment.FILE_SIZE,
235                                      CqAttachment.DESCRIPTION));
236    
237    /** The field meta-properties to be requested and displayed */
238    static final NestedPropertyName[] fieldMetaProperties = 
239        new PropertyRequest(CqFieldValue.NAME, 
240                            CqFieldValue.REQUIREDNESS, 
241                            CqFieldValue.TYPE,
242                            CqFieldValue.VALUE.nest(VALUE_PROPERTIES)).toArray();
243                  
244    /** 
245     * The PropertyRequest to use when reading data from a record to be
246     * displayed by this viewer. Note the level of indirection used to request
247     * the meta-properties of the fields in the ALL_FIELD_VALUES list rather
248     * than those meta-properties of the ALL_FIELD_VALUES property itself.
249     */
250    final static PropertyRequest RECORD_PROPERTIES =
251        new PropertyRequest(CqRecord.USER_FRIENDLY_LOCATION,
252                            CqRecord.ALL_FIELD_VALUES
253                            .nest(StpProperty.VALUE.nest(fieldMetaProperties)));
254    
255    /**
256     * Examines the property value of a field and, if it references a record,
257     * returns a proxy for the referenced record. Otherwise it returns null.
258     * @param fields The Property.List to examine.
259     * @param row The index of the element in the list to examine.
260     * @return A Record proxy if the field references a record; null otherwise
261     */
262    static CqRecord getRecordReferencedAt(StpProperty.List<CqFieldValue<?>> fields, 
263                                          int row)
264    {
265        try {
266            CqFieldValue field = fields.get(row);
267            
268            if (field.getFieldType() == ValueType.RESOURCE 
269                && field.getValue() instanceof CqRecord)
270                return (CqRecord)field.getValue();
271        } catch (WvcmException ex) { ex.printStackTrace(); }
272    
273        return null;
274    }
275    
276    /**
277     * Whether or not the indicated field is an attachment field.
278     * @param fields The Property.List to examine.
279     * @param row The index of the element in the list to examine.
280     * @return true iff the field at the given index is an attachment field
281     */
282    static boolean isAttachmentList(StpProperty.List<CqFieldValue<?>> fields, 
283                                    int row)
284    
285    {
286        if (row >= 0) try {
287            CqFieldValue field = fields.get(row);
288            
289            return field.getFieldType() == ValueType.ATTACHMENT_LIST;
290        } catch (WvcmException ex) { ex.printStackTrace(); }
291    
292        return false;
293    }
294    
295    /**
296     * Presents to the user a list of the attachments associated with a
297     * specified field of a record and allows the user to select one.
298     * 
299     * @param frame The parent frame for the dialog generated by this method.
300     * @param fields The Property.List to examine.
301     * @param row The index of the element in the list to examine.
302     * @param op A string identifying the operation that will be performed on
303     *            the selected attachment.
304     * @return An Attachment proxy for the selected attachment; null if the user
305     *         chooses to make no selection.
306     */
307    static CqAttachment 
308    selectAttachment(Component frame,
309                     StpProperty.List<CqFieldValue<?>> fields,
310                     int row,
311                     String op)
312    {
313        CqFieldValue field = fields.get(row);
314        
315        try {
316            CqAttachmentFolder folder = (CqAttachmentFolder)field.getValue();
317            ResourceList<CqAttachment> attachments = setUserFriendlyLocation
318                (folder.doReadProperties(ATTACHMENT_PROPERTIES)
319                    .getProperty(CqAttachmentFolder.ATTACHMENT_LIST));
320            
321            if (attachments.size() > 0) {
322                CqAttachment attachment =
323                    (CqAttachment) JOptionPane
324                        .showInputDialog(frame,
325                                         "Choose an Attachment to " + op,
326                                         op + " Attachment",
327                                         JOptionPane.INFORMATION_MESSAGE,
328                                         null,
329                                         attachments.toArray(),
330                                         attachments.get(0));
331                
332                return attachment;
333            }
334         } catch(Throwable t) { Utilities.exception(frame, op + " Attachment", t);}
335    
336        return null;
337    }
338    
339    /**
340     * The attachment properties to be displayed in the attachment selection
341     * list generated by {@link #selectAttachment}.
342     */
343    final static PropertyRequest ATTACHMENT_PROPERTIES =
344        new PropertyRequest(CqAttachmentFolder.ATTACHMENT_LIST
345                                .nest(CqAttachment.DISPLAY_NAME,
346                                      CqAttachment.FILE_NAME,
347                                      CqAttachment.FILE_SIZE,
348                                      CqAttachment.DESCRIPTION,
349                                      CqAttachment.USER_FRIENDLY_LOCATION));
350    
351    /**
352     * The main program for the ViewRecord example. Instantiates a Provider and
353     * then invokes the ExecuteQuery example, passing in a version of Viewer 
354     * that displays fields of a ClearQuest record.
355     * @param args not used.
356     */
357    public static void main(String[] args)
358    {
359        try {
360            CqProvider provider = Utilities.getProvider().cqProvider();
361            ExecuteQuery.run("View Record", provider, new Viewer(provider));
362        } catch(Throwable ex) {
363            Utilities.exception(null, "View Record", ex);
364            System.exit(0);
365        }
366    }
367    
368    /**
369     * An extension of JFrame for the record field display, 
370     * exposing to clients the JTable component of the frame and
371     * the field list that is being displayed in the table.
372     */
373    static class RecordFrame extends JFrame
374    {
375        RecordFrame(String title,
376                    JTable table,
377                    StpProperty.List fields)
378        {
379            super(title);
380    
381            m_table = table;
382            m_fields = fields;
383        }
384    
385        JTable m_table;
386        StpProperty.List<CqFieldValue<?>> m_fields;
387        private static final long serialVersionUID = 1L;
388    }
389    
390    /** X offset for the next window to be displayed */
391    private static int g_windowX = 200;
392    
393    /** Y offset for the next window to be displayed */
394    private static int g_windowY = 200;
395}