com.microsoft.alm.plugin.idea.utils.ServerContextSettings.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.alm.plugin.idea.utils.ServerContextSettings.java

Source

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

package com.microsoft.alm.plugin.idea.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.ide.passwordSafe.PasswordSafe;
import com.intellij.ide.passwordSafe.PasswordSafeException;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.components.StoragePathMacros;
import com.microsoft.alm.common.utils.UrlHelper;
import com.microsoft.alm.plugin.authentication.AuthenticationInfo;
import com.microsoft.alm.plugin.context.ServerContext;
import com.microsoft.alm.plugin.context.ServerContext.Type;
import com.microsoft.alm.plugin.context.ServerContextBuilder;
import com.microsoft.alm.plugin.context.ServerContextManager;
import com.microsoft.alm.plugin.services.ServerContextStore;
import com.microsoft.alm.plugin.services.ServerContextStore.Key;
import com.microsoft.teamfoundation.core.webapi.model.TeamProjectCollectionReference;
import com.microsoft.teamfoundation.core.webapi.model.TeamProjectReference;
import com.microsoft.teamfoundation.sourcecontrol.webapi.model.GitRepository;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Stores a ServerContextItemStore to file and handles writing and reading the objects
 */
@State(name = "TfServers", storages = { @Storage(file = StoragePathMacros.APP_CONFIG + "/tf_settings.xml") })
public class ServerContextSettings
        implements PersistentStateComponent<ServerContextSettings.ServerContextItemsStore> {

    private static final Logger logger = LoggerFactory.getLogger(ServerContextSettings.class);

    /**
     * Lifecycle:
     * <ol>
     * <li>During plugin startup, IntelliJ calls the settings file from disk and calls loadState() which sets restoreState</li>
     * <li>Later ServerContextManager loads and takes ownership of this data and sets restoreState to <code>null</code></li>
     * <li>Finally, during plugin shutdown, IntelliJ calls getState() get the contents to write back out to disk</li>
     * </ol>
     * One other interesting note, is that ServerContextManager makes calls to save the passwords as it goes
     * (but doesn't update any other state data).  Ideally, the passwords would be saved at the same time everything
     * is saved (i.e. in getState()), but this is not possible, because by the time IntelliJ calls into here the
     * PasswordManager service has already been shut down.
     */
    private ServerContextItemsStore restoreState;

    private final ObjectMapper mapper = new ObjectMapper();

    /**
     * IntelliJ calls this method to load from stored state during plugin initialization.
     * <p/>
     * Shortly thereafter, ServerContextManager will initialize and will take ownership of this data with actively
     * managed ServerContexts, and this state data should not be used again.
     *
     * @param state
     */
    public void loadState(final ServerContextItemsStore state) {
        this.restoreState = state;
    }

    /**
     * IntelliJ calls this method to save to stored state when it shuts down.
     */
    public ServerContextItemsStore getState() {
        return getServerContextsToPersist();
    }

    public static class ServerContextItemsStore {

        public ServerContextItemStore[] serverContextItemStores;

        public static class ServerContextItemStore {
            //fields have to be public, so IntelliJ can write them to the persistent store
            public Type type = null;
            public String uri = null;
            public String teamProjectCollectionReference = null;
            public String teamProjectReference = null;
            public String gitRepository = null;
        }
    }

    // This default instance is only returned in the case of tests or we are somehow running outside of IntelliJ
    private static ServerContextSettings DEFAULT_INSTANCE = new ServerContextSettings();

    public static ServerContextSettings getInstance() {
        if (ApplicationManager.getApplication() != null) {
            return ServiceManager.getService(ServerContextSettings.class);
        } else {
            return DEFAULT_INSTANCE;
        }
    }

    public void saveServerContextSecrets(final ServerContext context) {
        final Key key = ServerContextStore.Key.create(context);

        final AuthenticationInfo authenticationInfo = context.getAuthenticationInfo();
        final String stringValue = writeToJson(authenticationInfo);

        writePassword(key, stringValue);
    }

    private ServerContextItemsStore getServerContextsToPersist() {
        if (restoreState != null) {
            // ServerContextManager never loaded the data
            return restoreState;
        } else {
            final ServerContextItemsStore saveState = new ServerContextItemsStore();

            List<ServerContext> serverContexts = ServerContextManager.getInstance().getAllServerContexts();
            List<ServerContextItemsStore.ServerContextItemStore> stores = new ArrayList<ServerContextItemsStore.ServerContextItemStore>();
            for (ServerContext context : serverContexts) {
                ServerContextItemsStore.ServerContextItemStore store = new ServerContextItemsStore.ServerContextItemStore();
                store.type = context.getType();
                store.uri = context.getUri().toString();
                store.teamProjectCollectionReference = writeToJson(
                        restrict(context.getTeamProjectCollectionReference()));
                store.teamProjectReference = writeToJson(restrict(context.getTeamProjectReference()));
                store.gitRepository = writeToJson(context.getGitRepository());
                stores.add(store);
            }
            saveState.serverContextItemStores = stores
                    .toArray(new ServerContextItemsStore.ServerContextItemStore[stores.size()]);
            return saveState;
        }
    }

    // This method exists to make sure we can deserialize the collection reference.
    private TeamProjectCollectionReference restrict(final TeamProjectCollectionReference reference) {
        final TeamProjectCollectionReference newReference = new TeamProjectCollectionReference();
        newReference.setName(reference.getName());
        newReference.setId(reference.getId());
        newReference.setUrl(reference.getUrl());
        return newReference;
    }

    // This method exists to make sure we can deserialize the project reference.
    private TeamProjectReference restrict(final TeamProjectReference reference) {
        final TeamProjectReference newReference = new TeamProjectReference();
        newReference.setName(reference.getName());
        newReference.setId(reference.getId());
        newReference.setUrl(reference.getUrl());
        newReference.setAbbreviation(reference.getAbbreviation());
        newReference.setDescription(reference.getDescription());
        newReference.setRevision(reference.getRevision());
        newReference.setState(reference.getState());
        return newReference;
    }

    public List<ServerContext> getServerContextsToRestore() {
        if (restoreState == null || restoreState.serverContextItemStores == null) {
            return Collections.EMPTY_LIST;
        }

        final List<ServerContext> serverContexts = new ArrayList<ServerContext>();
        for (final ServerContextItemsStore.ServerContextItemStore toRestore : restoreState.serverContextItemStores) {
            Key key = null;
            try {
                final URI serverUri = UrlHelper.getBaseUri(toRestore.uri);
                key = ServerContextStore.Key.create(serverUri);
                final AuthenticationInfo authenticationInfo = getServerContextSecrets(key);
                if (authenticationInfo != null) {
                    serverContexts.add(new ServerContextBuilder().type(toRestore.type).uri(serverUri)
                            .authentication(authenticationInfo)
                            .collection(readFromJson(toRestore.teamProjectCollectionReference,
                                    TeamProjectCollectionReference.class))
                            .teamProject(readFromJson(toRestore.teamProjectReference, TeamProjectReference.class))
                            .repository(readFromJson(toRestore.gitRepository, GitRepository.class)).build());
                }
            } catch (final Throwable restoreThrowable) {
                logger.warn("Failed to restore server context", restoreThrowable);
                // attempt to clean up left over data
                if (key != null) {
                    try {
                        forgetServerContextSecrets(key);
                    } catch (final Throwable cleanupThrowable) {
                        logger.warn("Failed to cleanup invalid server context");
                    }
                }
            }
        }

        restoreState = null; // null this out since data ownership has now been passed
        return serverContexts;
    }

    public void forgetServerContextSecrets(final Key key) {
        forgetPassword(key);
    }

    public AuthenticationInfo getServerContextSecrets(final Key key) throws IOException {
        final String authInfoSerialized = readPassword(key);

        AuthenticationInfo info = null;
        if (StringUtils.isNotEmpty(authInfoSerialized)) {
            info = readFromJson(authInfoSerialized, AuthenticationInfo.class);
        }

        if (info == null) {
            forgetServerContextSecrets(key);
            logger.warn("getServerContextSecrets: info was null for key: ", key);
            return null;
        }
        return info;
    }

    private <T> String writeToJson(final T object) {
        String json = null;
        if (object != null) {
            try {
                json = mapper.writeValueAsString(object);
            } catch (JsonProcessingException e) {
                logger.warn("Failed to convert to string", e);
            }
        }
        return json;
    }

    private <T> T readFromJson(final String json, final Class<T> valueType) throws IOException {
        T object = null;
        if (json != null) {
            object = mapper.readValue(json, valueType);
        }
        return object;
    }

    private void forgetPassword(final Key key) {
        try {
            PasswordSafe.getInstance().removePassword(null, this.getClass(), key.stringValue());
        } catch (PasswordSafeException e) {
            logger.warn("Failed to clear password store", e);
        }
    }

    private void writePassword(final Key key, final String value) {
        try {
            PasswordSafe.getInstance().storePassword(null, this.getClass(), key.stringValue(), value);
        } catch (PasswordSafeException e) {
            logger.warn("Failed to get password", e);
        }
    }

    private String readPassword(final Key key) {
        String password = null;
        try {
            password = PasswordSafe.getInstance().getPassword(null, this.getClass(), key.stringValue());
        } catch (PasswordSafeException e) {
            logger.warn("Failed to read password", e);
        }
        return password;
    }

}