/*
* Copyright © 2015 Cask Data, 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 co.cask.cdap.data2.util.hbase;
import co.cask.cdap.data2.util.TableId;
import co.cask.cdap.proto.Id;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import org.apache.hadoop.hbase.HTableDescriptor;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* Common utility methods for dealing with HBase table name conversions.
*/
public abstract class HTableNameConverter {
private String getHBaseTableName(String tableName) {
return encodeTableName(tableName);
}
private String encodeTableName(String tableName) {
try {
return URLEncoder.encode(tableName, "ASCII");
} catch (UnsupportedEncodingException e) {
// this can never happen - we know that ASCII is a supported character set!
throw new RuntimeException(e);
}
}
private String getBackwardCompatibleTableName(String tablePrefix, TableId tableId) {
String tableName = tableId.getTableName();
// handle table names in default namespace so we do not have to worry about upgrades
if (Id.Namespace.DEFAULT.equals(tableId.getNamespace())) {
// if the table name starts with 'system.', then its a queue or stream table. Do not add namespace to table name
// e.g. namespace = default, tableName = system.queue.config. Resulting table name = cdap.system.queue.config
// also no need to prepend the table name if it already starts with 'user'.
// TODO: the 'user' should be prepended by the HBaseTableAdmin.
if (tableName.startsWith(String.format("%s.", Id.Namespace.SYSTEM.getId()))) {
return Joiner.on(".").join(tablePrefix, tableName);
}
// if the table name does not start with 'system.', then its a user dataset. Add 'user' to the table name to
// maintain backward compatibility. Also, do not add namespace to the table name
// e.g. namespace = default, tableName = purchases. Resulting table name = cdap.user.purchases
return Joiner.on(".").join(tablePrefix, "user", tableName);
}
// if the namespace is not default, do not need to change anything
return tableName;
}
/**
* @return Backward compatible, ASCII encoded table name
*/
protected String getHBaseTableName(String tablePrefix, TableId tableId) {
Preconditions.checkArgument(tablePrefix != null, "Table prefix should not be null.");
return getHBaseTableName(getBackwardCompatibleTableName(tablePrefix, tableId));
}
/**
* Gets the system configuration table prefix.
*
* @param htd Table descriptor for any table that can be associated with the
* @return System configuration table prefix (full table name minus the table qualifier).
* Example input: "cdap_ns.table.name" --> output: "cdap_system." (hbase 94)
* Example input: "cdap.table.name" --> output: "cdap_system." (hbase 94. input table is in default namespace)
* Example input: "cdap_ns:table.name" --> output: "cdap_system:" (hbase 96, 98)
*/
public abstract String getSysConfigTablePrefix(HTableDescriptor htd);
/**
* Returns {@link TableId} for the table represented by the given {@link HTableDescriptor}.
*/
public abstract TableId from(HTableDescriptor htd);
/**
* Returns the prefix prepended to the namespace.
*/
public abstract String getNamespacePrefix(HTableDescriptor htd);
protected String toHBaseNamespace(String hBaseNamespacePrefix, Id.Namespace namespace) {
// Handle backward compatibility to not add the prefix for default namespace
// TODO: CDAP-1601 - Conditional should be removed when we have a way to upgrade user datasets
return getHBaseTableName(Id.Namespace.DEFAULT.equals(namespace) ? namespace.getId() :
hBaseNamespacePrefix + "_" + namespace.getId());
}
protected PrefixedTableId fromHBaseTableName(String namespace, String qualifier) {
Preconditions.checkArgument(namespace != null, "Table namespace should not be null.");
Preconditions.checkArgument(qualifier != null, "Table qualifier should not be null.");
// Handle backward compatibility to not add the prefix for default namespace
if (Id.Namespace.DEFAULT.getId().equals(namespace)) {
// in Default namespace, qualifier is something like 'cdap.foo.table'
@SuppressWarnings("ConstantConditions")
String[] parts = qualifier.split("\\.", 2);
Preconditions.checkArgument(parts.length == 2,
String.format("expected table name to contain '.': %s", qualifier));
String prefix = parts[0];
qualifier = parts[1];
// strip 'user.' from the beginning of table name since we prepend it in getBackwardCompatibleTableName
parts = qualifier.split("\\.", 2);
if (parts.length == 2 && "user".equals(parts[0])) {
qualifier = parts[1];
}
return new PrefixedTableId(prefix, namespace, qualifier);
}
// If non-default HBase namespace is used, namespace is something like 'cdap_userNS'
@SuppressWarnings("ConstantConditions")
String[] parts = namespace.split("_", 2);
Preconditions.checkArgument(parts.length == 2,
String.format("expected hbase namespace to have a '_': %s", namespace));
// Id.Namespace already checks for non-null namespace
return new PrefixedTableId(parts[0], parts[1], qualifier);
}
/**
* Used internal to HTableNameConverter, so that one parsing method can extract both the prefix and TableId.
*/
protected static final class PrefixedTableId {
private final String tablePrefix;
private final TableId tableId;
private PrefixedTableId(String tablePrefix, String namespace, String tableName) {
this.tablePrefix = tablePrefix;
this.tableId = TableId.from(namespace, tableName);
}
public String getTablePrefix() {
return tablePrefix;
}
public TableId getTableId() {
return tableId;
}
}
}