001 /* 002 * file ProxyElement.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.teamapi.scout.ProxyElement 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.teamapi.scout; 016 017 import java.io.File; 018 import java.io.FileReader; 019 import java.lang.reflect.InvocationTargetException; 020 import java.util.ArrayList; 021 import java.util.HashMap; 022 import java.util.Iterator; 023 import java.util.List; 024 import java.util.Map; 025 026 import javax.wvcm.Folder; 027 import javax.wvcm.PropertyRequestItem; 028 import javax.wvcm.ProviderFactory; 029 import javax.wvcm.Resource; 030 import javax.wvcm.ResourceList; 031 import javax.wvcm.Workspace; 032 import javax.wvcm.WvcmException; 033 import javax.wvcm.PropertyNameList.PropertyName; 034 import javax.wvcm.PropertyRequestItem.PropertyRequest; 035 import javax.wvcm.ProviderFactory.Callback; 036 import javax.wvcm.ProviderFactory.Callback.Authentication; 037 038 import org.eclipse.jface.dialogs.InputDialog; 039 import org.eclipse.swt.widgets.Display; 040 import org.eclipse.swt.widgets.Shell; 041 042 import com.ibm.rational.wvcm.stp.StpException; 043 import com.ibm.rational.wvcm.stp.StpLocation; 044 import com.ibm.rational.wvcm.stp.StpProvider; 045 import com.ibm.rational.wvcm.stp.StpResource; 046 import com.ibm.rational.wvcm.stp.StpException.StpReasonCode; 047 import com.ibm.rational.wvcm.stp.StpLocation.Namespace; 048 import com.ibm.rational.wvcm.stp.cc.CcDirectory; 049 import com.ibm.rational.wvcm.stp.cq.CqAttachmentFolder; 050 import com.ibm.rational.wvcm.stp.cq.CqDbSet; 051 import com.ibm.rational.wvcm.stp.cq.CqQueryFolder; 052 import com.ibm.rational.wvcm.stp.cq.CqRecord; 053 import com.ibm.rational.wvcm.stp.cq.CqRecordType; 054 import com.ibm.rational.wvcm.stp.cq.CqUserDb; 055 056 057 /** 058 * A tree viewer model element for a CM API Resource. This model element also 059 * implements the IPropertySource interface so that the element selected in the 060 * tree viewer can be examined in the standard Eclipse property view. 061 */ 062 public class ProxyElement 063 extends ResourceSource 064 { 065 /** 066 * Constructs a model element for a given resource. Used to implement the 067 * public addChild methods. 068 * 069 * @param parent The parent of this element in the tree viewer. Is null 070 * only for the (unseen) root of the tree. 071 * @param resource The Resource proxy linking this element to the resource 072 * being viewed. Must not be null except in the root 073 * element of the tree. 074 */ 075 private ProxyElement(ProxyElement parent, 076 StpResource resource) 077 { 078 super(resource); 079 m_parent = parent; 080 m_namespace = resource.stpLocation().getNamespace(); 081 } 082 083 /** 084 * Constructs the root ProxyElement of a tree view. 085 * 086 * @param shell The display shell for this ProxyElement; used for context 087 * when requesting credentials from the user. 088 * 089 * @throws Exception if a CM API provider cannot be instantiated. 090 */ 091 ProxyElement(Shell shell) 092 throws Exception 093 { 094 super(null); 095 096 m_parent = createProvider(shell); 097 m_resource = null; 098 m_children = new ArrayList<ProxyElement>(); 099 } 100 101 /** 102 * Adds a child resource to this element of the tree view model 103 * 104 * @param child The Resource proxy for the new child element. 105 */ 106 void addChild(StpResource child) 107 { 108 m_children.add(new ProxyElement(this, child)); 109 } 110 111 /** 112 * Adds a child resource to this element of the tree view model 113 * 114 * @param selectorString A String containing the object selector for the 115 * resource to be added as a child of this element. 116 * 117 * @throws WvcmException If the resource proxy can't be constructed 118 */ 119 void addChild(String selectorString) 120 throws WvcmException 121 { 122 StpProvider provider = getProvider(); 123 124 addChild((StpResource)provider.resource(provider.location(selectorString))); 125 } 126 127 /** 128 * Removes a child model element from this model element. 129 * 130 * @param child The subordinate model element to be removed. 131 * 132 * @return true if the child was an element and was removed; false 133 * otherwise. 134 */ 135 boolean removeChild(Object child) 136 { 137 return m_children == null ? false : m_children.remove(child); 138 } 139 140 /** 141 * Computes a String to identify this model element in the tree view. 142 * 143 * @return A String containing the type and name of the resource. 144 */ 145 public String getText() 146 { 147 if (isRoot()) 148 return "Namespaces"; 149 150 if (getParent().isRoot()) 151 return m_resource.location().string(); 152 153 try { 154 return resourceType() + " " + m_resource.location().lastSegment(); 155 } catch (Throwable ex) { 156 return m_resource.location().lastSegment() + "[" 157 + ex.getLocalizedMessage() + "]"; 158 } 159 } 160 161 /** 162 * Returns the selector for this resource. 163 * 164 * @return A String containing the image of this resource's selector. 165 */ 166 public String getSelector() 167 { 168 if (m_resource == null) 169 return ""; 170 171 return m_resource.toString(); 172 } 173 174 /** 175 * (non-Javadoc) 176 * 177 * @see java.lang.Object#toString() 178 * 179 * @return The Text for this ProxyElement. 180 */ 181 public String toString() 182 { 183 return getText(); 184 } 185 186 /** 187 * For a resource in a given namespace, returns the PropertyName that 188 * identifies the property that serves as the bound member list of that 189 * resource in the namespace. 190 * 191 * @param res 192 * @param namespace 193 * @return the property name 194 * @throws WvcmException 195 */ 196 PropertyName 197 memberListProperty(StpResource res, 198 Namespace namespace) 199 throws WvcmException 200 { 201 if (g_memberListPropertyMap.isEmpty()) { 202 StpProvider provider = getProvider(); 203 204 for (int i = 0; i < table.length-1; ++i) { 205 if (table[i+1] instanceof Class) { 206 HashMap<Object, Object> map = new HashMap<Object, Object>(); 207 208 g_memberListPropertyMap.put(table[i], map); 209 210 for (; table[i+1] instanceof Class; i += 2) 211 map.put(provider.proxyType((Class<? extends Resource>)table[i+1]), 212 (PropertyName)table[i+2]); 213 } 214 } 215 } 216 217 Map map = (Map)g_memberListPropertyMap.get(namespace); 218 219 return (PropertyName)(map == null? null: map.get(res.proxyType())); 220 } 221 222 private static final HashMap<Object, Object> g_memberListPropertyMap = 223 new HashMap<Object, Object>(); 224 225 /** 226 * For each namespace, maps the proxy classes in that namespace to the 227 * property name for the property that defines the member list of that 228 * resource type in the namespace. 229 */ 230 private static final Object[] table = { 231 Namespace.ACTION, 232 CqDbSet.class, CqDbSet.USER_DATABASES, 233 CqUserDb.class, CqUserDb.RECORD_TYPE_SET, 234 CqRecordType.class, CqRecordType.ACTION_LIST, 235 Namespace.ACTIVITY, 236 Namespace.ATTYPE, 237 Namespace.BASELINE, 238 Namespace.BRTYPE, 239 Namespace.COMPONENT, 240 Namespace.DB_SET, 241 Namespace.DBID, 242 Namespace.DYNAMIC_CHOICE_LIST, 243 CqDbSet.class, CqDbSet.USER_DATABASES, 244 CqUserDb.class, CqUserDb.DYNAMIC_CHOICE_LISTS, 245 Namespace.ELTYPE, 246 Namespace.FIELD_DEFINITION, 247 CqDbSet.class, CqDbSet.USER_DATABASES, 248 CqUserDb.class, CqUserDb.RECORD_TYPE_SET, 249 CqRecordType.class, CqRecordType.FIELD_DEFINITIONS, 250 Namespace.FILE, 251 Namespace.FOLDER, 252 Namespace.FORM, 253 CqDbSet.class, CqDbSet.USER_DATABASES, 254 CqUserDb.class, CqUserDb.RECORD_TYPE_SET, 255 Namespace.GROUP, 256 Namespace.HLINK, 257 Namespace.HLTYPE, 258 Namespace.HOOK, 259 CqDbSet.class, CqDbSet.USER_DATABASES, 260 CqUserDb.class, CqUserDb.RECORD_TYPE_SET, 261 CqRecordType.class, CqRecordType.NAMED_HOOK_LIST, 262 Namespace.HTTP, 263 Namespace.HTTPS, 264 Namespace.LBTYPE, 265 Namespace.OID, 266 Namespace.PNAME, 267 Namespace.PNAME_IMPLIED, 268 Namespace.POOL, 269 Namespace.PROJECT, 270 Namespace.PROJECT_CONFIGURATION, 271 Namespace.QUERY, 272 CqDbSet.class, CqDbSet.USER_DATABASES, 273 CqUserDb.class, CqUserDb.QUERY_FOLDER_ITEMS, 274 CqQueryFolder.class, CqQueryFolder.QUERY_FOLDER_ITEMS, 275 Namespace.RECORD, 276 CqDbSet.class, CqDbSet.USER_DATABASES, 277 CqUserDb.class, CqUserDb.RECORD_TYPE_SET, 278 CqRecord.class, CqRecord.ATTACHMENT_FOLDERS, 279 CqAttachmentFolder.class, CqAttachmentFolder.ATTACHMENT_LIST, 280 Namespace.REPLICA, 281 Namespace.REPLICA_UUID, 282 Namespace.REPO, 283 Namespace.RPTYPE, 284 Namespace.STREAM, 285 Namespace.TRTYPE, 286 Namespace.USER, 287 Namespace.USER_DB, 288 Namespace.VIEW_UUID, 289 Namespace.VOB, 290 Namespace.WORKSPACE, 291 Workspace.class, Workspace.CHILD_LIST, 292 CcDirectory.class, CcDirectory.CHILD_LIST, 293 Namespace.NONE // End of list 294 }; 295 296 /** 297 * Determines if this model element could be a folder. 298 * 299 * @return true if the resource is a Folder proxy and it defines the 300 * CHILD_BINDING_LIST property; false otherwise. 301 */ 302 boolean couldBeFolder() 303 { 304 if (m_resource != null && m_resource instanceof Folder) { 305 Object obj = m_resource.lookupProperty(Folder.CHILD_LIST); 306 307 if (obj == null || obj instanceof List) 308 return true; 309 310 if (obj instanceof StpException 311 && ((StpException)obj).getStpReasonCode() 312 == StpReasonCode.PROPERTY_NOT_REQUESTED) 313 return true; 314 } 315 316 return false; 317 } 318 319 /** 320 * Determines if this model element should be considered a folder when 321 * sorting and filtering the tree view. 322 * 323 * @return true if the resource proxy is a folder proxy or if there is no 324 * resource associated with this element (i.e. the root of the 325 * tree). 326 */ 327 boolean isFolder() 328 { 329 return m_resource == null || m_resource instanceof Folder; 330 } 331 332 /** 333 * Determines if this model element is an empty folder; 334 * 335 * @return true if the model element represents a folder with no children. 336 */ 337 boolean isEmptyFolder() 338 { 339 return m_resource != null && m_resource instanceof Folder 340 && getChildren().length == 0; 341 } 342 343 /** 344 * Returns the children of the resource represented by this ProxyElement; 345 * reading them from the CHILD_BINDING_LIST. The results are cached in this 346 * element. A special case is made for selectors having no repository name. 347 * This form of selector is interpreted as a request for one of the folder 348 * lists available from the provider. Which method is used is based on the 349 * namespace and repository type provided 350 * 351 * <table cellpadding=5 border=1> 352 * <tr> 353 * <th>Repository Type</th> 354 * <th>Namespace</th> 355 * <th>Method</th> 356 * </tr> 357 * <tr> 358 * <td>CLEAR_CASE</td> 359 * <td>REPOSITORY</td> 360 * <td>serverWorkspaceFolderList</td> 361 * </tr> 362 * <tr> 363 * <td>CLEAR_CASE</td> 364 * <td>VIEW<br> 365 * WORKSPACE</td> 366 * <td>clientWorkspaceFolderList</td> 367 * </tr> 368 * </table> 369 * 370 * @return An Array of model elements for the children. 371 */ 372 Object[] getChildren() 373 { 374 if (m_children == null) { 375 m_children = EMPTY_ARRAY; 376 377 try { 378 Object obj = null; 379 StpLocation selector = m_resource.stpLocation(); 380 381 if (selector.getRepo().length() == 0) { 382 StpProvider provider = getProvider(); 383 384 if (selector.getDomain() == StpProvider.Domain.CLEAR_CASE) { 385 // if (selector.getNamespace() == Namespace.PROJECT) { 386 // obj = 387 // provider.ccProvider().(FOLDER_PROPERTIES); 388 // } else 389 if (selector.getNamespace() == Namespace.WORKSPACE) { 390 obj = 391 provider.ccProvider().getClientViewList(FOLDER_PROPERTIES); 392 } 393 } 394 } 395 396 if (obj == null && couldBeFolder()) { 397 PropertyName memberList = memberListProperty(m_resource, 398 m_namespace); 399 obj = m_resource.lookupProperty(memberList); 400 401 if (obj instanceof StpException 402 && ((StpException)obj).getStpReasonCode() 403 == StpReasonCode.PROPERTY_NOT_REQUESTED) { 404 final PropertyRequest FOLDER_PROPERTIES = 405 new PropertyRequest(new PropertyRequestItem[] { 406 StpResource.DISPLAY_NAME, 407 memberList.nest(new PropertyRequestItem[] {StpResource 408 .DISPLAY_NAME}) 409 }); 410 m_resource = 411 (StpResource)m_resource.doReadProperties(FOLDER_PROPERTIES); 412 obj = m_resource.lookupProperty(memberList); 413 } 414 } 415 416 if (obj != null) { 417 if (obj instanceof ResourceList) { 418 m_children = new ArrayList<ProxyElement>(); 419 420 for (Iterator child = ((List)obj).iterator(); 421 child.hasNext();) 422 addChild((StpResource)child.next()); 423 } else if (obj instanceof List) { 424 m_children = new ArrayList<ProxyElement>(); 425 426 for (Iterator child = ((List)obj).iterator(); 427 child.hasNext();) 428 addChild((StpResource)child.next()); 429 } 430 } 431 } catch (Throwable ex) { 432 ex.printStackTrace(); 433 } 434 } 435 436 return m_children.toArray(); 437 } 438 439 /** 440 * Empties the cache of child elements; forcing them to be reread the next 441 * time they are to be displayed. 442 */ 443 void refresh() 444 { 445 m_children = null; 446 m_resource.forgetProperty(Folder.CHILD_LIST); 447 } 448 449 /** 450 * Returns whether or not this object is the root of the tree view. 451 * 452 * @return true if this ProxyElement has not parent. 453 */ 454 boolean isRoot() 455 { 456 return m_resource == null; 457 } 458 459 /** 460 * The ProxyElement of which this is a child. 461 * 462 * @return Returns the parent. 463 */ 464 ProxyElement getParent() 465 { 466 return isRoot() ? null : (ProxyElement)m_parent; 467 } 468 469 /** 470 * The Provider for this element of the tree. 471 * 472 * @return A Provider object. 473 */ 474 StpProvider getProvider() 475 { 476 return isRoot() ? (StpProvider)m_parent 477 : (StpProvider)m_resource.provider(); 478 } 479 480 /** 481 * At the root, the CM API provider for the tree; otherwise the parent 482 * ProxyElement of this one 483 */ 484 Object m_parent = null; 485 486 /** 487 * Constructs an instance of the CM API provider with an authenticator. 488 * 489 * @param shell The Shell to be used for display context when requesting 490 * credentials. 491 * 492 * @return The instantiated Provider object 493 * 494 * @throws Exception If the Provider could not be instantiated 495 */ 496 static StpProvider createProvider(final Shell shell) 497 throws Exception 498 { 499 try { 500 Callback callback = 501 new ProviderFactory.Callback() { 502 private UnPw m_unpw; 503 504 Shell m_shell = shell; 505 506 public Authentication getAuthentication(final String realm, 507 final int retryCount) 508 { 509 // Try to reuse last credentials on each new repository 510 if (m_unpw != null && retryCount == 0) 511 return m_unpw; 512 513 m_unpw = null; 514 515 // Since we may be called from a non-UI thread, we need 516 // to take measures to ensure the dialog comes up on the 517 // UI thread. 518 Display display = m_shell.getDisplay(); 519 Runnable runnable = 520 new Runnable() { 521 public void run() 522 { 523 InputDialog dialog = 524 new InputDialog(m_shell, 525 "CM API Scout Login", 526 "Enter Username '+' Password for " 527 + realm + " [" 528 + retryCount + "]", 529 "admin+", null); 530 531 try { 532 if (dialog.open() == InputDialog.OK) { 533 String unpw = dialog.getValue(); 534 535 if (unpw.startsWith("@")) { 536 File file = 537 new File(unpw.substring(1)); 538 539 FileReader reader = 540 new FileReader(file); 541 char[] buf = new char[100]; 542 int count = 543 reader.read(buf); 544 545 unpw = 546 new String(buf, 0, count); 547 reader.close(); 548 } 549 550 m_unpw = 551 new UnPw(unpw.split("\\+", -2)); 552 } 553 } catch (Throwable t) { 554 t.printStackTrace(); 555 } 556 } 557 }; 558 559 display.syncExec(runnable); 560 561 if (m_unpw == null) 562 throw new UnsupportedOperationException("No credentials available"); 563 564 return m_unpw; 565 } 566 }; 567 568 // Instantiate a Provider 569 return (StpProvider)ProviderFactory 570 .createProvider(StpProvider.PROVIDER_CLASS, callback); 571 } catch (InvocationTargetException ite) { 572 System.out.println("*** " + ite.getLocalizedMessage()); 573 574 StpException ex = (StpException)ite.getTargetException(); 575 Throwable[] nested = ex.getNestedExceptions(); 576 577 for (int i = 0; i < nested.length; ++i) 578 System.out.println("*** " + nested[i].getLocalizedMessage()); 579 580 throw ex; 581 } 582 } 583 584 /** 585 * A simple Authentication object in which the username and password 586 * obtained from the user is cached for use by the CM API. 587 */ 588 static class UnPw 589 implements Authentication 590 { 591 /** 592 * Creates a new UnPw object. 593 * 594 * @param unpw The user name and password stored in a 1 or 2 element 595 * array 596 */ 597 UnPw(String[] unpw) 598 { 599 m_data = unpw; 600 } 601 602 /** 603 * loginName 604 * 605 * @return the login name for this user 606 */ 607 public String loginName() 608 { 609 return m_data[0]; 610 } 611 612 /** 613 * password 614 * 615 * @return The password for this user 616 */ 617 public String password() 618 { 619 return m_data.length > 1 ? m_data[1] : ""; 620 } 621 ; 622 623 /** The user-name and password array */ 624 private String[] m_data; 625 } 626 627 /** 628 * Returns a String suitable for displaying the type of resource as 629 * determined by its proxy class. 630 * 631 * @return A String containing the simple name of the most derived CM API 632 * interface implemented by the proxy of this ProxyElement. 633 */ 634 String resourceType() 635 { 636 if (isRoot() || getParent().isRoot()) 637 return ""; 638 639 Class proxyClass = m_resource.getClass(); 640 String name = (String)g_typeMap.get(proxyClass); 641 642 if (name == null) { 643 Class<?>[] interfaces = proxyClass.getInterfaces(); 644 Class<?> choice = StpResource.class; 645 646 for (int i = 0; i < interfaces.length; ++i) { 647 name = interfaces[i].getName(); 648 649 if (name.startsWith("com.ibm.rational.wvcm.") 650 || name.startsWith("javax.wvcm")) { 651 // Within the CM API, select the most derived interface 652 // this resource implements 653 if (choice.isAssignableFrom(interfaces[i])) 654 choice = interfaces[i]; 655 } 656 } 657 658 name = choice.getName(); 659 name = name.substring(1 + name.lastIndexOf('.')); 660 661 g_typeMap.put(proxyClass, name); 662 } 663 664 return name; 665 } 666 667 /** The children (virtual CHILD_BINDING_LIST) of this ProxyElement. */ 668 List<ProxyElement> m_children = null; 669 670 /** The namespace that is being traversed through this element */ 671 Namespace m_namespace; 672 673 /** A constant used for empty results */ 674 private static List<ProxyElement> EMPTY_ARRAY = new ArrayList<ProxyElement>(); 675 676 /** The properties requested from a Folder */ 677 private static final PropertyRequest FOLDER_PROPERTIES = 678 new PropertyRequest(new PropertyRequestItem[] { 679 StpResource.DISPLAY_NAME, 680 Folder.CHILD_LIST.nest(new PropertyRequestItem[] { 681 StpResource.DISPLAY_NAME}) 682 }); 683 684 /** Maps proxy classes to a string used to identify their type */ 685 private static final Map<Class<?>, String> g_typeMap = 686 new HashMap<Class<?>, String>(); 687 }