package com.linkedin.databus.client.pub; /* * * Copyright 2013 LinkedIn Corp. All rights reserved * * 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. * */ import java.io.StringWriter; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import org.apache.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; import com.linkedin.databus.client.pub.DatabusServerCoordinates.StateId; import com.linkedin.databus.core.data_model.DatabusSubscription; import com.linkedin.databus.core.data_model.LegacySubscriptionUriCodec; import com.linkedin.databus.core.data_model.SubscriptionUriCodec; import com.linkedin.databus.core.util.ConfigBuilder; import com.linkedin.databus.core.util.InvalidConfigException; import com.linkedin.databus2.core.DatabusException; /** * Contains address and sources/subscriptions supported by a Databus server (relay or bootstrap * server)*/ public class ServerInfo implements Comparable<ServerInfo> { public static final String MODULE = ServerInfo.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); /** * Describes where the relay is located ( ip address, port, friendly name and id associated ) */ protected final DatabusServerCoordinates _serverCoordinates; /** * The list of sources the client is interested in * Example : In Espresso (V3) it could be BizProfile.BizCompany * In Databus V2, it could be BizFollow or its fully qualified database name (com.linkedin.xxx) */ protected final List<String> _sources; /** A list of subscriptions supported by the server. This supersedes {@link #_sources} for V3. */ protected final List<DatabusSubscription> _subs; protected String _jsonString; /* The name of the physical source */ private String _PhysicalSourceName = "DefaultPhysicalSource"; /** * Constructor * @param name a human-readable name for he server * @param state availability of the server {@link StateId} * @param address the server address * @param sources the list of sources hosted by the server */ public ServerInfo(String name, String state, InetSocketAddress address, List<String> sources) { super(); _serverCoordinates = new DatabusServerCoordinates(name, address, state); _sources = sources; List<DatabusSubscription> subsList = null; try { subsList = DatabusSubscription.createFromUriList(sources); } catch (DatabusException e) { LOG.warn("unable to parse sources list: " + e.getMessage(), e); subsList = Collections.emptyList(); } catch (URISyntaxException e) { LOG.warn("unable to parse sources list: " + e.getMessage(), e); subsList = Collections.emptyList(); } _subs = subsList; } /** * Constructor * @param name a human-readable name for he server * @param state availability of the server {@link StateId} * @param address the server address * @param subs the list of sources hosted by the server * @param uriCode an option codec to populate the _sources field */ public ServerInfo(String name, StateId state, InetSocketAddress address, Collection<DatabusSubscription> subs, SubscriptionUriCodec uriCodec) { super(); _serverCoordinates = new DatabusServerCoordinates(name, address, state); _subs = new ArrayList<DatabusSubscription>(subs); if (null == uriCodec) { _sources = DatabusSubscription.createUriStringList(_subs, LegacySubscriptionUriCodec.getInstance()); } else { _sources = new ArrayList<String>(subs.size()); for (DatabusSubscription sub: subs) { String uri = uriCodec.encode(sub).toString(); _sources.add(uri); } } } /** * Constructor * @param name a human-readable name for he server * @param state availability of the server {@link StateId} * @param address the server address * @param sources the list of sources hosted by the server */ public ServerInfo(String name, String state, InetSocketAddress address, String... sources) { this(name, state, address, Arrays.asList(sources)); } /** * The name of the physical source that is hosted by the relay */ public String getPhysicalSourceName() { return _PhysicalSourceName; } public void setPhysicalSourceName(String PhysicalSourceName) { _PhysicalSourceName = PhysicalSourceName; } /** * A name that identifies the server (relay, bootstrap-server) */ public String getName() { return _serverCoordinates.getName(); } /** * The address of the server (relay, bootstrap-server) */ public InetSocketAddress getAddress() { return _serverCoordinates.getAddress(); } /** * The sources supported by the server (Relay, bootstrap-server) */ public List<String> getSources() { return _sources; } /** The list of subscriptions supported by he server */ public List<DatabusSubscription> getSubs() { return _subs; } public String toJsonString() { if (null == _jsonString) { StringWriter out = new StringWriter(); ObjectMapper objMapper = new ObjectMapper(); try { objMapper.writeValue(out, this); _jsonString = out.toString(); } catch (Exception e) { _jsonString = "serialiationError"; } } return _jsonString; } @Override public String toString() { return toJsonString(); } /** * Converts the server info to a human-readable representation * @param sb a StringBuilder to which to append the string representation; if null, a new one will be created * @return the StringBuilder */ public StringBuilder toSimpleString(StringBuilder sb) { if (null == sb) { sb = new StringBuilder(200); } sb.append("[server=").append(_serverCoordinates).append(", subs=["); boolean notFirst = false; for (DatabusSubscription sub: _subs) { if (notFirst) { sb.append(","); } sb.append(sub.toSimpleString()); notFirst = true; } sb.append("]]"); return sb; } /** * Converts the server info to a human-readable representation */ public String toSimpleString() { return toSimpleString(null).toString(); } /** * Checks if the server supports a list of sources. Order is significant * @param sources the list of source to check * @return true iff the server can serve the sources */ public boolean supportsSources(List<String> sources) { return checkSubsequence(sources, getSources()); } /** * Checks if the first list of sources is a sub-sequence of the second list of server sources * @param sources the list of sources to check * @param serverSources the server sources to check against * @return true iff sources is a subsequence of serverSources */ public static boolean checkSubsequence(List<String> sources, List<String> serverSources) { int maxPos = 0; for (String source : sources) { for (; maxPos < serverSources.size() && !serverSources.get(maxPos).equals(source); ++maxPos) ; if (maxPos == serverSources.size()) { return false; } } return true; } /** * Checks if the first list of subscriptions is a sub-sequence of the second list of server subscriptions * @param subs the list of subscriptions to check * @param serverSubs the server subscriptions to check against * @return true iff sources is a subsequence of serverSources */ public static boolean checkSubsequenceSubsV3(List<DatabusSubscription> subs, List<DatabusSubscription> serverSubs) { int maxPos = 0; for (DatabusSubscription sub : subs) { for (; maxPos < serverSubs.size() && !serverSubs.get(maxPos).equals(sub); ++maxPos) ; if (maxPos == serverSubs.size()) { return false; } } return true; } /** * Static method to build ServerInfo object from hostPort info. * * @param hostPort String containing host and port of the server * @param hostPortDelim Delimiter between host and port * @return ServerInfo * @throws Exception */ public static ServerInfo buildServerInfoFromHostPort(String serverHostPort, String hostPortDelim) throws Exception { ServerInfo serverInfo = null; try { if ( null != serverHostPort) { String[] hostInfo = serverHostPort.split(hostPortDelim); if (hostInfo.length == 2) { InetSocketAddress address = new InetSocketAddress(InetAddress.getByName(hostInfo[0]), Integer.parseInt(hostInfo[1])); serverInfo = new ServerInfo(serverHostPort, DatabusServerCoordinates.StateId.ONLINE.toString(), address); } } } catch(Exception ex) { LOG.error("Unable to extract Boostrap Server info from StartSCN response. ServerInfo was :" + serverHostPort, ex); throw ex; } return serverInfo; } /** * * @author pganti * */ public static class ServerInfoBuilder implements ConfigBuilder<ServerInfo> { public static final char NAME_SEPARATOR = ')'; public static final char PORT_SEPARATOR = ':'; public static final char SOURCES_LIST_SEPARATOR = ':'; public static final char SOURCE_SEPARATOR = ','; private String _host = "localhost"; private int _port = 9000; private String _sources = ""; private String _name = null; private String _address = null; private String _PhysicalSourceName = "DefaultPhysicalSource"; private SubscriptionUriCodec _uriCodec = LegacySubscriptionUriCodec.getInstance(); private final List<DatabusSubscription.Builder> _subs = new ArrayList<DatabusSubscription.Builder>(); public ServerInfoBuilder() { } public static String generateServerName(String prefix, int id) { StringBuilder resBuilder = new StringBuilder(); resBuilder.append(prefix); resBuilder.append('.'); resBuilder.append(id); return resBuilder.toString(); } /** * The name of the physical source that is hosted by the relay */ public String getPhysicalSourceName() { return _PhysicalSourceName; } public void setPhysicalSourceName(String PhysicalSourceName) { _PhysicalSourceName = PhysicalSourceName; } /** Host name or IP address of the server */ public String getHost() { return _host; } public void setHost(String host) { _host = host; } /** Comma-separated list of sources supported by the server */ public String getSources() { return _sources; } public void setSources(String sources) { _sources = sources; } /** The HTTP port on which the server listens */ public int getPort() { return _port; } public void setPort(int port) { _port = port; } /** A name that identifies the server */ public String getName() { return _name; } public void setName(String name) { _name = name; } public String getAddress() { return _address; } /** Format is: [name)]host:port:source1,source2,... */ public void setAddress(String address) { _address = address; } @Override public ServerInfo build() throws InvalidConfigException { if (null != _address) parseAddress(); String[] sources = getSources().split("[" + SOURCE_SEPARATOR + "]"); for (int i = 0; i < sources.length; ++i) sources[i] = sources[i].trim(); InetAddress serverAddr = null; try { serverAddr = InetAddress.getByName(getHost()); } catch (Exception e) { throw new InvalidConfigException("Invalid server address", e); } InetSocketAddress inetAddress = new InetSocketAddress(serverAddr, getPort()); if (null == _name) { StringBuilder serverName = new StringBuilder(); try { serverName.append(serverAddr.getHostName()); } catch (Exception e) { LOG.warn("Unable to resolve address:" + serverAddr.toString()); serverName.append("server"); } _name = serverName.toString(); } LOG.info("res name: " + _name); ServerInfo serverInfo = new ServerInfo(_name, StateId.ONLINE.toString(), inetAddress, sources); serverInfo.setPhysicalSourceName(getPhysicalSourceName()); return serverInfo; } public static String generateAddress(String name, String host, int port, String... sources) { StringBuilder res = new StringBuilder((null != name ? name.length() : 0) + host.length() + 8 + sources.length * 50); if (null != name && name.length() > 0) { res.append(name); res.append(NAME_SEPARATOR); } res.append(host); res.append(PORT_SEPARATOR); res.append(port); res.append(SOURCES_LIST_SEPARATOR); boolean first = true; for (String s: sources) { if (!first) res.append(SOURCE_SEPARATOR); first = false; res.append(s); } return res.toString(); } void parseAddress() throws InvalidConfigException { int nameIdx = _address.indexOf(NAME_SEPARATOR); if (0 < nameIdx) setName(_address.substring(0, nameIdx)); int portIdx = _address.indexOf(PORT_SEPARATOR, nameIdx + 1); if (0 > portIdx) throw new InvalidConfigException("no port specified in address:" + _address); setHost(_address.substring(nameIdx + 1, portIdx)); int sourceListIdx = _address.indexOf(SOURCES_LIST_SEPARATOR, portIdx + 1); if (0 > sourceListIdx) throw new InvalidConfigException("no sources list specified in address:" + _address); setPort(Integer.parseInt(_address.substring(portIdx + 1, sourceListIdx))); setSources(_address.substring(sourceListIdx + 1)); } public void uriCodec(SubscriptionUriCodec uriCodec) { _uriCodec = uriCodec; } public SubscriptionUriCodec uriCodec() { return _uriCodec; } public DatabusSubscription.Builder getSub(int index) { ensureSubsListIndex(index); return _subs.get(index); } private void ensureSubsListIndex(int targetIndex) { for (int i = _subs.size(); i <= targetIndex; ++i) { _subs.add(new DatabusSubscription.Builder()); } } } public static class ServerInfoSetBuilder implements ConfigBuilder<List<ServerInfo>> { public static final char SERVER_INFO_SEPARATOR = ';'; private String _servers; @Override public List<ServerInfo> build() throws InvalidConfigException { if (null == _servers) return Collections.<ServerInfo>emptyList(); String[] serverInfos = _servers.split("[" + SERVER_INFO_SEPARATOR + "]"); ServerInfoBuilder siBuilder = new ServerInfoBuilder(); ArrayList<ServerInfo> result = new ArrayList<ServerInfo>(serverInfos.length); for (String s: serverInfos) { siBuilder.setAddress(s); result.add(siBuilder.build()); } return result; } public String getServers() { return _servers; } public void setServers(String servers) { _servers = servers; } } @Override public boolean equals(Object obj) { if (null == obj) return false; if (!(obj instanceof ServerInfo)) return false; ServerInfo other = (ServerInfo) obj; return getAddress().equals(other.getAddress()); } @Override public int hashCode() { return getAddress().hashCode(); } @Override public int compareTo(ServerInfo o) { return _serverCoordinates.compareTo(o._serverCoordinates); } }