com.dell.doradus.service.schema.SchemaService Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2014 Dell, 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 com.dell.doradus.service.schema;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.dell.doradus.common.ApplicationDefinition;
import com.dell.doradus.common.CommonDefs;
import com.dell.doradus.common.ContentType;
import com.dell.doradus.common.UNode;
import com.dell.doradus.common.Utils;
import com.dell.doradus.core.DoradusServer;
import com.dell.doradus.core.ServerConfig;
import com.dell.doradus.service.Service;
import com.dell.doradus.service.StorageService;
import com.dell.doradus.service.db.DBService;
import com.dell.doradus.service.db.DBTransaction;
import com.dell.doradus.service.db.DColumn;
import com.dell.doradus.service.db.DRow;
import com.dell.doradus.service.db.Tenant;
import com.dell.doradus.service.rest.RESTCallback;
import com.dell.doradus.service.rest.RESTService;
import com.dell.doradus.service.taskmanager.TaskManagerService;
import com.dell.doradus.service.tenant.TenantService;
/**
* Provides common schema services for the Doradus server. The SchemaService parses new
* and modified application schemas, add them to the Applications table, and notify the
* appropriate storage service of the change.
*/
public class SchemaService extends Service {
// Application ColumnFamily name:
public static final String APPS_STORE_NAME = "Applications";
// Application definition row column names:
private static final String COLNAME_APP_SCHEMA = "_application";
private static final String COLNAME_APP_SCHEMA_FORMAT = "_format";
private static final String COLNAME_APP_SCHEMA_VERSION = "_version";
// Singleton instance:
private static final SchemaService INSTANCE = new SchemaService();
// Current format version with which we store schema definitions:
private static final int CURRENT_SCHEMA_LEVEL = 2;
// REST commands supported by the SchemaService:
private static final List> CMD_CLASSES = Arrays.asList(
ListApplicationsCmd.class,
ListApplicationCmd.class,
DefineApplicationCmd.class,
ModifyApplicationCmd.class,
DeleteApplicationCmd.class,
DeleteApplicationKeyCmd.class
);
//----- Service methods
/**
* Get the singleton instance of the StorageService. The object may or may not have
* been initialized yet.
*
* @return The singleton instance of the StorageService.
*/
public static SchemaService instance() {
return INSTANCE;
} // instance
// Called once before startService.
@Override
public void initService() {
RESTService.instance().registerCommands(CMD_CLASSES);
} // initService
// Wait for the DB service to be up and check application schemas.
@Override
public void startService() {
TenantService.instance().waitForFullService();
TenantService.instance().createDefaultTenant();
checkAppStores();
} // startService
// Currently, we have nothing special to do to "stop".
@Override
public void stopService() {
} // stopService
//----- Public SchemaService methods
/**
* Create the application with the given name in the default tenant. If the given
* application already exists, the request is treated as an application update. If the
* update is successfully validated, its schema is stored in the database, and the
* appropriate storage service is notified to implement required physical database
* changes, if any.
*
* @param appDef {@link ApplicationDefinition} of application to create or update.
* Note that appDef is updated with the "Tenant" option.
*/
public void defineApplication(ApplicationDefinition appDef) {
checkServiceState();
Tenant tenant = TenantService.instance().getDefaultTenant();
defineApplication(tenant, appDef);
} // defineApplication
/**
* Create the application with the given name in the given Tenant. If the given
* application already exists, the request is treated as an application update. If the
* update is successfully validated, its schema is stored in the database, and the
* appropriate storage service is notified to implement required physical database
* changes, if any.
*
* @param tenant {@link Tenant} in which application is being created or updated.
* @param appDef {@link ApplicationDefinition} of application to create or update.
* Note that appDef is updated with the "Tenant" option.
*/
public void defineApplication(Tenant tenant, ApplicationDefinition appDef) {
checkServiceState();
setTenant(appDef, tenant);
ApplicationDefinition currAppDef = checkApplicationKey(appDef);
StorageService storageService = verifyStorageServiceOption(currAppDef, appDef);
storageService.validateSchema(appDef);
initializeApplication(currAppDef, appDef);
} // defineApplication
/**
* Return the {@link ApplicationDefinition} for all applications in the given Tenant.
*
* @param tenant Tenant in which to query all applications.
* @return A collection of application definitions.
*/
public Collection getAllApplications(Tenant tenant) {
checkServiceState();
return findAllApplications(tenant);
} // getAllApplications
/**
* Return the {@link ApplicationDefinition} for the application in the default tenant.
* Null is returned if no application is found with the given name in the default
* tenant.
*
* @return The {@link ApplicationDefinition} for the given application or null if no
* no application such application is defined in the default tenant.
*
* @deprecated This method only works for the default tenant and hence only in
* single-tenant mode. {@link SchemaService#getApplication(Tenant, String)}
* should be used instead.
*/
public ApplicationDefinition getApplication(String appName) {
checkServiceState();
Tenant tenant = TenantService.instance().getDefaultTenant();
return getApplicationDefinition(tenant, appName);
} // getApplication
/**
* Return the {@link ApplicationDefinition} for the application in the given tenant.
* Null is returned if no application is found with the given name and tenant.
*
* @return The {@link ApplicationDefinition} for the given application or null if no
* no such application is defined in the default tenant.
*/
public ApplicationDefinition getApplication(Tenant tenant, String appName) {
checkServiceState();
return getApplicationDefinition(tenant, appName);
} // getApplication
/**
* Examine the given application's StorageService option and return the corresponding
* {@link StorageService}. An error is thrown if the storage service is unknown or has
* not been initialized.
*
* @param appDef {@link ApplicationDefinition} of an application.
* @return The application's assigned {@link StorageService}.
*/
public StorageService getStorageService(ApplicationDefinition appDef) {
checkServiceState();
String ssName = getStorageServiceOption(appDef);
StorageService storageService = DoradusServer.instance().findStorageService(ssName);
Utils.require(storageService != null, "StorageService is unknown or hasn't been initialized: " + ssName);
return storageService;
} // getStorageService
/**
* Get the given application's StorageService option. If none is found, assign and
* return the default. Unlike {@link #getStorageService(ApplicationDefinition)}, this
* method will not throw an exception if the storage service is unknown or has not
* been initialized.
*
* @param appDef {@link ApplicationDefinition} of an application.
* @return The application's declared or assigned StorageService option.
*/
public String getStorageServiceOption(ApplicationDefinition appDef) {
String ssName = appDef.getOption(CommonDefs.OPT_STORAGE_SERVICE);
if (Utils.isEmpty(ssName)) {
ssName = DoradusServer.instance().getDefaultStorageService();
appDef.setOption(CommonDefs.OPT_STORAGE_SERVICE, ssName);
}
return ssName;
} // getStorageServiceOption
/**
* Delete the given application, including all of its data, from the default tenant.
* If the given application doesn't exist, the call is a no-op. WARNING: This method
* deletes an application regardless of whether it has a key defined.
*
* @param appName Name of application to delete in default tenant.
*/
public void deleteApplication(String appName) {
checkServiceState();
ApplicationDefinition appDef = getApplication(appName);
if (appDef == null) {
return;
}
deleteApplication(appName, appDef.getKey());
} // deleteApplication
/**
* Delete the given application, including all of its data, from the default tenant.
* If the given application doesn't exist, the call is a no-op. If the application
* exists, the given key must match the current key, if one is defined, or be
* null/empty if no key is defined.
*
* @param appName Name of application to delete in the default tenant.
* @param key Application key of existing application, if any.
*/
public void deleteApplication(String appName, String key) {
checkServiceState();
ApplicationDefinition appDef = getApplication(appName);
if (appDef == null) {
return;
}
deleteApplication(appDef, key);
} // deleteApplication
/**
* Delete the application with the given definition, including all of its data. The
* given {@link ApplicationDefinition} must define the tenant in which the application
* resides. If the given application doesn't exist, the call is a no-op. If the
* application exists, the given key must match the current key, if one is defined, or
* be null/empty if no key is defined.
*
* @param appDef {@link ApplicationDefinition} of application to delete.
* @param key Application key of existing application, if any.
*/
public void deleteApplication(ApplicationDefinition appDef, String key) {
checkServiceState();
String appKey = appDef.getKey();
if (Utils.isEmpty(appKey)) {
Utils.require(Utils.isEmpty(key), "Application key does not match: %s", key);
} else {
Utils.require(appKey.equals(key), "Application key does not match: %s", key);
}
assert Tenant.getTenant(appDef) != null;
// Delete storage service-specific data first.
m_logger.info("Deleting application: {}", appDef.getAppName());
StorageService storageService = getStorageService(appDef);
storageService.deleteApplication(appDef);
TaskManagerService.instance().deleteApplicationTasks(appDef);
deleteAppProperties(appDef);
} // deleteApplication
//----- Private methods
// Singleton construction only
private SchemaService() {}
// Check to see if the storage manager is active for each application.
private void checkAppStores() {
m_logger.info("The following tenants and applications are defined:");
Collection tenantList = TenantService.instance().getTenants();
for (Tenant tenant : tenantList) {
m_logger.info(" Tenant: {}", tenant.getKeyspace());
Iterator rowIter =
DBService.instance().getAllRowsAllColumns(tenant, SchemaService.APPS_STORE_NAME);
if (!rowIter.hasNext()) {
m_logger.info(" ");
}
while (rowIter.hasNext()) {
DRow row = rowIter.next();
ApplicationDefinition appDef = loadAppRow(tenant, getColumnMap(row.getColumns()));
if (appDef != null) {
String appName = appDef.getAppName();
String ssName = getStorageServiceOption(appDef);
m_logger.info(" Application '{}': StorageService={}; keyspace={}",
new Object[]{appName, ssName, tenant.getKeyspace()});
if (DoradusServer.instance().findStorageService(ssName) == null) {
m_logger.warn(" >>>Application '{}' uses storage service '{}' which has not been " +
"initialized; application will not be accessible via this server",
appDef.getAppName(), ssName);
}
}
}
}
if (tenantList.size() == 0) {
m_logger.info(" ");
}
}
// Delete the given application's schema row from the Applications CF.
private void deleteAppProperties(ApplicationDefinition appDef) {
Tenant tenant = Tenant.getTenant(appDef);
DBTransaction dbTran = DBService.instance().startTransaction(tenant);
dbTran.deleteRow(SchemaService.APPS_STORE_NAME, appDef.getAppName());
DBService.instance().commit(dbTran);
} // deleteAppProperties
// Initialize storage and store the given schema for the given new or updated application.
private void initializeApplication(ApplicationDefinition currAppDef, ApplicationDefinition appDef) {
Tenant tenant = Tenant.getTenant(appDef);
if (tenant.getKeyspace().equals(ServerConfig.getInstance().keyspace)) {
TenantService.instance().createDefaultTenant();
}
getStorageService(appDef).initializeApplication(currAppDef, appDef);
storeApplicationSchema(appDef);
} // initializeApplication
// Store the application row with schema, version, and format.
private void storeApplicationSchema(ApplicationDefinition appDef) {
String appName = appDef.getAppName();
Tenant tenant = Tenant.getTenant(appDef);
DBTransaction dbTran = DBService.instance().startTransaction(tenant);
dbTran.addColumn(SchemaService.APPS_STORE_NAME, appName, COLNAME_APP_SCHEMA, appDef.toDoc().toJSON());
dbTran.addColumn(SchemaService.APPS_STORE_NAME, appName, COLNAME_APP_SCHEMA_FORMAT, ContentType.APPLICATION_JSON.toString());
dbTran.addColumn(SchemaService.APPS_STORE_NAME, appName, COLNAME_APP_SCHEMA_VERSION, Integer.toString(CURRENT_SCHEMA_LEVEL));
DBService.instance().commit(dbTran);
} // storeApplicationSchema
// Verify key match of an existing application, if any, and return it's definition.
private ApplicationDefinition checkApplicationKey(ApplicationDefinition appDef) {
Tenant tenant = Tenant.getTenant(appDef);
ApplicationDefinition currAppDef = getApplication(tenant, appDef.getAppName());
if (currAppDef == null) {
m_logger.info("Defining application: {}", appDef.getAppName());
} else {
m_logger.info("Updating application: {}", appDef.getAppName());
String appKey = currAppDef.getKey();
Utils.require(Utils.isEmpty(appKey) || appKey.equals(appDef.getKey()),
"Application key cannot be changed: %s", appDef.getKey());
}
return currAppDef;
} // checkApplicationKey
// Set the given application's "Tenant" option to the given tenant's keyspace.
private void setTenant(ApplicationDefinition appDef, Tenant tenant) {
appDef.setOption(CommonDefs.OPT_TENANT, tenant.getKeyspace());
}
// Verify the given application's StorageService option and, if this is a schema
// change, ensure it hasn't changed. Return the application's StorageService object.
private StorageService verifyStorageServiceOption(ApplicationDefinition currAppDef, ApplicationDefinition appDef) {
// Verify or assign StorageService
String ssName = getStorageServiceOption(appDef);
StorageService storageService = getStorageService(appDef);
Utils.require(storageService != null, "StorageService is unknown or hasn't been initialized: %s", ssName);
// Currently, StorageService can't be changed.
if (currAppDef != null) {
String currSSName = getStorageServiceOption(currAppDef);
Utils.require(currSSName.equals(ssName), "'StorageService' cannot be changed for application: %s", appDef.getAppName());
}
return storageService;
} // verifyStorageServiceOption
private Map getColumnMap(Iterator colIter) {
Map colMap = new HashMap<>();
while (colIter.hasNext()) {
DColumn col = colIter.next();
colMap.put(col.getName(), col.getValue());
}
return colMap;
} // getColumnMap
// Parse the application schema from the given application row.
private ApplicationDefinition loadAppRow(Tenant tenant, Map colMap) {
ApplicationDefinition appDef = new ApplicationDefinition();
String appSchema = colMap.get(COLNAME_APP_SCHEMA);
if (appSchema == null) {
return null; // Not a real application definition row
}
String format = colMap.get(COLNAME_APP_SCHEMA_FORMAT);
ContentType contentType = Utils.isEmpty(format) ? ContentType.TEXT_XML : new ContentType(format);
String versionStr = colMap.get(COLNAME_APP_SCHEMA_VERSION);
int schemaVersion = Utils.isEmpty(versionStr) ? CURRENT_SCHEMA_LEVEL : Integer.parseInt(versionStr);
if (schemaVersion > CURRENT_SCHEMA_LEVEL) {
m_logger.warn("Skipping schema with advanced version: {}", schemaVersion);
return null;
}
try {
appDef.parse(UNode.parse(appSchema, contentType));
} catch (Exception e) {
m_logger.warn("Error parsing schema for application '" + appDef.getAppName() + "'; skipped", e);
return null;
}
setTenant(appDef, tenant);
return appDef;
} // loadAppRow
// Get the given application's application. If it's not in our app-to-tenant map,
// refresh the map in case the application was just created.
private ApplicationDefinition getApplicationDefinition(Tenant tenant, String appName) {
Iterator colIter =
DBService.instance().getAllColumns(tenant, SchemaService.APPS_STORE_NAME, appName);
if (!colIter.hasNext()) {
return null;
}
return loadAppRow(tenant, getColumnMap(colIter));
} // getApplicationDefinition
// Get all application definitions for the given Tenant.
private Collection findAllApplications(Tenant tenant) {
List result = new ArrayList<>();
Iterator rowIter =
DBService.instance().getAllRowsAllColumns(tenant, SchemaService.APPS_STORE_NAME);
while (rowIter.hasNext()) {
DRow row = rowIter.next();
ApplicationDefinition appDef = loadAppRow(tenant, getColumnMap(row.getColumns()));
if (appDef != null) {
result.add(appDef);
}
}
return result;
} // findAllApplications
} // class SchemaService
© 2015 - 2025 Weber Informatics LLC | Privacy Policy