org.eclipse.datatools.connectivity.oda.design.internal.ui.profile.ProfileSelectionPageHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.datatools.connectivity.oda.design.internal.ui.profile.ProfileSelectionPageHelper.java

Source

/*
 *************************************************************************
 * Copyright (c) 2007, 2011 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *  Actuate Corporation - initial API and implementation
 *  
 *************************************************************************
 */

package org.eclipse.datatools.connectivity.oda.design.internal.ui.profile;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.core.runtime.Path;
import org.eclipse.datatools.connectivity.ICategory;
import org.eclipse.datatools.connectivity.oda.OdaException;
import org.eclipse.datatools.connectivity.oda.design.ResourceIdentifiers;
import org.eclipse.datatools.connectivity.oda.design.internal.designsession.DataSourceDesignSessionBase.IDesignNameValidatorBase;
import org.eclipse.datatools.connectivity.oda.design.internal.designsession.DataSourceDesignSessionBase.ProfileReferenceBase;
import org.eclipse.datatools.connectivity.oda.design.internal.ui.profile.browse.IBrowseButtonHost;
import org.eclipse.datatools.connectivity.oda.design.internal.ui.profile.browse.IMenuButtonProvider;
import org.eclipse.datatools.connectivity.oda.design.internal.ui.profile.browse.MenuButtonProvider;
import org.eclipse.datatools.connectivity.oda.design.internal.ui.profile.browse.ProfileStoreBrowseButton;
import org.eclipse.datatools.connectivity.oda.design.ui.nls.Messages;
import org.eclipse.datatools.connectivity.oda.design.ui.nls.TextProcessorWrapper;
import org.eclipse.datatools.connectivity.oda.design.util.DesignUtil;
import org.eclipse.datatools.connectivity.oda.profile.Constants;
import org.eclipse.datatools.connectivity.oda.profile.OdaProfileExplorer;
import org.eclipse.datatools.connectivity.oda.profile.internal.ProfileCategoryUtil;
import org.eclipse.datatools.connectivity.oda.util.manifest.ExtensionManifest;
import org.eclipse.datatools.connectivity.oda.util.manifest.ManifestExplorer;
import org.eclipse.datatools.connectivity.oda.util.manifest.ManifestExplorer.Filter;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PropertyPage;

/**
 * Internal UI helper that provides the UI controls and behavior for 
 * both the wizard page and editor page for selecting 
 * a connection profile instance.
 * @since   3.0.4
 */
class ProfileSelectionPageHelper implements IBrowseButtonHost {
    private static final String EMPTY_STRING = ""; //$NON-NLS-1$
    private static final String CONTEXT_ID_CONNECTIONPROFILE = "org.eclipse.datatools.oda.cshelp.Wizard_ConnectionProfile_ID";//$NON-NLS-1$
    private static final String DISABLE_AUTO_SELECT_ITEM = "DisableAutoSelect"; //$NON-NLS-1$

    private static final int TREE_ITEM_STYLE = SWT.NONE;
    private static final int BUTTON_MIN_WIDTH = 80;

    private WizardPage m_wizardPage;
    private PreferencePage m_propertyPage;

    private transient Label m_dataSourceNameLabel = null;
    private transient Text m_connectionProfilePath = null;
    private transient Text m_dataSourceDesignNameControl = null;
    private transient Button m_linkRefCheckBox = null;
    private transient String m_profileID;
    private transient String m_odaDataSourceID;
    private transient Button m_useDefaultDSNameCheckBox = null;
    private transient ProfileStoreBrowseButton m_browseButton = null;
    private transient Tree m_odaDataSourceTree = null;
    private transient String m_treeFilter;
    private transient SortedSet<OdaProfileCategoryInfo> m_dataSourceIDs = null;
    private transient IDesignNameValidatorBase m_designNameValidator;
    private transient HashMap<String, String> m_deprecatedDataSourceMap = new HashMap<String, String>(); // key= deprecated odaDataSourceId; value= replacing odaDataSourceId

    private String m_dataSourceDesignName = EMPTY_STRING;
    private ResourceIdentifiers m_hostResourceIdentifiers = null;

    ProfileSelectionPageHelper(WizardPage page) {
        m_wizardPage = page;
    }

    ProfileSelectionPageHelper(PropertyPage page) {
        m_propertyPage = page;
    }

    Control createControl(Composite parent) {
        Composite container = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.numColumns = 3;
        container.setLayout(layout);

        setupConnectionProfilePath(container);
        setupLinkCheckBox(container);
        setupTreeViewer(container);
        setupDataSourceName(container);

        PlatformUI.getWorkbench().getHelpSystem().setHelp(container, CONTEXT_ID_CONNECTIONPROFILE);

        return container;
    }

    void initControl(ProfileSelection selectedProfile) {
        if (selectedProfile == null)
            return; // nothing to initialize        

        // limits profile tree to include the selected oda data source type
        m_treeFilter = selectedProfile.getOdaDataSourceId();

        boolean hasInvalidProfileRef = false;
        ProfileReferenceBase profileRef = selectedProfile.getProfileRef();
        if (profileRef != null) {
            String profileStorePath = profileRef.getStorageFilePathPropertyValue();
            if (profileStorePath != null) {
                // triggers tree population; disable auto item selection
                setConnProfilePathControlText(profileStorePath, true);

                TreeItem profileItem = findProfileInTree(selectedProfile.getOdaDataSourceId(),
                        profileRef.getInstanceId());
                if (profileItem != null) {
                    assert (m_odaDataSourceTree != null);
                    m_odaDataSourceTree.setSelection(profileItem);
                    m_odaDataSourceTree.showSelection();

                    m_useDefaultDSNameCheckBox
                            .setSelection(profileItem.getText().equals(selectedProfile.getDataSourceDesignName()));
                    m_profileID = profileItem.getData().toString();
                    m_odaDataSourceID = getProfileItemDataSourceId(profileItem);
                    setExternalLinkOption(m_odaDataSourceID);
                    setPageComplete(true);
                } else // did not find matching linked profile in profile tree
                    hasInvalidProfileRef = true;
            }

            m_linkRefCheckBox.setSelection(profileRef.maintainExternalLink());
        }

        // initializes the designName variable first, which is checked 
        // by the name control's modify listener
        m_dataSourceDesignName = selectedProfile.getDataSourceDesignName();
        m_dataSourceDesignNameControl.setText(m_dataSourceDesignName);

        // when editing an existing data source design
        if (inEditMode()) {
            // hide controls to disallow user from changing the design name
            setDataSourceNameEditorVisible(false);

            // editing a design with invalid profile reference, set error message;
            // this is done last so the message won't be overwritten by controls' event handler
            if (profileRef != null && hasInvalidProfileRef) {
                String errorMessage = profileRef.getStorageFile() == null
                        ? Messages.profilePage_error_invalidProfileStorePath
                        : Messages.bind(Messages.designSession_invalidProfileName, profileRef.getName());
                setMessage(errorMessage, IMessageProvider.ERROR);
            }
        }
    }

    /**
     * Set the call-back validator provided by an ODA host, for validation of
     * the data source design named collected in the UI control.
     */
    void setDesignNameValidator(IDesignNameValidatorBase validator) {
        m_designNameValidator = validator;
    }

    /**
     * Collects the current selection of connection profile instance.
     * @return  the current selection if no error is found;
     *          null otherwise
     */
    ProfileSelection collectProfileSelection() {
        if (!hasSelectedProfile() || !isPageComplete())
            return null;

        String profileStorePathText = getConnProfilePathControlText();
        String resolvedProfileStorePath = resolveProfileStorePath(profileStorePathText);
        boolean isProfileStorePathRelative = false; // default behavior
        if (resolvedProfileStorePath != null && !resolvedProfileStorePath.equals(profileStorePathText)) // was resolved from relative path
            isProfileStorePathRelative = true;

        File profileStoreFile = new Path(resolvedProfileStorePath).toFile();

        return new ProfileSelection(m_odaDataSourceID, m_dataSourceDesignName,
                new ProfileReferenceBase(m_profileID, profileStoreFile, m_linkRefCheckBox.getSelection(),
                        DesignUtil.convertFileToResourcePath(profileStoreFile, m_hostResourceIdentifiers,
                                isProfileStorePathRelative)));
    }

    private boolean isPageComplete() {
        boolean isPageComplete = true;
        if (m_wizardPage != null)
            isPageComplete = m_wizardPage.isPageComplete();
        else if (m_propertyPage != null)
            isPageComplete = m_propertyPage.isValid();
        else
            isPageComplete = (getMessageType() != IMessageProvider.ERROR);

        if (!isPageComplete) // page itself is not complete
            return isPageComplete; // no need to do extra checking

        // the page itself considers itself complete, next 
        // check if external reference is selected, must have a selected profile
        if (m_linkRefCheckBox != null && m_linkRefCheckBox.getSelection())
            isPageComplete = hasSelectedProfile();

        return isPageComplete;
    }

    private void setPageComplete(boolean complete) {
        if (m_wizardPage != null)
            m_wizardPage.setPageComplete(complete);
        else if (m_propertyPage != null)
            m_propertyPage.setValid(complete);

        if (complete)
            setMessage(EMPTY_STRING, IMessageProvider.NONE);
        else
            setDefaultMessageAsError(true);
    }

    private void setMessage(String newMessage, int newType) {
        if (m_wizardPage != null)
            m_wizardPage.setMessage(newMessage, newType);
        else if (m_propertyPage != null) {
            // use default title instead of leaving the message area empty
            if (newMessage.length() == 0 && newType == IMessageProvider.NONE)
                newMessage = Messages.profilePage_pageTitle;
            m_propertyPage.setMessage(newMessage, newType);
        }
    }

    private void setDefaultMessageAsError(boolean isError) {
        int newType = isError ? IMessageProvider.ERROR : IMessageProvider.NONE;
        setMessage(Messages.profilePage_selectProfileDefaultMessage, newType);
    }

    int getMessageType() {
        if (m_wizardPage != null)
            return m_wizardPage.getMessageType();
        if (m_propertyPage != null)
            return m_propertyPage.getMessageType();
        return IMessageProvider.NONE;
    }

    private Shell getShell() {
        if (m_wizardPage != null)
            return m_wizardPage.getShell();
        assert (m_propertyPage != null);
        return m_propertyPage.getShell();
    }

    private void setupConnectionProfilePath(Composite composite) {
        Label label = new Label(composite, SWT.NONE);
        label.setText(Messages.profilePage_label_profileStore);
        label.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));

        m_connectionProfilePath = new Text(composite, SWT.BORDER);
        GridData layoutData = new GridData(GridData.FILL_HORIZONTAL);
        layoutData.widthHint = 260;
        m_connectionProfilePath.setLayoutData(layoutData);
        m_connectionProfilePath.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                BusyIndicator.showWhile(getShell() == null ? null : getShell().getDisplay(), new Runnable() {

                    public void run() {
                        populateTree();
                        setExternalLinkDefaultState();

                        // clear any previously selected profile settings
                        clearSelectedProfile();
                        setSelectedDataSourceName(EMPTY_STRING);
                        setDefaultMessageAsError(hasConnectionProfilePath());

                        if (hasConnectionProfilePath()
                                && !new Path(getResolvedConnProfilePathControlText()).toFile().exists())
                            setMessage(Messages.profilePage_error_invalidProfileStorePath, IMessageProvider.ERROR);

                        // check if profile tree has only one profile item, 
                        // automatically select it if feature is not disabled
                        if (hasConnectionProfilePath()
                                && m_connectionProfilePath.getData(DISABLE_AUTO_SELECT_ITEM) != Boolean.TRUE) {
                            TreeItem singleProfileItem = findSingleProfileInTree();
                            if (singleProfileItem != null) {
                                m_odaDataSourceTree.setSelection(singleProfileItem);
                                handleTreeSelection();
                            }
                        }
                    }
                });
            }
        });

        // Browse... button
        IMenuButtonProvider provider = new MenuButtonProvider(false);
        m_browseButton = new ProfileStoreBrowseButton(composite, SWT.PUSH, provider, this);
        GridData data = new GridData();
        data.widthHint = m_browseButton.computeButtonWidth();
        m_browseButton.setLayoutData(data);

        if (m_wizardPage != null && m_wizardPage instanceof ProfileSelectionWizardPage) {
            m_hostResourceIdentifiers = ((ProfileSelectionWizardPage) m_wizardPage).getHostResourceIdentifiers();
        }
        if (m_propertyPage != null && m_propertyPage instanceof ProfileSelectionEditorPage) {
            m_hostResourceIdentifiers = ((ProfileSelectionEditorPage) m_propertyPage).getHostResourceIdentifiers();
        }
        if (m_hostResourceIdentifiers != null) {
            File resourceBase = new File(m_hostResourceIdentifiers.getApplResourceBaseURI());
            m_browseButton.getMenuButtonProvider().setProperty(RESOURCE_FILE_DIR, resourceBase);
        }
        m_browseButton.getMenuButtonProvider().setProperty(IS_CREATE_PROFILE, Boolean.FALSE);

        m_browseButton.refreshMenuItems();

        // New... button
        createNewProfileStoreButton(composite);
    }

    private Button createNewProfileStoreButton(Composite parent) {
        Button newButton = new Button(parent, SWT.PUSH);
        newButton.setText(Messages.profilePage_button_new);
        {
            GridData data = new GridData();
            data.horizontalSpan = 3;
            data.horizontalAlignment = SWT.RIGHT;
            data.widthHint = computeButtonWidth(newButton);
            newButton.setLayoutData(data);
        }

        newButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                handleNewProfileStore();
            }

            private void handleNewProfileStore() {
                CreateProfileStoreAction createAction = new CreateProfileStoreAction(getShell(),
                        m_hostResourceIdentifiers);
                createAction.run();
                if (createAction.isCompleted()) {
                    // copy the newly created profile store file path
                    ProfileStoreCreationDialog dlg = createAction.getProfileStoreCreationDialog();
                    if (dlg != null && dlg.getFile() != null) {
                        String profileStoreTextPath = DesignUtil.convertFileToResourcePath(dlg.getFile(),
                                m_hostResourceIdentifiers, dlg.isProfileStorePathRelative());
                        setConnProfilePathControlText(profileStoreTextPath, false);
                    }
                }
            }
        });

        return newButton;
    }

    /**
     * Setup TreeViewer layout
     */
    private void setupTreeViewer(Composite composite) {
        m_odaDataSourceTree = new Tree(composite, SWT.BORDER | SWT.SINGLE | SWT.V_SCROLL);
        m_odaDataSourceTree.setLayout(new GridLayout());
        GridData layoutData = new GridData(GridData.FILL_BOTH);
        layoutData.horizontalSpan = 3;
        m_odaDataSourceTree.setLayoutData(layoutData);

        m_odaDataSourceTree.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                handleTreeSelection();
            }
        });

        populateTree();
    }

    private void handleTreeSelection() {
        TreeItem item = getSelectedProfileItem();

        if (item == null) {
            clearSelectedProfile();
            setSelectedDataSourceName(EMPTY_STRING);
            setExternalLinkOption(null);
            setDefaultMessageAsError(true);
        } else {
            m_profileID = item.getData().toString();
            m_odaDataSourceID = getProfileItemDataSourceId(item);
            setSelectedDataSourceName(item.getText());
            setExternalLinkOption(m_odaDataSourceID);

            if (getMessageType() == IMessageProvider.NONE) // no current error
                setDefaultMessageAsError(false);
        }
    }

    private String getProfileItemDataSourceId(TreeItem item) {
        return getActiveDataSourceId(item.getParentItem().getData().toString());
    }

    private void setSelectedDataSourceName(String name) {
        if (m_dataSourceDesignNameControl == null)
            return; // nothing to set

        String trimmedName = name.trim();
        m_dataSourceDesignNameControl.setText(trimmedName);
        // setText triggers its modifyListener to update data source design name if appropriate
    }

    /**
     * For use by the tree viewer selection listener to trigger setting 
     * the external link checkbox, based on the selected profile's oda category
     */
    private void setExternalLinkOption(String odaDataSourceId) {
        boolean isExternalLinkOptional = isExternalLinkOptional(odaDataSourceId);

        // set the external link control accordingly
        setExternalLinkOptionControl(isExternalLinkOptional);
    }

    private boolean isExternalLinkOptional(String odaDataSourceId) {
        // by default, if no category is specified, it is optional to maintain an external link 
        OdaProfileCategoryInfo categoryInfo = getOdaProfileCategoryInfo(odaDataSourceId);
        return (categoryInfo != null) ? categoryInfo.isExternalLinkOptional() : true;
    }

    boolean requiresExternalProfileLink() {
        if (!hasSelectedProfile())
            return false; // no selected profile to link
        return !isExternalLinkOptional(m_odaDataSourceID);
    }

    private boolean hasSelectedProfile() {
        return (m_profileID != null && m_odaDataSourceID != null);
    }

    private void clearSelectedProfile() {
        m_profileID = null;
        m_odaDataSourceID = null;
    }

    /**
     * Updates the data source design name only if creating a new design.
     */
    private void setDataSourceDesignName(String name) {
        /* when editing an existing data source design, 
         * do not allow to change the design name from this page
         */
        if (inEditMode())
            return;

        // updates design name only when creating a new design
        assert (name != null);
        m_dataSourceDesignName = name.trim();
    }

    /**
     * Populate the connection profile instances in the tree viewer
     */
    private void populateTree() {
        resetTreeViewer();
        if (!hasConnectionProfilePath())
            return; // done, nothing to populate

        // populate tree with ODA extensions' profile instances

        OdaProfileExplorer.getInstance().refresh(); // reset cached profiles in instance

        // iterate thru each type of applicable ODA data source categories
        SortedSet<OdaProfileCategoryInfo> odaCategoryInfoSet = getOdaCategoryInfoSet();
        TreeItem odaRoot = null;
        for (OdaProfileCategoryInfo categoryInfo : odaCategoryInfoSet) {
            TreeItem categoryItem = createCategoryTreeItems(m_odaDataSourceTree, odaRoot, categoryInfo);

            if (categoryItem != null) {
                if (odaRoot == null && categoryInfo.hasOdaParentCategory())
                    odaRoot = categoryItem.getParentItem(); // reuse same parent item for next ODA category
                categoryItem.setExpanded(true);
                m_odaDataSourceTree.showItem(categoryItem); // scroll to the latest category
            }
        }

        // if the specified profile store file has no profile instances at all,
        // create an empty ODA root item in the tree for UI visibility
        if (m_odaDataSourceTree.getItemCount() == 0)
            createOdaRootItem(m_odaDataSourceTree);
    }

    /**
     * Reset TreeViwewer layout
     */
    private void resetTreeViewer() {
        if (m_odaDataSourceTree != null)
            m_odaDataSourceTree.removeAll();
    }

    /**
     * Returns the current selected tree item that represents a 
     * connection profile instance.
     * @return  selected profile tree item; or null if no selection
     *          is made, or the selected item is not a profile item
     */
    private TreeItem getSelectedProfileItem() {
        TreeItem[] items = m_odaDataSourceTree.getSelection();
        if (items.length == 0)
            return null;

        TreeItem item = items[0];
        if (item.getParentItem() == null || item.getItemCount() > 0)
            return null; // selected item is not a profile instance

        return item;
    }

    /**
     * Search and return the tree item that represents the specified profile
     * instance based on its instanceId.
     * The odaDataSourceId type is specified to optimize the search for
     * matching tree branch. 
     * @return  selected profile tree item; or null if no match is found
     */
    private TreeItem findProfileInTree(String odaDataSourceId, String profileId) {
        if (profileId == null)
            return null;

        TreeItem dsCategory = findOdaCategoryInTree(odaDataSourceId);
        TreeItem profileItem = findProfileInCategoryItem(dsCategory, profileId);
        if (profileItem != null)
            return profileItem;

        // not found in own category;
        // try to find under the deprecated odaDataSourceId(s) replaced by this odaDataSourceId 
        Set<String> deprecatedIds = getDeprecatedDataSourceIds(odaDataSourceId);
        for (String deprecatedId : deprecatedIds) {
            dsCategory = findOdaCategoryInTree(deprecatedId);
            profileItem = findProfileInCategoryItem(dsCategory, profileId);
            if (profileItem != null)
                return profileItem;
        }

        // no match is found
        return null;
    }

    /**
     * Find by profileId within the specified odaDataSourceId category
     * @param dsCategory
     * @param profileId
     * @return
     */
    private TreeItem findProfileInCategoryItem(TreeItem dsCategory, String profileId) {
        if (dsCategory == null)
            return null; // specified oda data source category not in tree

        TreeItem[] profileItems = dsCategory.getItems();
        for (int i = 0; i < profileItems.length; i++) {
            TreeItem profileItem = profileItems[i];
            if (profileItem.getData().toString().equals(profileId))
                return profileItem;
        }
        // no match is found
        return null;
    }

    /**
     * Search and return the tree item that represents the specified 
     * odaDataSourceId type category.
     * @return  selected oda category tree item; or null if no match is found
     */
    private TreeItem findOdaCategoryInTree(String odaDataSourceId) {
        if (m_odaDataSourceTree == null || odaDataSourceId == null)
            return null;

        TreeItem[] topItems = m_odaDataSourceTree.getItems();
        if (topItems.length == 0)
            return null;

        // assumes an oda category is at one of the top 2 levels in tree
        for (int i = 0; i < topItems.length; i++) {
            TreeItem topLevelItem = topItems[i];
            if (isOdaCategoryItem(topLevelItem, odaDataSourceId))
                return topLevelItem;

            TreeItem[] subCategories = topLevelItem.getItems();
            for (int j = 0; j < subCategories.length; j++) {
                TreeItem subCategory = subCategories[j];
                if (isOdaCategoryItem(subCategory, odaDataSourceId))
                    return subCategory;
            }
        }

        return null;
    }

    private boolean isOdaCategoryItem(TreeItem item, String odaDataSourceId) {
        if (item.getItemCount() == 0)
            return false; // not a category folder item

        if (item.getData() != null && item.getData().toString().equals(odaDataSourceId))
            return true;
        return false;
    }

    private TreeItem findSingleProfileInTree() {
        if (m_odaDataSourceTree == null || m_odaDataSourceTree.getItemCount() != 1) // has none or more than one categories
            return null;
        TreeItem topLevelItem = m_odaDataSourceTree.getItems()[0];
        if (topLevelItem.getItemCount() != 1) // has more than one child
            return null; // can't possibly have only single profile item

        // has one child
        TreeItem secondLevelItem = topLevelItem.getItems()[0];
        if (secondLevelItem.getItemCount() > 1)
            return null;

        if (secondLevelItem.getItemCount() == 0) // is a leaf node
            return secondLevelItem; // this is the single profile item

        // has one third-level child
        return secondLevelItem.getItems()[0];
    }

    private SortedSet<OdaProfileCategoryInfo> getOdaCategoryInfoSet() {
        if (m_dataSourceIDs == null)
            m_dataSourceIDs = createOdaProfileCategoryInfoSet();
        return m_dataSourceIDs;
    }

    /**
     * Creates and returns a filtered collection of OdaProfileCategoryInfo
     * on ODA data source extensions found in the extensions registry,
     * sorted by their category display names.
     */
    @SuppressWarnings("deprecation")
    private SortedSet<OdaProfileCategoryInfo> createOdaProfileCategoryInfoSet() {
        Filter profileTypeFilter = ManifestExplorer.createFilter();
        profileTypeFilter.setMissingDataSetTypesFilter(true);
        profileTypeFilter.setDeprecatedFilter(false);
        profileTypeFilter.setHideWrapper(false);

        ExtensionManifest[] odaManifests = ManifestExplorer.getInstance().getExtensionManifests(profileTypeFilter);

        m_deprecatedDataSourceMap.clear();
        TreeSet<OdaProfileCategoryInfo> sortedSet = new TreeSet<OdaProfileCategoryInfo>();
        for (ExtensionManifest manifest : odaManifests) {
            String odaDataSourceId = manifest.getDataSourceElementID();
            String relatedDataSourceId = manifest.getRelatedDataSourceId();
            if (manifest.isDeprecated()) {
                m_deprecatedDataSourceMap.put(odaDataSourceId, relatedDataSourceId);
            }

            if (m_treeFilter != null && !m_treeFilter.equals(odaDataSourceId)
                    && !m_treeFilter.equals(relatedDataSourceId))
                continue; // skip oda data source type not in filter

            // sort the identifiers by their category display name
            sortedSet.add(new OdaProfileCategoryInfo(odaDataSourceId, manifest));
        }

        return sortedSet;
    }

    private String getActiveDataSourceId(String odaDataSourceId) {
        return m_deprecatedDataSourceMap.containsKey(odaDataSourceId)
                ? m_deprecatedDataSourceMap.get(odaDataSourceId)
                : // corresponding replacing odaDataSourceId
                odaDataSourceId;
    }

    private Set<String> getDeprecatedDataSourceIds(String odaDataSourceId) {
        if (!m_deprecatedDataSourceMap.containsValue(odaDataSourceId))
            return Collections.emptySet();

        Set<String> deprecatedIds = new HashSet<String>();
        for (Map.Entry<String, String> deprecatedEntry : m_deprecatedDataSourceMap.entrySet()) {
            if (deprecatedEntry.getValue().equals(odaDataSourceId))
                deprecatedIds.add(deprecatedEntry.getKey());
            // TODO - handle nested deprecated extension ids
        }
        return deprecatedIds;
    }

    private OdaProfileCategoryInfo getOdaProfileCategoryInfo(String odaDataSourceId) {
        if (odaDataSourceId == null)
            return null;

        SortedSet<OdaProfileCategoryInfo> odaCategoryInfoSet = getOdaCategoryInfoSet();
        for (OdaProfileCategoryInfo categoryInfo : odaCategoryInfoSet) {
            if (odaDataSourceId.equals(categoryInfo.getOdaDataSourceId()))
                return categoryInfo;
        }

        return null; // no match found
    }

    private Map<String, String> getProfileIdentifiersByCategory(String categoryId) {
        try {
            return OdaProfileExplorer.getInstance().getProfileIdsAndNamesByCategory(categoryId,
                    new Path(getResolvedConnProfilePathControlText()).toFile());
        } catch (OdaException ex) {
            setMessage(Messages.profilePage_error_invalidProfileStorePath, IMessageProvider.ERROR);
        }

        return null;
    }

    private TreeItem createOdaRootItem(Tree parent) {
        TreeItem odaRoot = new TreeItem(parent, TREE_ITEM_STYLE);
        odaRoot.setText(Messages.profilePage_odaTreeName);
        return odaRoot;
    }

    /**
     * Create child tree items for the given ODA data source category.
     */
    private TreeItem createCategoryTreeItems(Tree parent, TreeItem parentItem,
            OdaProfileCategoryInfo categoryInfo) {
        String categoryId = categoryInfo.getEffectiveCategoryId();
        Map<String, String> profileIds = getProfileIdentifiersByCategory(categoryId);
        // if no profile instances found under given category
        if (profileIds == null || profileIds.isEmpty())
            return null; // done; no need to create a child tree item for category

        // create a tree item for the specified category

        // first determine which parent item to use
        if (categoryInfo.hasOdaParentCategory()) {
            // if this is under the ODA parent category, put it under an ODA parent item
            if (parentItem == null)
                parentItem = createOdaRootItem(parent);
        } else // not under an ODA parent category
        {
            // create category item directly in tree as a top level item
            // TODO - handle sub-categories
            parentItem = null;
        }

        TreeItem categoryItem = (parentItem != null) ? new TreeItem(parentItem, TREE_ITEM_STYLE)
                : new TreeItem(parent, TREE_ITEM_STYLE);

        // the tree view expects a profile instance's parent item to contain an odaDataSourceId in item data
        categoryItem.setData(categoryInfo.getOdaDataSourceId());
        categoryItem.setText(categoryInfo.getDisplayName());

        // create children items for each of its profile instances
        createChildTreeItems(categoryItem, profileIds, TREE_ITEM_STYLE);

        return categoryItem;
    }

    /**
     * Build a child tree item for each connection profile identifier in the specified Map.
     */
    private void createChildTreeItems(TreeItem parentItem, Map<String, String> profileIds, int style) {
        if (profileIds == null)
            return;

        for (String profileInstanceId : profileIds.keySet()) {
            TreeItem item = new TreeItem(parentItem, style);
            item.setData(profileInstanceId);
            // profile instance name
            item.setText(profileIds.get(profileInstanceId));
        }
    }

    /**
     * Setup DataSourceName layout
     */
    private void setupDataSourceName(Composite composite) {
        GridData layoutData = new GridData(GridData.FILL_HORIZONTAL);
        layoutData.horizontalSpan = 3;
        m_useDefaultDSNameCheckBox = new Button(composite, SWT.CHECK);
        m_useDefaultDSNameCheckBox.setText(Messages.profilePage_checkboxLabel_useDefaultName);
        m_useDefaultDSNameCheckBox.setSelection(true);
        m_useDefaultDSNameCheckBox.setLayoutData(layoutData);
        m_useDefaultDSNameCheckBox.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                if (m_useDefaultDSNameCheckBox.getSelection()) {
                    // reset the design name to the default
                    TreeItem item = getSelectedProfileItem();
                    if (item != null)
                        setSelectedDataSourceName(item.getText());
                }

                enableDataSourceNameEditor(!m_useDefaultDSNameCheckBox.getSelection());
            }
        });

        m_dataSourceNameLabel = new Label(composite, SWT.NONE);
        m_dataSourceNameLabel.setText(Messages.profilePage_label_dataSourceName);
        m_dataSourceNameLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));

        layoutData = new GridData(GridData.FILL_HORIZONTAL);
        layoutData.horizontalSpan = 2;
        m_dataSourceDesignNameControl = new Text(composite, SWT.BORDER);
        m_dataSourceDesignNameControl.setLayoutData(layoutData);
        m_dataSourceDesignNameControl.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                setDataSourceDesignName(m_dataSourceDesignNameControl.getText());

                String invalidMessage = null;
                if (isDesignNameBlank()) {
                    setPageComplete(false);
                    setMessage(Messages.profilePage_error_emptyName, IMessageProvider.ERROR);
                } else if ((invalidMessage = validateAndGetErrorMessage()) != null) {
                    setPageComplete(false);
                    setMessage(invalidMessage, IMessageProvider.ERROR);
                } else // name value is ok
                {
                    // when creating a new data source, the name must be associated
                    // with a profile selection either by import or reference
                    boolean isNameOk = inEditMode() ? true : hasSelectedProfile();
                    setPageComplete(isNameOk);
                }
            }
        });

        enableDataSourceNameEditor(false);
    }

    private void setupLinkCheckBox(Composite composite) {
        m_linkRefCheckBox = new Button(composite, SWT.CHECK);
        m_linkRefCheckBox.setToolTipText(Messages.profilePage_checkboxTooltip_maintainLink);
        GridData data = new GridData();
        data.horizontalSpan = 3;
        m_linkRefCheckBox.setLayoutData(data);
        m_linkRefCheckBox.setText(Messages.profilePage_checkboxLabel_maintainLink);
        m_linkRefCheckBox.setSelection(true);
        setExternalLinkDefaultState();

        m_linkRefCheckBox.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                if (!inEditMode() || m_odaDataSourceTree == null)
                    return; // nothing to do

                // editing of selected profile is allowed only if a link is maintained
                boolean maintainLink = m_linkRefCheckBox.getSelection();
                m_odaDataSourceTree.setEnabled(maintainLink);

                if (isPageComplete())
                    setMessage(EMPTY_STRING, IMessageProvider.NONE);
                else
                    setDefaultMessageAsError(maintainLink);
            }
        });
    }

    /**
     * Validate whether the data source design name is blank
     */
    private boolean isDesignNameBlank() {
        return m_dataSourceDesignName == null || m_dataSourceDesignName.length() == 0;
    }

    /**
     * Validate whether the data source design name is valid.  
     * @return  null if the name is valid; 
     *          otherwise, return corresponding error message
     */
    private String validateAndGetErrorMessage() {
        if (m_designNameValidator != null) {
            try {
                if (m_designNameValidator.isValid(m_dataSourceDesignName))
                    return null; // is valid, no error message
                else // validator did not throw exception to provide error message, 
                     // use default error message
                    return Messages.profilePage_error_invalidName;
            } catch (OdaException ex) {
                return ex.getMessage(); // is invalid
            }
        }

        return null; // is valid, no error message
    }

    /**
     * Enable DataSourceNameEditor
     */
    private void enableDataSourceNameEditor(boolean enabled) {
        m_dataSourceNameLabel.setEnabled(enabled);
        m_dataSourceDesignNameControl.setEnabled(enabled);
    }

    private void setDataSourceNameEditorVisible(boolean visible) {
        m_useDefaultDSNameCheckBox.setVisible(visible);
        m_dataSourceNameLabel.setVisible(visible);
        m_dataSourceDesignNameControl.setVisible(visible);
    }

    /**
     * Set the checkbox state for maintaining a link to the selected connection profile
     * according to the specified state.
     * @param isExternalLinkOptional   true indicates whether an user is allowed to set/unset
     *              the external link option; false means that an external link is required
     */
    private void setExternalLinkOptionControl(boolean isExternalLinkOptional) {
        m_linkRefCheckBox.setEnabled(isExternalLinkOptional);
        if (!isExternalLinkOptional) // requires to maintain an external link
            m_linkRefCheckBox.setSelection(true);
    }

    private void setExternalLinkDefaultState() {
        // initalize the link control state based on whether a profile file path exists
        m_linkRefCheckBox.setEnabled(hasConnectionProfilePath());
    }

    private boolean inEditMode() {
        return (m_propertyPage != null);
    }

    private boolean hasConnectionProfilePath() {
        return getConnProfilePathControlText().trim().length() > 0;
    }

    /**
     * Process the given text in bi-directional locale for rendering in the
     * connection profile store file path UI text control.  
     * Also specify whether to disable the default behavior of auto selection
     * of single item found in specified profile file.
     * @param text  connection profile file path
     * @param disableAutoSelect true to disable default behavior
     */
    private void setConnProfilePathControlText(String text, boolean disableAutoSelect) {
        String localizedText = TextProcessorWrapper.process(text);

        // disable default behavior, set flag for path control's event handler
        if (disableAutoSelect)
            m_connectionProfilePath.setData(DISABLE_AUTO_SELECT_ITEM, Boolean.TRUE);
        m_connectionProfilePath.setText(localizedText);
        // reset to default behavior
        m_connectionProfilePath.setData(DISABLE_AUTO_SELECT_ITEM, null);
    }

    /**
     * Return a de-processed text value in the connection profile store file path 
     * UI text control.
     * @return  the user-specified text in the profile store path control;
     *          may be in relative or absolute format; and may point to a non-existing file
     */
    private String getConnProfilePathControlText() {
        String localizedText = m_connectionProfilePath.getText();
        return TextProcessorWrapper.deprocess(localizedText);
    }

    private String getResolvedConnProfilePathControlText() {
        return resolveProfileStorePath(getConnProfilePathControlText());
    }

    private String resolveProfileStorePath(String filePathText) {
        if (filePathText == null || filePathText.trim().length() == 0 || m_hostResourceIdentifiers == null)
            return filePathText; // nothing to resolve with

        String resolvedFilePathText = DesignUtil.resolveToApplResourcePath(filePathText, m_hostResourceIdentifiers);
        return (resolvedFilePathText != null) ? resolvedFilePathText : filePathText; // unable to resolve invalid filePathText, return original value
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.datatools.connectivity.oda.design.internal.ui.profile.browse.IBrowseButtonParent#setConnProfilePath(java.lang.String, boolean)
     */
    public void setProfileStorePath(String path, boolean isRelative) {
        if (path == null)
            path = EMPTY_STRING;
        setConnProfilePathControlText(path, false);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.datatools.connectivity.oda.design.internal.ui.profile.browse.IBrowseButtonHost#browseSelected()
     */
    public void browseSelected() {
        m_browseButton.getMenuButtonProvider().setProperty(IBrowseButtonHost.STORED_PATH,
                getResolvedConnProfilePathControlText());
    }

    /**
     * Compute the preferred width of the specified button based on
     * its label size or the minimum button width, whichever is wider.
     * @param button    
     * @return      preferred button width
     */
    static int computeButtonWidth(Button button) {
        int defaultWidth = button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
        return (defaultWidth < BUTTON_MIN_WIDTH) ? BUTTON_MIN_WIDTH : defaultWidth;
    }

    /**
     * Internal class that encapsulates related identifier information on 
     * an ODA connection profile category.  
     * It provides info for a category item in the connection profile tree viewer,
     * and can be used for Comparable sorting in a collection.
     * @since 3.0.6
     */
    private class OdaProfileCategoryInfo implements Comparable {
        private String m_odaDataSourceId;
        private boolean m_hasOdaParentCategory;
        private String m_categoryDisplayName;
        private String m_effectiveCategoryId;

        OdaProfileCategoryInfo(String id, ExtensionManifest manifest) {
            m_odaDataSourceId = id;

            ICategory profileCategory = ProfileCategoryUtil.getCategory(m_odaDataSourceId);
            m_hasOdaParentCategory = ProfileCategoryUtil.hasODAParentCategory(profileCategory);

            // if parent category is not ODA type, use the parent category info
            boolean useParentCategory = !m_hasOdaParentCategory;
            m_categoryDisplayName = getEffectiveDisplayName(profileCategory, useParentCategory, manifest);
            m_effectiveCategoryId = getEffectiveCategoryId(profileCategory, useParentCategory);
        }

        private String getEffectiveDisplayName(ICategory profileCategory, boolean useParentCategory,
                ExtensionManifest manifest) {
            String categoryDisplayName = getCategoryDisplayName(profileCategory, useParentCategory,
                    manifest.getDataSourceDisplayName());
            return useParentCategory ? categoryDisplayName : refineDisplayName(categoryDisplayName, manifest);
        }

        private String getCategoryDisplayName(ICategory profileCategory, boolean useParentCategory,
                String defaultDisplayName) {
            if (profileCategory == null)
                return defaultDisplayName;

            if (useParentCategory && profileCategory.getParent() != null)
                profileCategory = profileCategory.getParent();

            String displayName = profileCategory.getName();

            if (displayName == null || displayName.length() == 0
                    || ProfileCategoryUtil.isUnknownCategory(profileCategory))
                displayName = defaultDisplayName;

            return displayName;
        }

        @SuppressWarnings("deprecation")
        private String refineDisplayName(String categoryDisplayName, ExtensionManifest manifest) {
            if (!manifest.isDeprecated())
                return categoryDisplayName; // no need to refine the display name

            String replacingDataSourceId = manifest.getRelatedDataSourceId();
            try {
                String newDisplayName = ManifestExplorer.getInstance().getExtensionManifest(replacingDataSourceId)
                        .getDataSourceDisplayName();
                categoryDisplayName = Messages.bind(Messages.profilePage_deprecatedProfileDisplayName,
                        categoryDisplayName, newDisplayName);
            } catch (OdaException ex) {
                // ignore
            }
            return categoryDisplayName;
        }

        private String getEffectiveCategoryId(ICategory profileCategory, boolean useParentCategory) {
            String categoryId = null;
            if (profileCategory != null) {
                if (useParentCategory && profileCategory.getParent() != null)
                    profileCategory = profileCategory.getParent();
                categoryId = profileCategory.getId();
            }

            if (ProfileCategoryUtil.isUnknownCategory(categoryId))
                categoryId = m_odaDataSourceId; // default is the same as oda data source id  

            return categoryId;
        }

        String getOdaDataSourceId() {
            return m_odaDataSourceId;
        }

        boolean hasOdaParentCategory() {
            return m_hasOdaParentCategory;
        }

        String getDisplayName() {
            return m_categoryDisplayName;
        }

        String getEffectiveCategoryId() {
            return m_effectiveCategoryId;
        }

        boolean isExternalLinkOptional() {
            /* a profile category without an ODA parent category and non-Db category requires 
             * maintaining an external link to a connection profile, 
             * cuz importing a profile's properties to a data source design private properties 
             * (which are not encyrpted) is not supported due to security concern
             */
            return hasOdaParentCategory() || getEffectiveCategoryId().equals(Constants.DATABASE_CATEGORY_ID);
        }

        /* (non-Javadoc)
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        public int compareTo(Object obj) {
            if (!(obj instanceof OdaProfileCategoryInfo))
                return -1;
            return compareTo((OdaProfileCategoryInfo) obj);
        }

        public int compareTo(OdaProfileCategoryInfo anotherId) {
            // first compare the parent category type
            if (m_hasOdaParentCategory != anotherId.m_hasOdaParentCategory) {
                // non ODA parent category is lower in ascending order
                return !m_hasOdaParentCategory ? -1 : 1;
            }

            // same parent category type, compare its own category name
            int result = m_categoryDisplayName.compareTo(anotherId.m_categoryDisplayName);
            if (result != 0) // not the same display name
                return result;
            // next compare its id
            return m_odaDataSourceId.compareTo(anotherId.m_odaDataSourceId);
        }

        /* (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
        public boolean equals(Object obj) {
            if (!(obj instanceof OdaProfileCategoryInfo))
                return false;
            OdaProfileCategoryInfo anotherId = (OdaProfileCategoryInfo) obj;
            return m_categoryDisplayName.equals(anotherId.m_categoryDisplayName)
                    && m_odaDataSourceId.equals(anotherId.m_odaDataSourceId)
                    && m_hasOdaParentCategory == anotherId.m_hasOdaParentCategory
                    && m_effectiveCategoryId.equals(anotherId.m_effectiveCategoryId);
        }
    };

}