`
wangdf_jee
  • 浏览: 116561 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

SqlMapConfigParser

阅读更多
package com.ibatis.sqlmap.engine.builder.xml;

import com.ibatis.common.resources.*;
import com.ibatis.common.xml.*;
import com.ibatis.sqlmap.client.*;
import com.ibatis.sqlmap.engine.config.*;
import com.ibatis.sqlmap.engine.transaction.*;
import com.ibatis.sqlmap.engine.datasource.*;
import com.ibatis.sqlmap.engine.mapping.result.*;
import org.w3c.dom.Node;

import java.io.*;
import java.util.Properties;

//璇ョ被涓昏鏄鐞嗚В鏋怱QL Map閰嶇疆鏂囦欢
public class SqlMapConfigParser {

// parser鍙橀噺鏄В鏋怷ML鐨勬牳蹇�
protected final NodeletParser parser = new NodeletParser();

// state鍙橀噺鍏呭綋涓�釜涓棿鍙橀噺鐨勫鍣紝鎴栬�鏄竴涓厤缃俊鎭殑闂ㄦ埛
private XmlParserState state = new XmlParserState();

private boolean usingStreams = false;

public SqlMapConfigParser() {

// 楠岃瘉dtd鏂囦欢锛岃缃獙璇佸弬鏁�
parser.setValidation(true);
parser.setEntityResolver(new SqlMapClasspathEntityResolver());

//杩涜鍒濆鍖栫殑璧嬪�澶勭悊
addSqlMapConfigNodelets();
addGlobalPropNodelets();
addSettingsNodelets();
addTypeAliasNodelets();
addTypeHandlerNodelets();
addTransactionManagerNodelets();
addSqlMapNodelets();
addResultObjectFactoryNodelets();

}

public SqlMapClient parse(Reader reader, Properties props) {
if (props != null)
state.setGlobalProps(props);
return parse(reader);
}

//鍩轰簬瀛楃妯″紡瑙f瀽XML閰嶇疆鏂囦欢
public SqlMapClient parse(Reader reader) {
try {
usingStreams = false;

parser.parse(reader);
return state.getConfig().getClient();
} catch (Exception e) {
throw new RuntimeException("Error occurred.  Cause: " + e, e);
}
}

public SqlMapClient parse(InputStream inputStream, Properties props) {
if (props != null)
state.setGlobalProps(props);
return parse(inputStream);
}

    // 鍩轰簬瀛楄妭妯″紡瑙f瀽XML閰嶇疆鏂囦欢
public SqlMapClient parse(InputStream inputStream) {
try {
usingStreams = true;

parser.parse(inputStream);
return state.getConfig().getClient();
} catch (Exception e) {
throw new RuntimeException("Error occurred.  Cause: " + e, e);
}
}

// 澶勭悊sqlMapConfig鏍硅妭鐐逛俊鎭紝琛ㄦ槑鍏跺凡缁忕粨鏉�
private void addSqlMapConfigNodelets() {
parser.addNodelet("/sqlMapConfig/end()", new Nodelet() {
public void process(Node node) throws Exception {
state.getConfig().finalizeSqlMapConfig();
}
});
}

// 澶勭悊鑺傜偣/sqlMapConfig/properties淇℃伅锛屼骇鐢熶竴涓叏灞�殑Properties
private void addGlobalPropNodelets() {
parser.addNodelet("/sqlMapConfig/properties", new Nodelet() {
public void process(Node node) throws Exception {
// 鑾峰緱/sqlMapConfig/properties鑺傜偣涓嬬殑鍏ㄩ儴灞炴�鍊硷紝骞舵妸杩欎簺灞炴�鍊艰祴鍊肩粰Properties绫诲瀷鐨勫彉閲廰ttributes
Properties attributes = NodeletUtils.parseAttributes(node,
state.getGlobalProps());
String resource = attributes.getProperty("resource");
String url = attributes.getProperty("url");

// 鐢眘tate鍙橀噺浣滀负涓棿浣撲紶閫掍俊鎭�
state.setGlobalProperties(resource, url);
}
});
}


//澶勭悊鑺傜偣/sqlMapConfig/settings淇℃伅锛屼骇鐢熷叏灞�彉閲�
private void addSettingsNodelets() {
parser.addNodelet("/sqlMapConfig/settings", new Nodelet() {
public void process(Node node) throws Exception {
Properties attributes = NodeletUtils.parseAttributes(node,
state.getGlobalProps());
SqlMapConfiguration config = state.getConfig();

String classInfoCacheEnabledAttr = attributes
.getProperty("classInfoCacheEnabled");
boolean classInfoCacheEnabled = (classInfoCacheEnabledAttr == null || "true"
.equals(classInfoCacheEnabledAttr));
config.setClassInfoCacheEnabled(classInfoCacheEnabled);

String lazyLoadingEnabledAttr = attributes
.getProperty("lazyLoadingEnabled");
boolean lazyLoadingEnabled = (lazyLoadingEnabledAttr == null || "true"
.equals(lazyLoadingEnabledAttr));
config.setLazyLoadingEnabled(lazyLoadingEnabled);

String statementCachingEnabledAttr = attributes
.getProperty("statementCachingEnabled");
boolean statementCachingEnabled = (statementCachingEnabledAttr == null || "true"
.equals(statementCachingEnabledAttr));
config.setStatementCachingEnabled(statementCachingEnabled);

String cacheModelsEnabledAttr = attributes
.getProperty("cacheModelsEnabled");
boolean cacheModelsEnabled = (cacheModelsEnabledAttr == null || "true"
.equals(cacheModelsEnabledAttr));
config.setCacheModelsEnabled(cacheModelsEnabled);

String enhancementEnabledAttr = attributes
.getProperty("enhancementEnabled");
boolean enhancementEnabled = (enhancementEnabledAttr == null || "true"
.equals(enhancementEnabledAttr));
config.setEnhancementEnabled(enhancementEnabled);

String useColumnLabelAttr = attributes
.getProperty("useColumnLabel");
boolean useColumnLabel = (useColumnLabelAttr == null || "true"
.equals(useColumnLabelAttr));
config.setUseColumnLabel(useColumnLabel);

String forceMultipleResultSetSupportAttr = attributes
.getProperty("forceMultipleResultSetSupport");
boolean forceMultipleResultSetSupport = "true"
.equals(forceMultipleResultSetSupportAttr);
config
.setForceMultipleResultSetSupport(forceMultipleResultSetSupport);

String defaultTimeoutAttr = attributes
.getProperty("defaultStatementTimeout");
Integer defaultTimeout = defaultTimeoutAttr == null ? null
: Integer.valueOf(defaultTimeoutAttr);
config.setDefaultStatementTimeout(defaultTimeout);

String useStatementNamespacesAttr = attributes
.getProperty("useStatementNamespaces");
boolean useStatementNamespaces = "true"
.equals(useStatementNamespacesAttr);
state.setUseStatementNamespaces(useStatementNamespaces);
}
});
}


    // 澶勭悊鑺傜偣/sqlMapConfig/typeAlias淇℃伅锛屽鐞嗗埆鍚嶏紙typeAlias锛夎浆鍖�
private void addTypeAliasNodelets() {
parser.addNodelet("/sqlMapConfig/typeAlias", new Nodelet() {
public void process(Node node) throws Exception {
Properties prop = NodeletUtils.parseAttributes(node, state
.getGlobalProps());
String alias = prop.getProperty("alias");
String type = prop.getProperty("type");
//鑾峰彇鍒悕鍙婂叾瀵瑰簲鐨凜lass绫诲瀷锛屾敞鍐屽埌TypeHandlerFactory宸ュ巶瀵硅薄涓�
state.getConfig().getTypeHandlerFactory().putTypeAlias(alias,
type);
}
});
}

    // 澶勭悊鑺傜偣/sqlMapConfig/typeHandler淇℃伅锛屽鍔犳柊鐨刯dbcType
private void addTypeHandlerNodelets() {
parser.addNodelet("/sqlMapConfig/typeHandler", new Nodelet() {
public void process(Node node) throws Exception {
Properties prop = NodeletUtils.parseAttributes(node, state
.getGlobalProps());
String jdbcType = prop.getProperty("jdbcType");
String javaType = prop.getProperty("javaType");
String callback = prop.getProperty("callback");

javaType = state.getConfig().getTypeHandlerFactory()
.resolveAlias(javaType);
callback = state.getConfig().getTypeHandlerFactory()
.resolveAlias(callback);

state.getConfig().newTypeHandler(
Resources.classForName(javaType), jdbcType,
Resources.instantiate(callback));
}
});
}

    // 澶勭悊鑺傜偣/sqlMapConfig/transactionManager淇℃伅锛屽寘鎷瑃ransactionManager鍜宒ataSource
private void addTransactionManagerNodelets() {
        // 澶勭悊鑺傜偣/sqlMapConfig/transactionManager/property鐨勪俊鎭紝杞寲鍒皌ransactionManager瀵硅薄鐨勭浉鍏冲睘鎬т腑
parser.addNodelet("/sqlMapConfig/transactionManager/property",
new Nodelet() {
public void process(Node node) throws Exception {
Properties attributes = NodeletUtils.parseAttributes(
node, state.getGlobalProps());
String name = attributes.getProperty("name");
String value = NodeletUtils.parsePropertyTokens(
attributes.getProperty("value"), state
.getGlobalProps());
state.getTxProps().setProperty(name, value);
}
});

        // 澶勭悊鑺傜偣/sqlMapConfig/transactionManager/锛岀敓鎴恡ransactionManager瀵硅薄骞惰繘琛岀粨鏉熷鐞�
parser.addNodelet("/sqlMapConfig/transactionManager/end()",
new Nodelet() {
public void process(Node node) throws Exception {
Properties attributes = NodeletUtils.parseAttributes(
node, state.getGlobalProps());
String type = attributes.getProperty("type");
boolean commitRequired = "true".equals(attributes
.getProperty("commitRequired"));

state.getConfig().getErrorContext().setActivity(
"configuring the transaction manager");
type = state.getConfig().getTypeHandlerFactory()
.resolveAlias(type);
TransactionManager txManager;
try {
state.getConfig().getErrorContext().setMoreInfo(
"Check the transaction manager type or class.");
TransactionConfig config = (TransactionConfig) Resources
.instantiate(type);
config.setDataSource(state.getDataSource());
state
.getConfig()
.getErrorContext()
.setMoreInfo(
"Check the transactio nmanager properties or configuration.");
config.setProperties(state.getTxProps());
config.setForceCommit(commitRequired);
config.setDataSource(state.getDataSource());
state.getConfig().getErrorContext().setMoreInfo(
null);
txManager = new TransactionManager(config);
} catch (Exception e) {
if (e instanceof SqlMapException) {
throw (SqlMapException) e;
} else {
throw new SqlMapException(
"Error initializing TransactionManager.  Could not instantiate TransactionConfig.  Cause: "
+ e, e);
}
}
state.getConfig().setTransactionManager(txManager);
}
});


         // 澶勭悊鑺傜偣/sqlMapConfig/transactionManager/dataSource/property锛屽鐞哾ataSource鑺傜偣鐨刾roperty鍐呭
parser.addNodelet(
"/sqlMapConfig/transactionManager/dataSource/property",
new Nodelet() {
public void process(Node node) throws Exception {
Properties attributes = NodeletUtils.parseAttributes(
node, state.getGlobalProps());
String name = attributes.getProperty("name");
String value = NodeletUtils.parsePropertyTokens(
attributes.getProperty("value"), state
.getGlobalProps());
state.getDsProps().setProperty(name, value);
}
});

        // 澶勭悊鑺傜偣/sqlMapConfig/transactionManager/dataSource/end()锛屽姩鎬佸疄渚嬪寲dataSource瀵硅薄锛岀劧鍚庤繘琛屽睘鎬у垵濮嬪寲
parser.addNodelet("/sqlMapConfig/transactionManager/dataSource/end()",
new Nodelet() {
public void process(Node node) throws Exception {
state.getConfig().getErrorContext().setActivity(
"configuring the data source");

Properties attributes = NodeletUtils.parseAttributes(
node, state.getGlobalProps());

String type = attributes.getProperty("type");
Properties props = state.getDsProps();

type = state.getConfig().getTypeHandlerFactory()
.resolveAlias(type);
try {
state.getConfig().getErrorContext().setMoreInfo(
"Check the data source type or class.");
DataSourceFactory dsFactory = (DataSourceFactory) Resources
.instantiate(type);
state.getConfig().getErrorContext().setMoreInfo(
"Check the data source properties or configuration.");
dsFactory.initialize(props);
state.setDataSource(dsFactory.getDataSource());
state.getConfig().getErrorContext().setMoreInfo(
null);
} catch (Exception e) {
if (e instanceof SqlMapException) {
throw (SqlMapException) e;
} else {
throw new SqlMapException(
"Error initializing DataSource.  Could not instantiate DataSourceFactory.  Cause: "
+ e, e);
}
}
}
});
}


    //澶勭悊鑺傜偣/sqlMapConfig/sqlMap锛屽幓鑾峰彇sqlMap鐨勬槧灏勬枃浠讹紝鐒跺悗瀵瑰悇涓槧灏勬枃浠惰繘琛岃鍙栧鐞�
protected void addSqlMapNodelets() {
parser.addNodelet("/sqlMapConfig/sqlMap", new Nodelet() {
public void process(Node node) throws Exception {
state.getConfig().getErrorContext().setActivity(
"loading the SQL Map resource");

Properties attributes = NodeletUtils.parseAttributes(node,
state.getGlobalProps());

String resource = attributes.getProperty("resource");
String url = attributes.getProperty("url");

if (usingStreams) {
//閲囩敤瀛楄妭娴佹ā寮忔潵璇诲彇鏂囦欢
InputStream inputStream = null;
if (resource != null) {
state.getConfig().getErrorContext().setResource(
resource);
inputStream = Resources.getResourceAsStream(resource);
} else if (url != null) {
state.getConfig().getErrorContext().setResource(url);
inputStream = Resources.getUrlAsStream(url);
} else {
throw new SqlMapException(
"The <sqlMap> element requires either a resource or a url attribute.");
}

new SqlMapParser(state).parse(inputStream);
} else {
                     // 閲囩敤瀛楃娴佹ā寮忔潵璇诲彇鏂囦欢
Reader reader = null;
if (resource != null) {
state.getConfig().getErrorContext().setResource(
resource);
reader = Resources.getResourceAsReader(resource);
} else if (url != null) {
state.getConfig().getErrorContext().setResource(url);
reader = Resources.getUrlAsReader(url);
} else {
throw new SqlMapException(
"The <sqlMap> element requires either a resource or a url attribute.");
}

new SqlMapParser(state).parse(reader);
}
}
});
}

    // 澶勭悊鑺傜偣/sqlMapConfig/resultObjectFactory锛屽垵濮嬪寲鏂扮殑resultObjectFactory
private void addResultObjectFactoryNodelets() {
parser.addNodelet("/sqlMapConfig/resultObjectFactory", new Nodelet() {
public void process(Node node) throws Exception {
Properties attributes = NodeletUtils.parseAttributes(node,
state.getGlobalProps());
String type = attributes.getProperty("type");

state.getConfig().getErrorContext().setActivity(
"configuring the Result Object Factory");
ResultObjectFactory rof;
try {
rof = (ResultObjectFactory) Resources.instantiate(type);
state.getConfig().setResultObjectFactory(rof);
} catch (Exception e) {
throw new SqlMapException(
"Error instantiating resultObjectFactory: " + type,
e);
}

}
});
parser.addNodelet("/sqlMapConfig/resultObjectFactory/property",
new Nodelet() {
public void process(Node node) throws Exception {
Properties attributes = NodeletUtils.parseAttributes(
node, state.getGlobalProps());
String name = attributes.getProperty("name");
String value = NodeletUtils.parsePropertyTokens(
attributes.getProperty("value"), state
.getGlobalProps());
state.getConfig().getDelegate()
.getResultObjectFactory().setProperty(name,
value);
}
});
}

}
分享到:
评论

相关推荐

    ibatis-sqlmap-2.3.4.726-sources.jar.zip_birth84v_cutting1v2_ibat

    它通过SqlMapConfigParser解析SqlMapConfig.xml文件,创建SqlMap实例。SqlMap则包含了对数据库操作的具体配置,包括数据源、事务管理等。对于SQL语句的执行,SqlMapClient使用Executor接口,该接口定义了不同类型的...

    Spring 3.0.5+MyBatis3.0.4整合例子

    - **Spring与MyBatis的集成**:使用Spring的MyBatisNamespaceHandler和SqlMapConfigParser处理MyBatis的配置,使Spring能够管理MyBatis的生命周期。 4. **SpringMVCIbatisSmartClient**: - 这个文件名可能指的是...

    scratch少儿编程逻辑思维游戏源码-城堡战争.zip

    scratch少儿编程逻辑思维游戏源码-城堡战争.zip

    【Go语言编程】大厂Go工程师面试题集锦:涵盖并发、网络、数据库及算法设计要点

    内容概要:本文档汇集了来自字节跳动、腾讯、金山WPS、跟谁学和百度等大厂的Go工程师面试题,涵盖广泛的技术领域。主要包括Go语言特性(如goroutine调度、channel机制)、操作系统(进程间通信、线程调度)、计算机网络(TCP/IP协议栈、HTTP协议)、数据结构与算法(排序算法、LRU缓存)、数据库(MySQL索引优化、Redis内部机制)、分布式系统(负载均衡、服务发现)等方面的知识点。通过这些问题,不仅考察应聘者的理论基础,还测试其实际项目经验和技术深度。 适合人群:有一定Go语言编程经验和计算机基础知识的开发者,特别是准备应聘互联网大厂的中级及以上水平的后端工程师或全栈工程师。 使用场景及目标:①帮助求职者全面复习Go语言及其相关领域的核心概念;②为面试官提供有价值的参考题目,确保候选人具备解决复杂问题的能力;③指导工程师深入理解并掌握企业级应用开发所需的关键技能。 阅读建议:由于题目覆盖面广且难度较高,建议读者结合自身情况选择重点复习方向,同时配合实际编码练习加深理解。对于每个知识点,不仅要记住答案,更要理解背后的原理,这样才能在面试中灵活应对各种变体问题。

    scratch少儿编程逻辑思维游戏源码-堡垒之夜(吃鸡游戏).zip

    scratch少儿编程逻辑思维游戏源码-堡垒之夜(吃鸡游戏).zip

    少儿编程scratch项目源代码文件案例素材-派.zip

    少儿编程scratch项目源代码文件案例素材-派.zip

    scratch少儿编程逻辑思维游戏源码-Scratch 冒险.zip

    scratch少儿编程逻辑思维游戏源码-Scratch 冒险.zip

    2025 飞特舵机, Arduino版本

    2025 飞特舵机, Arduino版本

    scratch少儿编程逻辑思维游戏源码-躲避.zip

    scratch少儿编程逻辑思维游戏源码-躲避.zip

    PFC5.0纤维混凝土三点弯曲模拟:参数化建模与实验分析

    内容概要:本文详细介绍了利用PFC5.0进行纤维混凝土三点弯曲模拟的方法。首先,作者展示了如何通过定义纤维的体积含量、长度、半径和刚度等关键参数来构建纤维网络。接着,描述了三点弯曲加载的具体实现方式,包括加载速率控制和终止条件设定。最后,提供了后处理方法,如绘制并导出力-位移曲线图,以便于分析材料破坏机制。文中还给出了若干实用建议,如纤维半径的选择范围、加载速率的初始值以及不同类型纤维的接触模型选择。 适合人群:从事材料科学尤其是混凝土材料研究的专业人士,以及对离散元法和数值模拟感兴趣的科研工作者。 使用场景及目标:适用于希望深入了解纤维混凝土力学性能的研究人员,旨在帮助他们掌握PFC5.0软件的操作技巧,优化模拟参数设置,提高实验效率。 其他说明:文中提供的代码片段可以直接应用于实际项目中,同时附带了一些实践经验分享,有助于初学者快速入门并避免常见错误。

    少儿编程scratch项目源代码文件案例素材-生存V1(有BAG).zip

    少儿编程scratch项目源代码文件案例素材-生存V1(有BAG).zip

    少儿编程scratch项目源代码文件案例素材-披萨机器人.zip

    少儿编程scratch项目源代码文件案例素材-披萨机器人.zip

    少儿编程scratch项目源代码文件案例素材-气球滑雪板.zip

    少儿编程scratch项目源代码文件案例素材-气球滑雪板.zip

    少儿编程scratch项目源代码文件案例素材-使命召唤(苏联插旗).zip

    少儿编程scratch项目源代码文件案例素材-使命召唤(苏联插旗).zip

    可跨平台移植的模拟IIC实战项目STM32F407-TestIIC

    1. GPIO模拟I2C 实战项目,根据正点原子 STM32F407ZGT6 进行更改; 2. 可适配STM32、GD32、HC32等MCU;

    scratch少儿编程逻辑思维游戏源码-百米冲刺.zip

    scratch少儿编程逻辑思维游戏源码-百米冲刺.zip

    【蓝桥杯竞赛】历年试题精选与备考资源汇总:编程算法及硬件单片机试题解析与练习指导

    内容概要:本文档汇总了蓝桥杯历年试题及练习资源,涵盖编程类试题精选、硬件与单片机试题、练习资源与题库以及备考建议。编程类试题精选包括基础算法题(如数组求和、质因数分解)、经典算法案例(如最大子序列和、兰顿蚂蚁模拟)和数据结构应用(如字符全排列)。硬件与单片机试题主要涉及客观题考点,如BUCK电路和电源设计。练习资源与题库部分介绍了真题平台(如Dotcpp、CSDN专题)和专项训练包(如Python题库、Java百题集、C++真题解析)。备考建议分为分阶段练习(新手阶段、进阶提升)和模拟实战(如使用Dotcpp估分系统进行限时训练),强调按年份和组别分类练习,强化代码实现与调试能力。; 适合人群:准备参加蓝桥杯竞赛的学生及编程爱好者。; 使用场景及目标:①针对不同编程语言和难度级别的题目进行专项训练;②通过历年真题和模拟实战提高解题速度和准确性;③掌握算法设计、数据结构应用及硬件基础知识。; 阅读建议:此文档提供了丰富的试题和练习资源,建议根据自身水平选择合适的题目进行练习,并结合真题平台的估分系统和社区开源代码进行对比优化,逐步提升编程能力和竞赛水平。

    30kW储能PCS原理图设计:量产设计的关键要素与优化策略

    内容概要:本文详细介绍了30kW储能PCS(电力转换系统)原理图的设计要点及其量产化过程中需要注意的技术细节。首先阐述了储能PCS的基本概念和重要性,接着深入探讨了主拓扑结构的选择,特别是双级式结构的优势以及关键组件如IGBT的驱动时序配置。随后讨论了控制算法的智能化改进,包括加入前馈补偿以提高系统的稳定性。此外,还强调了EMC设计、PCB布局、元件选择等方面的注意事项,并分享了一些实际生产中遇到的问题及解决方案。最后提到了自动化测试方法和散热管理策略,确保产品在各种环境下的可靠运行。 适合人群:从事储能系统设计、电力电子产品研发的工程师和技术人员。 使用场景及目标:帮助读者掌握30kW储能PCS从原理图设计到量产实施的全流程关键技术,提升产品的性能和可靠性,避免常见错误。 其他说明:文中提供了具体的代码片段和实践经验,有助于理解和应用相关理论。

    少儿编程scratch项目源代码文件案例素材-喷气包多德.zip

    少儿编程scratch项目源代码文件案例素材-喷气包多德.zip

    机械工程中基于Python的齿轮啮合性能与动态响应分析

    内容概要:本文深入探讨了齿轮啮合性能及其动态特性,特别是直齿轮的基础参数计算、渐开线绘制以及接触力仿真的具体实现。首先介绍了齿轮的基本参数如模数、齿数、压力角等,并给出了具体的计算实例。接着详细讲解了如何利用Python进行渐开线的数学建模并绘图展示,强调了这种曲线对于确保齿轮平稳传动的重要性。然后讨论了齿轮在啮合过程中接触力的变化规律,提供了简化的Python代码来模拟这一现象。最后指出,在实际工程项目中应当借助专业的软件包如PyDy或ADAMS来进行更加精确的动力学分析,同时肯定了自行编写代码的价值在于能够更好地理解和排查问题。 适合人群:机械工程领域的研究人员、工程师以及相关专业的学生。 使用场景及目标:①帮助读者掌握齿轮基本理论知识;②指导读者运用Python编程技能完成简单的齿轮性能分析任务;③为后续深入研究提供思路和技术支持。 阅读建议:由于文中涉及较多的专业术语和数学公式,建议读者提前复习相关基础知识,并尝试运行提供的代码片段加深理解。此外,对于想要进一步探索该领域的读者来说,可以参考文末提到的专业工具包进行更复杂的研究。

Global site tag (gtag.js) - Google Analytics