io.jmnarloch.spring.cloud.discovery.DiscoveryClientPropertySource.java Source code

Java tutorial

Introduction

Here is the source code for io.jmnarloch.spring.cloud.discovery.DiscoveryClientPropertySource.java

Source

/**
 * Copyright (c) 2015 the original author or authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.jmnarloch.spring.cloud.discovery;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.PropertySource;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

/**
 * Resolves a SpEL expressions for the ${discoveryClient.*} operations.
 *
 * @author Jakub Narloch
 */
public class DiscoveryClientPropertySource extends PropertySource<DiscoveryClient> {

    /**
     * Logger instance used by this class.
     */
    private final Logger logger = LoggerFactory.getLogger(DiscoveryClientPropertySource.class);

    /**
     * The default SpEL prefix.
     */
    public static final String PREFIX = "discoveryClient.";

    /**
     * Creates new instance of {@link DiscoveryClientPropertySource} with specific name.
     *
     * @param name the property source name
     */
    public DiscoveryClientPropertySource(String name) {
        super(name, new DelegatingDiscoveryClient());
    }

    /**
     * Sets the application context.
     *
     * @param applicationContext the application context
     * @throws BeansException if any error occurs
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ((DelegatingDiscoveryClient) getSource()).setApplicationContext(applicationContext);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getProperty(String name) {
        if (!name.startsWith(PREFIX)) {
            return null;
        }
        return handleDiscoveryOperation(name.substring(PREFIX.length()));
    }

    /**
     * Processes the operation declared through SPEL expression.
     *
     * @param input the input
     * @return the resolved expression value
     */
    private Object handleDiscoveryOperation(String input) {
        if (input.startsWith("service")) {
            final String serviceName = getArgument("service", input);
            return findOne(serviceName);
        } else if (input.startsWith("url")) {
            final String argument = getArgument("url", input);
            return expandUri(argument);
        }
        return null;
    }

    /**
     * Finds single service URI from the discovery service.
     *
     * @param serviceName the service name
     * @return the service url, or {@code null} if no service has been found
     */
    private URI findOne(String serviceName) {
        final ServiceInstance serviceInstance = findOneService(serviceName);
        return serviceInstance != null ? serviceInstance.getUri() : null;
    }

    /**
     * Expands the service URI.
     *
     * @param uri the input uri
     * @return the result uri
     */
    private URI expandUri(String uri) {
        try {
            final URI inputUri = URI.create(uri);
            final ServiceInstance serviceInstance = findOneService(inputUri.getHost());
            if (serviceInstance == null) {
                return null;
            }
            return new URI((serviceInstance.isSecure() ? "https" : "http"), inputUri.getUserInfo(),
                    serviceInstance.getHost(), serviceInstance.getPort(), inputUri.getPath(), inputUri.getQuery(),
                    inputUri.getFragment());
        } catch (URISyntaxException e) {
            logger.error("Unexpected error occurred when expanding the property URI", e);
            throw new RuntimeException("Could not parse URI value: " + uri, e);
        }
    }

    /**
     * Retrieves single service instance.
     *
     * @param serviceName the service name
     * @return the service instance
     */
    private ServiceInstance findOneService(String serviceName) {
        final List<ServiceInstance> instances = getSource().getInstances(serviceName);
        if (instances.isEmpty()) {
            logger.error("No service found matching name: {}", serviceName);
            return null;
        }
        return instances.get((int) (Math.random() * instances.size()));
    }

    /**
     * Matches the operation argument.
     *
     * @param operation the operation name
     * @param input     the input string
     * @return the argument value
     */
    private String getArgument(String operation, String input) {
        final int index = operation.length() + 1;
        if (index >= input.length()) {
            return null;
        }
        return input.substring(index, input.length() - 1);
    }

    /**
     * Simple implementation that delegates to the {@link DiscoveryClient} registered in application context.
     *
     * @author Jakub Narloch
     */
    private static class DelegatingDiscoveryClient implements DiscoveryClient {

        /**
         * The instance of {@link ApplicationContext}.
         */
        private ApplicationContext applicationContext;

        /**
         * Sets the application context.
         *
         * @param applicationContext the applciation context
         */
        public void setApplicationContext(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        public String description() {
            return getDiscoveryClient().description();
        }

        @Override
        public ServiceInstance getLocalServiceInstance() {
            return getDiscoveryClient().getLocalServiceInstance();
        }

        @Override
        public List<ServiceInstance> getInstances(String serviceId) {
            return getDiscoveryClient().getInstances(serviceId);
        }

        @Override
        public List<String> getServices() {
            return getDiscoveryClient().getServices();
        }

        private DiscoveryClient getDiscoveryClient() {
            return applicationContext.getBean(DiscoveryClient.class);
        }
    }
}