com.nesscomputing.service.discovery.client.DiscoveryClientModule.java Source code

Java tutorial

Introduction

Here is the source code for com.nesscomputing.service.discovery.client.DiscoveryClientModule.java

Source

/**
 * Copyright (C) 2012 Ness Computing, Inc.
 *
 * 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 com.nesscomputing.service.discovery.client;

import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.nesscomputing.config.Config;
import com.nesscomputing.config.ConfigProvider;
import com.nesscomputing.httpclient.guice.HttpClientModule;
import com.nesscomputing.logging.Log;
import com.nesscomputing.service.discovery.client.internal.DiscoveryClientImpl;
import com.nesscomputing.service.discovery.client.internal.ReadOnlyDiscoveryClientImpl;

/**
 * Module for the client side of service discovery.
 */
public class DiscoveryClientModule extends AbstractModule {
    public static final String ZOOKEEPER_CONNECT_NAME = "_zookeeper_connect";
    public static final Named ZOOKEEPER_CONNECT_NAMED = Names.named(ZOOKEEPER_CONNECT_NAME);

    private static final Log LOG = Log.findLog();

    private final boolean readOnly;

    /**
     * Installs a read/write discovery client.
     */
    public DiscoveryClientModule() {
        this(false);
    }

    /**
     * Installs a read/write discovery client.
     *
     * @param readonly - True if the client should be read only (allow service lookup but no announcements.
     */
    public DiscoveryClientModule(final boolean readOnly) {
        this.readOnly = readOnly;
    }

    @Override
    public void configure() {
        bind(DiscoveryClientConfig.class).toProvider(ConfigProvider.of(DiscoveryClientConfig.class))
                .in(Scopes.SINGLETON);
        bind(ServiceURIConverter.class).in(Scopes.SINGLETON);

        if (readOnly) {
            bind(ReadOnlyDiscoveryClient.class).to(ReadOnlyDiscoveryClientImpl.class).in(Scopes.SINGLETON);
        } else {
            bind(DiscoveryClient.class).to(DiscoveryClientImpl.class).in(Scopes.SINGLETON);
            bind(ReadOnlyDiscoveryClient.class).to(DiscoveryClient.class).in(Scopes.SINGLETON);
            bind(ServiceAnnouncer.class).asEagerSingleton();
        }

        HttpClientModule.bindNewObserver(binder()).to(DiscoveryServiceInterceptor.class);
    }

    /**
     * Register a new Announcement with service discovery. This will only work with the read/write Discovery client and
     * it requires binding the ServiceAnnouncer as an eager singleton.
     * @return the binding builder you should register with
     */
    public static LinkedBindingBuilder<ServiceInformation> bindNewAnnouncement(final Binder binder) {
        return Multibinder.newSetBinder(binder, ServiceInformation.class).addBinding();
    }

    @Provides
    @Singleton
    @Named(ZOOKEEPER_CONNECT_NAME)
    String getConnectString(@Named(ZOOKEEPER_CONNECT_NAME) final Map<Integer, InetSocketAddress> zookeeperServers,
            final DiscoveryClientConfig clientConfig) {
        final StringBuilder sb = new StringBuilder();

        for (final InetSocketAddress address : zookeeperServers.values()) {
            if (sb.length() > 0) {
                sb.append(',');
            }
            sb.append(address.getAddress().getHostAddress()).append(':').append(address.getPort());
        }

        if (sb.length() == 0) {
            if (clientConfig.isEnabled()) {
                throw new IllegalStateException("Service discovery is enabled but no servers found!");
            } else {
                LOG.info("No servers found!");
            }
        }
        return sb.toString();
    }

    @Provides
    @Singleton
    @Named(ZOOKEEPER_CONNECT_NAME)
    Map<Integer, InetSocketAddress> getZookeeperServers(final Config config,
            final DiscoveryClientConfig clientConfig) {
        Map<Integer, InetSocketAddress> results = Maps.newHashMap();

        if (!clientConfig.isEnabled()) {
            LOG.warn("Service Discovery is administratively disabled.");
        } else {
            final Configuration zookeeperConfig = config.getConfiguration("ness.zookeeper");

            // This can be explicitly given to support the "Three servers, one host" configuration.
            final String[] servers = zookeeperConfig.getStringArray("clientConnect");

            if (ArrayUtils.isNotEmpty(servers)) {
                LOG.debug("Found explicit 'ness.zookeeper.clientConnect' string (%s)",
                        StringUtils.join(servers, ","));
                int serverId = 1;
                for (String server : servers) {
                    final String parts[] = StringUtils.split(server, ":");
                    InetSocketAddress serverAddress = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
                    results.put(serverId++, serverAddress);
                }
            } else {
                LOG.debug("Building connectString from server configuration.");

                final int clientPort = zookeeperConfig.getInt("clientPort");
                LOG.debug("ness.zookeeper.clientPort is %d", clientPort);

                for (final Iterator<?> it = zookeeperConfig.getKeys("server"); it.hasNext();) {
                    final String key = it.next().toString();
                    final String[] keyElements = StringUtils.split(key, ".");
                    final String value = zookeeperConfig.getString(key);
                    final Integer serverId = Integer.parseInt(keyElements[keyElements.length - 1]);
                    final String parts[] = StringUtils.split(value, ":");

                    InetSocketAddress serverAddress = new InetSocketAddress(parts[0], clientPort);
                    results.put(serverId, serverAddress);
                    LOG.debug("Server # %d : %s", serverId, serverAddress);
                }

                // If there are less than two servers, this is running in standalone mode. In that case,
                // use the clientAddress, because that is what the server will be using.
                if (results.size() < 2) {
                    LOG.info("Found less than two servers, falling back to clientPortAddress/clientPort!");
                    final String clientAddress = zookeeperConfig.getString("clientPortAddress");
                    Preconditions.checkState(clientAddress != null, "Client address must not be null!");

                    final InetSocketAddress serverAddress = new InetSocketAddress(clientAddress, clientPort);
                    LOG.debug("Server: %s", serverAddress);
                    results = ImmutableMap.of(1, serverAddress);
                }
            }
            if (LOG.isDebugEnabled()) {
                for (Map.Entry<Integer, InetSocketAddress> entry : results.entrySet()) {
                    LOG.debug("Server # %d : %s", entry.getKey(), entry.getValue());
                }
            }
        }

        return results;
    }

}