2021SC@SDUSC
在前2篇文章中,我们对processInstall类的部分代码进行了非常详细的分析,接下来,我们将继续分析processInstall类的剩下部分代码。
processInstall方法源码
// Admin.java
private void processInstall(String file) throws Exception {
...
// 上面部分的代码已经分析过,就省略了
if (cmdl.hasOption("file")) {
File backup = checkRestoreFile(file);
dropDB(connectionProperties);
ImportInitvalues importInit = getApplicationContext().getBean(ImportInitvalues.class);
importInit.loadSystem(cfg, force);
processRestore(backup);
} else {
checkAdminDetails();
dropDB(connectionProperties);
ImportInitvalues importInit = getApplicationContext().getBean(ImportInitvalues.class);
importInit.loadAll(cfg, force);
}
}
checkRestoreFile
// Admin.java
private File checkRestoreFile(String file) {
// 传入的应该是该文件的地址
File backup = new File(file);
// 命令行中不包含选项“file”或者备份不存在或者backup不是文件
if (!cmdl.hasOption("file") || !backup.exists() || !backup.isFile()) {
log("File should be specified, and point the existent zip file");
usage();
throw new ExitException();
}
return backup;
}
private void usage() {
// OmHelpFormatter继承自HelpFormatter,是当前命令行选项的帮助消息格式化程序
OmHelpFormatter formatter = new OmHelpFormatter();
// 设置每行字符数为100
formatter.setWidth(100);
// printHelp:使用指定的命令行语法打印选项的帮助
// 第一个参数:此应用程序的语法
// 第二个参数:在帮助开始时展示的头部信息
// 第三个参数:Options类实例
// 第四个参数:在帮助结束时展示的底部信息
formatter.printHelp("admin", "Please specify one of the required parameters.", opts, "Examples:\n" +
"\t./admin.sh -b\n" +
"\t./admin.sh -i -v -file backup_31_07_2012_12_07_51.zip --drop\n" +
"\t./admin.sh -i -v -user admin -email someemail@gmail.com -tz \"Asia/Tehran\" -group \"yourgroup\" --db-type mysql --db-host localhost");
}
所以checkRestoreFile方法会检查传入的file是否是合法的文件地址,并返回根据这个文件地址创建的一个新文件
dropDB
它会首先检查命令行是否带有drop选项,如果有,则执行immediateDropDB方法
// Admin.java
private void immediateDropDB(ConnectionProperties props) throws Exception {
if (context != null) {
destroyApplication();
context = null;
}
JDBCConfigurationImpl conf = new JDBCConfigurationImpl();
try {
conf.setPropertiesFile(OmFileHelper.getPersistence());
conf.setConnectionDriverName(props.getDriver());
conf.setConnectionURL(props.getURL());
conf.setConnectionUserName(props.getLogin());
conf.setConnectionPassword(props.getPassword());
//HACK to suppress all warnings
// 获取日志,设置为INFO级别
getLogImpl(conf).setLevel(Log.INFO);
runSchemaTool(conf, SchemaTool.ACTION_DROPDB);
runSchemaTool(conf, SchemaTool.ACTION_CREATEDB);
} finally {
conf.close();
}
}
JDBCConfigurationImpl是JDBCConfiguration接口的一个实现类,JDBCConfiguration定义了配置运行时和连接到JDBC DataSource所需的属性
接下来程序通过set方法,把props的属性值传递给JDBCConfigurationImpl
// Admin.java
private static void runSchemaTool(JDBCConfigurationImpl conf, String action) throws Exception {
SchemaTool st = new SchemaTool(conf, action);
// 设置为true,则打印模式操作期间会抛出SQLExceptions,但忽略它。
st.setIgnoreErrors(true);
// 设置为true,则会对OpenJPA组件用于记账的特殊表进行操作。
st.setOpenJPATables(true);
// 设置为false,表示不操纵现有表上的索引
st.setIndexes(false);
// 设置为false,表示不操纵现有表上的主键
st.setPrimaryKeys(false);
// 如果当前执行的操作不是ACTION_DROPDB
if (!SchemaTool.ACTION_DROPDB.equals(action)) {
// 设置工具将执行的模式组,getDBSchemaGroup会返回数据库的schema
st.setSchemaGroup(st.getDBSchemaGroup());
}
// 运行
st.run();
}
SchemaTool用于管理数据库模式。注意,该工具从不添加或删除现有表中的唯一约束,因为JDBC DatabaseMetaData不包含这些约束的信息。
SchemaTool(conf, action):构建一个方法去执行给定的动作,这里传入的是ACTION_DROPDB动作。
所以dropDB()方法会删除原有数据库数据,并且根据connectionProperties中的属性配置JDBCConfigurationImpl属性,并以此JDBCConfigurationImpl实例获得新的数据库模型,创建新的数据库
之后,程序从spring容器中获得ImportInitvalues实例,调用loadSystem方法加载系统配置,这个方法我们在前面的文章中也详细分析过了
processRestore
// Admin.java
private void processRestore(File backup) throws Exception {
try (InputStream is = new FileInputStream(backup)) {
BackupImport importCtrl = getApplicationContext().getBean(BackupImport.class);
importCtrl.performImport(is);
}
}
BackupImport来自与cli包同级的backup包,下面来看一下该类的performImport方法
// BackupImport.java
public void performImport(InputStream is) throws Exception {
// 清空一系列的哈希表
userMap.clear();
groupMap.clear();
calendarMap.clear();
appointmentMap.clear();
roomMap.clear();
messageFolderMap.clear();
userContactMap.clear();
fileMap.clear();
messageFolderMap.put(INBOX_FOLDER_ID, INBOX_FOLDER_ID);
messageFolderMap.put(SENT_FOLDER_ID, SENT_FOLDER_ID);
messageFolderMap.put(TRASH_FOLDER_ID, TRASH_FOLDER_ID);
File f = unzip(is);
...
}
private static File unzip(InputStream is) throws IOException {
// OmFileHelper来自openmeetings-util包
// 第一个参数是文件的路径,getNewDir会创建一个位于该路径的File类
// 第二个参数是该File类的名字
// CalendarPatterns来自openmeetings-util包
// getTimeForStreamId会把传入的Date实例进行格式化,作为字符串返回
File f = OmFileHelper.getNewDir(OmFileHelper.getUploadImportDir(), "import_" + CalendarPatterns.getTimeForStreamId(new Date()));
log.debug("##### EXTRACTING BACKUP TO: " + f);
try (ZipInputStream zis = new ZipInputStream(is)) {
ZipEntry zipentry = null;
while ((zipentry = zis.getNextEntry()) != null) {
// for each entry to be extracted
// validate方法验证每个文件是否都在目标提取的目录内,下文有详细的方法介绍
File fentry = validate(zipentry.getName(), f);
// 获取目录文件
File dir = zipentry.isDirectory() ? fentry : fentry.getParentFile();
// 若该目录文件不存在或者无法生成目录,则警告:无法产生文件夹
if (!dir.exists() && !dir.mkdirs()) {
log.warn("Failed to create folders: {}", dir);
}
// 若该文件不是目录文件
if (!fentry.isDirectory()) {
// FileUtils、IOUtils来自apache.commons.io
// apache.commons.io提供了输入流输出流的常用工具方法
// openOutputStream:打开流
try (FileOutputStream fos = FileUtils.openOutputStream(fentry)) {
// copy方法可以拷贝流,支持多种数据间的拷贝
// 在这里是将ZipInputStream流拷贝到openOutputStream流
IOUtils.copy(zis, fos);
}
zis.closeEntry();
}
}
}
return f;
}
private static File validate(String inEname, File intended) throws IOException {
// 获取文件路径
final String intendedPath = intended.getCanonicalPath();
// File.pathSeparatorChar:依赖于平台的默认名称分隔符,类型为char
// inEname.indexOf('\\') > -1:传入的第一个参数是否包含“\\”
// 若包含,则把其中的“\\”都换成“/”;否则保持原样
String ename = File.pathSeparatorChar != '\\' && inEname.indexOf('\\') > -1
? inEname.replace('\\', '/') : inEname;
// for each entry to be extracted
// 通过给定的父抽象路径名和子路径名字符串创建一个新的File实例
File fentry = new File(intended, ename);
// 获取该新建文件的路径
final String canonicalPath = fentry.getCanonicalPath();
// 如果该路径是以intendedPath开头,则返回该实例,否则报错
if (canonicalPath.startsWith(intendedPath)) {
return fentry;
} else {
throw new IllegalStateException("File is outside extraction target directory.");
}
}
先写这么多,performImport方法的剩下内容交给下篇文章分析吧