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 }