2021SC@SDUSC
上篇文章链接:openmeetings-install分析(二)——ImportInitvalues类分析(1)
经过前面的分析,我们知道同包下的Admin类会调用ImportInitvalues类下的loadAll方法去完成变量的初始化加载。下面我们来重点分析loadAll方法,这个方法涉及的内容几乎覆盖了整个ImportInitvalues类。
索引
loadAll方法
loadAll方法代码
//ImportInitvalues.java
public void loadAll(InstallationConfig cfg, boolean force) throws Exception {
checkInstalled(force);
loadSystem(cfg, force);
loadInitUserAndGroup(cfg);
progress = 80;
loadDefaultRooms(cfg.isCreateDefaultObjects(), cfg.getDefaultLangId());
progress = 100;
}
上篇文章我们已经分析了这个方法应传入的参数代表的含义,这里我们主要分析方法体
checkInstalled
//ImportInitvalues.java
private void checkInstalled(boolean force) {
// dummy check if installation was performed before
if (!force && userDao.count() > 0) {
log.debug("System contains users, no need to install data one more time.");
}
}
该方法用于仿真检验之前是否已经执行过安装操作,如果已经执行过,就在日志里面打印"System contains users, no need to install data one more time."
force参数在上篇文章中已经提过,表示是否在“安装时不检查数据库中是否存在旧数据”,在这里if语句条件为true的情况是force=false,即在安装时会检查数据库是否存在旧数据
userDao是该类注入的来自openmeetings-db包下的UserDao类的实例对象依赖,用于在持久层执行跟用户相关的操作。userDao.count()方法用于获取所有用户,这里稍微分析一下userDao.count()方法。
//UserDao.java
@Override
public long count() {
// get all users
TypedQuery<Long> q = em.createNamedQuery("countNondeletedUsers", Long.class);
return q.getSingleResult();
}
这里使用到了JPQL查询,JPQL是基于字符串的Java持久化查询语言,语法类似于SQL。
JPQL查询的基本使用方式如下:
// 获得实体管理器
EntityManager em = ...
// 建立JPQL查询
String getByFirstName = "SELECT c FROM Contact c WHERE c.firstName = :firstName";
// 创建查询实例
TypedQuery<Contact> query = em.createQuery(getByFirstName, Contact.class);
// 设置查询参数
query.setParameter("firstName", "John");
// 获取结果
List<Contact> contacts = query.getResultList();
在我们要分析的代码中执行的是“创建查询实例”这一步。关于createNamedQuery方法,官方文档解释如下:
简单来说,这个方法传入2个参数,第一个参数是元数据中定义的查询的名称,第二个参数是查询结果的类型,然后会返回一个新的查询实例
JPA的命名查询实际上就是给查询语句起个名字,执行查询的时候就是直接使用起的这个名字,避免重复写JPQL语句,使查询在代码中得到更多的重用
在User实体类中,使用@NamedQuery注解定义命名查询
//User.java
@NamedQuery(name = "countNondeletedUsers", query = "SELECT COUNT(u) FROM User u WHERE u.deleted = false"),
这段代码表示定义了一个名为“countNondeletedUsers”的查询语句,用于查询用户表中未被删除的用户
count()方法的最后一行代码:return q.getSingleResult();
中,getSingleResult()表示返回查询结果的其中一个结果,类型是Long
回到最初的checkInstalled()方法,若userDao.count()返回的结果大于0,表示用户表中存在的用户不为空,那么这个条件判断为true
所以,当在安装时会检查数据库是否存在旧数据并且数据库中的用户表不为空时,表明之前已经执行过安装操作,就会打印“系统包含用户,无需再次安装数据”的提示信息
下面再简单分析一下log.debug()方法
DEBUG是log4j开源日志的其中一种日志级别。使用方法即为log.debug(“message”)
在我们开发阶段有时候需要查看特定数据,我们可以把日志级别定为DEBUG级,调试信息会输出在日志里便于调试和跟踪修改bug。
推荐使用log日志输出调试信息而不要使用System.out.println()方法,主要是因为println()使用了同步锁,会影响程序的并发性能和系统的吞吐量。
(log.debug()部分参考博客:如何使用log.debug())
loadSystem
//ImportInitvalues.java
public void loadSystem(InstallationConfig cfg, boolean force) {
checkInstalled(force);
sipDao.delete();
progress = 20;
loadConfiguration(cfg);
progress = 40;
if (cfg.isCreateDefaultObjects()) {
loadInitialOAuthServers();
}
progress = 60;
}
首先会执行checkInstalled方法,该方法我们在上面已经分析过。
为什么在这里还要再重复执行一遍这个方法呢?因为在Admin类中,会有一种情况是不调用loadAll,而是调用loadSystem的,就是当命令行中输入的参数带"file"时。对于“file”参数的作用,options定义里面的描述是“file used for backup/restore/install”,意思是用于备份、存储、安装过程的文件。
在这种情况下,程序依然需要检测是否之前已经安装过,即依然需要checkInstalled方法。但是它不需要初始化用户名和组、房间信息,也就是没有loadAll方法下的loadInitUserAndGroup()方法和loadDefaultRooms()方法。
sipDao.delete
sipDao是该类注入的来自openmeetings-db包下的SipDao类的实例对象依赖。sip是一个会话发起协议,这部分内容涉及到了Asterisk应用程序,Asterisk是一个开放源代码的软件VoIP PBX系统,是一款实现电话用户交换机(PBX)功能的自由软件。这部分内容涉及到了网络电话相关的知识,内容比较多,后面一篇文章会对此进行深入的学习介绍,在这里就先不展开了。
progress
progress记录了程序初始化加载的进度,一开始process置为0,外界可以通过getProgress()方法获取当前初始化进度。可以看到,在loadAll方法的最后,progress赋值为100,表示已经初始化完成。
loadConfiguration
//ImportInitvalues.java
public void loadConfiguration(InstallationConfig cfg) {
for (Configuration c : initialCfgs(cfg)) {
cfgDao.update(c, null);
}
log.debug("Configurations ADDED");
}
传入的参数为InstallationConfig的一个实例对象,InstallationConfig在上篇文章中已简要分析过,它主要定义了一些基本的参数,比如用户名、密码、邮箱、组织、时区、应用程序名、程序运行的端口号、加密协议、插件地址等等。
initialCfgs方法会初始化配置类,并且返回一个配置类列表
//ImportInitvalues.java
public static List<Configuration> initialCfgs(InstallationConfig cfg) {
List<Configuration> list = new ArrayList<>();
addCfg(list, CONFIG_CRYPT, cfg.getCryptClassName(), Configuration.Type.string,
"This Class is used for Authentification-Crypting. "
+ "Be carefull what you do here! If you change it while "
+ "running previous Pass of users will not be workign anymore! "
+ "for more Information see https://openmeetings.apache.org/CustomCryptMechanism.html"
, VER_1_9);
...
}
//ImportInitvalues.java
private static void addCfg(List<Configuration> list
, String key
, String value
, Configuration.Type type
, String comment
, String fromVersion)
{
Configuration c = new Configuration();
c.setType(type);
c.setKey(key);
c.setValue(value);
c.setComment(comment);
c.setFromVersion(fromVersion);
list.add(c);
}
addCfg方法用于增加配置对象,并把它加入配置类列表里面。
Configuration类在openmeetings-db中定义,用于进行相关的配置,比如socket超时时间、默认的系统语言设置、各种插件的路径等等。
回到loadConfiguration方法中,
for (Configuration c : initialCfgs(cfg)) {
cfgDao.update(c, null);
}
这段代码遍历initialCfgs方法返回的Configuration对象列表,cfgDao是该类注入的来自openmeetings-db包下的ConfigurationDao类的实例对象依赖,它主要用于在持久层执行跟系统配置相关的操作。在这里是用于逐个更新列表的配置对象。
loadConfiguration方法体的最后日志打印"Configurations ADDED",log.debug()方法在前面已经分析过,这里就不再赘述。
loadInitialOAuthServers
在loadSystem方法中,若cfg.isCreateDefaultObjects()返回true,则会调用这个loadInitialOAuthServers方法。在InstallationConfig类中,设置createDefaultObjects变量的值默认为true,所以程序默认会满足if语句条件。
loadInitialOAuthServers方法用于加载其他软件的授权,比如Facebook、Google、VK等等
//ImportInitvalues.java
public void loadInitialOAuthServers() {
// Facebook
oauthDao.update(new OAuthServer()
.setName("Facebook")
.setIconUrl("https://www.facebook.com/images/fb_icon_325x325.png")
.setEnabled(false)
.setClientId(CLIENT_PLACEHOLDER)
.setClientSecret(SECRET_PLACEHOLDER)
.setRequestKeyUrl("https://www.facebook.com/v2.10/dialog/oauth?client_id={$client_id}&redirect_uri={$redirect_uri}&scope=email")
.setRequestTokenUrl("https://graph.facebook.com/v2.10/oauth/access_token")
.setRequestTokenMethod(RequestTokenMethod.POST)
.setRequestTokenAttributes("client_id={$client_id}&redirect_uri={$redirect_uri}&client_secret={$client_secret}&code={$code}")
.setRequestInfoUrl("https://graph.facebook.com/me?access_token={$access_token}&fields=id,first_name,last_name,email")
.setRequestInfoMethod(RequestInfoMethod.GET)
.addMapping(PARAM_LOGIN, "id")
.addMapping(PARAM_EMAIL, EMAIL_PARAM)
.addMapping(PARAM_FNAME, FNAME_PARAM)
.addMapping(PARAM_LNAME, LNAME_PARAM), null);
...
}
oauthDao.update
oauthDao是该类注入的来自openmeetings-db包下的OAuth2Dao类的实例对象依赖
update方法代码如下:
//OAuth2Dao.java
@Override
public OAuthServer update(OAuthServer server, Long userId) {
if (server.getId() == null) {
server.setInserted(new Date());
em.persist(server);
} else {
server.setUpdated(new Date());
server = em.merge(server);
}
return server;
}
OAuthServer也是在openmeetings-db包下的。这段代码会先判断传入的OAuthServer对象是否存在id,若没有,说明是一个新的服务器对象,就设置插入日期为当前日期。
em是EntityManager的对象。Entitymanager追踪persistence context中所有对象的修改和更新情况,并根据指定的flush模式将这些修改保存到数据库中。Persistence context是由一组受托管的实体对象实例所构成的集合。OAuthServer被标注为Entity,Entity注解表示该类是数据表对应到实体类的映射。
对于DAO层应用来说,主要的工作就是将EntityManager管理的实体持久化到数据库中保存起来
em.persist(server)
即表示将一个OAuthServer实体保存到数据库中
如果传入的服务器对象id不为空,则更新该服务器的更新时间
em.merge(server)
方法表示插入或更新,merge 方法不会改变传入的实体的状态,merge 方法会返回一个 Managed 状态的实体,可以进行 remove,setter 等操作
upldate方法的最后返回一个OAuthServer对象
new OAuthServer
新建一个OAuthServer对象,并且设置它的一系列属性,比如名字、图标地址、token地址等等。addMapping方法用于添加一组映射关系,保存在哈希表里面。前一个参数来自openmeetings-db包的OAuthUser类,表示map中的key;后一个参数直接传入或者在本类开头定义,表示map中的value。
总结
本篇文章以loadAll方法为入口,详细分析了checkInstalled方法和loadSystem方法,其中涉及到JPQL查询、日志、持久化实体管理(Entitymanager)等等新知识点,顺带分析了很多openmeetings-db包下与本类有着密切关联的部分代码。内容较多,然而并没有把loadAll方法内的代码全部分析完。在下篇文章,我打算先学习并介绍一下Asterisk网络电话相关的知识,因为在本文的loadSystem方法内涉及到这个知识点,没有Asterisk网络电话相关的知识就无法很好地对其进行分析解读。本篇文章如有分析不当之处,欢迎指正!