blob: 91e0f29a49618b3ecc7fe96322122624ccc12435 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<title>Apache BookKeeper&trade; - BP-37: Improve configuration management for better documentation</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/normalize.css">
<link rel="stylesheet" href="/css/tippy.css">
<link rel="stylesheet" href="/css/style.css">
<link rel="shortcut icon" href="/img/favicon.ico">
<script src="/js/tippy.min.js"></script>
<script type="text/javascript">
var shiftWindow = function() { scrollBy(0, -25); };
window.addEventListener("hashchange", shiftWindow);
window.addEventListener("pageshow", shiftWindow);
function load() { if (window.location.hash) shiftWindow(); }
</script>
</head>
<body class="body">
<main class="main">
<nav class="navbar bk-topnav">
<div class="navbar-brand">
<a class="navbar-item bk-brand" href="/">
Apache BookKeeper&trade;
</a>
<div class="navbar-burger burger" data-target="bkNav">
<span></span>
<span></span>
<span></span>
</div>
</div>
<div id="bkNav" class="navbar-menu">
<div class="navbar-start">
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Documentation</a>
<div class="navbar-dropdown is-boxed">
<a class="navbar-item" href="/docs/latest/overview/overview">
Version 4.15.0-SNAPSHOT
<span class="tag is-warning">Development</span>
</a>
<a class="navbar-item" href="/docs/latest/api/javadoc">
<span class="icon bk-javadoc-icon">
<img src="/img/java-icon.svg">
</span>
Javadoc
</a>
<hr class="dropdown-divider">
<a class="navbar-item" href="/docs/4.14.0/overview/overview">
Release 4.14.0
</a>
<a class="navbar-item" href="/docs/4.13.0/overview/overview">
Release 4.13.0
</a>
<a class="navbar-item" href="/docs/4.12.1/overview/overview">
Release 4.12.1
</a>
<a class="navbar-item" href="/docs/4.12.0/overview/overview">
Release 4.12.0
</a>
<a class="navbar-item" href="/docs/4.11.1/overview/overview">
Release 4.11.1
<span class="tag is-success">Stable</span>
</a>
<a class="navbar-item" href="/docs/4.11.0/overview/overview">
Release 4.11.0
</a>
<a class="navbar-item" href="/docs/4.10.0/overview/overview">
Release 4.10.0
</a>
<a class="navbar-item" href="/archives/docs/r4.9.2">
Release 4.9.2
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.9.1">
Release 4.9.1
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.9.0">
Release 4.9.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.8.2">
Release 4.8.2
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.8.1">
Release 4.8.1
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.8.0">
Release 4.8.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.7.3">
Release 4.7.3
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.7.2">
Release 4.7.2
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.7.1">
Release 4.7.1
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.7.0">
Release 4.7.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.6.2">
Release 4.6.2
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.6.1">
Release 4.6.1
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.6.0">
Release 4.6.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.5.1">
Release 4.5.1
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.5.0">
Release 4.5.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.4.0">
Release 4.4.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.3.2">
Release 4.3.2
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.3.1">
Release 4.3.1
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.3.0">
Release 4.3.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.2.4">
Release 4.2.4
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.2.3">
Release 4.2.3
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.2.2">
Release 4.2.2
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.2.1">
Release 4.2.1
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.2.0">
Release 4.2.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.1.0">
Release 4.1.0
<span class="tag is-warning">EOL</span>
</a>
<a class="navbar-item" href="/archives/docs/r4.0.0">
Release 4.0.0
<span class="tag is-warning">EOL</span>
</a>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Community</a>
<div class="navbar-dropdown is-boxed">
<a class="navbar-item" href="/community/mailing-lists">Mailing lists</a>
<a class="navbar-item" href="/community/slack">Slack</a>
<a class="navbar-item" href="https://github.com/apache/bookkeeper/issues">Github Issues</a>
<a class="navbar-item" href="/community/releases">Release Management</a>
<a class="navbar-item" href="/community/meeting">Community Meetings</a>
<hr class="dropdown-divider">
<a class="navbar-item" href="/community/contributing">Contribution Guide</a>
<a class="navbar-item" href="/community/coding_guide">Coding Guide</a>
<a class="navbar-item" href="/community/testing">Testing Guide</a>
<a class="navbar-item" href="/community/issue-report">Issue Report Guide</a>
<a class="navbar-item" href="/community/release_guide">Release Guide</a>
<hr class="dropdown-divider">
<a class="navbar-item" href="/community/presentations">Presentations</a>
<a class="navbar-item" href="/community/bookkeeper_proposals">BookKeeper Proposals</a>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Project</a>
<div class="navbar-dropdown is-boxed">
<a class="navbar-item" href="/project/who">Who are we?</a>
<a class="navbar-item" href="/project/bylaws">Bylaws</a>
<a class="navbar-item" href="http://www.apache.org/licenses/">License</a>
<hr class="dropdown-divider">
<a class="navbar-item" href="/project/privacy">Privacy policy</a>
<a class="navbar-item" href="http://www.apache.org/foundation/sponsorship.html">Sponsorship</a>
<a class="navbar-item" href="http://www.apache.org/foundation/thanks.html">Thanks</a>
</div>
</div>
</div>
<div class="navbar-end">
<div class="navbar-item">
<div class="field is-grouped">
<p class="control">
<a class="button bk-twitter" href="https://twitter.com/asfbookkeeper">
<span class="icon">
<i class="fa fa-twitter"></i>
</span>
<span>Twitter</span>
</a>
</p>
<p class="control">
<a class="button" href="https://github.com/apache/bookkeeper">
<span class="icon">
<i class="fa fa-github"></i>
</span>
<span>GitHub</span>
</a>
</p>
<p class="control">
<a class="button is-primary" href="/releases">
<span class="icon">
<i class="fa fa-download"></i>
</span>
<span>Download</span>
</a>
</p>
</div>
</div>
</div>
</div>
</nav>
<div class="bk-community-container">
<div class="columns">
<div class="column is-12">
<header class="docs-title">
<nav class="level">
<div class="level-left">
<div class="level-item">
<h1 class="title">BP-37: Improve configuration management for better documentation</h1>
</div>
</div>
</nav>
</header>
<hr />
<div class="content is-medium">
<section class="bk-community-content">
<h3 id="motivation">Motivation</h3>
<p>One common task in developing bookkeeper is to make sure all the configuration
settings are well documented, and the configuration file we ship in each release
is in-sync with the code itself.</p>
<p>However maintaining things in-sync is non-trivial. This proposal is exploring
a new way to manage configuration settings for better documentation.</p>
<h3 id="public-interfaces">Public Interfaces</h3>
<ol>
<li>Introduced <code class="highlighter-rouge">ConfigKey</code> for defining a configuration key. A configuration key
will include informations, such as required/optional, deprecated, documentation
and etc.</li>
</ol>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigKey</span> <span class="o">{</span>
<span class="cm">/**
* Flag indicates whether the setting is required.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">required</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="cm">/**
* Name of the configuration setting.
*/</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="cm">/**
* Type of the configuration setting.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">Type</span> <span class="n">type</span> <span class="o">=</span> <span class="nc">Type</span><span class="o">.</span><span class="na">STRING</span><span class="o">;</span>
<span class="cm">/**
* Description of the configuration setting.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">description</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="cm">/**
* Documentation of the configuration setting.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">documentation</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="cm">/**
* Default value as a string representation.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">Object</span> <span class="n">defaultValue</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="cm">/**
* The list of options for this setting.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">optionValues</span> <span class="o">=</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">();</span>
<span class="cm">/**
* The validator used for validating configuration value.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">Validator</span> <span class="n">validator</span> <span class="o">=</span> <span class="nc">NullValidator</span><span class="o">.</span><span class="na">of</span><span class="o">();</span>
<span class="cm">/**
* The key-group to group settings together.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">ConfigKeyGroup</span> <span class="n">group</span> <span class="o">=</span> <span class="nc">ConfigKeyGroup</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">;</span>
<span class="cm">/**
* The order of the setting in the key-group.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">orderInGroup</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">MIN_VALUE</span><span class="o">;</span>
<span class="cm">/**
* The list of settings dependents on this setting.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">dependents</span> <span class="o">=</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">();</span>
<span class="cm">/**
* Whether this setting is deprecated or not.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">deprecated</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="cm">/**
* The config key that deprecates this key.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">deprecatedByConfigKey</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="cm">/**
* The version when this settings was deprecated.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">deprecatedSince</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="cm">/**
* The version when this setting was introduced.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">since</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<ol>
<li>Introduced <code class="highlighter-rouge">ConfigKeyGroup</code> for grouping configuration keys together.</li>
</ol>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigKeyGroup</span> <span class="o">{</span>
<span class="cm">/**
* Name of the key group.
*/</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="cm">/**
* Description of the key group.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">description</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="cm">/**
* The list of sub key-groups of this key group.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">children</span> <span class="o">=</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">();</span>
<span class="cm">/**
* The order of the key-group in a configuration.
*/</span>
<span class="nd">@Default</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">order</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">MIN_VALUE</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="proposed-changes">Proposed Changes</h3>
<p>Besides introducing <code class="highlighter-rouge">ConfigKey</code> and <code class="highlighter-rouge">ConfigKeyGroup</code>, this BP will also introduce a class
<code class="highlighter-rouge">ConfigDef</code> - it defines the keys for a configuration.</p>
<p>The <code class="highlighter-rouge">ConfigDef</code> will be generated via <code class="highlighter-rouge">ConfigDef.of(Configuration.class)</code>. It will retrieve
all the static fields of <code class="highlighter-rouge">ConfigKey</code> defined in the configuration class and build the configuration
definition.</p>
<p>The <code class="highlighter-rouge">ConfigDef</code> will also provide a <code class="highlighter-rouge">save</code> method for saving the configuration definition
as a configuration file.</p>
<h3 id="example">Example</h3>
<p>Following is an example how to use <code class="highlighter-rouge">ConfigKey</code> and <code class="highlighter-rouge">ConfigKeyGroup</code> to organize
configuration settings.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Ledger Storage Settings</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ConfigKeyGroup</span> <span class="no">GROUP_LEDGER_STORAGE</span> <span class="o">=</span> <span class="nc">ConfigKeyGroup</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="s">"ledgerstorage"</span><span class="o">)</span>
<span class="o">.</span><span class="na">description</span><span class="o">(</span><span class="s">"Ledger Storage related settings"</span><span class="o">)</span>
<span class="o">.</span><span class="na">order</span><span class="o">(</span><span class="mi">10</span><span class="o">)</span> <span class="c1">// place a place holder here</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="kd">protected</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">LEDGER_STORAGE_CLASS</span> <span class="o">=</span> <span class="s">"ledgerStorageClass"</span><span class="o">;</span>
<span class="kd">protected</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ConfigKey</span> <span class="no">LEDGER_STORAGE_CLASS_KEY</span> <span class="o">=</span> <span class="nc">ConfigKey</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="no">LEDGER_STORAGE_CLASS</span><span class="o">)</span>
<span class="o">.</span><span class="na">type</span><span class="o">(</span><span class="nc">Type</span><span class="o">.</span><span class="na">CLASS</span><span class="o">)</span>
<span class="o">.</span><span class="na">description</span><span class="o">(</span><span class="s">"Ledger storage implementation class"</span><span class="o">)</span>
<span class="o">.</span><span class="na">defaultValue</span><span class="o">(</span><span class="nc">SortedLedgerStorage</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">())</span>
<span class="o">.</span><span class="na">optionValues</span><span class="o">(</span><span class="nc">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">(</span>
<span class="nc">InterleavedLedgerStorage</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span>
<span class="nc">SortedLedgerStorage</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span>
<span class="nc">DbLedgerStorage</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">()</span>
<span class="o">))</span>
<span class="o">.</span><span class="na">validator</span><span class="o">(</span><span class="nc">ClassValidator</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">LedgerStorage</span><span class="o">.</span><span class="na">class</span><span class="o">))</span>
<span class="o">.</span><span class="na">group</span><span class="o">(</span><span class="no">GROUP_LEDGER_STORAGE</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
</code></pre></div></div>
<p>Example on how to generate the <code class="highlighter-rouge">ConfigDef</code> and use the configuration definition to
validate if a configuration instance is valid.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// generate config def</span>
<span class="nc">ConfigDef</span> <span class="n">configDef</span> <span class="o">=</span> <span class="nc">ConfigDef</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">ServerConfiguration</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">configDef</span><span class="o">.</span><span class="na">validate</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ConfigException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">ConfigurationException</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">e</span><span class="o">.</span><span class="na">getCause</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Example on how to save the configuration definition to a configuration file.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ConfigDef</span> <span class="n">configDef</span> <span class="o">=</span> <span class="nc">ConfigDef</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">TestConfig2</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">savedConf</span><span class="o">;</span>
<span class="k">try</span> <span class="o">(</span><span class="nc">ByteArrayOutputStream</span> <span class="n">baos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">())</span> <span class="o">{</span>
<span class="n">configDef</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">baos</span><span class="o">);</span>
<span class="n">savedConf</span> <span class="o">=</span> <span class="n">baos</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="compatibility-deprecation-and-migration-plan">Compatibility, Deprecation, and Migration Plan</h3>
<p>It only changes the way how we organize configuration settings and how we document them.
It doesn’t change the public interfaces for existing configuration. So there is nothing
to deprecate and migrate.</p>
<h3 id="test-plan">Test Plan</h3>
<p>Existing testing is good enough to cover code changes. No new tests are needed.</p>
<h3 id="rejected-alternatives">Rejected Alternatives</h3>
<p>Alternatively, we have to manually maintain the configuration files and update each time
when a new configuration setting is added.</p>
</section>
</div>
</div>
</div>
</div>
</main>
<footer class="footer">
<div class="container">
<div class="content has-text-centered">
<p>
Copyright &copy; 2016 - 2021 <a href="https://www.apache.org/">The Apache Software Foundation</a>,<br /> licensed under the <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License, version 2.0</a>.
</p>
<p>
Apache BookKeeper, BookKeeper®, Apache®, the Apache feature logo, and the Apache BookKeeper logo are either registered trademarks or trademarks of The Apache Software Foundation.
</p>
</div>
</div>
</footer>
</body>
<script src="/js/app.js"></script>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-104419626-1', 'auto');
ga('send', 'pageview');
</script>
</html>