/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cassandra.config;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import com.google.common.collect.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.service.MigrationManager;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
import org.cliffc.high_scale_lib.NonBlockingHashMap;
public class Schema
{
private static final Logger logger = LoggerFactory.getLogger(Schema.class);
public static final Schema instance = new Schema();
/**
* longest permissible KS or CF name. Our main concern is that filename not be more than 255 characters;
* the filename will contain both the KS and CF names. Since non-schema-name components only take up
* ~64 characters, we could allow longer names than this, but on Windows, the entire path should be not greater than
* 255 characters, so a lower limit here helps avoid problems. See CASSANDRA-4110.
*/
public static final int NAME_LENGTH = 48;
/* metadata map for faster keyspace lookup */
private final Map<String, KSMetaData> keyspaces = new NonBlockingHashMap<String, KSMetaData>();
/* Keyspace objects, one per keyspace. Only one instance should ever exist for any given keyspace. */
private final Map<String, Keyspace> keyspaceInstances = new NonBlockingHashMap<String, Keyspace>();
/* metadata map for faster ColumnFamily lookup */
private final BiMap<Pair<String, String>, UUID> cfIdMap = HashBiMap.create();
private volatile UUID version;
// 59adb24e-f3cd-3e02-97f0-5b395827453f
public static final UUID emptyVersion;
public static final ImmutableSet<String> systemKeyspaceNames = ImmutableSet.of(Keyspace.SYSTEM_KS, Tracing.TRACE_KS);
static
{
try
{
emptyVersion = UUID.nameUUIDFromBytes(MessageDigest.getInstance("MD5").digest());
}
catch (NoSuchAlgorithmException e)
{
throw new AssertionError();
}
}
/**
* Initialize empty schema object
*/
public Schema()
{}
/**
* Load up non-system keyspaces
*
* @param keyspaceDefs The non-system keyspace definitions
*
* @return self to support chaining calls
*/
public Schema load(Collection<KSMetaData> keyspaceDefs)
{
for (KSMetaData def : keyspaceDefs)
load(def);
return this;
}
/**
* Load specific keyspace into Schema
*
* @param keyspaceDef The keyspace to load up
*
* @return self to support chaining calls
*/
public Schema load(KSMetaData keyspaceDef)
{
for (CFMetaData cfm : keyspaceDef.cfMetaData().values())
load(cfm);
setKeyspaceDefinition(keyspaceDef);
return this;
}
/**
* Get keyspace instance by name
*
* @param keyspaceName The name of the keyspace
*
* @return Keyspace object or null if keyspace was not found
*/
public Keyspace getKeyspaceInstance(String keyspaceName)
{
return keyspaceInstances.get(keyspaceName);
}
/**
* Store given Keyspace instance to the schema
*
* @param keyspace The Keyspace instance to store
*
* @throws IllegalArgumentException if Keyspace is already stored
*/
public void storeKeyspaceInstance(Keyspace keyspace)
{
if (keyspaceInstances.containsKey(keyspace.getName()))
throw new IllegalArgumentException(String.format("Keyspace %s was already initialized.", keyspace.getName()));
keyspaceInstances.put(keyspace.getName(), keyspace);
}
/**
* Remove keyspace from schema
*
* @param keyspaceName The name of the keyspace to remove
*
* @return removed keyspace instance or null if it wasn't found
*/
public Keyspace removeKeyspaceInstance(String keyspaceName)
{
return keyspaceInstances.remove(keyspaceName);
}
/**
* Remove keyspace definition from system
*
* @param ksm The keyspace definition to remove
*/
public void clearKeyspaceDefinition(KSMetaData ksm)
{
keyspaces.remove(ksm.name);
}
/**
* Given a keyspace name & column family name, get the column family
* meta data. If the keyspace name or column family name is not valid
* this function returns null.
*
* @param keyspaceName The keyspace name
* @param cfName The ColumnFamily name
*
* @return ColumnFamily Metadata object or null if it wasn't found
*/
public CFMetaData getCFMetaData(String keyspaceName, String cfName)
{
assert keyspaceName != null;
KSMetaData ksm = keyspaces.get(keyspaceName);
return (ksm == null) ? null : ksm.cfMetaData().get(cfName);
}
/**
* Get ColumnFamily metadata by its identifier
*
* @param cfId The ColumnFamily identifier
*
* @return metadata about ColumnFamily
*/
public CFMetaData getCFMetaData(UUID cfId)
{
Pair<String,String> cf = getCF(cfId);
return (cf == null) ? null : getCFMetaData(cf.left, cf.right);
}
public CFMetaData getCFMetaData(Descriptor descriptor)
{
return getCFMetaData(descriptor.ksname, descriptor.cfname);
}
/**
* Get type of the ColumnFamily but it's keyspace/name
*
* @param ksName The keyspace name
* @param cfName The ColumnFamily name
*
* @return The type of the ColumnFamily
*/
public ColumnFamilyType getColumnFamilyType(String ksName, String cfName)
{
assert ksName != null && cfName != null;
CFMetaData cfMetaData = getCFMetaData(ksName, cfName);
return (cfMetaData == null) ? null : cfMetaData.cfType;
}
/**
* Get column comparator for ColumnFamily but it's keyspace/name
*
* @param ksName The keyspace name
* @param cfName The ColumnFamily name
*
* @return The comparator of the ColumnFamily
*/
public AbstractType<?> getComparator(String ksName, String cfName)
{
assert ksName != null;
CFMetaData cfmd = getCFMetaData(ksName, cfName);
if (cfmd == null)
throw new IllegalArgumentException("Unknown ColumnFamily " + cfName + " in keyspace " + ksName);
return cfmd.comparator;
}
/**
* Get value validator for specific column
*
* @param ksName The keyspace name
* @param cfName The ColumnFamily name
* @param column The name of the column
*
* @return value validator specific to the column or default (per-cf) one
*/
public AbstractType<?> getValueValidator(String ksName, String cfName, ByteBuffer column)
{
return getCFMetaData(ksName, cfName).getValueValidator(column);
}
/**
* Get metadata about keyspace by its name
*
* @param keyspaceName The name of the keyspace
*
* @return The keyspace metadata or null if it wasn't found
*/
public KSMetaData getKSMetaData(String keyspaceName)
{
assert keyspaceName != null;
return keyspaces.get(keyspaceName);
}
/**
* @return collection of the non-system keyspaces
*/
public List<String> getNonSystemKeyspaces()
{
return ImmutableList.copyOf(Sets.difference(keyspaces.keySet(), systemKeyspaceNames));
}
/**
* Get metadata about keyspace inner ColumnFamilies
*
* @param keyspaceName The name of the keyspace
*
* @return metadata about ColumnFamilies the belong to the given keyspace
*/
public Map<String, CFMetaData> getKeyspaceMetaData(String keyspaceName)
{
assert keyspaceName != null;
KSMetaData ksm = keyspaces.get(keyspaceName);
assert ksm != null;
return ksm.cfMetaData();
}
/**
* @return collection of the all keyspace names registered in the system (system and non-system)
*/
public Set<String> getKeyspaces()
{
return keyspaces.keySet();
}
/**
* @return collection of the metadata about all keyspaces registered in the system (system and non-system)
*/
public Collection<KSMetaData> getKeyspaceDefinitions()
{
return keyspaces.values();
}
/**
* Update (or insert) new keyspace definition
*
* @param ksm The metadata about keyspace
*/
public void setKeyspaceDefinition(KSMetaData ksm)
{
assert ksm != null;
keyspaces.put(ksm.name, ksm);
}
/* ColumnFamily query/control methods */
/**
* @param cfId The identifier of the ColumnFamily to lookup
* @return The (ksname,cfname) pair for the given id, or null if it has been dropped.
*/
public Pair<String,String> getCF(UUID cfId)
{
return cfIdMap.inverse().get(cfId);
}
/**
* Lookup keyspace/ColumnFamily identifier
*
* @param ksName The keyspace name
* @param cfName The ColumnFamily name
*
* @return The id for the given (ksname,cfname) pair, or null if it has been dropped.
*/
public UUID getId(String ksName, String cfName)
{
return cfIdMap.get(Pair.create(ksName, cfName));
}
/**
* Load individual ColumnFamily Definition to the schema
* (to make ColumnFamily lookup faster)
*
* @param cfm The ColumnFamily definition to load
* pgaref
*/
public void load(CFMetaData cfm)
{
Pair<String, String> key = Pair.create(cfm.ksName, cfm.cfName);
if (cfIdMap.containsKey(key)){
logger.info("Attempting to load already loaded column family %s.%s", cfm.ksName, cfm.cfName);
return;
}
logger.debug("Adding {} to cfIdMap", cfm);
cfIdMap.put(key, cfm.cfId);
}
/**
* Used for ColumnFamily data eviction out from the schema
*
* @param cfm The ColumnFamily Definition to evict
*/
public void purge(CFMetaData cfm)
{
cfIdMap.remove(Pair.create(cfm.ksName, cfm.cfName));
}
/* Version control */
/**
* @return current schema version
*/
public UUID getVersion()
{
return version;
}
/**
* Read schema from system keyspace and calculate MD5 digest of every row, resulting digest
* will be converted into UUID which would act as content-based version of the schema.
*/
public void updateVersion()
{
try
{
MessageDigest versionDigest = MessageDigest.getInstance("MD5");
for (Row row : SystemKeyspace.serializedSchema())
{
if (invalidSchemaRow(row) || ignoredSchemaRow(row))
continue;
row.cf.updateDigest(versionDigest);
}
version = UUID.nameUUIDFromBytes(versionDigest.digest());
SystemKeyspace.updateSchemaVersion(version);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/*
* Like updateVersion, but also announces via gossip
*/
public void updateVersionAndAnnounce()
{
updateVersion();
MigrationManager.passiveAnnounce(version);
}
/**
* Clear all KS/CF metadata and reset version.
*/
public synchronized void clear()
{
for (String keyspaceName : getNonSystemKeyspaces())
{
KSMetaData ksm = getKSMetaData(keyspaceName);
for (CFMetaData cfm : ksm.cfMetaData().values())
purge(cfm);
clearKeyspaceDefinition(ksm);
}
updateVersionAndAnnounce();
}
public static boolean invalidSchemaRow(Row row)
{
return row.cf == null || (row.cf.isMarkedForDelete() && row.cf.getColumnCount() == 0);
}
public static boolean ignoredSchemaRow(Row row)
{
try
{
return systemKeyspaceNames.contains(ByteBufferUtil.string(row.key.key));
}
catch (CharacterCodingException e)
{
throw new RuntimeException(e);
}
}
}