2021SC@SDUSC
在上篇文章中,我结合Apache Commons CLI解析命令行的知识点对Admin类进行了相关方面的分析。接下来,我将具体分析该类询问阶段涉及到的一些方法,这些方法即为应用程序通过查询CommandLine的选项值确定采用的执行分支。
先回顾一下询问阶段的代码
// Admin.java
void process(String... args) throws Exception {
...
String file = cmdl.getOptionValue("file", "");
switch(cmd) {
case install:
step = "Install";
processInstall(file);
break;
case backup:
step = "Backup";
processBackup(file);
break;
case restore:
step = "Restore";
processRestore(checkRestoreFile(file));
break;
case files:
step = "Files";
processFiles();
break;
...
}
}
通过判断CommandLine的实例cmdl中包含的选项,赋给Command的实例cmd不同的值,若cmd值为“install”时,执行processInstall方法,其他以此类推。下面我们依次分析执行的这几个方法。
// Admin.java
private void processInstall(String file) throws Exception {
// 如果命令行包含参数file以及user/email/group中的一个,则需要用户指定文件或管理员
if (cmdl.hasOption("file") && (cmdl.hasOption("user") || cmdl.hasOption("email") || cmdl.hasOption("group"))) {
log("Please specify even 'file' option or 'admin user'.");
throw new ExitException();
}
boolean force = cmdl.hasOption("force");
// 以下是根据CommandLine中包含的选项,对cfg中的属性值进行设置,cfg是来自installation包下的InstallationConfig实例,之前已经分析过
if (cmdl.hasOption("skip-default-objects")) {
cfg.setCreateDefaultObjects(false);
}
if (cmdl.hasOption("disable-frontend-register")) {
cfg.setAllowFrontendRegister(false);
}
if (cmdl.hasOption(OPTION_MAIL_REFERRER)) {
cfg.setMailReferer(cmdl.getOptionValue(OPTION_MAIL_REFERRER));
}
if (cmdl.hasOption(OPTION_MAIL_SERVER)) {
cfg.setSmtpServer(cmdl.getOptionValue(OPTION_MAIL_SERVER));
}
if (cmdl.hasOption(OPTION_MAIL_PORT)) {
cfg.setSmtpPort(Integer.valueOf(cmdl.getOptionValue(OPTION_MAIL_PORT)));
}
if (cmdl.hasOption(OPTION_MAIL_USER)) {
cfg.setMailAuthName(cmdl.getOptionValue(OPTION_MAIL_USER));
}
if (cmdl.hasOption(OPTION_MAIL_PASS)) {
cfg.setMailAuthPass(cmdl.getOptionValue(OPTION_MAIL_PASS));
}
if (cmdl.hasOption("email-use-tls")) {
cfg.setMailUseTls(true);
}
if (cmdl.hasOption("default-language")) {
cfg.setDefaultLangId(Integer.parseInt(cmdl.getOptionValue("default-language")));
}
ConnectionProperties connectionProperties;
// 用于创建一个持久层的File实例,OmFileHelper来自util包,在这就不展开分析了
File conf = OmFileHelper.getPersistence();
if (!conf.exists() || cmdl.hasOption(OPTION_DB_TYPE) || cmdl.hasOption(OPTION_DB_HOST)
|| cmdl.hasOption(OPTION_DB_PORT) || cmdl.hasOption(OPTION_DB_NAME) || cmdl.hasOption(OPTION_DB_USER)
|| cmdl.hasOption(OPTION_DB_PASS))
{
String dbType = cmdl.getOptionValue(OPTION_DB_TYPE, DbType.derby.name());
// ConnectionPropertiesPatcher是同包下的一个类,在下文我将对其进行具体分析
connectionProperties = ConnectionPropertiesPatcher.patch(dbType
, cmdl.getOptionValue(OPTION_DB_HOST, "localhost")
, cmdl.getOptionValue(OPTION_DB_PORT, null)
, cmdl.getOptionValue(OPTION_DB_NAME, null)
, cmdl.getOptionValue(OPTION_DB_USER, null)
, cmdl.getOptionValue(OPTION_DB_PASS, null)
);
} else {
//get properties from existent persistence.xml
connectionProperties = ConnectionPropertiesPatcher.getConnectionProperties(conf);
}
...
}
我们来具体分析一下这段代码
// Admin.java
connectionProperties = ConnectionPropertiesPatcher.patch(dbType
, cmdl.getOptionValue(OPTION_DB_HOST, "localhost")
, cmdl.getOptionValue(OPTION_DB_PORT, null)
, cmdl.getOptionValue(OPTION_DB_NAME, null)
, cmdl.getOptionValue(OPTION_DB_USER, null)
, cmdl.getOptionValue(OPTION_DB_PASS, null)
);
看一下patch方法的源码
// ConnectionPropertiesPatcher.java
public static ConnectionProperties patch(String dbType, String host, String port, String db, String user, String pass) throws Exception {
ConnectionProperties props = getConnectionProperties(OmFileHelper.getPersistence(dbType));
props.setLogin(user);
props.setPassword(pass);
ConnectionPropertiesPatcher patcher = getPatcher(props);
props.setURL(patcher.getUrl(props.getURL(), host, port, db));
patch(props);
return props;
}
该方法传入6个参数,第一个是dbType,后面几个是数据库的主机名、端口号、数据库名、用户名、密码,这些都通过CommandLine里的Option选项值获得
// ConnectionPropertiesPatcher.java
public static ConnectionProperties getConnectionProperties(File conf) throws Exception {
// 新建一个ConnectionProperties对象
ConnectionProperties props = new ConnectionProperties();
// 通过传入的File实例获取Document实例
Document doc = getDocument(conf);
Attr attr = getConnectionProperties(doc);
String[] tokens = attr.getValue().split(",");
loadProperties(tokens, props);
return props;
}
这里的Document是一个接口,表示整个HTML或XML文档。Attr类表示Element对象的属性,Element对象是是HTML DOM 节点。HTML DOM 是 W3C 标准,定义了用于 HTML 的一系列标准的对象,以及访问和处理 HTML 文档的标准方法。HTML DOM 独立于平台和编程语言,它可被任何编程语言诸如 Java、JavaScript 和 VBScript 使用。
Document、Attr、Element都是来自org.w3c.dom(java dom)包,org.w3c.dom为文档对象模型 (DOM) 提供接口,该模型是 Java API for XML Processing 的组件 API,用于Java对XML文件进行操作,可以将XML看做是一颗树,DOM就是对这颗树的一个数据结构的描述。
Attr attr = getConnectionProperties(doc);中的getConnectionProperties方法体如下
private static Attr getConnectionProperties(Document doc) throws Exception {
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expr = xPath.compile("/persistence/persistence-unit/properties/property[@name='openjpa.ConnectionProperties']");
Element element = (Element)expr.evaluate(doc, XPathConstants.NODE);
return element.getAttributeNode("value");
}
XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。
XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上。因此,对 XPath 的理解是很多高级 XML 应用的基础。
newInstance方法使用默认的对象模型DEFAULT_OBJECT_MODEL_URI去创建一个XPathFactory实例,newXPath()使用在实例化XPathFactory时确定的底层对象模型返回一个新的XPath。
XPathExpression接口提供对编译后的XPath表达式的访问。
XPath的compile方法会编译一个XPath表达式以供以后计算。
-
如果表达式包含XPathFunction,那它们必须可以通过XPathFunctionResolver解析表明其是可用的,否则会抛出XPathExpressionException 异常;
-
如果表达式包含一些变量,那将使用在编译时有效的XPathVariableResolver来解析它们;
-
如果表达式为null,则会抛NullPointerException异常。
关于XPath的路径表达式,可以参考这篇文章:https://www.w3school.com.cn/xpath/xpath_syntax.asp
在我们的代码中,"/persistence/persistence-unit/properties/property[@name='openjpa.ConnectionProperties']"
的含义是选取persistence的子元素persistence-unit的子元素properties的子元素中name的值为openjpa.ConnectionProperties的property元素。
evaluate()方法用于对已编译好的XPath表达式求值,依据特定的上下文,并且返回特定的类型。该方法为InputSource构建一个数据模型,并对结果文档对象调用evaluate(Object item, QName returnType) 。我们的代码中返回得到的是一个element节点。
getAttributeNode() 方法从当前元素中通过名称获取属性节点。在这里会返回以"value"为名称的节点。
后面的内容留到下一篇分析吧…