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 */
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 }