All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.ejb.protocol.remote.NodeInformation Maven / Gradle / Ivy

/*

 * JBoss, Home of Professional Open Source.
 * Copyright 2017 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.jboss.ejb.protocol.remote;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.jboss.ejb._private.Logs;
import org.jboss.ejb.client.EJBClientContext;
import org.jboss.ejb.client.EJBModuleIdentifier;
import org.jboss.logging.Logger;
import org.jboss.remoting3.Connection;
import org.wildfly.common.Assert;
import org.wildfly.common.net.CidrAddress;
import org.wildfly.common.net.CidrAddressTable;
import org.wildfly.common.net.Inet;
import org.wildfly.discovery.AttributeValue;
import org.wildfly.discovery.FilterSpec;
import org.wildfly.discovery.ServiceType;
import org.wildfly.discovery.ServiceURL;
import org.wildfly.discovery.spi.DiscoveryResult;

/**
 * @author David M. Lloyd
 */
final class NodeInformation {
    private final String nodeName;

    private final ConcurrentMap> modulesByConnection = new ConcurrentHashMap<>(1);
    private final ConcurrentMap clustersByName = new ConcurrentHashMap<>(1);
    private final ConcurrentMap addressesByConnection = new ConcurrentHashMap<>(1);

    private volatile List serviceURLCache;

    private volatile boolean invalid;

    NodeInformation(final String nodeName) {
        this.nodeName = nodeName;
    }

    String getNodeName() {
        return nodeName;
    }

    boolean discover(ServiceType serviceType, FilterSpec filterSpec, DiscoveryResult discoveryResult) {
        if (invalid) return false;
        boolean found = false;
        List serviceURLCache = getServiceURLCache();
        for (ServiceURL serviceURL : serviceURLCache) {
            if (serviceURL.satisfies(filterSpec) && serviceType.implies(serviceURL)) {
                found = true;
                discoveryResult.addMatch(serviceURL);
            }
        }
        return found;
    }

    ConcurrentMap getClustersByName() {
        return clustersByName;
    }

    // this structure mashes together the cluster topo reports from various nodes, and the mod avail report from this node
    static class TempInfo {
        Map clusters;
        Set modules;
        URI destination;

        TempInfo(URI destination) {
            this.destination = destination;
        }
    }

    private List getServiceURLCache() {
        List serviceURLCache = this.serviceURLCache;
        if (serviceURLCache == null) {
            synchronized (this) {
                serviceURLCache = this.serviceURLCache;
                if (serviceURLCache == null) {
                    // the final list to store
                    serviceURLCache = new ArrayList<>();
                    HashMap map = new HashMap<>();
                    // hostname representations encountered - these can vary
                    Set hostReps = new HashSet();
                    // populate standalone nodes
                    for (Map.Entry entry : addressesByConnection.entrySet()) {
                        final EJBClientChannel channel = entry.getKey();
                        final Connection peerConnection = channel.getChannel().getConnection();
                        final URI peerURI = buildURI(peerConnection.getProtocol(), peerConnection.getPeerAddress(InetSocketAddress.class));
                        // keep track of equivalent hostnames
                        hostReps.add(peerURI.getHost());
                        map.computeIfAbsent(peerURI, TempInfo::new);
                    }
                    // populate the modules
                    for (Map.Entry> entry : modulesByConnection.entrySet()) {
                        final EJBClientChannel channel = entry.getKey();
                        final Connection peerConnection = channel.getChannel().getConnection();
                        final URI peerURI = buildURI(peerConnection.getProtocol(), peerConnection.getPeerAddress(InetSocketAddress.class));
                        // keep track of equivalent hostnames
                        hostReps.add(peerURI.getHost());
                        TempInfo tempInfo = new TempInfo(peerURI);
                        tempInfo.modules = entry.getValue();
                        map.put(tempInfo.destination, tempInfo);
                    }
                    // populate the clusters next
                    for (final Map.Entry entry : clustersByName.entrySet()) {
                        final String clusterName = entry.getKey();
                        final ClusterNodeInformation clusterNodeInformation = entry.getValue();
                        for (Map.Entry> entry1 : clusterNodeInformation.getAddressTablesByProtocol().entrySet()) {
                            final String protocol = entry1.getKey();
                            final CidrAddressTable table = entry1.getValue();
                            for (CidrAddressTable.Mapping mapping : table) {
                                try {
                                    CidrAddress cidrAddress = mapping.getRange();
                                    final InetSocketAddress address = Inet.getResolved(mapping.getValue());
                                    final URI uri = buildURI(protocol, address);
                                    // keep track of equivalent hostnames
                                    hostReps.add(uri.getHost());
                                    TempInfo tempInfo = map.computeIfAbsent(uri, TempInfo::new);
                                    if (tempInfo.clusters == null) {
                                        tempInfo.clusters = new HashMap<>();
                                    }
                                    tempInfo.clusters.put(clusterName, cidrAddress);
                                } catch (UnknownHostException e) {
                                    Logs.MAIN.logf(Logger.Level.DEBUG, "Cannot resolve %s host during discovery attempt, skipping", mapping.getValue());
                                }
                            }
                        }
                    }

                    // merge information from equivalent URIs, if any (EJBCLIENT-349)
                    // due to mixed-mode hostnames, we can have module information and cluster information for "the same" URLs spread across multiple TempInfo structures
                    // here, we make sure that for each equivalent URL, they hold the same module and cluster information
                    for (URI destination : map.keySet()) {
                        Set equivalentURIs = getEquivalentURIs(destination, hostReps);
                        for (URI equivalentURI: equivalentURIs) {
                            // for each equivalent URI, check if we need to merge modules and clusters
                            TempInfo originalTempInfo = map.get(destination);
                            TempInfo equivalentTempInfo = map.get(equivalentURI);
                            if (originalTempInfo == null || equivalentTempInfo == null) {
                                continue;
                            }
                            // take information in each and combine
                            mergeEquivalentTempInfoStructures(originalTempInfo, equivalentTempInfo);
                        }
                    }

                    // populate the service URLs from the cross product (!) of clusters and modules
                    final AttributeValue nodeNameValue = AttributeValue.fromString(nodeName);
                    for (TempInfo info : map.values()) {
                        final ServiceURL.Builder builder = new ServiceURL.Builder();
                        builder.setUri(info.destination);
                        builder.setAbstractType(EJBClientContext.EJB_SERVICE_TYPE.getAbstractType());
                        builder.setAbstractTypeAuthority(EJBClientContext.EJB_SERVICE_TYPE.getAbstractTypeAuthority());
                        builder.addAttribute(EJBClientContext.FILTER_ATTR_NODE, nodeNameValue);
                        if (info.modules != null) for (EJBModuleIdentifier moduleIdentifier : info.modules) {
                            final String appName = moduleIdentifier.getAppName();
                            final String moduleName = moduleIdentifier.getModuleName();
                            final String distinctName = moduleIdentifier.getDistinctName();
                            if (distinctName.isEmpty()) {
                                if (appName.isEmpty()) {
                                    builder.addAttribute(EJBClientContext.FILTER_ATTR_EJB_MODULE, AttributeValue.fromString(moduleName));
                                } else {
                                    builder.addAttribute(EJBClientContext.FILTER_ATTR_EJB_MODULE, AttributeValue.fromString(appName + "/" + moduleName));
                                }
                            } else {
                                if (appName.isEmpty()) {
                                    builder.addAttribute(EJBClientContext.FILTER_ATTR_EJB_MODULE_DISTINCT, AttributeValue.fromString(moduleName + "/" + distinctName));
                                } else {
                                    builder.addAttribute(EJBClientContext.FILTER_ATTR_EJB_MODULE_DISTINCT, AttributeValue.fromString(appName + "/" + moduleName + "/" + distinctName));
                                }
                            }
                        }
                        // create a no-cluster mapping
                        serviceURLCache.add(builder.create());
                        if (info.clusters != null) for (Map.Entry entry : info.clusters.entrySet()) {
                            final String clusterName = entry.getKey();
                            builder.addAttribute(EJBClientContext.FILTER_ATTR_CLUSTER, AttributeValue.fromString(clusterName));
                            final CidrAddress cidrAddress = entry.getValue();
                            if (cidrAddress.getNetmaskBits() == 0) {
                                // historically we treat IPv4 and IPv6 any addresses as any
                                builder.removeAttribute(EJBClientContext.FILTER_ATTR_SOURCE_IP);
                            } else {
                                final AttributeValue value = AttributeValue.fromString(cidrAddress.toString());
                                builder.addAttribute(EJBClientContext.FILTER_ATTR_SOURCE_IP, value);
                            }
                            serviceURLCache.add(builder.create());
                        }
                    }

                    this.serviceURLCache = serviceURLCache;
                }
            }
        }
        return serviceURLCache;
    }


    /*
     * Take two TempInfo structures which are equivalent and merge their understanding of modules and clusters
     * Due to the way TempInfo structures are populated, these are the only combinations possible.
     */
    private void mergeEquivalentTempInfoStructures(TempInfo original, TempInfo equivalent) {
        if ((original.modules != null && equivalent.modules == null) && (original.clusters == null && equivalent.clusters != null)) {
            equivalent.modules = original.modules;
            original.clusters = equivalent.clusters;
        } else if ((original.modules == null && equivalent.modules != null) && (original.clusters != null && equivalent.clusters == null)) {
            original.modules = equivalent.modules;
            equivalent.clusters = original.clusters;
        }
    }

    /*
     * Given a URI and a set of equivalent hostnames, build a set of equivalent URIs
     */
    private Set getEquivalentURIs(URI uri, Set hostReps) {
        Set equivalentURIs = new HashSet();
        if (hostReps.size() == 1)
            return equivalentURIs;
        Set equivalentReps = new HashSet(hostReps);
        equivalentReps.remove(uri.getHost());
        for (String hostRep : equivalentReps) {
            URI equivalentURI = buildURI(uri.getScheme(), new InetSocketAddress(hostRep, uri.getPort()));
            equivalentURIs.add(equivalentURI);
        }
        return equivalentURIs;
    }

    private URI buildURI(final String protocol, final InetSocketAddress socketAddress) {
        final InetAddress address = socketAddress.getAddress();
        String hostName = Inet.getHostNameIfResolved(socketAddress);
        if (hostName == null) {
            if (address instanceof Inet6Address) {
                hostName = '[' + Inet.toOptimalString(address) + ']';
            } else {
                hostName = Inet.toOptimalString(address);
            }
        }
        try {
            return new URI(protocol, null, hostName, socketAddress.getPort(), null, null, null);
        } catch (URISyntaxException e) {
            Assert.unreachableCode();
            return null;
        }
    }

    boolean isInvalid() {
        return invalid;
    }

    void setInvalid(final boolean invalid) {
        this.invalid = invalid;
    }

    void addAddress(final String protocol, final String clusterName, final CidrAddress block, final InetSocketAddress destination) {
        synchronized (this) {
            serviceURLCache = null;
            clustersByName.computeIfAbsent(clusterName, name -> new ClusterNodeInformation())
                .getAddressTablesByProtocol()
                .computeIfAbsent(protocol, ignored -> new CidrAddressTable<>())
                .put(block, destination);
        }
    }


    void removeCluster(final String clusterName) {
        synchronized (this) {
            serviceURLCache = null;
            clustersByName.remove(clusterName);
        }
    }

    void addModules(final EJBClientChannel clientChannel, final EJBModuleIdentifier[] moduleList) {
        synchronized (this) {
            serviceURLCache = null;
            Collections.addAll(modulesByConnection.computeIfAbsent(clientChannel, ignored -> new HashSet<>()), moduleList);
        }
    }

    void removeModules(final EJBClientChannel clientChannel, final HashSet toRemove) {
        synchronized (this) {
            serviceURLCache = null;
            final Set set = modulesByConnection.get(clientChannel);
            if (set != null) {
                set.removeAll(toRemove);
            }
        }
    }

    void removeModule(final EJBClientChannel clientChannel, final EJBModuleIdentifier toRemove) {
        synchronized (this) {
            serviceURLCache = null;
            final Set set = modulesByConnection.get(clientChannel);
            if (set != null) {
                set.remove(toRemove);
            }
        }
    }

    void addAddress(EJBClientChannel clientChannel) {
        synchronized (this) {
            addressesByConnection.put(clientChannel, (InetSocketAddress) clientChannel.getChannel().getConnection().getPeerAddress());
            serviceURLCache = null;
        }
    }

    void removeConnection(EJBClientChannel clientChannel) {
        synchronized (this) {
            boolean moduleRemoved = modulesByConnection.remove(clientChannel) != null;
            boolean addressRemoved = addressesByConnection.remove(clientChannel) != null;
            if (moduleRemoved || addressRemoved) {
                serviceURLCache = null;
            }
        }
    }

    static final class ClusterNodeInformation {
        private final Map> addressTablesByProtocol = new HashMap<>();

        ClusterNodeInformation() {
        }

        Map> getAddressTablesByProtocol() {
            return addressTablesByProtocol;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy