package cassandra.metadata; import cassandra.CassandraCluster; import cassandra.cql.*; import cassandra.routing.RoutingPolicy; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.InetAddress; import java.util.*; public class MetadataService extends Metadata implements CassandraCluster.EventListener { private static final Logger logger = LoggerFactory.getLogger(MetadataService.class); private static final ObjectMapper mapper; static { mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); } private final CassandraCluster cluster; private RoutingPolicy routingPolicy; public MetadataService(CassandraCluster cluster) { this.cluster = cluster; routingPolicy = new LocalRoutingPolicy(); } @JsonIgnore public boolean isInitialized() { return super.getClusterName() != null; } public boolean initialize() { if (!updateLocal()) { return false; } updateKeyspaces(); updateTables(); updateColumns(); return true; } public List<InetAddress> selectPeers() { List<InetAddress> peers = null; for (Row row : executeLocal("SELECT peer FROM system.peers")) { if (peers == null) { peers = new ArrayList<InetAddress>(); } peers.add(row.getInet("peer")); } if (peers == null) { return Collections.emptyList(); } return peers; } @Override public void setLocal(InetAddress local) { super.setLocal(local); if (!isInitialized()) { initialize(); } } @Override public String getClusterName() { String clusterName = super.getClusterName(); if (clusterName == null) { clusterName = "unknown"; } return clusterName; } @Override public void onJoinCluster(CassandraCluster cluster, InetAddress endpoint) { validate(cluster).updatePeer(endpoint); if (!isInitialized() && cluster.seeds().contains(endpoint)) { initialize(); } } @Override public void onLeaveCluster(CassandraCluster cluster, InetAddress endpoint) { validate(cluster).removePeer(endpoint); } @Override public void onMove(CassandraCluster cluster, InetAddress endpoint) { validate(cluster).updatePeers(); } @Override public void onUp(CassandraCluster cluster, InetAddress endpoint) { if (validate(cluster).hasPeer(endpoint)) { setPeerAsUp(endpoint); } else { updatePeer(endpoint); } } @Override public void onDown(CassandraCluster cluster, InetAddress endpoint) { validate(cluster).setPeerAsDown(endpoint); } @Override public void onCreateKeyspace(CassandraCluster cluster, String keyspace) { validate(cluster).updateKeyspace(keyspace); } @Override public void onUpdateKeyspace(CassandraCluster cluster, String keyspace) { validate(cluster).updateKeyspace(keyspace); } @Override public void onDropKeyspace(CassandraCluster cluster, String keyspace) { validate(cluster).removeKeyspace(keyspace); } @Override public void onCreateTable(CassandraCluster cluster, String keyspace, String table) { validate(cluster).updateTable(keyspace, table); updateColumns(keyspace, table); } @Override public void onUpdateTable(CassandraCluster cluster, String keyspace, String table) { validate(cluster).updateTable(keyspace, table); updateColumns(keyspace, table); } @Override public void onDropTable(CassandraCluster cluster, String keyspace, String table) { validate(cluster).removeTable(keyspace, table); } public boolean updateLocal() { ResultSet rs = executeLocal("SELECT cluster_name,data_center,host_id,partitioner,rack,release_version,schema_version,tokens FROM system.local WHERE key='local'"); if (!rs.hasNext()) { return false; } Row row = rs.next(); setClusterName(row.getString("cluster_name")); setPartitioner(row.getString("partitioner")); PeerMetadata newPeer = PeerMetadata.newBuilder() .setMetadata(this) .setAddress(getLocal()) .setDatacenter(row.getString("data_center")) .setRack(row.getString("rack")) .setHostId(row.getUUID("host_id")) .setSchemaVersion(row.getUUID("schema_version")) .setReleaseVersion(row.getString("release_version")) .setTokens(row.getSet("tokens", String.class)) .build(); addPeer(newPeer); return true; } public void updatePeers() { ResultSet rs = executeLocal("SELECT * FROM system.peers"); for (Row row : rs) { boolean down = false; PeerMetadata oldPeer = getPeer(row.getInet("peer")); if (oldPeer != null) { down = oldPeer.isDown(); } addPeer(PeerMetadata.newBuilder().mergeFrom(this, row).setDown(down).build()); } } public boolean updatePeer(InetAddress endpoint) { ResultSet rs = executeLocal("SELECT * FROM system.peers WHERE peer=?", endpoint); if (!rs.hasNext()) { return false; } addPeer(PeerMetadata.newBuilder().mergeFrom(this, rs.next()).build()); return true; } public void updateKeyspaces() { for (Row row : executeLocal("SELECT * FROM system.schema_keyspaces")) { addKeyspace(KeyspaceMetadata.newBuilder().mergeFrom(this, row).build()); } } public boolean updateKeyspace(String keyspace) { ResultSet rs = executeLocal("SELECT * FROM system.schema_keyspaces WHERE keyspace_name=?", keyspace); if (!rs.hasNext()) { return false; } addKeyspace(KeyspaceMetadata.newBuilder().mergeFrom(this, rs.next()).build()); return true; } public void updateTables() { for (Row row : executeLocal("SELECT * FROM system.schema_columnfamilies")) { addTable(TableMetadata.newBuilder().mergeFrom(this, row).build()); } } public boolean updateTable(String keyspace, String table) { ResultSet rs = executeLocal("SELECT * FROM system.schema_columnfamilies WHERE keyspace_name=? AND columnfamily_name=?", keyspace, table); if (!rs.hasNext()) { return false; } addTable(TableMetadata.newBuilder().mergeFrom(this, rs.next()).build()); return true; } public void updateColumns() { for (Row row : executeLocal("SELECT * FROM system.schema_columns")) { addColumn(ColumnMetadata.newBuilder().mergeFrom(this, row).build()); } } public void updateColumns(String keyspace, String table) { for (Row row : executeLocal("SELECT * FROM system.schema_columns WHERE keyspace_name=? AND columnfamily_name=?", keyspace, table)) { addColumn(ColumnMetadata.newBuilder().mergeFrom(this, row).build()); } } public void clear() { super.clear(); } private ResultSet executeLocal(String query) { return cluster.session().statement(query).setConsistency(Consistency.ONE).setRoutingPolicy(routingPolicy).execute(); } private ResultSet executeLocal(String query, Object... values) { return cluster.session().statement(query, values).setConsistency(Consistency.ONE).setRoutingPolicy(routingPolicy).execute(); } private MetadataService validate(CassandraCluster cluster) { if (!this.cluster.equals(cluster)) { throw new IllegalStateException(); } return this; } static JsonNode valueToJsonNode(Object value) { return mapper.valueToTree(value); } static String valueToJsonString(Object value) { try { return mapper.writeValueAsString(value); } catch (JsonProcessingException e) { return null; } } static <E> List<E> convertAsList(String json, Class<E> elementClass) { try { return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, elementClass)); } catch (IOException e) { logger.warn(e.getMessage(), e); return Collections.emptyList(); } } static <K, V> Map<K, V> convertAsMap(String json, Class<K> keyClass, Class<V> valueClass) { try { return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, keyClass, valueClass)); } catch (IOException e) { logger.warn(e.getMessage(), e); return Collections.emptyMap(); } } private class LocalRoutingPolicy implements RoutingPolicy { @Override public boolean isLocal(InetAddress endpoint) { return true; } @Override public Iterator<InetAddress> activeEndpoints(AbstractStatement<?> statement) { InetAddress local = getLocal(); if (local == null) { return cluster.options().getRoutingPolicy().activeEndpoints(statement); } return Collections.singletonList(local).iterator(); } @Override public void addEndpoint(InetAddress endpoint) { throw new UnsupportedOperationException(); } @Override public void removeEndpoint(InetAddress endpoint) { throw new UnsupportedOperationException(); } } }