com.microsoft.tfs.client.common.util.TeamContextCache.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.common.util.TeamContextCache.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.client.common.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.runtime.Preferences;

import com.microsoft.tfs.client.common.TFSCommonClientPlugin;
import com.microsoft.tfs.client.common.server.TFSServer;
import com.microsoft.tfs.client.common.server.cache.project.ServerProjectCache;
import com.microsoft.tfs.core.TFSTeamProjectCollection;
import com.microsoft.tfs.core.clients.commonstructure.ProjectInfo;
import com.microsoft.tfs.core.clients.teamsettings.TeamConfiguration;
import com.microsoft.tfs.core.memento.Memento;
import com.microsoft.tfs.core.memento.XMLMemento;
import com.microsoft.tfs.util.Check;
import com.microsoft.tfs.util.GUID;

/**
 * Provides persistence for the following things using the Eclipse preference
 * API:
 * <ul>
 * <li>Active team projects (the ones in the collection the user is working
 * with)</li>
 * <li>Current team project (the single project the Team Explorer has selected)
 * </li>
 * <li>Current TFS 2012 team (the single team the Team Explorer has selected)
 * </li>
 * </ul>
 * <p>
 * Consider using {@link ServerProjectCache}, available from
 * {@link TFSServer#getProjectCache()}, instead of using this class directly.
 * {@link ServerProjectCache} handles retrieving the available projects and
 * teams from the server, which this class doesn't.
 */
public final class TeamContextCache {
    private static final Log log = LogFactory.getLog(TeamContextCache.class);

    private static final String PREFERENCE_CHARSET = "UTF-8"; //$NON-NLS-1$
    private static final String PREFERENCE_KEY_PREFIX = "com.microsoft.tfs.projectsAndTeams"; //$NON-NLS-1$

    private static final String MEMENTO_ROOT_KEY = "projectsAndTeams"; //$NON-NLS-1$

    // 0 or more of these can appear under the root
    private static final String MEMENTO_ACTIVE_PROJECT_KEY = "activeTeamProject"; //$NON-NLS-1$
    private static final String MEMENTO_ACTIVE_PROJECT_NAME_KEY = "name"; //$NON-NLS-1$
    private static final String MEMENTO_ACTIVE_PROJECT_GUID_KEY = "guid"; //$NON-NLS-1$

    // 0 or 1 of these can appear under the root
    private static final String MEMENTO_CURRENT_PROJECT_KEY = "currentTeamProject"; //$NON-NLS-1$
    private static final String MEMENTO_CURRENT_PROJECT_NAME_KEY = "name"; //$NON-NLS-1$
    private static final String MEMENTO_CURRENT_PROJECT_GUID_KEY = "guid"; //$NON-NLS-1$

    // 0 or 1 of these can appear under the root
    private static final String MEMENTO_CURRENT_TEAM_KEY = "currentTeam"; //$NON-NLS-1$
    private static final String MEMENTO_CURRENT_TEAM_NAME_KEY = "name"; //$NON-NLS-1$
    private static final String MEMENTO_CURRENT_TEAM_GUID_KEY = "guid"; //$NON-NLS-1$

    private static final TeamContextCache instance = new TeamContextCache();

    /**
     * The in-memory cache. Synchronized on itself.
     */
    private final Map<String, XMLMemento> cache = new HashMap<String, XMLMemento>();

    private TeamContextCache() {
    }

    public static TeamContextCache getInstance() {
        return instance;
    }

    /**
     * Gets the active team projects.
     *
     * @param connection
     *        the connection (must not be <code>null</code>)
     * @param allProjects
     *        the current set of all team projects in the collection (the
     *        returned values are taken from this array) (must not be
     *        <code>null</code>)
     * @return the active projects or an empty array if there are no active
     *         projects
     */
    public ProjectInfo[] getActiveTeamProjects(final TFSTeamProjectCollection connection,
            final ProjectInfo[] allProjects) {
        Check.notNull(connection, "connection"); //$NON-NLS-1$
        Check.notNull(allProjects, "allProjects"); //$NON-NLS-1$

        final String key = getCacheKey(connection);

        final List<ProjectInfo> activeProjectList = new ArrayList<ProjectInfo>();

        final Memento root = getMemento(key);

        // Ensure each saved project is still available
        final Memento[] activeProjects = root.getChildren(MEMENTO_ACTIVE_PROJECT_KEY);
        for (final Memento activeProject : activeProjects) {
            final GUID activeProjectGUID = new GUID(activeProject.getString(MEMENTO_ACTIVE_PROJECT_GUID_KEY));
            for (final ProjectInfo project : allProjects) {
                if (activeProjectGUID.equals(new GUID(project.getGUID()))) {
                    activeProjectList.add(project);
                    break;
                }
            }
        }

        return activeProjectList.toArray(new ProjectInfo[activeProjectList.size()]);
    }

    /**
     * Sets the active team projects.
     *
     * @param connection
     *        the connection (must not be <code>null</code>)
     * @param activeTeamProjects
     *        the projects to save (may be <code>null</code> or empty)
     */
    public void setActiveTeamProjects(final TFSTeamProjectCollection connection,
            final ProjectInfo[] activeTeamProjects) {
        Check.notNull(connection, "connection"); //$NON-NLS-1$

        final String key = getCacheKey(connection);

        final XMLMemento root = getMemento(key);

        root.removeChildren(MEMENTO_ACTIVE_PROJECT_KEY);

        if (activeTeamProjects != null) {
            for (final ProjectInfo project : activeTeamProjects) {
                final Memento child = root.createChild(MEMENTO_ACTIVE_PROJECT_KEY);
                child.putString(MEMENTO_ACTIVE_PROJECT_NAME_KEY, project.getName());
                child.putString(MEMENTO_ACTIVE_PROJECT_GUID_KEY, project.getGUID());
            }
        }

        setMemento(key, root);
    }

    /**
     * Gets the current team project.
     *
     * @param connection
     *        the connection (must not be <code>null</code>)
     * @param allProjects
     *        the current set of all team projects in the collection (the
     *        returned value is taken from this array) (must not be
     *        <code>null</code>)
     * @return the current team project or <code>null</code> if there is no
     *         current team project
     */
    public ProjectInfo getCurrentTeamProject(final TFSTeamProjectCollection connection,
            final ProjectInfo[] allProjects) {
        Check.notNull(connection, "connection"); //$NON-NLS-1$
        Check.notNull(allProjects, "allProjects"); //$NON-NLS-1$

        final String key = getCacheKey(connection);

        final Memento root = getMemento(key);

        final Memento currentProject = root.getChild(MEMENTO_CURRENT_PROJECT_KEY);
        if (currentProject != null) {
            // Ensure the saved project is still available
            final GUID savedProjectGUID = new GUID(currentProject.getString(MEMENTO_CURRENT_PROJECT_GUID_KEY));
            for (final ProjectInfo project : allProjects) {
                if (savedProjectGUID.equals(new GUID(project.getGUID()))) {
                    return project;
                }
            }
        }

        return null;
    }

    /**
     * Sets the current team projects.
     *
     * @param connection
     *        the connection (must not be <code>null</code>)
     * @param currentProject
     *        the current project to save (may be <code>null</code>)
     */
    public void setCurrentTeamProject(final TFSTeamProjectCollection connection, final ProjectInfo currentProject) {
        Check.notNull(connection, "connection"); //$NON-NLS-1$

        final String key = getCacheKey(connection);

        final XMLMemento root = getMemento(key);

        root.removeChildren(MEMENTO_CURRENT_PROJECT_KEY);

        if (currentProject != null) {
            final Memento child = root.createChild(MEMENTO_CURRENT_PROJECT_KEY);
            child.putString(MEMENTO_CURRENT_PROJECT_NAME_KEY, currentProject.getName());
            child.putString(MEMENTO_CURRENT_PROJECT_GUID_KEY, currentProject.getGUID());
        }

        setMemento(key, root);
    }

    /**
     * Gets the current TFS 2012 team.
     *
     * @param connection
     *        the connection (must not be <code>null</code>)
     * @param allTeams
     *        the current set of all teams in the collection (the returned value
     *        is taken from this array) (must not be <code>null</code>)
     * @return the current team or <code>null</code> if there is no current team
     */
    public TeamConfiguration getCurrentTeam(final TFSTeamProjectCollection connection,
            final TeamConfiguration[] allTeams) {
        Check.notNull(connection, "connection"); //$NON-NLS-1$
        Check.notNull(allTeams, "allTeams"); //$NON-NLS-1$

        final String key = getCacheKey(connection);

        final Memento root = getMemento(key);

        final Memento currentTeam = root.getChild(MEMENTO_CURRENT_TEAM_KEY);
        if (currentTeam != null) {
            // Ensure the saved project is still available
            final GUID savedTeamGUID = new GUID(currentTeam.getString(MEMENTO_CURRENT_TEAM_GUID_KEY));
            for (final TeamConfiguration team : allTeams) {
                if (savedTeamGUID.equals(team.getTeamID())) {
                    return team;
                }
            }
        }

        return null;
    }

    /**
     * Sets the current TFS 2012 team.
     *
     * @param connection
     *        the connection (must not be <code>null</code>)
     * @param currentTeam
     *        the current team to save (may be <code>null</code>)
     */
    public void setCurrentTeam(final TFSTeamProjectCollection connection, final TeamConfiguration currentTeam) {
        Check.notNull(connection, "connection"); //$NON-NLS-1$

        final String key = getCacheKey(connection);

        final XMLMemento root = getMemento(key);

        root.removeChildren(MEMENTO_CURRENT_TEAM_KEY);

        if (currentTeam != null) {
            final Memento child = root.createChild(MEMENTO_CURRENT_TEAM_KEY);
            child.putString(MEMENTO_CURRENT_TEAM_NAME_KEY, currentTeam.getTeamName());
            child.putString(MEMENTO_CURRENT_TEAM_GUID_KEY, currentTeam.getTeamID().getGUIDString());
        }

        setMemento(key, root);
    }

    /**
     * Gets the memento for the specified key, trying the in-memory cache first,
     * then trying the Eclipse preference store. If no saved memento was found,
     * a new {@link XMLMemento} is returned.
     *
     * @param key
     *        the key (must not be <code>null</code>)
     * @return the {@link XMLMemento}, never <code>null</code>
     */
    private XMLMemento getMemento(final String key) {
        Check.notNull(key, "key"); //$NON-NLS-1$

        synchronized (cache) {
            XMLMemento memento = cache.get(key);

            if (memento == null) {
                try {
                    final Preferences preferences = TFSCommonClientPlugin.getDefault().getPluginPreferences();
                    final String preferenceName = getPreferenceName(key);

                    final String mementoString = preferences.getString(preferenceName);

                    if (mementoString != null && mementoString.length() > 0) {
                        memento = XMLMemento.read(
                                new ByteArrayInputStream(mementoString.getBytes(PREFERENCE_CHARSET)),
                                PREFERENCE_CHARSET);
                    } else {
                        memento = new XMLMemento(MEMENTO_ROOT_KEY);
                    }
                } catch (final Exception e) {
                    log.warn("Error loading active project and team information", e); //$NON-NLS-1$
                    memento = new XMLMemento(MEMENTO_ROOT_KEY);
                }

                cache.put(key, memento);
            }

            return memento;
        }
    }

    /**
     * Sets the memento for the specified key into the in-memory cache and the
     * Eclipse preference store.
     * <p>
     * Must hold {@link #cacheLock} while calling this method.
     *
     * @param key
     *        the key (must not be <code>null</code>)
     */
    private void setMemento(final String key, final XMLMemento memento) {
        Check.notNull(key, "key"); //$NON-NLS-1$
        Check.notNull(memento, "memento"); //$NON-NLS-1$

        synchronized (cache) {
            cache.put(key, memento);
        }

        try {
            Check.notNull(memento, "memento"); //$NON-NLS-1$

            final Preferences preferences = TFSCommonClientPlugin.getDefault().getPluginPreferences();
            final String preferenceName = getPreferenceName(key);

            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            memento.write(outputStream, PREFERENCE_CHARSET);

            preferences.setValue(preferenceName, outputStream.toString(PREFERENCE_CHARSET));
            TFSCommonClientPlugin.getDefault().savePluginPreferences();
        } catch (final Exception e) {
            log.warn("Error saving active project and team information", e); //$NON-NLS-1$
        }
    }

    private String getPreferenceName(final String key) {
        return PREFERENCE_KEY_PREFIX + "." + key; //$NON-NLS-1$
    }

    private String getCacheKey(final TFSTeamProjectCollection connection) {
        /*
         * So TeamContextCache is useful offline, we must use only attributes
         * from the connection that can be read offline. The connection's
         * instance ID plus the authorized user name would combine to make an
         * ideal key here, but those require a round-trip to the server. The URI
         * will do instead.
         */

        final URI uri = connection.getBaseURI();

        // Convert the URI parts to something more pref-key-like
        return String.format("%s/%s/%d/%s", uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath()) //$NON-NLS-1$
                .toLowerCase();
    }
}