/* * Copyright © 2014-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.datafabric.dataset.service.mds; import co.cask.cdap.api.dataset.DatasetSpecification; import co.cask.cdap.api.dataset.module.EmbeddedDataset; import co.cask.cdap.api.dataset.table.Table; import co.cask.cdap.data2.dataset2.lib.table.MDSKey; import co.cask.cdap.data2.dataset2.lib.table.MetadataStoreDataset; import co.cask.cdap.proto.DatasetModuleMeta; import co.cask.cdap.proto.DatasetTypeMeta; import co.cask.cdap.proto.Id; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import java.util.Collection; import java.util.List; import java.util.Map; import javax.annotation.Nullable; /** * Dataset types and modules metadata store */ public class DatasetTypeMDS extends MetadataStoreDataset { /** * Prefix for rows containing module info. * NOTE: we store in same table list of modules, with keys being <MODULES_PREFIX><module_name> and * types to modules mapping with keys being <TYPE_TO_MODULE_PREFIX><type_name> */ public static final String MODULES_PREFIX = "m_"; /** * Prefix for rows containing type -> module mapping * see {@link #MODULES_PREFIX} for more info. */ private static final String TYPE_TO_MODULE_PREFIX = "t_"; public DatasetTypeMDS(DatasetSpecification spec, @EmbeddedDataset("") Table table) { super(table); } /** * Retrieves a module from the given namespace * * @param datasetModuleId the {@link Id.DatasetModule} for the module to retrieve * @return {@link DatasetModuleMeta} for the module if found in the specified namespace, null otherwise */ @Nullable public DatasetModuleMeta getModule(Id.DatasetModule datasetModuleId) { return get(getModuleKey(datasetModuleId.getNamespaceId(), datasetModuleId.getId()), DatasetModuleMeta.class); } /** * Tries to find a module in the specified namespace first. If it fails, tries to find it in the system namespace * * @param datasetModuleId {@link Id.DatasetModule} for the module to retrieve * @return {@link DatasetModuleMeta} for the module if found either in the specified namespace or in the system * namespace, null otherwise */ @Nullable public DatasetModuleMeta getModuleWithFallback(Id.DatasetModule datasetModuleId) { // Try to find module in the specified namespace first DatasetModuleMeta moduleMeta = getModule(datasetModuleId); // if not found, try to load it from system namespace if (moduleMeta == null) { moduleMeta = getModule(Id.DatasetModule.from(Id.Namespace.SYSTEM, datasetModuleId.getId())); } return moduleMeta; } @Nullable public DatasetModuleMeta getModuleByType(Id.DatasetType datasetTypeId) { Id.DatasetModule datasetModuleId = get(getTypeKey(datasetTypeId.getNamespaceId(), datasetTypeId.getTypeName()), Id.DatasetModule.class); if (datasetModuleId == null) { return null; } // TODO: Slightly strange. Maybe change signature to accept Id.Namespace separately from typeName return getModule(datasetModuleId); } public DatasetTypeMeta getType(Id.DatasetType datasetTypeId) { DatasetModuleMeta moduleName = getModuleByType(datasetTypeId); if (moduleName == null) { return null; } return getTypeMeta(datasetTypeId.getNamespace(), datasetTypeId.getTypeName(), moduleName); } public Collection<DatasetModuleMeta> getModules(Id.Namespace namespaceId) { return list(getModuleKey(namespaceId.getId()), DatasetModuleMeta.class); } public Collection<DatasetTypeMeta> getTypes(Id.Namespace namespaceId) { List<DatasetTypeMeta> types = Lists.newArrayList(); for (Map.Entry<MDSKey, Id.DatasetModule> entry : getTypesMapping(namespaceId).entrySet()) { MDSKey.Splitter splitter = entry.getKey().split(); // first part is TYPE_TO_MODULE_PREFIX splitter.skipString(); // second part is namespace splitter.skipString(); // third part is the type name, which is what we expect String typeName = splitter.getString(); types.add(getTypeMeta(namespaceId, typeName, entry.getValue())); } return types; } public void writeModule(Id.Namespace namespaceId, DatasetModuleMeta moduleMeta) { Id.DatasetModule datasetModuleId = Id.DatasetModule.from(namespaceId, moduleMeta.getName()); write(getModuleKey(namespaceId.getId(), moduleMeta.getName()), moduleMeta); for (String type : moduleMeta.getTypes()) { writeTypeToModuleMapping(Id.DatasetType.from(namespaceId, type), datasetModuleId); } } public void deleteModule(Id.DatasetModule datasetModuleId) { DatasetModuleMeta module = getModule(datasetModuleId); if (module == null) { // that's fine: module is not there return; } deleteAll(getModuleKey(datasetModuleId.getNamespaceId(), datasetModuleId.getId())); for (String type : module.getTypes()) { deleteAll(getTypeKey(datasetModuleId.getNamespaceId(), type)); } } public void deleteModules(Id.Namespace namespaceId) { Collection<DatasetModuleMeta> modules = getModules(namespaceId); for (DatasetModuleMeta module : modules) { deleteModule(Id.DatasetModule.from(namespaceId, module.getName())); } } private DatasetTypeMeta getTypeMeta(Id.Namespace namespaceId, String typeName, Id.DatasetModule datasetModuleId) { DatasetModuleMeta moduleMeta = getModule(datasetModuleId); return getTypeMeta(namespaceId, typeName, moduleMeta); } private DatasetTypeMeta getTypeMeta(Id.Namespace namespaceId, String typeName, DatasetModuleMeta moduleMeta) { List<DatasetModuleMeta> modulesToLoad = Lists.newArrayList(); // adding first all modules we depend on, then myself for (String usedModule : moduleMeta.getUsesModules()) { // Try to find module in the specified namespace first, then the system namespace DatasetModuleMeta usedModuleMeta = getModuleWithFallback(Id.DatasetModule.from(namespaceId, usedModule)); // Module could not be found in either user or system namespace, bail out Preconditions.checkState(usedModuleMeta != null, String.format("Unable to find metadata about module %s that module %s uses.", usedModule, moduleMeta.getName())); modulesToLoad.add(usedModuleMeta); } modulesToLoad.add(moduleMeta); return new DatasetTypeMeta(typeName, modulesToLoad); } // type -> moduleName private Map<MDSKey, Id.DatasetModule> getTypesMapping(Id.Namespace namespaceId) { return listKV(getTypeKey(namespaceId.getId()), Id.DatasetModule.class); } private void writeTypeToModuleMapping(Id.DatasetType datasetTypeId, Id.DatasetModule datasetModuleId) { write(getTypeKey(datasetTypeId.getNamespaceId(), datasetTypeId.getTypeName()), datasetModuleId); } private MDSKey getModuleKey(String namespace) { return getModuleKey(namespace, null); } private MDSKey getModuleKey(String namespace, @Nullable String moduleName) { return getKey(MODULES_PREFIX, namespace, moduleName); } private MDSKey getTypeKey(String namespace) { return getTypeKey(namespace, null); } private MDSKey getTypeKey(String namespace, @Nullable String typeName) { return getKey(TYPE_TO_MODULE_PREFIX, namespace, typeName); } private MDSKey getKey(String type, String namespace, @Nullable String name) { MDSKey.Builder builder = new MDSKey.Builder().add(type).add(namespace); if (name != null) { builder.add(name); } return builder.build(); } }