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     * © 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    package com.ibm.rational.stp.client.samples;
016    
017    import java.awt.BorderLayout;
018    import java.awt.Cursor;
019    import java.awt.FlowLayout;
020    import java.awt.event.ActionEvent;
021    import java.awt.event.ActionListener;
022    import java.io.File;
023    import java.io.FileOutputStream;
024    import java.util.Arrays;
025    import java.util.Comparator;
026    import java.util.List;
027    
028    import javax.swing.DefaultComboBoxModel;
029    import javax.swing.JButton;
030    import javax.swing.JComboBox;
031    import javax.swing.JFrame;
032    import javax.swing.JLabel;
033    import javax.swing.JOptionPane;
034    import javax.swing.JPanel;
035    import javax.wvcm.PropertyRequestItem;
036    import javax.wvcm.Resource;
037    import javax.wvcm.ResourceList;
038    import javax.wvcm.WvcmException;
039    import javax.wvcm.PropertyNameList.PropertyName;
040    import javax.wvcm.PropertyRequestItem.NestedPropertyName;
041    import javax.wvcm.PropertyRequestItem.PropertyRequest;
042    
043    import com.ibm.rational.stp.client.samples.BrowserDataModel.Operations;
044    import com.ibm.rational.wvcm.stp.StpException;
045    import com.ibm.rational.wvcm.stp.StpProperty;
046    import com.ibm.rational.wvcm.stp.StpPropertyException;
047    import com.ibm.rational.wvcm.stp.StpProvider;
048    import com.ibm.rational.wvcm.stp.StpResource;
049    import com.ibm.rational.wvcm.stp.StpException.StpReasonCode;
050    import com.ibm.rational.wvcm.stp.StpProperty.MetaPropertyName;
051    import com.ibm.rational.wvcm.stp.cq.CqDbSet;
052    import com.ibm.rational.wvcm.stp.cq.CqProvider;
053    import 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     */
080    public 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    }