com.microsoft.tfs.client.common.ui.protocolhandler.ProtocolHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.common.ui.protocolhandler.ProtocolHandler.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.ui.protocolhandler;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.microsoft.tfs.client.common.framework.command.ICommand;
import com.microsoft.tfs.client.common.git.utils.GitHelpers;
import com.microsoft.tfs.core.util.ServerURIUtils;
import com.microsoft.tfs.core.util.URIUtils;
import com.microsoft.tfs.util.Platform;
import com.microsoft.tfs.util.StringUtil;

/**
 *
 */
public class ProtocolHandler {
    private static final Log log = LogFactory.getLog(ProtocolHandler.class);

    private final static String PROTOCOL_HANDLER_ENCODING_PARAM = "EncFormat="; //$NON-NLS-1$
    private final static String PROTOCOL_HANDLER_TFS_LINK_PARAM = "tfslink="; //$NON-NLS-1$

    // tfsLink items
    private final static String PROTOCOL_HANDLER_PROJECT_ITEM = "project"; //$NON-NLS-1$
    private final static String PROTOCOL_HANDLER_REPOSITORY_ITEM = "repository"; //$NON-NLS-1$
    private final static String PROTOCOL_HANDLER_CLONE_URL_ITEM = "cloneUrl"; //$NON-NLS-1$
    private final static String PROTOCOL_HANDLER_SERVER_URL_ITEM = "serverUrl"; //$NON-NLS-1$
    private final static String PROTOCOL_HANDLER_COLLECTION_ID_ITEM = "collectionId"; //$NON-NLS-1$
    private final static String PROTOCOL_HANDLER_COLLECTION_URL_ITEM = "collectionUrl"; //$NON-NLS-1$
    private final static String PROTOCOL_HANDLER_BRANCH_ITEM = "ref"; //$NON-NLS-1$

    public final static String PROTOCOL_HANDLER_ARG = "-clonefromtfs"; //$NON-NLS-1$
    public final static String PROTOCOL_HANDLER_SCHEME = "vsoeclipse"; //$NON-NLS-1$

    private static ProtocolHandler instance = new ProtocolHandler();

    private URI protocolHandlerUri;

    private boolean isParsed = false;
    private boolean isAvailable = false;
    private boolean isCollectionUrlAvailable = false;
    private String encoding;

    // tfsLink values
    private final AtomicReference<String> serverUrl = new AtomicReference<String>();
    private final AtomicReference<String> cloneUrl = new AtomicReference<String>();
    private final AtomicReference<String> collectionId = new AtomicReference<String>();
    private final AtomicReference<String> collectionUrl = new AtomicReference<String>();
    private final AtomicReference<String> repository = new AtomicReference<String>();
    private final AtomicReference<String> project = new AtomicReference<String>();
    private final AtomicReference<String> branchName = new AtomicReference<String>();

    private ProtocolHandler() {
        this.protocolHandlerUri = findProtocolHandlerUriArgument(
                org.eclipse.core.runtime.Platform.getApplicationArgs());
    }

    // For testing purposes
    ProtocolHandler(final String protocolHandlerUri) {
        this.protocolHandlerUri = URIUtils.newURI(protocolHandlerUri);
    }

    private Map<String, AtomicReference<String>> prepareTfsLinkItemMap() {
        final Map<String, AtomicReference<String>> t = new TreeMap<String, AtomicReference<String>>(
                String.CASE_INSENSITIVE_ORDER);

        t.put(PROTOCOL_HANDLER_SERVER_URL_ITEM, serverUrl);
        t.put(PROTOCOL_HANDLER_CLONE_URL_ITEM, cloneUrl);
        t.put(PROTOCOL_HANDLER_COLLECTION_ID_ITEM, collectionId);
        t.put(PROTOCOL_HANDLER_COLLECTION_URL_ITEM, collectionUrl);
        t.put(PROTOCOL_HANDLER_PROJECT_ITEM, project);
        t.put(PROTOCOL_HANDLER_REPOSITORY_ITEM, repository);
        t.put(PROTOCOL_HANDLER_BRANCH_ITEM, branchName);

        // The branch parameter is not sent if the repository is empty.
        // We cannot infer the master branch however, because an empty
        // repository does not have any branches at all.
        branchName.set(StringUtil.EMPTY);

        return t;
    }

    public static ProtocolHandler getInstance() {
        if (instance == null) {
            instance = new ProtocolHandler();
        }
        return instance;
    }

    public boolean hasProtocolHandlerRequest() {
        return tryParseProtocolHandlerUri();
    }

    public String getProtocolHandlerServerUrl() {
        tryParseProtocolHandlerUri();
        return isAvailable ? serverUrl.get() : StringUtil.EMPTY;
    }

    public String getProtocolHandlerCollectionId() {
        tryParseProtocolHandlerUri();
        return isAvailable ? collectionId.get() : StringUtil.EMPTY;
    }

    public boolean hasProtocolHandlerCollectionUrl() {
        tryParseProtocolHandlerUri();
        return isAvailable ? isCollectionUrlAvailable : false;
    }

    public String getProtocolHandlerCollectionUrl() {
        tryParseProtocolHandlerUri();
        return isAvailable ? collectionUrl.get() : StringUtil.EMPTY;
    }

    public String getProtocolHandlerProject() {
        tryParseProtocolHandlerUri();
        return isAvailable ? project.get() : StringUtil.EMPTY;
    }

    public String getProtocolHandlerBranch() {
        tryParseProtocolHandlerUri();
        return isAvailable ? URIUtils.decodeForDisplay(branchName.get()) : StringUtil.EMPTY;
    }

    public String getProtocolHandlerBranchForHtml() {
        tryParseProtocolHandlerUri();
        return isAvailable ? StringUtil.escapeXml(URIUtils.decodeForDisplay(branchName.get())) : StringUtil.EMPTY;
    }

    public String getProtocolHandlerRepository() {
        tryParseProtocolHandlerUri();
        return isAvailable ? repository.get() : StringUtil.EMPTY;
    }

    public String getProtocolHandlerRepositoryForHtml() {
        tryParseProtocolHandlerUri();
        return isAvailable ? StringUtil.escapeXml(repository.get()) : StringUtil.EMPTY;
    }

    public String getProtocolHandlerCloneUrl() {
        tryParseProtocolHandlerUri();
        return isAvailable ? cloneUrl.toString() : StringUtil.EMPTY;
    }

    public String getProtocolHandlerEncoding() {
        tryParseProtocolHandlerUri();
        return isAvailable ? encoding : StringUtil.EMPTY;
    }

    static URI findProtocolHandlerUriArgument(final String[] applicationArgs) {
        if (applicationArgs == null) {
            return null;
        }
        boolean found = false;

        /*
         * @formatter:off
         * We're looking for a protocol handler argument among all command line
         * arguments passed by eclipse.launcher to the Eclipse application. The
         * protocol handler argument should have the following syntax:
         * 
         * -clonefromtfs <uri>
         * 
         * At this point we do not parse the value of the argument. We'll do it later.
         * @formatter:on
         */

        for (final String arg : applicationArgs) {
            if (found) {
                log.info(MessageFormat.format("Found the protocol handler argument: {0} {1}", //$NON-NLS-1$
                        PROTOCOL_HANDLER_ARG, arg));

                try {
                    final URI foundUrl = URIUtils.newURI(arg);
                    if (foundUrl != null && PROTOCOL_HANDLER_SCHEME.equalsIgnoreCase(foundUrl.getScheme())) {
                        return foundUrl;
                    }
                } catch (final Exception e) {
                    log.error("   Incorrect URL in the protocol handler argument", e); //$NON-NLS-1$
                }

                break;
            } else if (arg.equalsIgnoreCase(PROTOCOL_HANDLER_ARG)) {
                found = true;
            } else {
                found = false;
            }
        }

        return null;
    }

    /*
     * @formatter:off
     * The protocol handler argument generated by TFS should have
     * the following syntax:
     * 
     * -clonefromtfs vsoeclipse://checkout/?EncFormat=UTF8&tfslink=<base64 encoded parameters>
     * 
     * @formatter:on
     */
    private synchronized boolean tryParseProtocolHandlerUri() {
        if (isParsed) {
            return isAvailable;
        }
        isParsed = true;

        if (protocolHandlerUri == null) {
            return false;
        }

        if (!PROTOCOL_HANDLER_SCHEME.equalsIgnoreCase(protocolHandlerUri.getScheme())) {
            log.error(MessageFormat.format("   Incorrect scheme in the protocol handler URL: {0}", //$NON-NLS-1$
                    protocolHandlerUri.getScheme() == null ? "NULL" : protocolHandlerUri.getScheme())); //$NON-NLS-1$
            return false;
        }

        boolean tfsLinkAvailable = false;

        final String queryString = protocolHandlerUri.getQuery();
        if (StringUtil.isNullOrEmpty(queryString)) {
            log.error("   Incorrect (empty) query string in the protocol handler URL"); //$NON-NLS-1$
            return false;
        }

        final String[] queryItems = queryString.split("&"); //$NON-NLS-1$

        for (final String queryItem : queryItems) {
            if (StringUtil.startsWithIgnoreCase(queryItem, PROTOCOL_HANDLER_TFS_LINK_PARAM)) {
                final String value = queryItem.substring(PROTOCOL_HANDLER_TFS_LINK_PARAM.length());

                log.info(MessageFormat.format("   Found query parameter: {0}{1}", //$NON-NLS-1$
                        PROTOCOL_HANDLER_TFS_LINK_PARAM, value));

                tfsLinkAvailable = tryParseTfsLink(value);

            } else if (StringUtil.startsWithIgnoreCase(queryItem, PROTOCOL_HANDLER_ENCODING_PARAM)) {
                final String value = queryItem.substring(PROTOCOL_HANDLER_ENCODING_PARAM.length());

                log.info(MessageFormat.format("   Found query parameter: {0}{1}", //$NON-NLS-1$
                        PROTOCOL_HANDLER_ENCODING_PARAM, value));

                encoding = value;
            }
        }

        if (tfsLinkAvailable) {
            isAvailable = true;
        } else {
            log.error(
                    MessageFormat.format("   Incorrect or missing {0} query parameter in the protocol handler URL", //$NON-NLS-1$
                            PROTOCOL_HANDLER_TFS_LINK_PARAM));
        }

        return isAvailable;
    }

    /*
     * @formatter:off
     * The decoded tfslink value generated by TFS should have
     * the following syntax:
     * 
     *     cloneUrl=<clone-url>&
     *     project=<project-name>&
     *     repository=<repository-name>&
     *     Ref=<branch-name>
     *     and    
     *         serverUrl=<server-url>&
     *         collectionId=<GUID>&
     *      or    
     *         collectionUrl=<collection-url>&
     * 
     * At this moment we ignore ideType and ideExe.
     * 
     * @formatter:on
     */
    private boolean tryParseTfsLink(final String tfsLink) {
        final String decodedTfsLink;
        try {
            decodedTfsLink = new String(Base64.decodeBase64(tfsLink), "UTF-8"); //$NON-NLS-1$
        } catch (final UnsupportedEncodingException e) {
            log.error("Incorrectly encoded the tfslink query parameter in the protocol handler URI", e); //$NON-NLS-1$
            return false;
        }

        final String[] tfsLinkItems = decodedTfsLink.split("&"); //$NON-NLS-1$
        Map<String, AtomicReference<String>> tfsLinkItemMap = prepareTfsLinkItemMap();

        for (final String tfsLinkItem : tfsLinkItems) {
            final int idx = tfsLinkItem.indexOf("="); //$NON-NLS-1$

            final String itemName;
            final String itemValue;
            if (idx < 0) {
                itemName = tfsLinkItem;
                itemValue = StringUtil.EMPTY;
            } else {
                itemName = tfsLinkItem.substring(0, idx);
                itemValue = tfsLinkItem.substring(idx + 1);
            }

            if (tfsLinkItemMap.containsKey(itemName)) {
                log.info(MessageFormat.format("                          {0}={1}", //$NON-NLS-1$
                        itemName, itemValue));

                tfsLinkItemMap.get(itemName).set(itemValue);
            }
        }

        if (tfsLinkItemMap.get(PROTOCOL_HANDLER_SERVER_URL_ITEM).get() != null) {
            /*
             * A temporary patch. Should be removed after the TFS server is
             * fixed and sends a collection URL instead of server URL and
             * collection ID.
             */
            final URI cloneUrl = URIUtils.newURI(tfsLinkItemMap.get(PROTOCOL_HANDLER_CLONE_URL_ITEM).get());
            if (ServerURIUtils.isHosted(cloneUrl)) {
                final URI uriCandidate = URIUtils.newURI(cloneUrl.getScheme(), cloneUrl.getAuthority());
                tfsLinkItemMap.get(PROTOCOL_HANDLER_COLLECTION_URL_ITEM).set(uriCandidate.toASCIIString());
            }
        }

        if (tfsLinkItemMap.get(PROTOCOL_HANDLER_COLLECTION_URL_ITEM).get() == null) {
            tfsLinkItemMap.remove(PROTOCOL_HANDLER_COLLECTION_URL_ITEM);
            isCollectionUrlAvailable = false;
        } else {
            tfsLinkItemMap.remove(PROTOCOL_HANDLER_COLLECTION_ID_ITEM);
            tfsLinkItemMap.remove(PROTOCOL_HANDLER_SERVER_URL_ITEM);
            isCollectionUrlAvailable = true;
        }

        for (final AtomicReference<String> value : tfsLinkItemMap.values()) {
            if (value.get() == null) {
                return false;
            }
        }

        return true;
    }

    public void removeProtocolHandlerArguments() {
        isAvailable = false;
    }

    public ICommand getRegistrationCommand() {
        if (!GitHelpers.isEGitInstalled(false)) {
            return null;
        }

        if (Platform.isCurrentPlatform(Platform.WINDOWS)) {
            return new ProtocolHandlerWindowsRegistrationCommand();
        }

        return null;
    }
}