openmeetings-install分析(十)——Admin类询问阶段processInstall分析(3)

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方法的剩下内容交给下篇文章分析吧

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值