/*
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.opentides.service.impl;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.Date;
import org.apache.log4j.Logger;
import org.opentides.bean.user.MultitenantUser;
import org.opentides.bean.user.Tenant;
import org.opentides.dao.TenantDao;
import org.opentides.persistence.hibernate.MultiTenantSchemaUpdate;
import org.opentides.persistence.jdbc.MultitenantJdbcTemplate;
import org.opentides.service.MultitenantUserService;
import org.opentides.service.TenantService;
import org.opentides.util.DateUtil;
import org.opentides.util.FileUtil;
import org.opentides.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* @author allantan
*
*/
@Service("tenantService")
public class TenantServiceImpl extends BaseCrudServiceImpl<Tenant> implements
TenantService {
private static final Logger _log = Logger
.getLogger(TenantServiceImpl.class);
@Value("${database.default_schema}")
private String defaultSchema = "master";
@Autowired
private MultiTenantSchemaUpdate multiTenantSchemaUpdate;
@Autowired
private MultitenantUserService multitenantUserService;
@Autowired
private MultitenantJdbcTemplate jdbcTemplate;
@Value("${jpa.log_ddl.directory}")
private String ddlLogs = "/var/log/ss_ddl/";
@Value("${database.username}")
private String sqlUsername = "ss";
@Value("${database.password}")
private String sqlPassword = "ideyatech";
@Value("${database.mysql.bin}")
private String mySQLBin = "/usr/local/mysql/bin";
@Value("${multitenant.post_create_script}")
private File updateSchamanameScript;
@Override
public String findUniqueSchemaName(final String company) {
final String schema = company.replaceAll("[^a-zA-Z]", "");
final StringBuffer uniqueSchema = new StringBuffer(defaultSchema + "_"
+ schema);
Tenant t = ((TenantDao) getDao()).loadBySchema(uniqueSchema.toString());
while (t != null) {
uniqueSchema.append(StringUtil.generateRandomString(3));
t = ((TenantDao) getDao()).loadBySchema(uniqueSchema.toString());
}
return uniqueSchema.toString();
}
@Override
public Tenant findByName(String company) {
return ((TenantDao) getDao()).findByName(company);
}
@Override
public Tenant findBySchema(String schema) {
return ((TenantDao) getDao()).loadBySchema(schema);
}
@Override
public void createTenantSchema(final Tenant tenant,
final MultitenantUser owner) {
final String company = tenant.getCompany();
final String schema = findUniqueSchemaName(company);
tenant.setSchema(schema);
tenant.setDbVersion(1l);
multiTenantSchemaUpdate.schemaEvolve(schema);
multitenantUserService.persistUserToTenantDb(tenant, owner, new String[] {"Administrator"});
// disable the copy in the master db so the owner won't be able to log
// in there
owner.getCredential().setEnabled(Boolean.FALSE);
}
@Override
public void createTemplateSchema(Tenant tenant) {
final String schema = "tpl_" + tenant.getCompany();
tenant.setSchema(schema);
tenant.setDbVersion(1l);
save(tenant);
multiTenantSchemaUpdate.schemaEvolve(schema);
}
@Override
public boolean deleteTenantSchema(final Tenant tenant, final boolean createBackup) {
throw new UnsupportedOperationException(
"Deleting of tenant schema is not yet supported.");
}
@Override
public String getTenantSchemaName(String tenantName) {
return ((TenantDao) getDao()).getTenantSchemaName(tenantName);
}
@Override
public void changeSchema(String schemaName) throws SQLException {
if (!jdbcTemplate.getCurrentSchemaName().equalsIgnoreCase(schemaName)) {
jdbcTemplate.switchSchema(schemaName);
}
}
@Override
public void cloneTenantSchema(Tenant template, Tenant tenant) {
final String dir = ddlLogs + "/"
+ DateUtil.convertShortDate(new Date());
final String company = tenant.getCompany();
final String schema = findUniqueSchemaName(company);
tenant.setSchema(schema);
tenant.setDbVersion(1l);
final File sqlFile = new File(dir + "/schema-" + schema + ".sql");
final File logFile = new File(dir + "/schema-" + schema + ".log");
String credential = " -u "+sqlUsername+" -p"+sqlPassword;
_log.info("DDL logs can be found in " + sqlFile.getAbsolutePath());
FileUtil.createDirectory(dir);
// sample command is
// mysqladmin -u gt -pideyatech create test
// mysqldump -u root tpl_Retail_Master | mysql -u root test --verbose
final String createSchema = mySQLBin + "/mysqladmin " + credential + " create "+ schema;
final String dumpSchema = mySQLBin + "/mysqldump " +credential+" " + template.getSchema() + " > " +sqlFile.getAbsolutePath();
final String loadSchema = mySQLBin +"/mysql " + credential + " " + schema + " < " + sqlFile.getAbsolutePath();
String updateSchema = null;
if (updateSchamanameScript != null && updateSchamanameScript.exists()) {
updateSchema = mySQLBin +"/mysql " + credential + " " + schema + " < " + updateSchamanameScript.getAbsolutePath();
}
try {
this.executeShell(createSchema, logFile);
this.executeShell(dumpSchema, logFile);
this.executeShell(loadSchema, logFile);
if(updateSchema != null) this.executeShell(updateSchema, logFile);
} catch (Exception e) {
_log.error("Failed to execute command for cloning tenant.",e);
}
}
/**
* Private helper that executes shell command.
* Used for cloning database schema.
* @param command
*/
private void executeShell(String command, File outputFile) throws Exception {
Process p;
BufferedWriter writer = null;
BufferedReader reader = null;
BufferedReader error = null;
int exitStatus = 0;
String line = "";
_log.info("Executing shell command :" + command);
try {
String[] script = {
"/bin/sh",
"-c",
command
};
p = Runtime.getRuntime().exec(script);
reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
writer = new BufferedWriter(new FileWriter(outputFile));
while (true) {
try {
exitStatus = p.waitFor();
break;
} catch (java.lang.InterruptedException e) {
// do nothing...
}
}
// write any result to file
while ((line = reader.readLine())!= null) {
writer.write(line + "\n");
}
if (exitStatus != 0) {
// get the error stream of the process and print it
error = new BufferedReader(
new InputStreamReader(p.getErrorStream()));
StringBuffer errorMsg = new StringBuffer();
while ((line = error.readLine()) != null) {
errorMsg.append(line);
}
throw new Exception("Error executing command" + command
+ " with return value :" + exitStatus + "\n" + errorMsg);
}
_log.info("Execution of " +command+" successful.");
} catch (Exception e) {
throw new Exception("Failed to record log file during tenant creation.", e);
} finally {
if ( writer != null)
try { writer.close( ); } catch (Exception e) { };
if (reader != null)
try { reader.close( ); } catch (Exception e) { };
}
}
}