You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@unomi.apache.org by sh...@apache.org on 2018/11/05 14:08:37 UTC

svn commit: r1845794 [3/19] - in /incubator/unomi/website/manual: 1_1_x/ 1_1_x/images/ 1_2_x/ 1_2_x/connectors/ 1_2_x/images/ 1_2_x/samples/ 1_3_x/ 1_3_x/asciidoc/ 1_3_x/connectors/ 1_3_x/images/ 1_3_x/samples/ latest/ latest/connectors/ latest/images/...

Added: incubator/unomi/website/manual/1_1_x/index.html
URL: http://svn.apache.org/viewvc/incubator/unomi/website/manual/1_1_x/index.html?rev=1845794&view=auto
==============================================================================
--- incubator/unomi/website/manual/1_1_x/index.html (added)
+++ incubator/unomi/website/manual/1_1_x/index.html Mon Nov  5 14:08:37 2018
@@ -0,0 +1,1725 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 1.5.6.1">
+<meta name="author" content="Apache Software Foundation">
+<title>Apache Unomi 1.1.x - Documentation</title>
+<link rel="stylesheet" href="./apache.css">
+</head>
+<body class="article toc2 toc-left">
+<div id="header">
+<h1>Apache Unomi 1.1.x - Documentation</h1>
+<div class="details">
+<span id="author" class="author">Apache Software Foundation</span><br>
+</div>
+<div id="toc" class="toc2">
+<div id="toctitle">Table of Contents</div>
+<ul class="sectlevel1">
+<li><a href="#_concepts">1. Concepts</a>
+<ul class="sectlevel2">
+<li><a href="#_concepts_2">1.1. Concepts</a>
+<ul class="sectlevel3">
+<li><a href="#_items_and_types">1.1.1. Items and types</a></li>
+<li><a href="#_events">1.1.2. Events</a></li>
+<li><a href="#_profiles">1.1.3. Profiles</a></li>
+<li><a href="#_sessions">1.1.4. Sessions</a></li>
+</ul>
+</li>
+<li><a href="#_extending_unomi_via_plugins">1.2. Extending Unomi via plugins</a>
+<ul class="sectlevel3">
+<li><a href="#_types_vs_instances">1.2.1. Types vs. instances</a></li>
+<li><a href="#_plugin_structure">1.2.2. Plugin structure</a></li>
+<li><a href="#_extension_points">1.2.3. Extension points</a></li>
+<li><a href="#_other_unomi_entities">1.2.4. Other Unomi entities</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_quick_start">2. Quick start</a>
+<ul class="sectlevel2">
+<li><a href="#_building">2.1. Building</a>
+<ul class="sectlevel3">
+<li><a href="#_deploying_the_generated_package">2.1.1. Deploying the generated package</a></li>
+<li><a href="#_deploying_into_an_existing_karaf_server">2.1.2. Deploying into an existing Karaf server</a></li>
+<li><a href="#_jdk_selection_on_mac_os_x">2.1.3. JDK Selection on Mac OS X</a></li>
+<li><a href="#_running_the_integration_tests">2.1.4. Running the integration tests</a></li>
+<li><a href="#_running_the_performance_tests">2.1.5. Running the performance tests</a></li>
+<li><a href="#_testing_with_an_example_page">2.1.6. Testing with an example page</a></li>
+<li><a href="#_integrating_onto_a_page">2.1.7. Integrating onto a page</a></li>
+</ul>
+</li>
+<li><a href="#_getting_started_with_unomi">2.2. Getting started with Unomi</a>
+<ul class="sectlevel3">
+<li><a href="#_prerequisites">2.2.1. Prerequisites</a></li>
+<li><a href="#_running_unomi">2.2.2. Running Unomi</a></li>
+<li><a href="#_interacting_with_the_context_server">2.2.3. Interacting with the context server</a></li>
+<li><a href="#_retrieving_context_information_from_unomi_using_the_context_servlet">2.2.4. Retrieving context information from Unomi using the context servlet</a></li>
+</ul>
+</li>
+<li><a href="#_example">2.3. Example</a>
+<ul class="sectlevel3">
+<li><a href="#_overview">2.3.1. Overview</a></li>
+<li><a href="#_html_page">2.3.2. HTML page</a></li>
+<li><a href="#_javascript">2.3.3. Javascript</a></li>
+</ul>
+</li>
+<li><a href="#_conclusion">2.4. Conclusion</a></li>
+<li><a href="#_annex">2.5. Annex</a></li>
+<li><a href="#_configuration">2.6. Configuration</a>
+<ul class="sectlevel3">
+<li><a href="#_changing_the_default_configuration">2.6.1. Changing the default configuration</a></li>
+<li><a href="#_installing_the_maxmind_geoiplite2_ip_lookup_database">2.6.2. Installing the MaxMind GeoIPLite2 IP lookup database</a></li>
+<li><a href="#_installing_geonames_database">2.6.3. Installing Geonames database</a></li>
+<li><a href="#_rest_api_security">2.6.4. REST API Security</a></li>
+<li><a href="#_automatic_profile_merging">2.6.5. Automatic profile merging</a></li>
+<li><a href="#_securing_a_production_environment">2.6.6. Securing a production environment</a></li>
+<li><a href="#_integrating_with_an_apache_http_web_server">2.6.7. Integrating with an Apache HTTP web server</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_cluster_setup">3. Cluster setup</a>
+<ul class="sectlevel2">
+<li><a href="#_cluster_setup_2">3.1. Cluster setup</a>
+<ul class="sectlevel3">
+<li><a href="#_recommended_configurations">3.1.1. Recommended configurations</a></li>
+<li><a href="#_specific_configuration">3.1.2. Specific configuration</a></li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+</div>
+</div>
+<div id="content">
+<div id="preamble">
+<div class="sectionbody">
+<div class="imageblock" style="text-align: center">
+<div class="content">
+<img src="images/incubator-logo.png" alt="incubator logo">
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_concepts">1. Concepts</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_concepts_2">1.1. Concepts</h3>
+<div class="paragraph">
+<p>Apache Unomi gathers information about users actions, information that is processed and stored by Unomi services. The collected information can then be used to personalize content, derive insights on user behavior, categorize the user profiles into segments along user-definable dimensions or acted upon by algorithms.</p>
+</div>
+<div class="sect3">
+<h4 id="_items_and_types">1.1.1. Items and types</h4>
+<div class="paragraph">
+<p>Unomi structures the information it collects using the concept of <code>Item</code> which provides the base information (an identifier and a type) the context server needs to process and store the data. Items are persisted according to their type (structure) and identifier (identity). This base structure can be extended, if needed, using properties in the form of key-value pairs.</p>
+</div>
+<div class="paragraph">
+<p>These properties are further defined by the <code>Item</code>’s type definition which explicits the <code>Item</code>’s structure and semantics. By defining new types, users specify which properties (including the type of values they accept) are available to items of that specific type.</p>
+</div>
+<div class="paragraph">
+<p>Unomi defines default value types: <code>date</code>, <code>email</code>, <code>integer</code> and <code>string</code>, all pretty self-explanatory. While you can think of these value types as "primitive" types, it is possible to extend Unomi by providing additional value types.</p>
+</div>
+<div class="paragraph">
+<p>Additionally, most items are also associated to a scope, which is a concept that Unomi uses to group together related items. A given scope is represented in Unomi by a simple string identifier and usually represents an application or set of applications from which Unomi gathers data, depending on the desired analysis granularity. In the context of web sites, a scope could, for example, represent a site or family of related sites being analyzed. Scopes allow clients accessing the context server to filter data to only see relevant data.</p>
+</div>
+<div class="paragraph">
+<p><em>Base <code>Item</code> structure:</em></p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+  "itemType": &lt;type of the item&gt;,
+  "scope": &lt;scope&gt;,
+  "itemId": &lt;item identifier&gt;,
+  "properties": &lt;optional properties&gt;
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Some types can be dynamically defined at runtime by calling to the REST API while other extensions are done via Unomi plugins. Part of extending Unomi, therefore, is a matter of defining new types and specifying which kind of Unomi entity (e.g. profiles) they can be affected to. For example, the following JSON document can be passed to Unomi to declare a new property type identified (and named) <code>tweetNb</code>, tagged with the <code>social</code> tag, targeting profiles and using the <code>integer</code> value type.</p>
+</div>
+<div class="paragraph">
+<p><em>Example JSON type definition:</em></p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+    "itemId": "tweetNb",
+    "itemType": "propertyType",
+    "metadata": {
+        "id": "tweetNb",
+        "name": "tweetNb",
+        "tags": ["social"]
+    },
+    "target": "profiles",
+    "type": "integer"
+}</code></pre>
+</div>
+</div>
+<div class="quoteblock">
+<blockquote>
+<div class="paragraph">
+<p>Unomi defines a built-in scope (called <code>systemscope</code>) that clients can use to share data across scopes.</p>
+</div>
+</blockquote>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_events">1.1.2. Events</h4>
+<div class="paragraph">
+<p>Users' actions are conveyed from clients to the context server using events. Of course, the required information depends on what is collected and users' interactions with the observed systems but events minimally provide a type, a scope and source and target items. Additionally, events are timestamped. Conceptually, an event can be seen as a sentence, the event&#8217;s type being the verb, the source the subject and the target the object.</p>
+</div>
+<div class="paragraph">
+<p><em>Event structure:</em></p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+    "eventType": &lt;type of the event&gt;,
+    "scope": &lt;scope of the event&gt;,
+    "source": &lt;Item&gt;,
+    "target": &lt;Item&gt;,
+    "properties": &lt;optional properties&gt;
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Source and target can be any Unomi item but are not limited to them. In particular, as long as they can be described using properties and Unomi’s type mechanism and can be processed either natively or via extension plugins, source and target can represent just about anything. Events can also be triggered as part of Unomi’s internal processes for example when a rule is triggered.</p>
+</div>
+<div class="paragraph">
+<p>Events are sent to Unomi from client applications using the JSON format and a typical page view event from a web site could look something like the following:</p>
+</div>
+<div class="paragraph">
+<p><em>Example page view event:</em></p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+    "eventType": "view",
+    "scope": "ACMESPACE",
+    "source": {
+        "itemType": "site",
+        "scope": "ACMESPACE",
+        "itemId": "c4761bbf-d85d-432b-8a94-37e866410375"
+    },
+    "target": {
+        "itemType": "page",
+        "scope": "ACMESPACE",
+        "itemId": "b6acc7b3-6b9d-4a9f-af98-54800ec13a71",
+        "properties": {
+            "pageInfo": {
+            "pageID": "b6acc7b3-6b9d-4a9f-af98-54800ec13a71",
+            "pageName": "Home",
+            "pagePath": "/sites/ACMESPACE/home",
+            "destinationURL": "http://localhost:8080/sites/ACMESPACE/home.html",
+            "referringURL": "http://localhost:8080/",
+            "language": "en"
+        },
+        "category": {},
+        "attributes": {}
+      }
+    }
+}</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_profiles">1.1.3. Profiles</h4>
+<div class="paragraph">
+<p>By processing events, Unomi progressively builds a picture of who the user is and how they behave. This knowledge is embedded in <code>Profile</code> object. A profile is an <code>Item</code> with any number of properties and optional segments and scores. Unomi provides default properties to cover common data (name, last name, age, email, etc.) as well as default segments to categorize users. Unomi users are, however, free and even encouraged to create additional properties and segments to better suit their needs.</p>
+</div>
+<div class="paragraph">
+<p>Contrary to other Unomi items, profiles are not part of a scope since we want to be able to track the associated user across applications. For this reason, data collected for a given profile in a specific scope is still available to any scoped item that accesses the profile information.</p>
+</div>
+<div class="paragraph">
+<p>It is interesting to note that there is not necessarily a one to one mapping between users and profiles as users can be captured across applications and different observation contexts. As identifying information might not be available in all contexts in which data is collected, resolving profiles to a single physical user can become complex because physical users are not observed directly. Rather, their portrait is progressively patched together and made clearer as Unomi captures more and more traces of their actions. Unomi will merge related profiles as soon as collected data permits positive association between distinct profiles, usually as a result of the user performing some identifying action in a context where the user hadn’t already been positively identified.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_sessions">1.1.4. Sessions</h4>
+<div class="paragraph">
+<p>A session represents a time-bounded interaction between a user (via their associated profile) and a Unomi-enabled application. A session represents the sequence of actions the user performed during its duration. For this reason, events are associated with the session during which they occurred. In the context of web applications, sessions are usually linked to HTTP sessions.</p>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_extending_unomi_via_plugins">1.2. Extending Unomi via plugins</h3>
+<div class="paragraph">
+<p>Unomi is architected so that users can provided extensions in the form of plugins.</p>
+</div>
+<div class="sect3">
+<h4 id="_types_vs_instances">1.2.1. Types vs. instances</h4>
+<div class="paragraph">
+<p>Several extension points in Unomi rely on the concept of type: the extension defines a prototype for what the actual items will be once parameterized with values known only at runtime. This is similar to the concept of classes in object-oriented programming: types define classes, providing the expected structure and which fields are expected to be provided at runtime, that are then instantiated when needed with actual values.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_plugin_structure">1.2.2. Plugin structure</h4>
+<div class="paragraph">
+<p>Being built on top of Apache Karaf, Unomi leverages OSGi to support plugins. A Unomi plugin is, thus, an OSGi bundle specifying some specific metadata to tell Unomi the kind of entities it provides. A plugin can provide the following entities to extend Unomi, each with its associated definition (as a JSON file), located in a specific spot within the <code>META-INF/cxs/</code> directory of the bundle JAR file:</p>
+</div>
+<table class="tableblock frame-all grid-all spread">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Entity</th>
+<th class="tableblock halign-left valign-top">Location in <code>cxs</code> directory</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">ActionType</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">actions</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">ConditionType</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">conditions</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Persona</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">personas</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">PropertyMergeStrategyType</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">mergers</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">PropertyType</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">properties then profiles or sessions subdirectory then <code>&lt;category name&gt;</code> directory</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Rule</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">rules</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Scoring</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">scorings</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Segment</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">segments</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Tag</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">tags</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">ValueType</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">values</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p><a href="http://aries.apache.org/modules/blueprint.html">Blueprint</a> is used to declare what the plugin provides and inject any required dependency. The Blueprint file is located, as usual, at <code>OSGI-INF/blueprint/blueprint.xml</code> in the bundle JAR file.</p>
+</div>
+<div class="paragraph">
+<p>The plugin otherwise follows a regular maven project layout and should depend on the Unomi API maven artifact:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-xml" data-lang="xml">&lt;dependency&gt;
+    &lt;groupId&gt;org.apache.unomi&lt;/groupId&gt;
+    &lt;artifactId&gt;unomi-api&lt;/artifactId&gt;
+    &lt;version&gt;...&lt;/version&gt;
+&lt;/dependency&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Some plugins consists only of JSON definitions that are used to instantiate the appropriate structures at runtime while some more involved plugins provide code that extends Unomi in deeper ways.</p>
+</div>
+<div class="paragraph">
+<p>In both cases, plugins can provide more that one type of extension. For example, a plugin could provide both `ActionType`s and `ConditionType`s.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_extension_points">1.2.3. Extension points</h4>
+<div class="sect4">
+<h5 id="_actiontype">ActionType</h5>
+<div class="paragraph">
+<p><code>ActionType`s define new actions that can be used as consequences of Rules being triggered. When a rule triggers, it creates new actions based on the event data and the rule internal processes, providing values for parameters defined in the associated `ActionType</code>. Example actions include: “Set user property x to value y” or “Send a message to service x”.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_conditiontype">ConditionType</h5>
+<div class="paragraph">
+<p>`ConditionType`s define new conditions that can be applied to items (for example to decide whether a rule needs to be triggered or if a profile is considered as taking part in a campaign) or to perform queries against the stored Unomi data. They may be implemented in Java when attempting to define a particularly complex test or one that can better be optimized by coding it. They may also be defined as combination of other conditions. A simple condition could be: “User is male”, while a more generic condition with parameters may test whether a given property has a specific value: “User property x has value y”.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_persona">Persona</h5>
+<div class="paragraph">
+<p>A persona is a "virtual" profile used to represent categories of profiles, and may also be used to test how a personalized experience would look like using this virtual profile. A persona can define predefined properties and sessions. Persona definition make it possible to “emulate” a certain type of profile, e.g : US visitor, non-US visitor, etc.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_propertymergestrategytype">PropertyMergeStrategyType</h5>
+<div class="paragraph">
+<p>A strategy to resolve how to merge properties when merging profile together.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_propertytype">PropertyType</h5>
+<div class="paragraph">
+<p>Definition for a profile or session property, specifying how possible values are constrained, if the value is multi-valued (a vector of values as opposed to a scalar value). `PropertyType`s can also be categorized using tags or file system structure, using sub-directories to organize definition files.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_rule">Rule</h5>
+<div class="paragraph">
+<p>`Rule`s are conditional sets of actions to be executed in response to incoming events. Triggering of rules is guarded by a condition: the rule is only triggered if the associated condition is satisfied. That condition can test the event itself, but also the profile or the session. Once a rule triggers, a list of actions can be performed as consequences. Also, when rules trigger, a specific event is raised so that other parts of Unomi can react accordingly.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_scoring">Scoring</h5>
+<div class="paragraph">
+<p>`Scoring`s are set of conditions associated with a value to assign to profiles when matching so that the associated users can be scored along that dimension. Each scoring element is evaluated and matching profiles' scores are incremented with the associated value.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_segments">Segments</h5>
+<div class="paragraph">
+<p>`Segment`s represent dynamically evaluated groups of similar profiles in order to categorize the associated users. To be considered part of a given segment, users must satisfies the segment’s condition. If they match, users are automatically added to the segment. Similarly, if at any given point during, they cease to satisfy the segment’s condition, they are automatically removed from it.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_tag">Tag</h5>
+<div class="paragraph">
+<p>`Tag`s are simple labels that are used to classify all other objects inside Unomi.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_valuetype">ValueType</h5>
+<div class="paragraph">
+<p>Definition for values that can be assigned to properties ("primitive" types).</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_other_unomi_entities">1.2.4. Other Unomi entities</h4>
+<div class="sect4">
+<h5 id="_userlist">UserList</h5>
+<div class="paragraph">
+<p>User list are simple static lists of users. The associated profile stores the lists it belongs to in a specific property.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_goal">Goal</h5>
+<div class="paragraph">
+<p>Goals represent tracked activities / actions that can be accomplished by site (or more precisely scope) visitors. These are tracked in general because they relate to specific business objectives or are relevant to measure site/scope performance.</p>
+</div>
+<div class="paragraph">
+<p>Goals can be defined at the scope level or in the context of a particular <code>Campaign</code>. Either types of goals behave exactly the same way with the exception of two notable differences:
+ - duration: scope-level goals are considered until removed while campaign-level goals are only considered for the campaign duration
+ - audience filtering: any visitor is considered for scope-level goals while campaign-level goals only consider visitors who match the campaign&#8217;s conditions</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_campaign">Campaign</h5>
+<div class="paragraph">
+<p>A goal-oriented, time-limited marketing operation that needs to be evaluated for return on investment performance by tracking the ratio of visits to conversions.</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_quick_start">2. Quick start</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_building">2.1. Building</h3>
+<div class="paragraph">
+<p>Simply type at the root of the project:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>mvn clean install -P generate-package</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The Maven build process will generate both a standalone package you can use directly to start the context server
+(see "Deploying the generated package") or a KAR file that you can then deploy using a manual deployment process into
+an already installed Apache Karaf server (see "Deploying into an existing Karaf server")</p>
+</div>
+<div class="paragraph">
+<p>If you want to build and run the integration tests, you should instead use :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>mvn -P integration-tests clean install</code></pre>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_deploying_the_generated_package">2.1.1. Deploying the generated package</h4>
+<div class="paragraph">
+<p>The "package" sub-project generates a pre-configured Apache Karaf installation that is the simplest way to get started.
+Simply uncompress the <code>package/target/unomi-VERSION.tar.gz</code> (for Linux or Mac OS X) or
+ <code>package/target/unomi-VERSION.zip</code> (for Windows) archive into the directory of your choice.</p>
+</div>
+<div class="paragraph">
+<p>You can then start the server simply by using the command on UNIX/Linux/MacOS X :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>./bin/karaf</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>or on Windows shell :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>bin\karaf.bat</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_deploying_into_an_existing_karaf_server">2.1.2. Deploying into an existing Karaf server</h4>
+<div class="paragraph">
+<p>This is only needed if you didn&#8217;t use the generated package. Also, this is the preferred way to install a development
+environment if you intend to re-deploy the context server KAR iteratively.</p>
+</div>
+<div class="paragraph">
+<p>Additional requirements:
+ - Apache Karaf 3.0.2+, <a href="http://karaf.apache.org">http://karaf.apache.org</a>
+ - Local copy of the Elasticsearch ZIP package, available here : <a href="http://www.elasticsearch.org">http://www.elasticsearch.org</a></p>
+</div>
+<div class="paragraph">
+<p>.</p>
+</div>
+<div class="paragraph">
+<p>Before deploying, make sure that you have Apache Karaf properly installed. You will also have to increase the
+default maximum memory size and perm gen size by adjusting the following environment values in the bin/setenv(.bat)
+files (at the end of the file):</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>   MY_DIRNAME=`dirname $0`
+   MY_KARAF_HOME=`cd "$MY_DIRNAME/.."; pwd`
+   export KARAF_OPTS="-Djava.library.path=$MY_KARAF_HOME/lib/sigar"
+   export JAVA_MAX_MEM=3G
+   export JAVA_MAX_PERM_MEM=384M</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>.</p>
+</div>
+<div class="paragraph">
+<p>You will also need to have the Hyperic Sigar native libraries in your Karaf installation, so in order to this
+go to the Elasticsearch website (<a href="http://www.elasticsearch.org">http://www.elasticsearch.org</a>) and download the ZIP package. Decompress it somewhere
+on your disk and copy all the files from the lib/sigar directory into Karaf&#8217;s lib/sigar directory
+(must be created first) EXCEPT THE SIGAR.JAR file.</p>
+</div>
+<div class="paragraph">
+<p>.</p>
+</div>
+<div class="paragraph">
+<p>Install the WAR support, CXF into Karaf by doing the following in the Karaf command line:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>   feature:install -v war
+   feature:repo-add cxf 2.7.11
+   feature:install -v cxf/2.7.11</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>.</p>
+</div>
+<div class="paragraph">
+<p>Create a new $MY_KARAF_HOME/etc/org.apache.cxf.osgi.cfg file and put the following property inside :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>   org.apache.cxf.servlet.context=/cxs</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>.</p>
+</div>
+<div class="paragraph">
+<p>Copy the following KAR to the Karaf deploy directory, as in this example line:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>  cp kar/target/unomi-kar-1.0.0-SNAPSHOT.kar ~/java/deployments/unomi/apache-karaf-3.0.1/deploy/</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>.</p>
+</div>
+<div class="paragraph">
+<p>If all went smoothly, you should be able to access the context script here : <a href="http://localhost:8181/cxs/cluster">http://localhost:8181/cxs/cluster</a> .
+ You should be able to login with karaf / karaf and see basic server information. If not something went wrong during the install.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_jdk_selection_on_mac_os_x">2.1.3. JDK Selection on Mac OS X</h4>
+<div class="paragraph">
+<p>You might need to select the JDK to run the tests in the itests subproject. In order to do so you can list the
+installed JDKs with the following command :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>/usr/libexec/java_home -V</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>which will output something like this :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>Matching Java Virtual Machines (7):
+    1.7.0_51, x86_64:   "Java SE 7"   /Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk/Contents/Home
+    1.7.0_45, x86_64:   "Java SE 7"   /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home
+    1.7.0_25, x86_64:   "Java SE 7"   /Library/Java/JavaVirtualMachines/jdk1.7.0_25.jdk/Contents/Home
+    1.6.0_65-b14-462, x86_64:   "Java SE 6"   /Library/Java/JavaVirtualMachines/1.6.0_65-b14-462.jdk/Contents/Home
+    1.6.0_65-b14-462, i386: "Java SE 6" /Library/Java/JavaVirtualMachines/1.6.0_65-b14-462.jdk/Contents/Home
+    1.6.0_65-b14-462, x86_64:   "Java SE 6"   /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
+    1.6.0_65-b14-462, i386: "Java SE 6" /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>You can then select the one you want using :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>export JAVA_HOME=`/usr/libexec/java_home -v 1.7.0_51`</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>and then check that it was correctly referenced using:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>java -version</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>which should give you a result such as this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>java version "1.7.0_51"
+Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
+Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_running_the_integration_tests">2.1.4. Running the integration tests</h4>
+<div class="paragraph">
+<p>The integration tests are not executed by default to make build time minimal, but it is recommended to run the
+integration tests at least once before using the server to make sure that everything is ok in the build. Another way
+to use these tests is to run them from a continuous integration server such as Jenkins, Apache Gump, Atlassian Bamboo or
+ others.</p>
+</div>
+<div class="paragraph">
+<p>Note : the integration tests require a JDK 7 or more recent !</p>
+</div>
+<div class="paragraph">
+<p>To run the tests simply activate the following profile :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>mvn -P integration-tests clean install</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_running_the_performance_tests">2.1.5. Running the performance tests</h4>
+<div class="paragraph">
+<p>Performance tests are based on Gatling. You need to have a running context server or cluster of servers before
+executing the tests.</p>
+</div>
+<div class="paragraph">
+<p>Test parameteres are editable in the performance-tests/src/test/scala/unomi/Parameters.scala file. baseUrls should
+contains the URLs of all your cluster nodes</p>
+</div>
+<div class="paragraph">
+<p>Run the test by using the gatling.conf file in performance-tests/src/test/resources :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>    export GATLING_CONF=&lt;path&gt;/performance-tests/src/test/resources
+    gatling.sh</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Reports are generated in performance-tests/target/results.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_testing_with_an_example_page">2.1.6. Testing with an example page</h4>
+<div class="paragraph">
+<p>A default test page is provided at the following URL:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>   http://localhost:8181/index.html</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This test page will trigger the loading of the /context.js script, which will try to retrieving the user context
+or create a new one if it doesn&#8217;t exist yet. It also contains an experimental integration with Facebook Login, but it
+doesn&#8217;t yet save the context back to the context server.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_integrating_onto_a_page">2.1.7. Integrating onto a page</h4>
+<div class="paragraph">
+<p>Simply reference the context script in your HTML as in the following example:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">&lt;script type="text/javascript"&gt;
+    (function(){ var u=(("https:" == document.location.protocol) ? "https://localhost:8181/" : "http://localhost:8181/");
+    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript'; g.defer=true; g.async=true; g.src=u+'context.js';
+    s.parentNode.insertBefore(g,s); })();
+&lt;/script&gt;</code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_getting_started_with_unomi">2.2. Getting started with Unomi</h3>
+<div class="paragraph">
+<p>We will first get you up and running with an example. We will then lift the corner of the cover somewhat and explain in greater details what just happened.</p>
+</div>
+<div class="sect3">
+<h4 id="_prerequisites">2.2.1. Prerequisites</h4>
+<div class="paragraph">
+<p>This document assumes that you are already familiar with Unomi&#8217;s <a href="concepts.html">concepts</a>. On the technical side, we also assume working knowledge of <a href="https://git-scm.com/">git</a> to be able to retrieve the code for Unomi and the example. Additionnally, you will require a working Java 7 or above install. Refer to <a href="http://www.oracle.com/technetwork/java/javase/">http://www.oracle.com/technetwork/java/javase/</a> for details on how to download and install Java SE 7 or greater.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_running_unomi">2.2.2. Running Unomi</h4>
+<div class="sect4">
+<h5 id="_building_unomi">Building Unomi</h5>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Get the code: <code>git clone <a href="https://git-wip-us.apache.org/repos/asf/incubator-unomi.git" class="bare">https://git-wip-us.apache.org/repos/asf/incubator-unomi.git</a></code></p>
+</li>
+<li>
+<p>Build and install according to the <a href="building-and-deploying.html">instructions</a> and install Unomi.</p>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_start_unomi">Start Unomi</h5>
+<div class="paragraph">
+<p>Start Unomi according to the <a href="building-and-deploying.html#Deploying_the_generated_package">instructions</a>. Once you have Karaf running,
+ you should wait until you see the following messages on the Karaf console:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>Initializing user list service endpoint...
+Initializing geonames service endpoint...
+Initializing segment service endpoint...
+Initializing scoring service endpoint...
+Initializing campaigns service endpoint...
+Initializing rule service endpoint...
+Initializing profile service endpoint...
+Initializing cluster service endpoint...</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This indicates that all the Unomi services are started and ready to react to requests. You can then open a browser and go to <code><a href="http://localhost:8181/cxs" class="bare">http://localhost:8181/cxs</a></code> to see the list of
+available RESTful services or retrieve an initial context at <code><a href="http://localhost:8181/context.json" class="bare">http://localhost:8181/context.json</a></code> (which isn&#8217;t very useful at this point).</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_building_the_tweet_button_sample">Building the tweet button sample</h5>
+<div class="paragraph">
+<p>In your local copy of the Unomi repository and run:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>cd samples/tweet-button-plugin
+mvn clean install</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This will compile and create the OSGi bundle that can be deployed on Unomi to extend it.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_deploying_the_tweet_button_sample">Deploying the tweet button sample</h5>
+<div class="paragraph">
+<p>In standard Karaf fashion, you will need to copy the sample bundle to your Karaf <code>deploy</code> directory.</p>
+</div>
+<div class="paragraph">
+<p>If you are using the packaged version of Unomi (as opposed to deploying it to your own Karaf version), you can simply run, assuming your current directory is <code>samples/tweet-button-plugin</code> and that you uncompressed the archive in the directory it was created:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>cp target/tweet-button-plugin-1.0.0-incubating-SNAPSHOT.jar ../../package/target/unomi-1.0.0-incubating-SNAPSHOT/deploy</code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_testing_the_sample">Testing the sample</h5>
+<div class="paragraph">
+<p>You can now go to <a href="http://localhost:8181/index.html">http://localhost:8181/index.html</a> to test the sample code. The page is very simple, you will see a Twitter button, which, once clicked, will open a new window to tweet about the current page. The original page should be updated with the new values of the properties coming from Unomi. Additionnally, the raw JSON response is displayed.</p>
+</div>
+<div class="paragraph">
+<p>We will now explain in greater details some concepts and see how the example works.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_interacting_with_the_context_server">2.2.3. Interacting with the context server</h4>
+<div class="paragraph">
+<p>There are essentially two modalities to interact with the context server, reflecting different types of Unomi users: context server clients and context server integrators.</p>
+</div>
+<div class="paragraph">
+<p><strong>Context server clients</strong> are usually web applications or content management systems. They interact with Unomi by providing raw, uninterpreted contextual data in the form of events and associated metadata. That contextual data is then processed by the context server to be fed to clients once actionable. In that sense context server clients are both consumers and producers of contextual data. Context server clients will mostly interact with Unomi using a single entry point called the <code>ContextServlet</code>, requesting context for the current user and providing any triggered events along the way.</p>
+</div>
+<div class="paragraph">
+<p>On the other hand, <strong>context server integrators</strong> provide ways to feed more structured data to the context server either to integrate with third party services or to provide analysis of the uninterpreted data provided by context server clients. Such integration will mostly be done using Unomi&#8217;s API either directly using Unomi plugins or via the provided REST APIs. However, access to REST APIs is restricted due for security reasons, requiring privileged access to the Unomi server, making things a little more complex to set up.</p>
+</div>
+<div class="paragraph">
+<p>For simplicity&#8217;s sake, this document will focus solely on the first use case and will interact only with the context servlet.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_retrieving_context_information_from_unomi_using_the_context_servlet">2.2.4. Retrieving context information from Unomi using the context servlet</h4>
+<div class="paragraph">
+<p>Unomi provides two ways to retrieve context: either as a pure JSON object containing strictly context information or as a couple of JSON objects augmented with javascript functions that can be used to interact with the Unomi server using the <code>&lt;context server base URL&gt;/context.json</code> or <code>&lt;context server base URL&gt;/context.js</code> URLs, respectively.</p>
+</div>
+<div class="paragraph">
+<p>Below is an example of asynchronously loading the initial context using the javascript version, assuming a default Unomi install running on <code><a href="http://localhost:8181" class="bare">http://localhost:8181</a></code>:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">// Load context from Unomi asynchronously
+(function (document, elementToCreate, id) {
+    var js, fjs = document.getElementsByTagName(elementToCreate)[0];
+    if (document.getElementById(id)) return;
+    js = document.createElement(elementToCreate);
+    js.id = id;
+    js.src = 'http://localhost:8181/context.js';
+    fjs.parentNode.insertBefore(js, fjs);
+}(document, 'script', 'context'));</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This initial context results in a javascript file providing some functions to interact with the context server from javascript along with two objects: a <code>cxs</code> object containing
+information about the context for the current user and a <code>digitalData</code> object that is injected into the browser’s <code>window</code> object (leveraging the
+<a href="http://www.w3.org/2013/12/ceddl-201312.pdf">Customer Experience Digital Data Layer</a> standard). Note that this last object is not under control of the context server and clients
+ are free to use it or not. Our example will not make use of it.</p>
+</div>
+<div class="paragraph">
+<p>On the other hand, the <code>cxs</code> top level object contains interesting contextual information about the current user:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+  "profileId":&lt;identifier of the profile associated with the current user&gt;,
+  "sessionId":&lt;identifier of the current user session&gt;,
+  "profileProperties":&lt;requested profile properties, if any&gt;,
+  "sessionProperties":&lt;requested session properties, if any&gt;,
+  "profileSegments":&lt;segments the profile is part of if requested&gt;,
+  "filteringResults":&lt;result of the evaluation of personalization filters&gt;,
+  "trackedConditions":&lt;tracked conditions in the source page, if any&gt;
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>We will look at the details of the context request and response later.</p>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_example">2.3. Example</h3>
+<div class="sect3">
+<h4 id="_overview">2.3.1. Overview</h4>
+<div class="paragraph">
+<p>We will examine how a simple HTML page can interact with Unomi to enrich a user&#8217;s profile. The use case we will follow is a rather simple one: we want to react to Twitter events by associating information to their profile. We will record the number of times the user tweeted (as a <code>tweetNb</code> profile integer property) as well as the URLs they tweeted from (as a <code>tweetedFrom</code> multi-valued string profile property). We will accomplish this using a simple HTML page on which we position a standard "Tweet" button. A javascript script will use the Twitter API to react to clicks on this button and update the user profile using a <code>ContextServlet</code> request triggering a custom event. This event will, in turn, trigger a Unomi action on the server implemented using a Unomi plugin, a standard extension point for the server.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_html_page">2.3.2. HTML page</h4>
+<div class="paragraph">
+<p>The code for the HTML page with our Tweet button can be found at <a href="https://github.com/apache/incubator-unomi/blob/master/wab/src/main/webapp/index.html">https://github.com/apache/incubator-unomi/blob/master/wab/src/main/webapp/index.html</a>.</p>
+</div>
+<div class="paragraph">
+<p>This HTML page is fairly straightforward: we create a tweet button using the Twitter API while a Javascript script performs the actual logic.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_javascript">2.3.3. Javascript</h4>
+<div class="paragraph">
+<p>Globally, the script loads both the twitter widget and the initial context asynchronously (as shown previously). This is accomplished using fairly standard javascript code and we won&#8217;t look at it here. Using the Twitter API, we react to the <code>tweet</code> event and call the Unomi server to update the user&#8217;s profile with the required information, triggering a custom <code>tweetEvent</code> event. This is accomplished using a <code>contextRequest</code> function which is an extended version of a classic <code>AJAX</code> request:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">function contextRequest(successCallback, errorCallback, payload) {
+    var data = JSON.stringify(payload);
+    // if we don't already have a session id, generate one
+    var sessionId = cxs.sessionId || generateUUID();
+    var url = 'http://localhost:8181/context.json?sessionId=' + sessionId;
+    var xhr = new XMLHttpRequest();
+    var isGet = data.length &lt; 100;
+    if (isGet) {
+        xhr.withCredentials = true;
+        xhr.open("GET", url + "&amp;payload=" + encodeURIComponent(data), true);
+    } else if ("withCredentials" in xhr) {
+        xhr.open("POST", url, true);
+        xhr.withCredentials = true;
+    } else if (typeof XDomainRequest != "undefined") {
+        xhr = new XDomainRequest();
+        xhr.open("POST", url);
+    }
+    xhr.onreadystatechange = function () {
+        if (xhr.readyState != 4) {
+            return;
+        }
+        if (xhr.status == 200) {
+            var response = xhr.responseText ? JSON.parse(xhr.responseText) : undefined;
+            if (response) {
+                cxs.sessionId = response.sessionId;
+                successCallback(response);
+            }
+        } else {
+            console.log("contextserver: " + xhr.status + " ERROR: " + xhr.statusText);
+            if (errorCallback) {
+                errorCallback(xhr);
+            }
+        }
+    };
+    xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8"); // Use text/plain to avoid CORS preflight
+    if (isGet) {
+        xhr.send();
+    } else {
+        xhr.send(data);
+    }
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>There are a couple of things to note here:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>If we specify a payload, it is expected to use the JSON format so we <code>stringify</code> it and encode it if passed as a URL parameter in a <code>GET</code> request.</p>
+</li>
+<li>
+<p>We need to make a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS"><code>CORS</code></a> request since the Unomi server is most likely not running on the same host than the one from which the request originates. The specific details are fairly standard and we will not explain them here.</p>
+</li>
+<li>
+<p>We need to either retrieve (from the initial context we retrieved previously using <code>cxs.sessionId</code>) or generate a session identifier for our request since Unomi currently requires one.</p>
+</li>
+<li>
+<p>We&#8217;re calling the <code>ContextServlet</code> using the default install URI, specifying the session identifier: <code><a href="http://localhost:8181/context.json?sessionId=&#39" class="bare">http://localhost:8181/context.json?sessionId=&#39</a>; + sessionId</code>. This URI requests context from Unomi, resulting in an updated <code>cxs</code> object in the javascript global scope. The context server can reply to this request either by returning a JSON-only object containing solely the context information as is the case when the requested URI is <code>context.json</code>. However, if the client requests <code>context.js</code> then useful functions to interact with Unomi are added to the <code>cxs</code> object in addition to the context information as depicted above.</p>
+</li>
+<li>
+<p>We don&#8217;t need to provide any authentication at all to interact with this part of Unomi since we only have access to read-only data (as well as providing events as we shall see later on). If we had been using the REST API, we would have needed to provide authentication information as well.</p>
+</li>
+</ul>
+</div>
+<div class="sect4">
+<h5 id="_context_request_and_response_structure">Context request and response structure</h5>
+<div class="paragraph">
+<p>The interesting part, though, is the payload. This is where we provide Unomi with contextual information as well as ask for data in return. This allows clients to specify which type of information they are interested in getting from the context server as well as specify incoming events or content filtering or property/segment overrides for personalization or impersonation. This conditions what the context server will return with its response.</p>
+</div>
+<div class="paragraph">
+<p>Let&#8217;s look at the context request structure:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+    source: &lt;Item source of the context request&gt;,
+    events: &lt;optional array of triggered events&gt;,
+    requiredProfileProperties: &lt;optional array of property identifiers&gt;,
+    requiredSessionProperties: &lt;optional array of property identifiers&gt;,
+    filters: &lt;optional array of filters to evaluate&gt;,
+    profileOverrides: &lt;optional profile containing segments,scores or profile properties to override&gt;,
+        - segments: &lt;optional array of segment identifiers&gt;,
+        - profileProperties: &lt;optional map of property name / value pairs&gt;,
+        - scores: &lt;optional map of score id / value pairs&gt;
+    sessionPropertiesOverrides: &lt;optional map of property name / value pairs&gt;,
+    requireSegments: &lt;boolean, whether to return the associated segments&gt;
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>We will now look at each part in greater details.</p>
+</div>
+<div class="sect5">
+<h6 id="_source">Source</h6>
+<div class="paragraph">
+<p>A context request payload needs to at least specify some information about the source of the request in the form of an <code>Item</code> (meaning identifier, type and scope plus any additional properties we might have to provide), via the <code>source</code> property of the payload. Of course the more information can be provided about the source, the better.</p>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_filters">Filters</h6>
+<div class="paragraph">
+<p>A client wishing to perform content personalization might also specify filtering conditions to be evaluated by the context server so that it can tell the client whether the content associated with the filter should be activated for this profile/session. This is accomplished by providing a list of filter definitions to be evaluated by the context server via the <code>filters</code> field of the payload. If provided, the evaluation results will be provided in the <code>filteringResults</code> field of the resulting <code>cxs</code> object the context server will send.</p>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_overrides">Overrides</h6>
+<div class="paragraph">
+<p>It is also possible for clients wishing to perform user impersonation to specify properties, segments or scores to override the proper ones so as to emulate a specific profile, in which case the overridden value will temporarily replace the proper values so that all rules will be evaluated with these values instead of the proper ones. The <code>segments</code> (array of segment identifiers), <code>profileProperties</code> (maps of property name and associated object value) and <code>scores</code> (maps of score id and value) all wrapped in a profileOverrides object and the <code>sessionPropertiesOverrides</code> (maps of property name and associated object value) fields allow to provide such information. Providing such overrides will, of course, impact content filtering results and segments matching for this specific request.</p>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_controlling_the_content_of_the_response">Controlling the content of the response</h6>
+<div class="paragraph">
+<p>The clients can also specify which information to include in the response by setting the <code>requireSegments</code> property to true if segments the current profile matches should be returned or provide an array of property identifiers for <code>requiredProfileProperties</code> or <code>requiredSessionProperties</code> fields to ask the context server to return the values for the specified profile or session properties, respectively. This information is provided by the <code>profileProperties</code>, <code>sessionProperties</code> and <code>profileSegments</code> fields of the context server response.</p>
+</div>
+<div class="paragraph">
+<p>Additionally, the context server will also returns any tracked conditions associated with the source of the context request. Upon evaluating the incoming request, the context server will determine if there are any rules marked with the <code>trackedCondition</code> tag and which source condition matches the source of the incoming request and return these tracked conditions to the client. The client can use these tracked conditions to learn that the context server can react to events matching the tracked condition and coming from that source. This is, in particular, used to implement form mapping (a solution that allows clients to update user profiles based on values provided when a form is submitted).</p>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_events_2">Events</h6>
+<div class="paragraph">
+<p>Finally, the client can specify any events triggered by the user actions, so that the context server can process them, via the <code>events</code> field of the context request.</p>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_default_response">Default response</h6>
+<div class="paragraph">
+<p>If no payload is specified, the context server will simply return the minimal information deemed necessary for client applications to properly function: profile identifier, session identifier and any tracked conditions that might exist for the source of the request.</p>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_context_request_for_our_example">Context request for our example</h5>
+<div class="paragraph">
+<p>Now that we&#8217;ve seen the structure of the request and what we can expect from the context response, let&#8217;s examine the request our component is doing.</p>
+</div>
+<div class="paragraph">
+<p>In our case, our <code>source</code> item looks as follows: we specify a scope for our application (<code>unomi-tweet-button-sample</code>), specify that the item type (i.e. the kind of element that is the source of our event) is a <code>page</code> (which corresponds, as would be expected, to a web page), provide an identifier (in our case, a Base-64 encoded version of the page&#8217;s URL) and finally, specify extra properties (here, simply a <code>url</code> property corresponding to the page&#8217;s URL that will be used when we process our event in our Unomi extension).</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">var scope = 'unomi-tweet-button-sample';
+var itemId = btoa(window.location.href);
+var source = {
+    itemType: 'page',
+    scope: scope,
+    itemId: itemId,
+    properties: {
+        url: window.location.href
+    }
+};</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>We also specify that we want the context server to return the values of the <code>tweetNb</code> and <code>tweetedFrom</code> profile properties in its response. Finally, we provide a custom event of type <code>tweetEvent</code> with associated scope and source information, which matches the source of our context request in this case.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">var contextPayload = {
+    source: source,
+    events: [
+        {
+            eventType: 'tweetEvent',
+            scope: scope,
+            source: source
+        }
+    ],
+    requiredProfileProperties: [
+        'tweetNb',
+        'tweetedFrom'
+    ]
+};</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The <code>tweetEvent</code> event type is not defined by default in Unomi. This is where our Unomi plugin comes into play since we need to tell Unomi how to react when it encounters such events.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_unomi_plugin_overview">Unomi plugin overview</h5>
+<div class="paragraph">
+<p>In order to react to <code>tweetEvent</code> events, we will define a new Unomi rule since this is exactly what Unomi rules are supposed to do. Rules are guarded by conditions and if these
+ conditions match, the associated set of actions will be executed. In our case, we want our new
+ <a href="https://github.com/apache/incubator-unomi/blob/master/samples/tweet-button-plugin/src/main/resources/META-INF/cxs/rules/incrementTweetNumber.json"><code>incrementTweetNumber</code></a> rule to only react to <code>tweetEvent</code> events and
+ we want it to perform the profile update accordingly: create the property types for our custom properties if they don&#8217;t exist and update them. To do so, we will create a
+ custom
+ <a href="https://github.com/apache/incubator-unomi/blob/master/samples/tweet-button-plugin/src/main/resources/META-INF/cxs/actions/incrementTweetNumberAction.json"><code>incrementTweetNumberAction</code></a> action that will be triggered any time our rule matches. An action is some custom code that is deployed in the context server and can access the
+ Unomi API to perform what it is that it needs to do.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_rule_definition">Rule definition</h5>
+<div class="paragraph">
+<p>Let&#8217;s look at how our custom <a href="https://github.com/apache/incubator-unomi/blob/master/samples/tweet-button-plugin/src/main/resources/META-INF/cxs/rules/incrementTweetNumber.json"><code>incrementTweetNumber</code></a> rule is defined:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+  "metadata": {
+    "id": "smp:incrementTweetNumber",
+    "name": "Increment tweet number",
+    "description": "Increments the number of times a user has tweeted after they click on a tweet button"
+  },
+  "raiseEventOnlyOnceForSession": false,
+  "condition": {
+    "type": "eventTypeCondition",
+    "parameterValues": {
+      "eventTypeId": "tweetEvent"
+    }
+  },
+  "actions": [
+    {
+      "type": "incrementTweetNumberAction",
+      "parameterValues": {}
+    }
+  ]
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Rules define a metadata section where we specify the rule name, identifier and description.</p>
+</div>
+<div class="paragraph">
+<p>When rules trigger, a specific event is raised so that other parts of Unomi can react to it accordingly. We can control how that event should be raised. Here we specify that the event should be raised each time the rule triggers and not only once per session by setting <code>raiseEventOnlyOnceForSession</code> to <code>false</code>, which is not strictly required since that is the default. A similar setting (<code>raiseEventOnlyOnceForProfile</code>) can be used to specify that the event should only be raised once per profile if needed.</p>
+</div>
+<div class="paragraph">
+<p>We could also specify a priority for our rule in case it needs to be executed before other ones when similar conditions match. This is accomplished using the <code>priority</code> property. We&#8217;re using the default priority here since we don&#8217;t have other rules triggering on `tweetEvent`s and don&#8217;t need any special ordering.</p>
+</div>
+<div class="paragraph">
+<p>We then tell Unomi which condition should trigger the rule via the <code>condition</code> property. Here, we specify that we want our rule to trigger on an <code>eventTypeCondition</code> condition. Unomi can be extended by adding new condition types that can enrich how matching or querying is performed. The condition type definition file specifies which parameters are expected for our condition to be complete. In our case, we use the built-in event type condition that will match if Unomi receives an event of the type specified in the condition&#8217;s <code>eventTypeId</code> parameter value: <code>tweetEvent</code> here.</p>
+</div>
+<div class="paragraph">
+<p>Finally, we specify a list of actions that should be performed as consequences of the rule matching. We only need one action of type <code>incrementTweetNumberAction</code> that doesn&#8217;t require any parameters.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_action_definition">Action definition</h5>
+<div class="paragraph">
+<p>Let&#8217;s now look at our custom <a href="https://github.com/apache/incubator-unomi/blob/master/samples/tweet-button-plugin/src/main/resources/META-INF/cxs/actions/incrementTweetNumberAction.json"><code>incrementTweetNumberAction</code></a> action type definition:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+  "id": "incrementTweetNumberAction",
+  "actionExecutor": "incrementTweetNumber",
+  "tags": [
+    "event"
+  ],
+  "parameters": []
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>We specify the identifier for the action type, a list of tags if needed: here we say that our action is a consequence of events using the <code>event</code> tag. Our actions does not require any parameters so we don&#8217;t define any.</p>
+</div>
+<div class="paragraph">
+<p>Finally, we provide a mysterious <code>actionExecutor</code> identifier: <code>incrementTweetNumber</code>.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_action_executor_definition">Action executor definition</h5>
+<div class="paragraph">
+<p>The action executor references the actual implementation of the action as defined in our <a href="https://github.com/apache/incubator-unomi/blob/master/samples/tweet-button-plugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml">blueprint definition</a>:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-xml" data-lang="xml">&lt;blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd"&gt;
+
+    &lt;reference id="profileService" interface="org.apache.unomi.api.services.ProfileService"/&gt;
+
+    &lt;!-- Action executor --&gt;
+    &lt;service id="incrementTweetNumberAction" auto-export="interfaces"&gt;
+        &lt;service-properties&gt;
+            &lt;entry key="actionExecutorId" value="incrementTweetNumber"/&gt;
+        &lt;/service-properties&gt;
+        &lt;bean class="org.apache.unomi.examples.unomi_tweet_button_plugin.actions.IncrementTweetNumberAction"&gt;
+            &lt;property name="profileService" ref="profileService"/&gt;
+        &lt;/bean&gt;
+    &lt;/service&gt;
+&lt;/blueprint&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>In standard Blueprint fashion, we specify that we will need the <code>profileService</code> defined by Unomi and then define a service of our own to be exported for Unomi to use. Our service specifies one property: <code>actionExecutorId</code> which matches the identifier we specified in our action definition. We then inject the profile service in our executor and we&#8217;re done for the configuration side of things!</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_action_executor_implementation">Action executor implementation</h5>
+<div class="paragraph">
+<p>Our action executor definition specifies that the bean providing the service is implemented in the <a href="https://github.com/apache/incubator-unomi/blob/master/samples/tweet-button-plugin/src/main/java/org/apache/unomi/samples/tweet_button_plugin/actions/IncrementTweetNumberAction.java"><code>org.apache.unomi.samples.tweet_button_plugin.actions
+.IncrementTweetNumberAction</code></a> class. This class implements the Unomi <code>ActionExecutor</code> interface which provides a single <code>int execute(Action action, Event event)</code> method: the executor gets the action instance to execute along with the event that triggered it, performs its work and returns an integer status corresponding to what happened as defined by public constants of the <code>EventService</code> interface of Unomi: <code>NO_CHANGE</code>, <code>SESSION_UPDATED</code> or <code>PROFILE_UPDATED</code>.</p>
+</div>
+<div class="paragraph">
+<p>Let&#8217;s now look at the implementation of the method:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-java" data-lang="java">final Profile profile = event.getProfile();
+Integer tweetNb = (Integer) profile.getProperty(TWEET_NB_PROPERTY);
+List&lt;String&gt; tweetedFrom = (List&lt;String&gt;) profile.getProperty(TWEETED_FROM_PROPERTY);
+
+if (tweetNb == null || tweetedFrom == null) {
+    // create tweet number property type
+    PropertyType propertyType = new PropertyType(new Metadata(event.getScope(), TWEET_NB_PROPERTY, TWEET_NB_PROPERTY, "Number of times a user tweeted"));
+    propertyType.setValueTypeId("integer");
+    service.createPropertyType(propertyType);
+
+    // create tweeted from property type
+    propertyType = new PropertyType(new Metadata(event.getScope(), TWEETED_FROM_PROPERTY, TWEETED_FROM_PROPERTY, "The list of pages a user tweeted from"));
+    propertyType.setValueTypeId("string");
+    propertyType.setMultivalued(true);
+    service.createPropertyType(propertyType);
+
+    tweetNb = 0;
+    tweetedFrom = new ArrayList&lt;&gt;();
+}
+
+profile.setProperty(TWEET_NB_PROPERTY, tweetNb + 1);
+final String sourceURL = extractSourceURL(event);
+if (sourceURL != null) {
+    tweetedFrom.add(sourceURL);
+}
+profile.setProperty(TWEETED_FROM_PROPERTY, tweetedFrom);
+
+return EventService.PROFILE_UPDATED;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>It is fairly straightforward: we retrieve the profile associated with the event that triggered the rule and check whether it already has the properties we are interested in. If not, we create the associated property types and initialize the property values.</p>
+</div>
+<div class="quoteblock">
+<blockquote>
+<div class="paragraph">
+<p>Note that it is not an issue to attempt to create the same property type multiple times as Unomi will not add a new property type if an identical type already exists.</p>
+</div>
+</blockquote>
+</div>
+<div class="paragraph">
+<p>Once this is done, we update our profile with the new property values based on the previous values and the metadata extracted from the event using the <code>extractSourceURL</code> method which uses our <code>url</code> property that we&#8217;ve specified for our event source. We then return that the profile was updated as a result of our action and Unomi will properly save it for us when appropriate. That&#8217;s it!</p>
+</div>
+<div class="paragraph">
+<p>For reference, here&#8217;s the <code>extractSourceURL</code> method implementation:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-java" data-lang="java">private String extractSourceURL(Event event) {
+    final Item sourceAsItem = event.getSource();
+    if (sourceAsItem instanceof CustomItem) {
+        CustomItem source = (CustomItem) sourceAsItem;
+        final String url = (String) source.getProperties().get("url");
+        if (url != null) {
+            return url;
+        }
+    }
+
+    return null;
+}</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_conclusion">2.4. Conclusion</h3>
+<div class="paragraph">
+<p>We have seen a simple example how to interact with Unomi using a combination of client-side code and Unomi plugin. Hopefully, this provided an introduction to the power of what Unomi can do and how it can be extended to suit your needs.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_annex">2.5. Annex</h3>
+<div class="paragraph">
+<p>Here is an overview of how Unomi processes incoming requests to the <code>ContextServlet</code>.
+<span class="image"><img src="../images/unomi-request.png" alt="Unomi request overview"></span></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_configuration">2.6. Configuration</h3>
+<div class="sect3">
+<h4 id="_changing_the_default_configuration">2.6.1. Changing the default configuration</h4>
+<div class="paragraph">
+<p>If you want to change the default configuration, you can perform any modification you want in the $MY_KARAF_HOME/etc directory.</p>
+</div>
+<div class="paragraph">
+<p>The context server configuration is kept in the $MY_KARAF_HOME/etc/org.apache.unomi.web.cfg . It defines the
+addresses and port where it can be found :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>contextserver.address=localhost
+contextserver.port=8181
+contextserver.secureAddress=localhost
+contextserver.securePort=9443
+contextserver.domain=apache.org</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>If you need to specify an Elasticsearch cluster name that is different than the default, it is recommended to do this
+BEFORE you start the server for the first time, or you will loose all the data you have stored previously.</p>
+</div>
+<div class="paragraph">
+<p>To change the cluster name, first create a file called</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>$MY_KARAF_HOME/etc/org.apache.unomi.persistence.elasticsearch.cfg</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>with the following contents:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>cluster.name=contextElasticSearch
+index.name=context
+elasticSearchConfig=file:${karaf.etc}/elasticsearch.yml</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>And replace the cluster.name parameter here by your cluster name.</p>
+</div>
+<div class="paragraph">
+<p>You can also put an elasticsearch configuration file in $MY_KARAF_HOME/etc/elasticsearch.yml ,
+and put any standard Elasticsearch configuration options in this last file.</p>
+</div>
+<div class="paragraph">
+<p>If you want your context server to be a client only on a cluster of elasticsearch nodes, just set the node.data property
+to false.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_installing_the_maxmind_geoiplite2_ip_lookup_database">2.6.2. Installing the MaxMind GeoIPLite2 IP lookup database</h4>
+<div class="paragraph">
+<p>The Context Server requires an IP database in order to resolve IP addresses to user location.
+The GeoLite2 database can be downloaded from MaxMind here :
+<a href="http://dev.maxmind.com/geoip/geoip2/geolite2/">http://dev.maxmind.com/geoip/geoip2/geolite2/</a></p>
+</div>
+<div class="paragraph">
+<p>Simply download the GeoLite2-City.mmdb file into the "etc" directory.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_installing_geonames_database">2.6.3. Installing Geonames database</h4>
+<div class="paragraph">
+<p>Context server includes a geocoding service based on the geonames database ( <a href="http://www.geonames.org/">http://www.geonames.org/</a> ). It can be
+used to create conditions on countries or cities.</p>
+</div>
+<div class="paragraph">
+<p>In order to use it, you need to install the Geonames database into . Get the "allCountries.zip" database from here :
+<a href="http://download.geonames.org/export/dump/">http://download.geonames.org/export/dump/</a></p>
+</div>
+<div class="paragraph">
+<p>Download it and put it in the "etc" directory, without unzipping it.
+Edit $MY_KARAF_HOME/etc/org.apache.unomi.geonames.cfg and set request.geonamesDatabase.forceImport to true, import should start right away.
+Otherwise, import should start at the next startup. Import runs in background, but can take about 15 minutes.
+At the end, you should have about 4 million entries in the geonames index.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_rest_api_security">2.6.4. REST API Security</h4>
+<div class="paragraph">
+<p>The Context Server REST API is protected using JAAS authentication and using Basic or Digest HTTP auth.
+By default, the login/password for the REST API full administrative access is "karaf/karaf".</p>
+</div>
+<div class="paragraph">
+<p>The generated package is also configured with a default SSL certificate. You can change it by following these steps :</p>
+</div>
+<div class="paragraph">
+<p>.</p>
+</div>
+<div class="paragraph">
+<p>Replace the existing keystore in $MY_KARAF_HOME/etc/keystore by your own certificate :</p>
+</div>
+<div class="paragraph">
+<p><a href="http://wiki.eclipse.org/Jetty/Howto/Configure_SSL">http://wiki.eclipse.org/Jetty/Howto/Configure_SSL</a></p>
+</div>
+<div class="paragraph">
+<p>.</p>
+</div>
+<div class="paragraph">
+<p>Update the keystore and certificate password in $MY_KARAF_HOME/etc/custom.properties file :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>    org.osgi.service.http.secure.enabled = true
+    org.ops4j.pax.web.ssl.keystore=${karaf.etc}/keystore
+    org.ops4j.pax.web.ssl.password=changeme
+    org.ops4j.pax.web.ssl.keypassword=changeme
+    org.osgi.service.http.port.secure=9443</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>You should now have SSL setup on Karaf with your certificate, and you can test it by trying to access it on port 9443.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_automatic_profile_merging">2.6.5. Automatic profile merging</h4>
+<div class="paragraph">
+<p>The context server is capable of merging profiles based on a common property value. In order to use this, you must
+add the MergeProfileOnPropertyAction to a rule (such as a login rule for example), and configure it with the name
+ of the property that will be used to identify the profiles to be merged. An example could be the "email" property,
+ meaning that if two (or more) profiles are found to have the same value for the "email" property they will be merged
+ by this action.</p>
+</div>
+<div class="paragraph">
+<p>Upon merge, the old profiles are marked with a "mergedWith" property that will be used on next profile access to delete
+the original profile and replace it with the merged profile (aka "master" profile). Once this is done, all cookie tracking
+will use the merged profile.</p>
+</div>
+<div class="paragraph">
+<p>To test, simply configure the action in the "login" or "facebookLogin" rules and set it up on the "email" property.
+Upon sending one of the events, all matching profiles will be merged.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_securing_a_production_environment">2.6.6. Securing a production environment</h4>
+<div class="paragraph">
+<p>Before going live with a project, you should <em>absolutely</em> read the following section that will help you setup a proper
+secure environment for running your context server.</p>
+</div>
+<div class="paragraph">
+<p>Step 1: Install and configure a firewall</p>
+</div>
+<div class="paragraph">
+<p>You should setup a firewall around your cluster of context servers and/or Elasticsearch nodes. If you have an
+application-level firewall you should only allow the following connections open to the whole world :</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><a href="http://localhost:8181/context.js">http://localhost:8181/context.js</a></p>
+</li>
+<li>
+<p><a href="http://localhost:8181/eventcollector">http://localhost:8181/eventcollector</a></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>All other ports should not be accessible to the world.</p>
+</div>
+<div class="paragraph">
+<p>For your Context Server client applications (such as the Jahia CMS), you will need to make the following ports
+accessible :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>8181 (Context Server HTTP port)
+9443 (Context Server HTTPS port)</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The context server actually requires HTTP Basic Auth for access to the Context Server administration REST API, so it is
+highly recommended that you design your client applications to use the HTTPS port for accessing the REST API.</p>
+</div>
+<div class="paragraph">
+<p>The user accounts to access the REST API are actually routed through Karaf&#8217;s JAAS support, which you may find the
+documentation for here :</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><a href="http://karaf.apache.org/manual/latest/users-guide/security.html">http://karaf.apache.org/manual/latest/users-guide/security.html</a></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>The default username/password is</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>karaf/karaf</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>You should really change this default username/password as soon as possible. To do so, simply modify the following
+file :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>$MY_KARAF_HOME/etc/users.properties</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>For your context servers, and for any standalone Elasticsearch nodes you will need to open the following ports for proper
+node-to-node communication : 9200 (Elasticsearch REST API), 9300 (Elasticsearch TCP transport)</p>
+</div>
+<div class="paragraph">
+<p>Of course any ports listed here are the default ports configured in each server, you may adjust them if needed.</p>
+</div>
+<div class="paragraph">
+<p>Step 2 : Adjust the Context Server IP filtering</p>
+</div>
+<div class="paragraph">
+<p>By default the Context Server limits to connections to port 9200 and 9300 to the following IP ranges</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>- localhost
+- 127.0.0.1
+- ::1
+- the current subnet (i.e., 192.168.1.0-192.168.1.255)</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>(this is done using a custom plugin for Elasticsearch, that you may find here :
+<a href="https://git-wip-us.apache.org/repos/asf/incubator-unomi/context-server/persistence-elasticsearch/plugins/security">https://git-wip-us.apache.org/repos/asf/incubator-unomi/context-server/persistence-elasticsearch/plugins/security</a>)</p>
+</div>
+<div class="paragraph">
+<p>You can adjust this setting by using the following setting in the $MY_KARAF_HOME/etc/elasticsearch.yml file :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>security.ipranges: localhost,127.0.0.1,::1,10.0.1.0-10.0.1.255</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Step 3 : Follow industry recommended best practices for securing Elasticsearch</p>
+</div>
+<div class="paragraph">
+<p>You may find more valuable recommendations here :</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><a href="https://www.elastic.co/blog/found-elasticsearch-security">https://www.elastic.co/blog/found-elasticsearch-security</a></p>
+</li>
+<li>
+<p><a href="https://www.elastic.co/blog/scripting-security">https://www.elastic.co/blog/scripting-security</a></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Step 4 : Setup a proxy in front of the context server</p>
+</div>
+<div class="paragraph">
+<p>As an alternative to an application-level firewall, you could also route all traffic to the context server through
+a proxy, and use it to filter any communication.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_integrating_with_an_apache_http_web_server">2.6.7. Integrating with an Apache HTTP web server</h4>
+<div class="paragraph">
+<p>If you want to setup an Apache HTTP web server in from of Apache Unomi, here is an example configuration using
+mod_proxy.</p>
+</div>
+<div class="paragraph">
+<p>In your Unomi package directory, in /etc/org.apache.unomi.web.cfg for unomi.apache.org</p>
+</div>
+<div class="paragraph">
+<p>contextserver.address=unomi.apache.org
+ contextserver.port=80
+ contextserver.secureAddress=unomi.apache.org
+ contextserver.securePort=443
+ contextserver.domain=apache.org</p>
+</div>
+<div class="paragraph">
+<p>Main virtual host config:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>&lt;VirtualHost *:80&gt;
+        Include /var/www/vhosts/unomi.apache.org/conf/common.conf
+&lt;/VirtualHost&gt;
+
+&lt;IfModule mod_ssl.c&gt;
+    &lt;VirtualHost *:443&gt;
+        Include /var/www/vhosts/unomi.apache.org/conf/common.conf
+
+        SSLEngine on
+
+        SSLCertificateFile    /var/www/vhosts/unomi.apache.org/conf/ssl/24d5b9691e96eafa.crt
+        SSLCertificateKeyFile /var/www/vhosts/unomi.apache.org/conf/ssl/apache.org.key
+        SSLCertificateChainFile /var/www/vhosts/unomi.apache.org/conf/ssl/gd_bundle-g2-g1.crt
+
+        &lt;FilesMatch "\.(cgi|shtml|phtml|php)$"&gt;
+                SSLOptions +StdEnvVars
+        &lt;/FilesMatch&gt;
+        &lt;Directory /usr/lib/cgi-bin&gt;
+                SSLOptions +StdEnvVars
+        &lt;/Directory&gt;
+        BrowserMatch "MSIE [2-6]" \
+                nokeepalive ssl-unclean-shutdown \
+                downgrade-1.0 force-response-1.0
+        BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
+
+    &lt;/VirtualHost&gt;
+&lt;/IfModule&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>common.conf:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>ServerName unomi.apache.org
+ServerAdmin webmaster@apache.org
+
+DocumentRoot /var/www/vhosts/unomi.apache.org/html
+CustomLog /var/log/apache2/access-unomi.apache.org.log combined
+&lt;Directory /&gt;
+        Options FollowSymLinks
+        AllowOverride None
+&lt;/Directory&gt;
+&lt;Directory /var/www/vhosts/unomi.apache.org/html&gt;
+        Options FollowSymLinks MultiViews
+        AllowOverride None
+        Order allow,deny
+        allow from all
+&lt;/Directory&gt;
+&lt;Location /cxs&gt;
+    Order deny,allow
+    deny from all
+    allow from 88.198.26.2
+    allow from www.apache.org
+&lt;/Location&gt;
+
+RewriteEngine On
+RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
+RewriteRule .* - [F]
+ProxyPreserveHost On
+ProxyPass /server-status !
+ProxyPass /robots.txt !
+
+RewriteCond %{HTTP_USER_AGENT} Googlebot [OR]
+RewriteCond %{HTTP_USER_AGENT} msnbot [OR]
+RewriteCond %{HTTP_USER_AGENT} Slurp
+RewriteRule ^.* - [F,L]
+
+ProxyPass / http://localhost:8181/ connectiontimeout=20 timeout=300 ttl=120
+ProxyPassReverse / http://localhost:8181/</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_cluster_setup">3. Cluster setup</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_cluster_setup_2">3.1. Cluster setup</h3>
+<div class="paragraph">
+<p>Context server relies on Elasticsearch to discover and configure its cluster. You just need to install multiple context
+servers on the same network, and enable the discovery protocol in $MY_KARAF_HOME/etc/org.apache.unomi.persistence.elasticsearch.cfg file :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>discovery.zen.ping.multicast.enabled=true</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>All nodes on the same network, sharing the same cluster name will be part of the same cluster.</p>
+</div>
+<div class="sect3">
+<h4 id="_recommended_configurations">3.1.1. Recommended configurations</h4>
+<div class="paragraph">
+<p>It is recommended to have one node dedicated to the context server, where the other nodes take care of the
+Elasticsearch persistence. The node dedicated to the context server will have node.data set to false.</p>
+</div>
+<div class="sect4">
+<h5 id="_2_nodes_configuration">2 nodes configuration</h5>
+<div class="paragraph">
+<p>One node dedicated to context server, 1 node for elasticsearch storage.</p>
+</div>
+<div class="paragraph">
+<p>Node A :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>node.data=true
+numberOfReplicas=0
+monthlyIndex.numberOfReplicas=0</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Node B :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>node.data=false
+numberOfReplicas=0
+monthlyIndex.numberOfReplicas=0</code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_3_nodes_configuration">3 nodes configuration</h5>
+<div class="paragraph">
+<p>One node dedicated to context server, 2 nodes for elasticsearch storage with fault-tolerance</p>
+</div>
+<div class="paragraph">
+<p>Node A :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>node.data=false
+numberOfReplicas=1
+monthlyIndex.numberOfReplicas=1</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Node B :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>node.data=true
+numberOfReplicas=1
+monthlyIndex.numberOfReplicas=1</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Node C :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>node.data=true
+numberOfReplicas=1
+monthlyIndex.numberOfReplicas=1</code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_specific_configuration">3.1.2. Specific configuration</h4>
+<div class="paragraph">
+<p>If multicast is not allowed on your network, you&#8217;ll need to switch to unicast protocol and manually configure the server IPs. This can be
+done by disabling the elasticsearch automatic discovery in $MY_KARAF_HOME/etc/org.apache.unomi.persistence.elasticsearch.cfg :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>discovery.zen.ping.multicast.enabled=false</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>And then set the property discovery.zen.ping.unicast.hosts in $MY_KARAF_HOME/etc/elasticsearch.yml files :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>discovery.zen.ping.unicast.hosts: [‘192.168.0.1:9300', ‘192.168.0.2:9300']</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>More information and configuration options can be found at :
+<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html</a></p>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2018-09-10 11:30:03 CEST
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file