com.cloudbees.plugins.credentials.CredentialsSelectHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudbees.plugins.credentials.CredentialsSelectHelper.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2013-2016, CloudBees, Inc., Stephen Connolly.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.cloudbees.plugins.credentials;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.Util;
import hudson.cli.declarative.CLIResolver;
import hudson.model.ComputerSet;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.model.ModelObject;
import hudson.model.User;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.util.FormApply;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jenkins.ui.icon.IconSpec;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.Localizable;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;

/**
 * A descriptor used to assist the c:select tag with allowing in-place addition of credentials.
 *
 * @author Stephen Connolly
 */
@Extension
public class CredentialsSelectHelper extends Descriptor<CredentialsSelectHelper>
        implements Describable<CredentialsSelectHelper> {

    /**
     * Expose the {@link CredentialsProvider#CREATE} permission for Jelly.
     */
    public static final Permission CREATE = CredentialsProvider.CREATE;

    /**
     * {@inheritDoc}
     */
    public CredentialsSelectHelper() {
        super(CredentialsSelectHelper.class);
    }

    /**
     * {@inheritDoc}
     */
    public CredentialsSelectHelper getDescriptor() {
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getDisplayName() {
        return Messages.CredentialsSelectHelper_DisplayName();
    }

    /**
     * Fixes up the context in case we are called from a page where the context is not a ModelObject.
     *
     * @param context the initial guess of the context.
     * @return the resolved context.
     * @since 2.0.7
     */
    @CheckForNull
    @Restricted(NoExternalUse.class)
    public ModelObject resolveContext(Object context) {
        if (context instanceof ModelObject) {
            return (ModelObject) context;
        }
        StaplerRequest request = Stapler.getCurrentRequest();
        if (request != null) {
            return request.findAncestorObject(ModelObject.class);
        }
        return null;
    }

    /**
      * Returns the {@link StoreItem} instances for the current Stapler request.
      *
      * @param context  the context in which to retrieve the store items.
      * @param includeUser {@code true} to also include any User scoped stores.
      * @return the {@link StoreItem} instances for the current Stapler request.
      * @since 2.0.5
      */
    @Restricted(NoExternalUse.class)
    public List<StoreItem> getStoreItems(ModelObject context, boolean includeUser) {
        Set<String> urls = new HashSet<String>();
        List<StoreItem> result = new ArrayList<StoreItem>();
        if (context == null) {
            StaplerRequest request = Stapler.getCurrentRequest();
            if (request != null) {
                context = request.findAncestorObject(ModelObject.class);
            }
        }
        if (context != null) {
            for (CredentialsStore store : CredentialsProvider.lookupStores(context)) {
                StoreItem item = new StoreItem(store);
                String url = item.getUrl();
                if (item.getUrl() != null && !urls.contains(url)) {
                    result.add(item);
                    urls.add(url);
                }
            }
        }
        if (includeUser) {
            boolean hasPermission = false;
            ModelObject current = context;
            while (current != null) {
                if (current instanceof AccessControlled) {
                    hasPermission = ((AccessControlled) current).hasPermission(CredentialsProvider.USE_OWN);
                    break;
                } else if (current instanceof ComputerSet) {
                    current = Jenkins.getActiveInstance();
                } else {
                    // fall back to Jenkins as the ultimate parent of everything else
                    current = Jenkins.getActiveInstance();
                }
            }
            if (hasPermission) {
                for (CredentialsStore store : CredentialsProvider.lookupStores(User.current())) {
                    StoreItem item = new StoreItem(store);
                    String url = item.getUrl();
                    if (item.getUrl() != null && !urls.contains(url)) {
                        result.add(item);
                        urls.add(url);
                    }
                }
            }
        }
        return result;
    }

    /**
     * Checks if the current user has permission to create a credential.
     *
     * @param context     the context.
     * @param includeUser whether they can use their own credentials store.
     * @return {@code true} if they can create a permission.
     * @since FIXME
     */
    @Restricted(NoExternalUse.class)
    @SuppressWarnings("unused") // used via jelly
    public boolean hasCreatePermission(ModelObject context, boolean includeUser) {
        if (includeUser) {
            User current = User.current();
            if (current != null && current.hasPermission(CREATE)) {
                return true;
            }
        }
        if (context == null) {
            StaplerRequest request = Stapler.getCurrentRequest();
            if (request != null) {
                context = request.findAncestorObject(ModelObject.class);
            }
        }
        for (CredentialsStore store : CredentialsProvider.lookupStores(context)) {
            if (store.hasPermission(CREATE)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Stapler binding for the resolver URL segment.
     *
     * @param className the class name of the resolver.
     * @return the wrapped resolver.
     * @since 2.0
     */
    @Restricted(NoExternalUse.class)
    public WrappedContextResolver getResolver(String className) {
        for (ContextResolver r : ExtensionList.lookup(ContextResolver.class)) {
            if (r.getClass().getName().equals(className)) {
                return new WrappedContextResolver(r);
            }
        }
        return null;
    }

    /**
     * Resolves a {@link CredentialsStore} instance for CLI commands.
     *
     * @param storeId the store identifier.
     * @return the {@link CredentialsStore} instance.
     * @throws CmdLineException if the store cannot be resolved.
     * @since 2.1.1
     */
    @CLIResolver
    public static CredentialsStore resolveForCLI(
            @Argument(required = true, metaVar = "STORE", usage = "Store ID") String storeId)
            throws CmdLineException {
        int index1 = storeId.indexOf("::");
        int index2 = index1 == -1 ? -1 : storeId.indexOf("::", index1 + 2);
        if (index1 == -1 || index1 == 0 || index2 == -1 || index2 < (index1 + 2)
                || index2 == storeId.length() - 2) {
            throw new CmdLineException(null, new Localizable() {
                @Override
                public String formatWithLocale(Locale locale, Object... objects) {
                    return Messages._CredentialsSelectHelper_CLIMalformedStoreId(objects[0]).toString(locale);
                }

                @Override
                public String format(Object... objects) {
                    return Messages._CredentialsSelectHelper_CLIMalformedStoreId(objects[0]).toString();
                }
            }, storeId);
        }
        String providerName = storeId.substring(0, index1);
        String resolverName = storeId.substring(index1 + 2, index2);
        String token = storeId.substring(index2 + 2);

        CredentialsProvider provider = getProvidersByName().get(providerName);
        if (provider == null || provider == CredentialsProvider.NONE) {
            throw new CmdLineException(null, new Localizable() {
                @Override
                public String formatWithLocale(Locale locale, Object... objects) {
                    return Messages._CredentialsSelectHelper_CLINoSuchProvider(objects[0]).toString(locale);
                }

                @Override
                public String format(Object... objects) {
                    return Messages._CredentialsSelectHelper_CLINoSuchProvider(objects[0]).toString();
                }
            }, storeId);
        }
        ContextResolver resolver = getResolversByName().get(resolverName);
        if (resolver == null || resolver == ContextResolver.NONE) {
            throw new CmdLineException(null, new Localizable() {
                @Override
                public String formatWithLocale(Locale locale, Object... objects) {
                    return Messages._CredentialsSelectHelper_CLINoSuchResolver(objects[0]).toString(locale);
                }

                @Override
                public String format(Object... objects) {
                    return Messages._CredentialsSelectHelper_CLINoSuchResolver(objects[0]).toString();
                }
            }, storeId);
        }
        ModelObject context = resolver.getContext(token);
        if (context == null) {
            throw new CmdLineException(null, new Localizable() {
                @Override
                public String formatWithLocale(Locale locale, Object... objects) {
                    return Messages._CredentialsSelectHelper_CLINoSuchContext(objects[0]).toString(locale);
                }

                @Override
                public String format(Object... objects) {
                    return Messages._CredentialsSelectHelper_CLINoSuchContext(objects[0]).toString();
                }
            }, storeId);
        }
        CredentialsStore store = provider.getStore(context);
        if (store == null) {
            throw new CmdLineException(null, new Localizable() {
                @Override
                public String formatWithLocale(Locale locale, Object... objects) {
                    return Messages._CredentialsSelectHelper_CLINoStore().toString(locale);
                }

                @Override
                public String format(Object... objects) {
                    return Messages._CredentialsSelectHelper_CLINoStore().toString();
                }
            }, storeId);
        }
        return store;
    }

    /**
     * Returns a map of the {@link ContextResolver} instances keyed by their name. A resolver may have more than one
     * entry if there are inferred unique short nicknames.
     *
     * @return a map of the {@link ContextResolver} instances keyed by their name
     * @since 2.1.1
     */
    public static Map<String, ContextResolver> getResolversByName() {
        Map<String, ContextResolver> resolverByName = new TreeMap<String, ContextResolver>();
        for (ContextResolver r : ExtensionList.lookup(ContextResolver.class)) {
            resolverByName.put(r.getClass().getName(), r);
            String shortName = r.getClass().getSimpleName();
            resolverByName.put(shortName, resolverByName.containsKey(shortName) ? ContextResolver.NONE : r);
            shortName = shortName.toLowerCase(Locale.ENGLISH).replaceAll("(context|resolver|impl)*", "");
            if (StringUtils.isNotBlank(shortName)) {
                resolverByName.put(shortName, resolverByName.containsKey(shortName) ? ContextResolver.NONE : r);
            }
        }
        for (Iterator<ContextResolver> iterator = resolverByName.values().iterator(); iterator.hasNext();) {
            ContextResolver r = iterator.next();
            if (r == ContextResolver.NONE) {
                iterator.remove();
            }
        }
        return resolverByName;
    }

    /**
     * Returns a map of the {@link CredentialsProvider} instances keyed by their name. A provider may have more than one
     * entry if there are inferred unique short nicknames.
     *
     * @return a map of the {@link CredentialsProvider} instances keyed by their name
     * @since 2.1.1
     */
    public static Map<String, CredentialsProvider> getProvidersByName() {
        Map<String, CredentialsProvider> providerByName = new TreeMap<String, CredentialsProvider>();
        for (CredentialsProvider r : ExtensionList.lookup(CredentialsProvider.class)) {
            providerByName.put(r.getClass().getName(), r);
            Class<?> clazz = r.getClass();
            while (clazz != null) {
                String shortName = clazz.getSimpleName();
                clazz = clazz.getEnclosingClass();
                String simpleName = shortName.toLowerCase(Locale.ENGLISH).replaceAll("(credentials|provider|impl)*",
                        "");
                if (StringUtils.isBlank(simpleName))
                    continue;
                providerByName.put(shortName, providerByName.containsKey(shortName) ? CredentialsProvider.NONE : r);
                providerByName.put(simpleName,
                        providerByName.containsKey(simpleName) ? CredentialsProvider.NONE : r);
            }
        }
        for (Iterator<CredentialsProvider> iterator = providerByName.values().iterator(); iterator.hasNext();) {
            CredentialsProvider p = iterator.next();
            if (p == CredentialsProvider.NONE) {
                iterator.remove();
            }
        }
        return providerByName;
    }

    /**
     * Value class to hold the details of a {@link CredentialsStore}.
     *
     * @since 2.0
     */
    @Restricted(NoExternalUse.class)
    public static final class StoreItem implements IconSpec, ModelObject {
        /**
         * The store.
         */
        private final CredentialsStore store;
        /**
         * The URL we will expose the store at.
         */
        private final String url;

        /**
         * Constructor.
         *
         * @param store the store.
         */
        public StoreItem(CredentialsStore store) {
            this.store = store;
            String provider = store.getProvider().getClass().getName();
            String resolver = null;
            String token = null;
            ModelObject storeContext = store.getContext();
            // we only support the cases where the
            for (ContextResolver r : ExtensionList.lookup(ContextResolver.class)) {
                String t = r.getToken(storeContext);
                if (t != null) {
                    resolver = r.getClass().getName();
                    token = t;
                    break;
                }
            }
            this.url = token == null ? null
                    : String.format("descriptor/%s/resolver/%s/provider/%s/context/%s",
                            CredentialsSelectHelper.class.getName(), Util.rawEncode(resolver),
                            Util.rawEncode(provider), Util.rawEncode(token));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getIconClassName() {
            return store.getProvider().getIconClassName();
        }

        /**
         * Exposes if this store is enabled for the current user.
         *
         * @return {@code true} if the current user can add credentials to this store.
         */
        public boolean isEnabled() {
            return url != null && store.hasPermission(CREATE) && !store.getCredentialsDescriptors().isEmpty();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getDisplayName() {
            return store.getContextDisplayName();
        }

        /**
         * Exposes the description of this store (i.e. the {@link CredentialsProvider#getDisplayName()}.
         *
         * @return the description of this store (i.e. the {@link CredentialsProvider#getDisplayName()}.
         */
        public String getDescription() {
            return store.getProvider().getDisplayName();
        }

        /**
         * Exposes the URL of this store's {@link WrappedCredentialsStore}.
         *
         * @return the URL of this store's {@link WrappedCredentialsStore}
         */
        public String getUrl() {
            return url;
        }
    }

    /**
     * Stapler binding for {@link ContextResolver}.
     *
     * @since 2.0
     */
    @Restricted(NoExternalUse.class)
    public static final class WrappedContextResolver {

        /**
         * Our {@link ContextResolver}
         */
        @NonNull
        private final ContextResolver resolver;

        /**
         * Our constructor.
         *
         * @param resolver the {@link ContextResolver}
         */
        public WrappedContextResolver(@NonNull ContextResolver resolver) {
            this.resolver = resolver;
        }

        /**
         * Stapler web binding for the {@link CredentialsProvider}.
         *
         * @param className the class name of the {@link CredentialsProvider}.
         * @return the {@link WrappedContextResolverCredentialsProvider} or {@code null}
         */
        @CheckForNull
        public WrappedContextResolverCredentialsProvider getProvider(String className) {
            for (CredentialsProvider p : CredentialsProvider.enabled()) {
                if (p.getClass().getName().equals(className)) {
                    return new WrappedContextResolverCredentialsProvider(resolver, p);
                }
            }
            return null;
        }
    }

    /**
     * Stapler binding for a {@link ContextResolver} and {@link CredentialsProvider}.
     *
     * @since 2.0
     */
    @Restricted(NoExternalUse.class)
    public static final class WrappedContextResolverCredentialsProvider {

        /**
         * Our {@link ContextResolver}
         */
        @NonNull
        private final ContextResolver resolver;
        /**
         * Our {@link CredentialsProvider}
         */
        @NonNull
        private final CredentialsProvider provider;

        /**
         * Our constructor.
         *
         * @param resolver the {@link ContextResolver}
         * @param provider the {@link CredentialsProvider}
         */
        public WrappedContextResolverCredentialsProvider(@NonNull ContextResolver resolver,
                @NonNull CredentialsProvider provider) {
            this.resolver = resolver;
            this.provider = provider;
        }

        /**
         * Stapler web binding for the {@link ModelObject} representing the context of the store.
         *
         * @param token the {@link ContextResolver#getToken(ModelObject)} of the context of the store.
         * @return the {@link WrappedContextResolverCredentialsProvider} or {@code null}
         */
        public WrappedCredentialsStore getContext(String token) {
            ModelObject context = resolver.getContext(token);
            if (context != null) {
                CredentialsStore store = provider.getStore(context);
                if (store != null) {
                    return new WrappedCredentialsStore(resolver, provider, token, store);
                }
            }
            return null;
        }
    }

    /**
     * Stapler binding for a {@link CredentialsStore}.
     *
     * @since 2.0
     */
    @Restricted(NoExternalUse.class)
    public static final class WrappedCredentialsStore implements IconSpec, ModelObject {
        /**
         * Our {@link ContextResolver}
         */
        @NonNull
        private final ContextResolver resolver;
        /**
         * Our {@link CredentialsProvider}
         */
        @NonNull
        private final CredentialsProvider provider;
        /**
         * Our context's {@link ContextResolver#getToken(ModelObject)}.
         */
        @NonNull
        private final String token;
        /**
         * Our {@link CredentialsStore}.
         */
        private final CredentialsStore store;

        /**
         * Our constructor.
         *
         * @param resolver the {@link ContextResolver}
         * @param provider the {@link CredentialsProvider}
         * @param token    the context's {@link ContextResolver#getToken(ModelObject)}.
         * @param store    the {@link CredentialsStore}
         */
        public WrappedCredentialsStore(ContextResolver resolver, CredentialsProvider provider, String token,
                CredentialsStore store) {
            this.store = store;
            this.resolver = resolver;
            this.provider = provider;
            this.token = token;
        }

        /**
         * Stapler web binding for adding credentials to the domain.
         *
         * @param req the request.
         * @param rsp the response.
         * @throws IOException      if something goes wrong.
         * @throws ServletException if something goes wrong.
         */
        @RequirePOST
        public void doAddCredentials(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
            if (!store.isDomainsModifiable()) {
                hudson.util.HttpResponses.status(400).generateResponse(req, rsp, null);
                FormApply.applyResponse("window.alert('Domain is read-only')").generateResponse(req, rsp, null);
            }
            store.checkPermission(CredentialsStoreAction.CREATE);
            JSONObject data = req.getSubmittedForm();
            String domainName = data.getString("domain");
            CredentialsStoreAction.DomainWrapper wrapper = getWrappers().get(domainName);
            if (!store.getDomains().contains(wrapper.getDomain())) {
                hudson.util.HttpResponses.status(400).generateResponse(req, rsp, null);
                FormApply.applyResponse("window.alert('Store does not have selected domain')").generateResponse(req,
                        rsp, null);
            }
            store.checkPermission(CredentialsStoreAction.CREATE);
            Credentials credentials = req.bindJSON(Credentials.class, data.getJSONObject("credentials"));
            store.addCredentials(wrapper.getDomain(), credentials);
            FormApply.applyResponse("window.credentials.refreshAll();").generateResponse(req, rsp, null);
        }

        /**
         * Returns a {@link CredentialsStoreAction.DomainWrapper} to use for contextualizing the credentials form.
         *
         * @return a {@link CredentialsStoreAction.DomainWrapper} to use for contextualizing the credentials form.
         */
        public CredentialsStoreAction.DomainWrapper getWrapper() {
            Collection<CredentialsStoreAction.DomainWrapper> values = getWrappers().values();
            return values.isEmpty() ? null : values.iterator().next();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getIconClassName() {
            return store.getProvider().getIconClassName();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getDisplayName() {
            return store.getContextDisplayName();
        }

        /**
         * Exposes the description of this store (i.e. the {@link CredentialsProvider#getDisplayName()}.
         *
         * @return the description of this store (i.e. the {@link CredentialsProvider#getDisplayName()}.
         */
        public String getDescription() {
            return store.getProvider().getDisplayName();
        }

        /**
         * Exposes our URL (as we will be invoked from an unknown page so we need an absolute URL).
         *
         * @return our URL.
         */
        public String getUrl() {
            return String.format("%sdescriptor/%s/resolver/%s/provider/%s/context/%s",
                    Jenkins.getActiveInstance().getRootUrlFromRequest(), CredentialsSelectHelper.class.getName(),
                    Util.rawEncode(resolver.getClass().getName()), Util.rawEncode(provider.getClass().getName()),
                    Util.rawEncode(token));
        }

        /**
         * Exposes the {@link CredentialsDescriptor} instances appropriate for this {@link CredentialsStore}.
         *
         * @return the {@link CredentialsDescriptor} instances appropriate for this {@link CredentialsStore}.
         */
        public List<CredentialsDescriptor> getCredentialsDescriptors() {
            return store.getCredentialsDescriptors();
        }

        /**
         * The {@link CredentialsStoreAction.DomainWrapper} instances.
         *
         * @return the {@link CredentialsStoreAction.DomainWrapper} instances.
         */
        public Map<String, CredentialsStoreAction.DomainWrapper> getWrappers() {
            CredentialsStoreAction action = store.getStoreAction();
            return action != null ? action.getDomains() : new CredentialsStoreAction() {
                /**
                 * {@inheritDoc}
                 */
                @NonNull
                @Override
                public CredentialsStore getStore() {
                    return store;
                }
            }.getDomains();
        }

        /**
         * The backing {@link CredentialsStore}.
         * @return backing {@link CredentialsStore}.
         * @since 2.1.5
         */
        public CredentialsStore getStore() {
            return store;
        }
    }

    /**
     * An extension point to allow plugging in additional resolution of {@link ModelObject} instances.
     *
     * @since 2.0
     */
    public static abstract class ContextResolver implements ExtensionPoint {

        /**
         * A {@link ContextResolver} that can be used as a marker.
         *
         * @since 2.1.1
         */
        public static final ContextResolver NONE = new ContextResolver() {
            /**
             * {@inheritDoc}
             */
            @Override
            public String getToken(ModelObject context) {
                return null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public ModelObject getContext(String token) {
                return null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public String getDisplayName() {
                return "Nothing";
            }
        };

        /**
         * Maps a context object (a {@link ModelObject}) into a token that can be used to recover the context.
         *
         * @param context the {@link ModelObject}.
         * @return a token if this {@link ContextResolver} can recover the object or {@code null} if the
         * {@link ModelObject} type is not supported by this {@link ContextResolver}.
         */
        @CheckForNull
        public abstract String getToken(ModelObject context);

        /**
         * Maps a token from {@link #getToken(ModelObject)} back to its original {@link ModelObject}.
         *
         * @param token the token.
         * @return the corresponding {@link ModelObject} or {@code null} if the object no longer exists or if the
         * user does not have permission to access that object.
         */
        @CheckForNull
        public abstract ModelObject getContext(String token);

        /**
         * Returns a human readable descripton of the type of context objects that this resolver resolves.
         * @return a human readable descripton of the type of context objects that this resolver resolves.
         * @throws AbstractMethodError if somebody compiled against pre-2.1.1 implementations. Use
         * {@link CredentialsSelectHelper.ContextResolver#displayName(CredentialsSelectHelper.ContextResolver)}
         * if you would prefer not to have to catch them.
         * @since 2.1.1
         */
        @NonNull
        public abstract String getDisplayName();

        /**
         * Returns a human readable descripton of the type of context objects that the specified resolver resolves.
         *
         * @param resolver the context resolver to get the display name of.
         * @return a human readable descripton of the type of context objects that the specified resolver resolves.
         * @since 2.1.1
         */
        public static String displayName(ContextResolver resolver) {
            try {
                return resolver.getDisplayName();
            } catch (AbstractMethodError e) {
                // should not get here as do not anticipate new implementations that cannot target 2.1.1 as a baseline
                return resolver.getClass().getName();
            }
        }
    }

    /**
     * A {@link ContextResolver} for {@link Jenkins}.
     *
     * @since 2.0
     */
    @Extension(ordinal = 1000)
    public static class SystemContextResolver extends ContextResolver {

        /**
         * {@inheritDoc}
         */
        @Override
        public String getToken(ModelObject context) {
            return context instanceof Jenkins ? "jenkins" : null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public ModelObject getContext(String token) {
            return "jenkins".equals(token) ? Jenkins.getActiveInstance() : null;
        }

        /**
         * {@inheritDoc}
         */
        @NonNull
        @Override
        public String getDisplayName() {
            return "Jenkins";
        }
    }

    /**
     * A {@link ContextResolver} for {@link Item} instances resolvable by {@link Jenkins#getItemByFullName(String)}.
     *
     * @since 2.0
     */
    @Extension
    public static class ItemContextResolver extends ContextResolver {

        /**
         * {@inheritDoc}
         */
        @Override
        public String getToken(ModelObject context) {
            return context instanceof Item ? ((Item) context).getFullName() : null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public ModelObject getContext(String token) {
            return Jenkins.getActiveInstance().getItemByFullName(token);
        }

        /**
         * {@inheritDoc}
         */
        @NonNull
        @Override
        public String getDisplayName() {
            return "Items";
        }
    }

    /**
     * A {@link ContextResolver} for {@link User} instances.
     *
     * @since 2.0
     */
    @Extension
    public static class UserContextResolver extends ContextResolver {

        /**
         * {@inheritDoc}
         */
        @Override
        public String getToken(ModelObject context) {
            return context instanceof User ? ((User) context).getId() : null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public ModelObject getContext(String token) {
            // TODO invoke User.getById directly once Jenkins 2.3+
            try {
                Method getById = User.class.getMethod("getById", String.class, boolean.class);
                return (ModelObject) getById.invoke(null, token, false);
            } catch (NoSuchMethodException e) {
                // old Jenkins pre SECURITY-243
                return User.get(token, false, Collections.emptyMap());
            } catch (InvocationTargetException e) {
                return null;
            } catch (IllegalAccessException e) {
                return null;
            }
        }

        /**
         * {@inheritDoc}
         */
        @NonNull
        @Override
        public String getDisplayName() {
            return "Users";
        }
    }
}