001/*
002 * file Browser.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.Browser
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 */
015package com.ibm.rational.stp.client.samples;
016
017import java.awt.BorderLayout;
018import java.awt.Cursor;
019import java.awt.FlowLayout;
020import java.awt.event.ActionEvent;
021import java.awt.event.ActionListener;
022import java.io.File;
023import java.io.FileOutputStream;
024import java.util.Arrays;
025import java.util.Comparator;
026import java.util.List;
027
028import javax.swing.DefaultComboBoxModel;
029import javax.swing.JButton;
030import javax.swing.JComboBox;
031import javax.swing.JFrame;
032import javax.swing.JLabel;
033import javax.swing.JOptionPane;
034import javax.swing.JPanel;
035import javax.wvcm.PropertyRequestItem;
036import javax.wvcm.Resource;
037import javax.wvcm.ResourceList;
038import javax.wvcm.WvcmException;
039import javax.wvcm.PropertyNameList.PropertyName;
040import javax.wvcm.PropertyRequestItem.NestedPropertyName;
041import javax.wvcm.PropertyRequestItem.PropertyRequest;
042
043import com.ibm.rational.stp.client.samples.BrowserDataModel.Operations;
044import com.ibm.rational.wvcm.stp.StpException;
045import com.ibm.rational.wvcm.stp.StpProperty;
046import com.ibm.rational.wvcm.stp.StpPropertyException;
047import com.ibm.rational.wvcm.stp.StpProvider;
048import com.ibm.rational.wvcm.stp.StpResource;
049import com.ibm.rational.wvcm.stp.StpException.StpReasonCode;
050import com.ibm.rational.wvcm.stp.StpProperty.MetaPropertyName;
051import com.ibm.rational.wvcm.stp.cq.CqDbSet;
052import com.ibm.rational.wvcm.stp.cq.CqProvider;
053import com.ibm.rational.wvcm.stp.cq.CqUserDb;
054
055/**
056 * This is a SWING application that reads and displays the properties of a
057 * resource using the generic Resource/Property interfaces of the CM API.
058 * Starting from the name or type of a resource, a user can read and display
059 * resource properties, following references from one resource to the next.
060 * <p>
061 * The user can start browsing either by entering the object selector for the
062 * resource to be viewed or the user can select a resource type and ask the API
063 * for all known folders containing that resource type.
064 * <p>
065 * In the latter case, the list of folders is displayed by
066 * {@link Browser#showResourceList Browser.showResourceList}. By selecting an
067 * entry in the display for a Resource, ResourceList or Property.List value, the
068 * user can traverse to that object or list of objects and display them.
069 * <p>
070 * In the former case, the user types in the object selector for a specific
071 * resource he is interested in, such as a specific record. All properties of
072 * this record are then displayed by {@link Browser#showResource 
073 * Browser.showResource} The Property.List property Record.ALL_FIELD_VALUES
074 * contains all the schema-defined fields of the record and could be selected to
075 * view those fields.
076 * <p>
077 * If a resource has content, that data can be displayed using
078 * {@link BrowserDataModel#showContent BrowserDataModel.showContent()}.
079 */
080public class Browser {
081
082    /**
083     * Displays a list of folders in which are found resources of a specified
084     * type or a resource at a given location.
085     * 
086     * @param name
087     *            Either a String specifying the type of resource for which a
088     *            folder list is to be generated or a String containing the
089     *            location of the resource to be displayed. Must not be null.
090     */
091    static void showRoot(String name) {
092        // We've had to defer allocation of the provider until we're here on the
093        // event thread to satisfy the Apartment threading model used by COM.
094        if (g_provider == null)
095            try {
096                g_provider = Utilities.getProvider();
097            } catch (Exception ex) {
098                Utilities.exception(null, "Provider", ex);
099            }
100        try {
101            if (name.equals(CLEAR_QUEST_DB_SETS)) {
102                showResourceList("ClearQuest Connections",
103                                 g_provider.cqProvider().doGetDbSetList(PROPS),
104                                 PROPS.toArray(),
105                                 true);
106            } else if (name.equals(CLEAR_CASE_WORKSPACES)) {
107                showResourceList("ClearCase Workspaces",
108                                 g_provider.ccProvider().getClientViewList(PROPS),
109                                 PROPS.toArray(),
110                                 true);
111            } else {
112                showResource("Resource " + name, (StpResource) g_provider
113                    .resource(g_provider.location(name)), 
114                    META_PROPS.toArray(), true);
115            }
116        } catch (Throwable ex) {
117            Utilities.exception(null, "Show root", ex);
118        }
119    }
120    
121    /**
122     * Displays the elements of a ResourceList in a table.
123     * 
124     * @param title
125     *            The title for the window in which the resources are displayed.
126     * @param resources
127     *            The ResoureList containing the resources to be displayed.
128     * @param properties
129     *            A PropertyName array containing the PropertyName's for the
130     *            properties to be displayed for each Resource. These properties
131     *            should be defined in the proxies in the ResourceList.
132     * @return The handle on the window in which the resource list is displayed.
133     */
134    static JFrame showResourceList(String title,
135                                   final ResourceList<? extends StpResource> resources,
136                                   final NestedPropertyName<?>[] properties,
137                                   final boolean validOnly) {
138        // Generate the data model for displaying the resource list,
139        // one resource per row, one property per column
140        BrowserDataModel dataModel = new BrowserDataModel() {
141            public int getColumnCount() {
142                return 1+properties.length;
143            }
144
145            public int getRowCount() {
146                return resources.size();
147            }
148
149            public Object getValueAt(int row, int col) {
150                if (col == 0)
151                    return Utilities.resourceType(resources.get(row));
152                
153                try {
154                    return resources.get(row).getProperty(properties[col - 1]
155                        .getRoot());
156                } catch (Throwable ex) {
157                    // If an object name selector was to be displayed
158                    // substitute the resource location if that property
159                    // is unavailable. (Not all resources have them.)
160                    return StpResource.USER_FRIENDLY_LOCATION
161                        .equals(properties[col-1]) ? resources.get(row)
162                        : exceptionImage(ex, resources.get(row));
163                }
164            }
165
166            public String getColumnName(int col) {
167                return col==0? "resource-type": properties[col-1].getRoot().getName();
168            }
169
170            // The only showable objects are Resource's, so use showResource()
171            void show(Object viewable) throws WvcmException {
172                StpResource res = (StpResource) viewable;
173
174                try {
175                    m_frame.setCursor(Cursor
176                        .getPredefinedCursor(Cursor.WAIT_CURSOR));
177                    showResource(Utilities.resourceType(res) + " "
178                        + res.location().string(), res, 
179                        META_PROPS.toArray(), validOnly);
180                } finally {
181                    m_frame.setCursor(Cursor
182                        .getPredefinedCursor(Cursor.DEFAULT_CURSOR));
183                }
184            }
185
186            // Since each row represents a resource, each row is always
187            // showable
188            Object getViewable(int row) {
189                return resources.get(row);
190            }
191
192            private static final long serialVersionUID = 1L;
193        };
194
195        return dataModel.showModel(title, false);
196    }
197
198    /**
199     * Retrieves all properties of a given resource and displays them in a
200     * table.
201     * 
202     * @param title
203     *            The title for the window in which the resource is displayed.
204     * @param res
205     *            A Resource proxy for the resource to be displayed.
206     * @param metaProperties
207     *            A MetaPropertyName[] containing the meta-properties to be
208     *            displayed for each property of the resource.
209     * @return A handle for the window containing the display.
210     * @throws WvcmException
211     *             if resource denoted by the given Resource proxy can not be
212     *             read.
213     */
214    static JFrame showResource(String title,
215                               StpResource res,
216                               final PropertyRequestItem.NestedPropertyName<?>[] metaProperties,
217                               boolean validOnly) throws WvcmException {
218        return showPropertyList(title,
219                                res,
220                                StpResource.ALL_PROPERTIES,
221                                metaProperties,
222                                validOnly);
223    }
224
225    /**
226     * Retrieves the value of a Property.List-valued property from a given
227     * resource and displays the results in a table. The display includes an
228     * option to view the content of the resource as well as the resource or
229     * resource list referenced by any of the displayed properties.
230     * 
231     * @param title
232     *            The title for the window in which the property list is
233     *            displayed
234     * @param resource
235     *            A proxy for the Resource whose properties are to be displayed.
236     * @param property
237     *            A PropertyName specifying the Property.List-valued property of
238     *            the resource that is to be retrieved and displayed.
239     * @param metaProperties
240     *            The meta-properties of each Property on the list that are to
241     *            be retrieved and displayed.
242     * @return A handle for the window in which the Property.List is displayed.
243     * @throws WvcmException
244     *             If the specified resource or property cannot be retrieved
245     *             from the repository.
246     */
247    static JFrame showPropertyList(final String title,
248                                   final StpResource resource,
249                                   final PropertyName<StpProperty.List<StpProperty<?>>> property,
250                                   final NestedPropertyName<?>[] metaProperties,
251                                   final boolean validOnly)
252                    throws WvcmException {
253        PropertyRequest wantedProps =
254            new PropertyRequest(property.nest(StpProperty.VALUE
255                .nest(metaProperties)));
256        final StpResource res = (StpResource) resource.doReadProperties(wantedProps);
257        final StpProperty.List<?> total = res.getProperty(property);
258        final Operations operations = getOperations(res);
259
260        // Filter out properties that could not be read.
261        final StpProperty.List<StpProperty<?>> valid = 
262            new StpProperty.List<StpProperty<?>>();
263
264        for(StpProperty<?> prop: total) {
265            try {
266                // If getValue() doesn't blow up, we can display the property
267                prop.getValue();
268                valid.add(prop);
269            } catch (WvcmException ex) {
270            }
271        }
272
273        // Sort the properties by their simple name
274        StpProperty<?>[] props;
275
276        if (validOnly)
277            props = (StpProperty[]) valid.toArray(new StpProperty[valid.size()]);
278        else
279            props = (StpProperty[]) total.toArray(new StpProperty[total.size()]);
280
281        Arrays.sort(props, new Comparator<StpProperty<?>>() {
282            public int compare(StpProperty<?> arg0, StpProperty<?> arg1) {
283                return arg0.getPropertyName().getName()
284                        .compareTo(arg1.getPropertyName().getName());
285            }
286        });
287
288        final StpProperty.List<StpProperty<?>> properties = 
289                                        new StpProperty.List<StpProperty<?>>();
290
291        properties.addAll(Arrays.asList(props));
292
293        // Build the data model for displaying one property per row, one
294        // meta-property per column.
295        BrowserDataModel dataModel = new BrowserDataModel() {
296            public int getColumnCount() {
297                return metaProperties.length;
298            }
299
300            public int getRowCount() {
301                return properties.size();
302            }
303
304            public Object getValueAt(int row, int col) {
305                try {
306                    return ((StpProperty<?>) properties.get(row))
307                        .getMetaProperty((MetaPropertyName<?>)
308                                         metaProperties[col].getRoot());
309                } catch (Throwable ex) {
310                    if (StpProperty.TYPE.equals(metaProperties[col]))
311                        try {
312                            return typeImage(((StpProperty<?>) properties.get(row))
313                                .getValue());
314                        } catch (WvcmException e) {
315                        }
316
317                    return exceptionImage(ex, properties.get(row));
318                }
319            }
320
321            public String getColumnName(int col) {
322                return metaProperties[col].getRoot().getName();
323            }
324
325            /**
326             * Properties whose values are resources or lists of resources,
327             * properties ChildBinding objects or ParentBinding objects are
328             * viewable.
329             */
330            Object getViewable(int row) {
331                StpProperty<?> result = properties.get(row);
332                try {
333                    Object val = result.getValue();
334
335                    if (val != null
336                        && (val instanceof StpResource 
337                            || Utilities.isListOfResources(val)
338                            || val instanceof StpProperty.List))
339                        return result;
340                } catch (WvcmException ex) {
341                }
342
343                return null;
344            }
345
346            /**
347             * Generates a new table window for a resource, resource list or
348             * property list.
349             */
350            void show(Object viewable) throws WvcmException {
351                StpProperty<?> property = (StpProperty<?>) viewable;
352                Object val = property.getValue();
353
354                try {
355                    m_frame.setCursor(Cursor
356                        .getPredefinedCursor(Cursor.WAIT_CURSOR));
357
358                    if (Utilities.isListOfResources(val)) {
359                        showResourceList(property.getName() + " of "
360                                             + Utilities.resourceType(res)
361                                             + " " + res.location().string(),
362                                         Utilities.toResourceList(res.stpProvider(), 
363                                                                  (List<?>)val),
364                                         PROPS.toArray(),
365                                         validOnly);
366                    } else if (val instanceof StpResource) {
367                        StpResource res = (StpResource) val;
368                        showResource(Utilities.resourceType(res) + " "
369                                         + res.location().string(),
370                                     res,
371                                     metaProperties,
372                                     validOnly);
373                    } else if (val instanceof StpProperty.List) {
374                        PropertyName<StpProperty.List<StpProperty<?>>> prop =
375                            StpException
376                                .<PropertyName<StpProperty.List<StpProperty<?>>>> 
377                                    unchecked_cast(property
378                                                       .getPropertyName());
379                        
380                        showPropertyList(property.getName() + " of "
381                                             + Utilities.resourceType(res)
382                                             + " " + res.location().string(),
383                                         res,
384                                         prop,
385                                         metaProperties,
386                                         validOnly);
387                    }
388                } finally {
389                    m_frame.setCursor(Cursor
390                        .getPredefinedCursor(Cursor.DEFAULT_CURSOR));
391                }
392            }
393
394            String toggleErrorsLabel() {
395                return m_validPropertyCount == m_totalPropertyCount ? ""
396                    : properties.size() == m_validPropertyCount ? "Show Errors"
397                        : "Hide Errors";
398            }
399
400            void toggleErrors() throws WvcmException {
401                try {
402                    m_frame.setCursor(Cursor
403                        .getPredefinedCursor(Cursor.WAIT_CURSOR));
404                    showPropertyList(title,
405                                     resource,
406                                     property,
407                                     metaProperties,
408                                     !validOnly);
409                } finally {
410                    m_frame.dispose();
411                }
412            }
413
414            void redisplay() throws WvcmException {
415                try {
416                    m_frame.setCursor(Cursor
417                        .getPredefinedCursor(Cursor.WAIT_CURSOR));
418
419                    showPropertyList(title,
420                                     resource,
421                                     property,
422                                     metaProperties,
423                                     validOnly);
424                } finally {
425                    m_frame.dispose();
426                }
427            }
428
429            /**
430             * Generates a display for the content of this resource
431             */
432            void showContent() throws Throwable {
433                File temp = File.createTempFile("browser", "tmp");
434                FileOutputStream stream = new FileOutputStream(temp);
435
436                res.doReadContent(stream, null);
437                showFile("Content of " + Utilities.resourceType(res) + " "
438                    + res.location().string(), temp);
439            }
440
441            /**
442             * Returns the Operations object associated with this window.
443             * @see CcBrowser
444             */
445            Operations getOperationsObject() {
446                return operations;
447            }
448
449            private static final long serialVersionUID = 1L;
450
451            /** The total number of properties defined by the resource proxy */
452            private int m_totalPropertyCount = total.size();
453
454            /** The total number of valid properties defined by the proxy */
455            private int m_validPropertyCount = valid.size();
456        };
457
458        return dataModel.showModel(title, true);
459    }
460
461    /**
462     * Starts browsing at a user-supplied resource location or in the folders
463     * designated for a user-supplied type of resource.
464     * 
465     * @param args
466     *            Not used in this application
467     */
468    public static void main(String[] args) throws Exception {
469        /**
470         * Present the list to the user and marshal requests for folder lists
471         * and specific resources to the showRoot() method.
472         */
473        String help = "Enter a Location or Select a resource category and Click 'Browse'";
474        String prompt = "<type>.<namespace>:<name>@<repository>";
475
476        final JFrame frame = new JFrame("Resource Browser");
477        final JPanel panel = new JPanel(new BorderLayout());
478        final JPanel subpanel = new JPanel(new FlowLayout());
479        final DefaultComboBoxModel model = new DefaultComboBoxModel();
480
481        // Add object selectors for sample databases. Edit this code to match
482        // installed resources as desired.
483        model.insertElementAt("cq.record:Defect/SAMPL00000005@7.0.0/SAMPL", 0);
484        model.insertElementAt(CLEAR_CASE_WORKSPACES, 0);
485        model.insertElementAt(CLEAR_QUEST_DB_SETS, 0);
486        model.insertElementAt(prompt, 0);
487        model.setSelectedItem(prompt);
488
489        final JComboBox box = new JComboBox(model);
490
491        panel.add(box, BorderLayout.CENTER);
492        box.setEditable(true);
493
494        final JButton browse = new JButton("Browse");
495
496        subpanel.add(browse, BorderLayout.SOUTH);
497        browse.addActionListener(new ActionListener() {
498            public void actionPerformed(ActionEvent arg0) {
499                String selection = (String) box.getSelectedItem();
500
501                if (model.getIndexOf(selection) < 0)
502                    model.insertElementAt(selection, 0);
503
504                try {
505                    frame.setCursor(Cursor
506                        .getPredefinedCursor(Cursor.WAIT_CURSOR));
507
508                    showRoot(selection);
509                } finally {
510                    frame.setCursor(Cursor
511                        .getPredefinedCursor(Cursor.DEFAULT_CURSOR));
512                }
513            }
514        });
515
516        JButton exit = new JButton("Exit");
517        subpanel.add(exit);
518        exit.addActionListener(new ActionListener() {
519            public void actionPerformed(ActionEvent arg0) {
520                System.exit(0);
521            }
522        });
523
524        final JButton cqurl = new JButton("URL...");
525        subpanel.add(cqurl);
526        cqurl.addActionListener(new ActionListener() {
527            public void actionPerformed(ActionEvent arg0) {
528                getUrl("http://qwin115:9082/TeamWeb/services/Team");
529        }});
530
531        panel.add(new JLabel(help), BorderLayout.NORTH);
532        panel.add(subpanel, BorderLayout.SOUTH);
533        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
534        frame.setContentPane(panel);
535        frame.setBounds(200, 200, 500, 110);
536        frame.setVisible(true);
537    }
538
539    static boolean getUrl(String defaultURL)
540    {
541        try {
542            if (g_provider == null)
543                    g_provider = Utilities.getProvider();
544
545            String url = g_provider.getServerUrl();
546            
547            if (url == null || url.length() == 0)
548                url =defaultURL;
549            
550            url =
551                JOptionPane.showInputDialog("Enter a URL for the server", url);
552            
553            g_provider.setServerUrl(url);
554            return true;
555        } catch (Exception ex) {
556            Utilities.exception(null, "Provider", ex);
557        }
558        
559        return false;
560    }
561    /**
562     * Generates a short string to identify a given throwable
563     * 
564     * @param ex
565     *            A Throwable that is to be identified.
566     * @param obj
567     *            The object that promulgated the exception.
568     * @return A short String providing, in a concise format, identification of
569     *         and relevant information from the throwable.
570     */
571    static Object exceptionImage(Throwable ex, Object obj) {
572        StpReasonCode code = null;
573        String prefix = "";
574        
575        if (ex instanceof StpException) {
576            code = ((StpException) ex).getStpReasonCode();
577        } else if (obj instanceof StpPropertyException) {
578            StpPropertyException pe = (StpPropertyException) obj;
579            PropertyName<?> name = pe.getPropertyName();
580
581            code = pe.getStpReasonCode();
582
583            if (name != null)
584                prefix = name.getName() + ": ";
585        } else {
586            String name = ex.getClass().getName();
587            return name.substring(name.lastIndexOf(".") + 1);
588        }
589        
590        // Look through the wrapper to display more information
591        if (code == StpReasonCode.PROPERTY_RETRIEVAL_FAILED)
592            code = ((StpException)ex.getCause()).getStpReasonCode();
593        
594        return prefix + code;
595    }
596
597    /**
598     * Generates the unqualified name of the type of a given object
599     * 
600     * @param obj
601     *            The object whose value is to be displayed. May be null.
602     * @return The leaf name of the type of the object.
603     */
604    static Object typeImage(Object obj) {
605        if (obj == null)
606            return "(null)";
607
608        String name = obj.getClass().getName();
609        int dot = name.lastIndexOf('.');
610
611        return dot < 0 ? name : name.substring(dot + 1);
612    }
613
614    /**
615     * If the g_operationsClass variable is set, returns a new instance of that
616     * class initialized with the given Resource proxy.
617     * 
618     * @param res
619     *            A Resource proxy. Must not be null.
620     * @return A new Operations object if the operations class has been assigned
621     *         to g_operationsClass; otherwise null.
622     */
623    private static Operations getOperations(StpResource res) {
624        if (g_operationsClass != null)
625            try {
626                return (Operations) g_operationsClass
627                    .getConstructor(new Class[] { StpResource.class })
628                    .newInstance(new Object[] { res });
629            } catch (Throwable t) {
630                Utilities.exception(null, "getOperations", t);
631            }
632
633        return null;
634    }
635
636    private static final MetaPropertyName<Object> VALUE_AS_OBJECT = 
637                new MetaPropertyName<Object>(StpProperty.VALUE.getNamespace(),
638                                             StpProperty.VALUE.getName());
639
640    /**
641     * Properties to be displayed for elements of a ResourceList Also the
642     * properties requested for resource-valued properties.
643     */
644    private static final PropertyRequest PROPS = new PropertyRequest(
645                    StpResource.USER_FRIENDLY_LOCATION  /*,
646                        StpResource.COMMENT*/ );
647
648    /**
649     * Meta-properties to be displayed for each property Also the properties
650     * requested for property-valued properties
651     */
652    static final PropertyRequest META_PROPS = 
653        new PropertyRequest( new PropertyRequestItem[] { 
654                                StpProperty.NAME,
655                                StpProperty.TYPE, 
656                                StpProperty.VALUE.nest(PROPS)});
657
658    /** Drop-down entry for requesting ClearQuest connections */
659    private static final String CLEAR_QUEST_DB_SETS = "<ClearQuest Connections>";
660
661    /** Drop-down entry for requesting local ClearCase views */
662    private static final String CLEAR_CASE_WORKSPACES = "<Local ClearCase Views>";
663
664    /** 
665     * The class to use for Operations if supported by the application using
666     * the Browser class.
667     * 
668     *  @see CcBrowser#main(String[])
669     */
670    static Class<?> g_operationsClass = null;
671
672    /** The Provider instance used by all instances of the Browser in a process */
673    private static StpProvider g_provider = null;
674}