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    }