Added: unomi/website/manual/1_6_x/index.html
URL: 
http://svn.apache.org/viewvc/unomi/website/manual/1_6_x/index.html?rev=1898498&view=auto
==============================================================================
--- unomi/website/manual/1_6_x/index.html (added)
+++ unomi/website/manual/1_6_x/index.html Tue Mar  1 08:17:59 2022
@@ -0,0 +1,9408 @@
+<!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.8">
+<meta name="author" content="Apache Software Foundation">
+<title>Apache Unomi 1.x - Documentation</title>
+<link rel="stylesheet" href="./apache.css">
+</head>
+<body class="article toc2 toc-left">
+<div id="header">
+<h1>Apache Unomi 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="#_quick_start">1. Quick start</a>
+<ul class="sectlevel2">
+<li><a href="#_five_minutes_quickstart">1.1. Five Minutes QuickStart</a></li>
+</ul>
+</li>
+<li><a href="#_first_steps_with_apache_unomi">2. First steps with Apache 
Unomi</a>
+<ul class="sectlevel2">
+<li><a href="#_getting_started_with_unomi">2.1. Getting started with Unomi</a>
+<ul class="sectlevel3">
+<li><a href="#_prerequisites">2.1.1. Prerequisites</a></li>
+<li><a href="#_running_unomi">2.1.2. Running Unomi</a></li>
+</ul>
+</li>
+<li><a href="#_recipes">2.2. Recipes</a>
+<ul class="sectlevel3">
+<li><a href="#_introduction">2.2.1. Introduction</a></li>
+<li><a href="#_how_to_read_a_profile">2.2.2. How to read a profile</a></li>
+<li><a href="#_how_to_update_a_profile_from_the_public_internet">2.2.3. How to 
update a profile from the public internet</a></li>
+<li><a href="#_how_to_search_for_profile_events">2.2.4. How to search for 
profile events</a></li>
+<li><a href="#_how_to_create_a_new_rule">2.2.5. How to create a new 
rule</a></li>
+<li><a href="#_how_to_search_for_profiles">2.2.6. How to search for 
profiles</a></li>
+<li><a href="#_getting_updating_consents">2.2.7. Getting / updating 
consents</a></li>
+<li><a href="#_how_to_send_a_login_event_to_unomi">2.2.8. How to send a login 
event to Unomi</a></li>
+</ul>
+</li>
+<li><a href="#_request_examples">2.3. Request examples</a>
+<ul class="sectlevel3">
+<li><a href="#_retrieving_your_first_context">2.3.1. Retrieving your first 
context</a></li>
+<li><a href="#_retrieving_a_context_as_a_json_object">2.3.2. Retrieving a 
context as a JSON object.</a></li>
+<li><a href="#_accessing_profile_properties_in_a_context">2.3.3. Accessing 
profile properties in a context</a></li>
+<li><a href="#_sending_events_using_the_context_servlet">2.3.4. Sending events 
using the context servlet</a></li>
+<li><a href="#_sending_events_using_the_eventcollector_servlet">2.3.5. Sending 
events using the eventcollector servlet</a></li>
+<li><a href="#_where_to_go_from_here">2.3.6. Where to go from here</a></li>
+</ul>
+</li>
+<li><a href="#_web_tracker">2.4. Web Tracker</a>
+<ul class="sectlevel3">
+<li><a href="#_getting_started">2.4.1. Getting started</a></li>
+<li><a href="#_how_to_contribute">2.4.2. How to contribute</a></li>
+<li><a href="#_tracking_page_views">2.4.3. Tracking page views</a></li>
+<li><a href="#_tracking_form_submissions">2.4.4. Tracking form 
submissions</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_configuration">3. Configuration</a>
+<ul class="sectlevel2">
+<li><a href="#_centralized_configuration">3.1. Centralized 
configuration</a></li>
+<li><a 
href="#_changing_the_default_configuration_using_environment_variables_i_e_docker_configuration">3.2.
 Changing the default configuration using environment variables (i.e. Docker 
configuration)</a></li>
+<li><a href="#_changing_the_default_configuration_using_property_files">3.3. 
Changing the default configuration using property files</a></li>
+<li><a href="#_secured_events_configuration">3.4. Secured events 
configuration</a></li>
+<li><a href="#_installing_the_maxmind_geoiplite2_ip_lookup_database">3.5. 
Installing the MaxMind GeoIPLite2 IP lookup database</a></li>
+<li><a href="#_installing_geonames_database">3.6. Installing Geonames 
database</a></li>
+<li><a href="#_rest_api_security">3.7. REST API Security</a></li>
+<li><a href="#_scripting_security">3.8. Scripting security</a>
+<ul class="sectlevel3">
+<li><a href="#_multi_layer_scripting_filtering_system">3.8.1. Multi-layer 
scripting filtering system</a></li>
+<li><a href="#_scripts_and_expressions">3.8.2. Scripts and expressions</a></li>
+<li><a href="#_scripting_expression_filtering_configuration_parameters">3.8.3. 
Scripting expression filtering configuration parameters</a></li>
+<li><a href="#_groovy_actions">3.8.4. Groovy Actions</a></li>
+<li><a href="#_scripting_roadmap">3.8.5. Scripting roadmap</a></li>
+</ul>
+</li>
+<li><a href="#_automatic_profile_merging">3.9. Automatic profile 
merging</a></li>
+<li><a href="#_securing_a_production_environment">3.10. Securing a production 
environment</a></li>
+<li><a href="#_integrating_with_an_apache_http_web_server">3.11. Integrating 
with an Apache HTTP web server</a></li>
+<li><a href="#_changing_the_default_tracking_location">3.12. Changing the 
default tracking location</a></li>
+<li><a href="#_apache_karaf_ssh_console">3.13. Apache Karaf SSH 
Console</a></li>
+<li><a href="#_elasticsearch_authentication_and_security">3.14. ElasticSearch 
authentication and security</a>
+<ul class="sectlevel3">
+<li><a href="#_user_authentication">3.14.1. User authentication !</a></li>
+<li><a href="#_ssl_communication">3.14.2. SSL communication</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_migrations">4. Migrations</a>
+<ul class="sectlevel2">
+<li><a href="#_from_version_1_4_to_1_5">4.1. From version 1.4 to 1.5</a>
+<ul class="sectlevel3">
+<li><a href="#_data_model_and_elasticsearch_7">4.1.1. Data model and 
ElasticSearch 7</a></li>
+<li><a href="#_api_changes">4.1.2. API changes</a></li>
+<li><a href="#_migration_steps">4.1.3. Migration steps</a></li>
+</ul>
+</li>
+<li><a 
href="#_important_changes_in_public_servlets_since_version_1_5_5_and_2_0_0">4.2.
 Important changes in public servlets since version 1.5.5 and 2.0.0</a></li>
+</ul>
+</li>
+<li><a href="#_queries_and_aggregations">5. Queries and aggregations</a>
+<ul class="sectlevel2">
+<li><a href="#_query_counts">5.1. Query counts</a></li>
+<li><a href="#_metrics">5.2. Metrics</a></li>
+<li><a href="#_aggregations">5.3. Aggregations</a>
+<ul class="sectlevel3">
+<li><a href="#_aggregation_types">5.3.1. Aggregation types</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_profile_import_export">6. Profile import &amp; export</a>
+<ul class="sectlevel2">
+<li><a href="#_importing_profiles">6.1. Importing profiles</a>
+<ul class="sectlevel3">
+<li><a href="#_import_api">6.1.1. Import API</a></li>
+</ul>
+</li>
+<li><a href="#_exporting_profiles">6.2. Exporting profiles</a>
+<ul class="sectlevel3">
+<li><a href="#_export_api">6.2.1. Export API</a></li>
+</ul>
+</li>
+<li><a href="#_configuration_in_details">6.3. Configuration in details</a></li>
+</ul>
+</li>
+<li><a href="#_consent_management">7. Consent management</a>
+<ul class="sectlevel2">
+<li><a href="#_consent_api">7.1. Consent API</a>
+<ul class="sectlevel3">
+<li><a href="#_profiles_with_consents">7.1.1. Profiles with consents</a></li>
+<li><a href="#_consent_type_definitions">7.1.2. Consent type 
definitions</a></li>
+<li><a href="#_creating_update_a_visitor_consent">7.1.3. Creating / update a 
visitor consent</a></li>
+<li><a href="#_how_it_works_internally">7.1.4. How it works 
(internally)</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_privacy_management">8. Privacy management</a>
+<ul class="sectlevel2">
+<li><a href="#_setting_up_access_to_the_privacy_endpoint">8.1. Setting up 
access to the privacy endpoint</a></li>
+<li><a href="#_anonymizing_a_profile">8.2. Anonymizing a profile</a></li>
+<li><a href="#_downloading_profile_data">8.3. Downloading profile data</a></li>
+<li><a href="#_deleting_a_profile">8.4. Deleting a profile</a></li>
+<li><a href="#_related">8.5. Related</a></li>
+</ul>
+</li>
+<li><a href="#_cluster_setup">9. Cluster setup</a>
+<ul class="sectlevel2">
+<li><a href="#_cluster_setup_2">9.1. Cluster setup</a></li>
+</ul>
+</li>
+<li><a href="#_reference">10. Reference</a>
+<ul class="sectlevel2">
+<li><a href="#_useful_apache_unomi_urls">10.1. Useful Apache Unomi 
URLs</a></li>
+<li><a href="#_how_profile_tracking_works">10.2. How profile tracking works</a>
+<ul class="sectlevel3">
+<li><a href="#_steps">10.2.1. Steps</a></li>
+</ul>
+</li>
+<li><a href="#_context_request_flow">10.3. Context Request Flow</a></li>
+<li><a href="#_data_model_overview">10.4. Data Model Overview</a></li>
+<li><a href="#_scope">10.5. Scope</a>
+<ul class="sectlevel3">
+<li><a href="#_example">10.5.1. Example</a></li>
+</ul>
+</li>
+<li><a href="#_item">10.6. Item</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition">10.6.1. Structure definition</a></li>
+</ul>
+</li>
+<li><a href="#_metadata">10.7. Metadata</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_2">10.7.1. Structure definition</a></li>
+<li><a href="#_example_2">10.7.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_metadataitem">10.8. MetadataItem</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_3">10.8.1. Structure definition</a></li>
+<li><a href="#_example_3">10.8.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_event">10.9. Event</a>
+<ul class="sectlevel3">
+<li><a href="#_fields">10.9.1. Fields</a></li>
+<li><a href="#_event_types">10.9.2. Event types</a></li>
+</ul>
+</li>
+<li><a href="#_profile">10.10. Profile</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_4">10.10.1. Structure definition</a></li>
+<li><a href="#_example_4">10.10.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_persona">10.11. Persona</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_5">10.11.1. Structure definition</a></li>
+<li><a href="#_example_5">10.11.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_consent">10.12. Consent</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_6">10.12.1. Structure definition</a></li>
+<li><a href="#_example_6">10.12.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_session">10.13. Session</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_7">10.13.1. Structure definition</a></li>
+<li><a href="#_example_7">10.13.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_segment">10.14. Segment</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_8">10.14.1. Structure definition</a></li>
+<li><a href="#_example_8">10.14.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_condition">10.15. Condition</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_9">10.15.1. Structure definition</a></li>
+<li><a href="#_example_9">10.15.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_rule">10.16. Rule</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_10">10.16.1. Structure definition</a></li>
+<li><a href="#_example_10">10.16.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_action">10.17. Action</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_11">10.17.1. Structure definition</a></li>
+<li><a href="#_example_11">10.17.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_list">10.18. List</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_12">10.18.1. Structure definition</a></li>
+<li><a href="#_example_12">10.18.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_goal">10.19. Goal</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_13">10.19.1. Structure definition</a></li>
+<li><a href="#_example_13">10.19.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_campaign">10.20. Campaign</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_14">10.20.1. Structure definition</a></li>
+<li><a href="#_example_14">10.20.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_scoring_plan">10.21. Scoring plan</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_15">10.21.1. Structure definition</a></li>
+<li><a href="#_example_15">10.21.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_event_types">10.22. Built-in Event types</a>
+<ul class="sectlevel3">
+<li><a href="#_login_event_type">10.22.1. Login event type</a></li>
+<li><a href="#_view_event_type">10.22.2. View event type</a></li>
+<li><a href="#_form_event_type">10.22.3. Form event type</a></li>
+<li><a href="#_update_properties_event_type">10.22.4. Update properties event 
type</a></li>
+<li><a href="#_identify_event_type">10.22.5. Identify event type</a></li>
+<li><a href="#_session_created_event_type">10.22.6. Session created event 
type</a></li>
+<li><a href="#_goal_event_type">10.22.7. Goal event type</a></li>
+<li><a href="#_modify_consent_event_type">10.22.8. Modify consent event 
type</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_condition_types">10.23. Built-in condition types</a>
+<ul class="sectlevel3">
+<li><a href="#_existing_condition_type_descriptors">10.23.1. Existing 
condition type descriptors</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_action_types">10.24. Built-in action types</a>
+<ul class="sectlevel3">
+<li><a href="#_existing_action_types_descriptors">10.24.1. Existing action 
types descriptors</a></li>
+</ul>
+</li>
+<li><a href="#_updating_events_using_the_context_servlet">10.25. Updating 
Events Using the Context Servlet</a>
+<ul class="sectlevel3">
+<li><a href="#_solution">10.25.1. Solution</a></li>
+<li><a href="#_defining_rules">10.25.2. Defining Rules</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_integration_samples">11. Integration samples</a>
+<ul class="sectlevel2">
+<li><a href="#_samples">11.1. Samples</a></li>
+<li><a href="#_login_sample">11.2. Login sample</a>
+<ul class="sectlevel3">
+<li><a href="#_warning">11.2.1. Warning !</a></li>
+<li><a href="#_installing_the_samples">11.2.2. Installing the samples</a></li>
+</ul>
+</li>
+<li><a href="#_twitter_sample">11.3. Twitter sample</a>
+<ul class="sectlevel3">
+<li><a href="#_overview">11.3.1. Overview</a></li>
+<li><a href="#_interacting_with_the_context_server">11.3.2. Interacting with 
the context server</a></li>
+<li><a 
href="#_retrieving_context_information_from_unomi_using_the_context_servlet">11.3.3.
 Retrieving context information from Unomi using the context servlet</a></li>
+</ul>
+</li>
+<li><a href="#_example_24">11.4. Example</a>
+<ul class="sectlevel3">
+<li><a href="#_html_page">11.4.1. HTML page</a></li>
+<li><a href="#_javascript">11.4.2. Javascript</a></li>
+</ul>
+</li>
+<li><a href="#_conclusion">11.5. Conclusion</a></li>
+<li><a href="#_annex">11.6. Annex</a></li>
+<li><a href="#_weather_update_sample">11.7. Weather update sample</a></li>
+</ul>
+</li>
+<li><a href="#_connectors">12. Connectors</a>
+<ul class="sectlevel2">
+<li><a href="#_connectors_2">12.1. Connectors</a>
+<ul class="sectlevel3">
+<li><a href="#_call_for_contributors">12.1.1. Call for contributors</a></li>
+</ul>
+</li>
+<li><a href="#_salesforce_connector">12.2. Salesforce Connector</a>
+<ul class="sectlevel3">
+<li><a href="#_getting_started_2">12.2.1. Getting started</a></li>
+<li><a href="#_properties">12.2.2. Properties</a></li>
+<li><a 
href="#_hot_deploying_updates_to_the_salesforce_connector_for_developers">12.2.3.
 Hot-deploying updates to the Salesforce connector (for developers)</a></li>
+<li><a href="#_using_the_salesforce_workbench_for_testing_rest_api">12.2.4. 
Using the Salesforce Workbench for testing REST API</a></li>
+<li><a href="#_setting_up_streaming_push_queries">12.2.5. Setting up Streaming 
Push queries</a></li>
+<li><a href="#_executing_the_unit_tests">12.2.6. Executing the unit 
tests</a></li>
+</ul>
+</li>
+<li><a href="#_mailchimp_connector">12.3. MailChimp Connector</a>
+<ul class="sectlevel3">
+<li><a href="#_getting_started_3">12.3.1. Getting started</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_developers">13. Developers</a>
+<ul class="sectlevel2">
+<li><a href="#_building">13.1. Building</a>
+<ul class="sectlevel3">
+<li><a href="#_initial_setup">13.1.1. Initial Setup</a></li>
+<li><a href="#_building_2">13.1.2. Building</a></li>
+<li><a href="#_installing_an_elasticsearch_server">13.1.3. Installing an 
ElasticSearch server</a></li>
+<li><a href="#_deploying_the_generated_binary_package">13.1.4. Deploying the 
generated binary package</a></li>
+<li><a href="#_deploying_into_an_existing_karaf_server">13.1.5. Deploying into 
an existing Karaf server</a></li>
+<li><a href="#_jdk_selection_on_mac_os_x">13.1.6. JDK Selection on Mac OS 
X</a></li>
+<li><a href="#_running_the_integration_tests">13.1.7. Running the integration 
tests</a></li>
+<li><a href="#_testing_with_an_example_page">13.1.8. Testing with an example 
page</a></li>
+</ul>
+</li>
+<li><a href="#_ssh_shell_commands">13.2. SSH Shell Commands</a>
+<ul class="sectlevel3">
+<li><a href="#_using_the_shell">13.2.1. Using the shell</a></li>
+<li><a href="#_lifecycle_commands">13.2.2. Lifecycle commands</a></li>
+<li><a href="#_runtime_commands">13.2.3. Runtime commands</a></li>
+</ul>
+</li>
+<li><a href="#_writing_plugins">13.3. Writing Plugins</a></li>
+<li><a href="#_types_vs_instances">13.4. Types vs. instances</a></li>
+<li><a href="#_plugin_structure">13.5. Plugin structure</a></li>
+<li><a href="#_extension_points">13.6. Extension points</a>
+<ul class="sectlevel3">
+<li><a href="#_actiontype">13.6.1. ActionType</a></li>
+<li><a href="#_conditiontype">13.6.2. ConditionType</a></li>
+<li><a href="#_persona_2">13.6.3. Persona</a></li>
+<li><a href="#_propertymergestrategytype">13.6.4. 
PropertyMergeStrategyType</a></li>
+<li><a href="#_propertytype">13.6.5. PropertyType</a></li>
+<li><a href="#_rule_2">13.6.6. Rule</a></li>
+<li><a href="#_scoring">13.6.7. Scoring</a></li>
+<li><a href="#_segments">13.6.8. Segments</a></li>
+<li><a href="#_tag">13.6.9. Tag</a></li>
+<li><a href="#_valuetype">13.6.10. ValueType</a></li>
+</ul>
+</li>
+<li><a href="#_custom_plugins">13.7. Custom plugins</a>
+<ul class="sectlevel3">
+<li><a href="#_creating_a_plugin">13.7.1. Creating a plugin</a></li>
+<li><a href="#_deployment_and_custom_definition">13.7.2. Deployment and custom 
definition</a></li>
+<li><a href="#_predefined_segments">13.7.3. Predefined segments</a></li>
+<li><a href="#_predefined_rules">13.7.4. Predefined rules</a></li>
+<li><a href="#_predefined_properties">13.7.5. Predefined properties</a></li>
+<li><a href="#_predefined_child_conditions">13.7.6. Predefined child 
conditions</a></li>
+<li><a href="#_predefined_personas">13.7.7. Predefined personas</a></li>
+<li><a href="#_custom_action_types">13.7.8. Custom action types</a></li>
+<li><a href="#_custom_condition_types">13.7.9. Custom condition types</a></li>
+</ul>
+</li>
+<li><a href="#_migration_patches">13.8. Migration patches</a></li>
+</ul>
+</li>
+</ul>
+</div>
+</div>
+<div id="content">
+<div id="preamble">
+<div class="sectionbody">
+<div class="imageblock text-center">
+<div class="content">
+<img src="images/asf_logo_url.png" alt="asf logo url">
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_quick_start">1. Quick start</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_five_minutes_quickstart">1.1. Five Minutes QuickStart</h3>
+<div class="paragraph">
+<p>1) Install JDK 8 (<a 
href="https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html";
 
class="bare">https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html</a>)
 and make sure you set the
+JAVA_HOME variable <a 
href="https://docs.oracle.com/cd/E19182-01/820-7851/inst_cli_jdk_javahome_t/"; 
class="bare">https://docs.oracle.com/cd/E19182-01/820-7851/inst_cli_jdk_javahome_t/</a>
 (see our <a href="#_jdk_compatibility">Getting Started</a> guide for more 
information on JDK compatibility)</p>
+</div>
+<div class="paragraph">
+<p>2) Download ElasticSearch here : <a 
href="https://www.elastic.co/downloads/past-releases/elasticsearch-7-4-2"; 
class="bare">https://www.elastic.co/downloads/past-releases/elasticsearch-7-4-2</a>
 (please &lt;strong&gt;make sure&lt;/strong&gt; you use the proper version : 
7.4.2)</p>
+</div>
+<div class="paragraph">
+<p>3) Uncompress it and change the <code>config/elasticsearch.yml</code> to 
include the following config : &lt;code&gt;cluster.name: 
contextElasticSearch&lt;/code&gt;</p>
+</div>
+<div class="paragraph">
+<p>4) Launch ElasticSearch using : <code>bin/elasticsearch</code></p>
+</div>
+<div class="paragraph">
+<p>5) Download Apache Unomi here : <a 
href="https://unomi.apache.org/download.html"; 
class="bare">https://unomi.apache.org/download.html</a></p>
+</div>
+<div class="paragraph">
+<p>6) Start it using : <code>./bin/karaf</code></p>
+</div>
+<div class="paragraph">
+<p>7) Start the Apache Unomi packages using <code>unomi:start</code> in the 
Apache Karaf Shell</p>
+</div>
+<div class="paragraph">
+<p>8) Wait for startup to complete</p>
+</div>
+<div class="paragraph">
+<p>9) Try accessing <a href="https://localhost:9443/cxs/cluster"; 
class="bare">https://localhost:9443/cxs/cluster</a> with username/password: 
<code>karaf/karaf</code> . You might get a certificate warning in your browser, 
just accept it despite the warning it is safe.</p>
+</div>
+<div class="paragraph">
+<p>10) Request your first context by simply accessing : <a 
href="http://localhost:8181/cxs/context.js?sessionId=1234"; 
class="bare">http://localhost:8181/cxs/context.js?sessionId=1234</a></p>
+</div>
+<div class="paragraph">
+<p>11) If something goes wrong, you should check the logs in 
<code>./data/log/karaf.log</code>. If you get errors on ElasticSearch,
+make sure you are using the proper version.</p>
+</div>
+<div class="paragraph">
+<p>Next steps:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Connect to <a href="http://localhost:8181"; 
class="bare">http://localhost:8181</a> to try our some live examples (such as 
the web tracker)</p>
+</li>
+<li>
+<p>Trying our integration <a href="#_samples">samples page</a></p>
+</li>
+<li>
+<p>Learning more about the <a href="#_web_tracker">web tracker</a></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_first_steps_with_apache_unomi">2. First steps with Apache Unomi</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_getting_started_with_unomi">2.1. 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.1.1. Prerequisites</h4>
+<div class="paragraph">
+<p>This document assumes working knowledge of <a 
href="https://git-scm.com/";>git</a> to be able to retrieve the code for Unomi 
and the example.
+Additionally, you will require a working Java 8 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 8 or greater.</p>
+</div>
+<div class="sect4">
+<h5 id="_jdk_compatibility">JDK compatibility</h5>
+<div class="paragraph">
+<p>Starting with Java 9, Oracle made some big changes to the Java platform 
releases. This is why Apache Unomi is focused on
+supporting the Long Term Supported versions of the JDK, currently versions 8 
and 11. We do not test with intermediate
+versions so they may or may not work properly. Currently the most tested 
version is version 8 and version 11 is also
+supported.</p>
+</div>
+<div class="paragraph">
+<p>Also, as there are new licensing restrictions on JDKs provided by Oracle 
for production usages, Apache Unomi has also
+added support for OpenJDK builds. Other JDK distributions might also work but 
are not regularly tested so you should use
+them at your own risks.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_elasticsearch_compatibility">ElasticSearch compatibility</h5>
+<div class="paragraph">
+<p>Starting with version 1.5.0 Apache Unomi adds compatibility with 
ElasticSearch 7.4 . It is highly recommended to use the
+ElasticSearch version provided by the documentation when possible. However 
minor versions (7.4.x) should also work, and
+one version higher (7.5) will usually work. Going higher than that is risky 
given the way that ElasticSearch is developed
+and breaking changes are introduced quite often. If in doubt, don&#8217;t 
hesitate to check with the Apache Unomi community
+to get the latest information about ElasticSearch version compatibility.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_running_unomi">2.1.2. Running Unomi</h4>
+<div class="sect4">
+<h5 id="_start_unomi">Start Unomi</h5>
+<div class="paragraph">
+<p>Start Unomi according to the <a href="#_five_minutes_quickstart">five 
minutes quick start</a> or by compiling using the
+<a href="#_building">building 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/cxs/context.json"; 
class="bare">http://localhost:8181/cxs/context.json</a></code> (which 
isn&#8217;t very useful at this point).</p>
+</div>
+<div class="paragraph">
+<p>You can now find an introduction page at the following location: <a 
href="http://localhost:8181"; class="bare">http://localhost:8181</a></p>
+</div>
+<div class="paragraph">
+<p>Also now that your service is up and running you can go look at the
+<a href="#_request_examples">request examples</a> to learn basic
+requests you can do once your server is up and running.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_recipes">2.2. Recipes</h3>
+<div class="sect3">
+<h4 id="_introduction">2.2.1. Introduction</h4>
+<div class="paragraph">
+<p>In this section of the documentation we provide quick recipes focused on 
helping you achieve a specific result with
+Apache Unomi.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_read_a_profile">2.2.2. How to read a profile</h4>
+<div class="paragraph">
+<p>The simplest way to retrieve profile data for the current profile is to 
simply send a request to the /cxs/context.json
+endpoint. However you will need to send a body along with that request. 
Here&#8217;s an example:</p>
+</div>
+<div class="paragraph">
+<p>Here is an example that will retrieve all the session and profile 
properties, as well as the profile&#8217;s segments and scores</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST 
http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{
+    "source": {
+        "itemId":"homepage",
+        "itemType":"page",
+        "scope":"example"
+    },
+    "requiredProfileProperties":["*"],
+    "requiredSessionProperties":["*"],
+    "requireSegments":true,
+    "requireScores":true
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The <code>requiredProfileProperties</code> and 
<code>requiredSessionProperties</code> are properties that take an array of 
property names
+that should be retrieved. In this case we use the wildcard character '*' to 
say we want to retrieve all the available
+properties. The structure of the JSON object that you should send is a 
JSON-serialized version of the <a 
href="http://unomi.apache.org/unomi-api/apidocs/org/apache/unomi/api/ContextRequest.html";>ContextRequest</a>
+Java class.</p>
+</div>
+<div class="paragraph">
+<p>Note that it is also possible to access a profile&#8217;s data through the 
/cxs/profiles/ endpoint but that really should be
+reserved to administrative purposes. All public accesses should always use the 
/cxs/context.json endpoint for consistency
+and security.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_update_a_profile_from_the_public_internet">2.2.3. How to 
update a profile from the public internet</h4>
+<div class="paragraph">
+<p>Before we get into how to update a profile directly from a request coming 
from the public internet, we&#8217;ll quickly talk
+first about how NOT to do it, because we often see users using the following 
anti-patterns.</p>
+</div>
+<div class="sect4">
+<h5 id="_how_not_to_update_a_profile_from_the_public_internet">How NOT to 
update a profile from the public internet</h5>
+<div class="paragraph">
+<p>Please avoid using the /cxs/profile endpoint. This endpoint was initially 
the only way to update a profile but it has
+multiple issues:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>it requires authenticated access. The temptation can be great to use this 
endpoint because it is simple to access
+but the risk is that developers might include the credentials to access it in 
non-secure parts of code such as
+client-side code. Since there is no difference between this endpoint and any 
other administration-focused endpoints,
+attackers could easily re-use stolen credentials to wreak havock on the whole 
platform.</p>
+</li>
+<li>
+<p>No history of profile modifications is kept: this can be a problem for 
multiple reasons: you might want to keep an
+trail of profile modifications, or even a history of profile values in case 
you want to understand how a profile
+property was modified.</p>
+</li>
+<li>
+<p>Even when protected using some kind of proxy, potentially the whole profile 
properties might be modified, including
+ones that you might not want to be overriden.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_recommended_ways_to_update_a_profile">Recommended ways to update a 
profile</h5>
+<div class="paragraph">
+<p>Instead you can use the following solutions to update profiles:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>(Preferred) Use you own custom event(s) to send data you want to be 
inserted in a profile, and use rules to map the
+event data to the profile. This is simpler than it sounds, as usually all it 
requires is setting up a simple rule and
+you&#8217;re ready to update profiles using events. This is also the safest 
way to update a profile because if you design your
+events to be as specific as possible to your needs, only the data that you 
specified will be copied to the profile,
+making sure that even in the case an attacker tries to send more data using 
your custom event it will simply be ignored.</p>
+</li>
+<li>
+<p>Use the protected built-in "updateProperties" event. This event is designed 
to be used for administrative purposes
+only. Again, prefer the custom events solution because as this is a protected 
event it will require sending the Unomi
+key as a request header, and as Unomi only supports a single key for the 
moment it could be problematic if the key is
+intercepted. But at least by using an event you will get the benefits of 
auditing and historical property modification
+tracing.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Let&#8217;s go into more detail about the preferred way to update a 
profile. Let&#8217;s consider the following example of a rule:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST http://localhost:8181/cxs/rules \
+--user karaf:karaf \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{
+  "metadata": {
+    "id": "setContactInfo",
+    "name": "Copy the received contact info to the current profile",
+    "description": "Copies the contact info received in a custom event called 
'contactInfoSubmitted' to the current profile"
+  },
+  "raiseEventOnlyOnceForSession": false,
+  "condition": {
+    "type": "eventTypeCondition",
+    "parameterValues": {
+      "eventTypeId": "contactInfoSubmitted"
+    }
+  },
+  "actions": [
+    {
+      "type": "setPropertyAction",
+      "parameterValues": {
+        "setPropertyName": "properties(firstName)",
+        "setPropertyValue": "eventProperty::properties(firstName)",
+        "setPropertyStrategy": "alwaysSet"
+      }
+    },
+    {
+      "type": "setPropertyAction",
+      "parameterValues": {
+        "setPropertyName": "properties(lastName)",
+        "setPropertyValue": "eventProperty::properties(lastName)",
+        "setPropertyStrategy": "alwaysSet"
+      }
+    },
+    {
+      "type": "setPropertyAction",
+      "parameterValues": {
+        "setPropertyName": "properties(email)",
+        "setPropertyValue": "eventProperty::properties(email)",
+        "setPropertyStrategy": "alwaysSet"
+      }
+    }
+  ]
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>What this rule does is that it listen for a custom event (events 
don&#8217;t need any registration, you can simply start
+sending them to Apache Unomi whenever you like) of type 'contactInfoSubmitted' 
and it will search for properties called
+'firstName', 'lastName' and 'email' and copy them over to the profile with 
corresponding property names. You could of
+course change any of the property names to find your needs. For example you 
might want to prefix the profile properties
+with the source of the event, such as 'mobileApp:firstName'.</p>
+</div>
+<div class="paragraph">
+<p>You could then simply send the <code>contactInfoSubmitted</code> event 
using a request similar to this one:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST 
http://localhost:8181/cxs/eventcollector \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{
+    "sessionId" : "1234",
+    "events":[
+        {
+            "eventType":"contactInfoSubmitted",
+            "scope": "example",
+            "source":{
+                "itemType": "site",
+                "scope":"example",
+                "itemId": "mysite"
+            },
+            "target":{
+                "itemType":"form",
+                "scope":"example",
+                "itemId":"contactForm"
+            },
+            "properties" : {
+              "firstName" : "John",
+              "lastName" : "Doe",
+              "email" : "[email protected]"
+            }
+        }
+    ]
+}
+EOF</code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_search_for_profile_events">2.2.4. How to search for profile 
events</h4>
+<div class="paragraph">
+<p>Sometimes you want to retrieve events for a known profile. You will need to 
provide a query in the body of the request
+that looks something like this (and <a 
href="https://unomi.apache.org/rest-api-doc/#1768188821";>documentation is 
available in the REST API</a>) :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST 
http://localhost:8181/cxs/events/search \
+--user karaf:karaf \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{ "offset" : 0,
+  "limit" : 20,
+  "condition" : {
+    "type": "eventPropertyCondition",
+    "parameterValues" : {
+      "propertyName" : "profileId",
+      "comparisonOperator" : "equals",
+      "propertyValue" : "PROFILE_ID"
+    }
+  }
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>where PROFILE_ID is a profile identifier. This will indeed retrieve all the 
events for a given profile.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_create_a_new_rule">2.2.5. How to create a new rule</h4>
+<div class="paragraph">
+<p>There are basically two ways to create a new rule :</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Using the REST API</p>
+</li>
+<li>
+<p>Packaging it as a predefined rule in a plugin</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>In both cases the JSON structure for the rule will be exactly the same, and 
in most scenarios it will be more
+interesting to use the REST API to create and manipulate rules, as they 
don&#8217;t require any development or deployments
+on the Apache Unomi server.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST http://localhost:8181/cxs/rules \
+--user karaf:karaf \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{
+  "metadata": {
+    "id": "exampleEventCopy",
+    "name": "Example Copy Event to Profile",
+    "description": "Copy event properties to profile properties"
+  },
+  "condition": {
+      "type": "eventTypeCondition",
+      "parameterValues": {
+        "eventTypeId" : "myEvent"
+      }
+  },
+  "actions": [
+    {
+      "parameterValues": {
+      },
+      "type": "allEventToProfilePropertiesAction"
+    }
+  ]
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The above rule will be executed if the incoming event is of type 
<code>myEvent</code> and will simply copy all the properties
+contained in the event to the current profile.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_search_for_profiles">2.2.6. How to search for profiles</h4>
+<div class="paragraph">
+<p>In order to search for profiles you will have to use the 
/cxs/profiles/search endpoint that requires a Query JSON
+structure. Here&#8217;s an example of a profile search with a Query object:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST 
http://localhost:8181/cxs/profiles/search \
+--user karaf:karaf \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{
+  "text" : "unomi",
+  "offset" : 0,
+  "limit" : 10,
+  "sortby" : "properties.lastName:asc,properties.firstName:desc",
+  "condition" : {
+    "type" : "booleanCondition",
+    "parameterValues" : {
+      "operator" : "and",
+      "subConditions" : [
+        {
+          "type": "profilePropertyCondition",
+          "parameterValues": {
+            "propertyName": "properties.leadAssignedTo",
+            "comparisonOperator": "exists"
+          }
+        },
+        {
+          "type": "profilePropertyCondition",
+          "parameterValues": {
+            "propertyName": "properties.lastName",
+            "comparisonOperator": "exists"
+          }
+        }
+      ]
+    }
+  }
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>In the above example, you search for all the profiles that have the 
<code>leadAssignedTo</code> and <code>lastName</code> properties and that
+have the <code>unomi</code> value anywhere in their profile property values. 
You are also specifying that you only want 10 results
+beginning at offset 0. The results will be also sorted in alphabetical order 
for the <code>lastName</code> property value, and then
+by reverse alphabetical order for the <code>firstName</code> property 
value.</p>
+</div>
+<div class="paragraph">
+<p>As you can see, queries can be quite complex. Please remember that the more 
complex the more resources it will consume
+on the server and potentially this could affect performance.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_getting_updating_consents">2.2.7. Getting / updating consents</h4>
+<div class="paragraph">
+<p>You can find information on how to retrieve or create/update consents in 
the <a href="#_consent_api">Consent API</a> section.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_send_a_login_event_to_unomi">2.2.8. How to send a login event 
to Unomi</h4>
+<div class="paragraph">
+<p>Tracking logins must be done carefully with Unomi. A login event is 
considered a "privileged" event and therefore for
+not be initiated from the public internet. Ideally user authentication should 
always be validated by a trusted third-
+party even if it is a well-known social platform such as Facebook or Twitter. 
Basically what should NEVER be done:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Login to a social platform</p>
+</li>
+<li>
+<p>Call back to the originating page</p>
+</li>
+<li>
+<p>Send a login event to Unomi from the page originating the login in step 
1</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>The problem with this, is that any attacker could simply directly call step 
3 without any kind of security. Instead the
+flow should look something like this:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Login to a social platform</p>
+</li>
+<li>
+<p>Call back to a special secured system that performs an server-to-server 
call to send the login event to Apache
+Unomi using the Unomi key.</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>For simplicity reasons, in our login example, the first method is used, but 
it really should never be done like this
+in production because of the aforementioned security issues. The second 
method, although a little more involved, is
+much preferred.</p>
+</div>
+<div class="paragraph">
+<p>When sending a login event, you can setup a rule that can check a profile 
property to see if profiles can be merged on an
+universal identifier such as an email address.</p>
+</div>
+<div class="paragraph">
+<p>In our login sample we provide an example of such a rule. You can find it 
here:</p>
+</div>
+<div class="paragraph">
+<p><a 
href="https://github.com/apache/unomi/blob/master/samples/login-integration/src/main/resources/META-INF/cxs/rules/exampleLogin.json";
 
class="bare">https://github.com/apache/unomi/blob/master/samples/login-integration/src/main/resources/META-INF/cxs/rules/exampleLogin.json</a></p>
+</div>
+<div class="paragraph">
+<p>As you can see in this rule, we call an action called :</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>mergeProfilesOnPropertyAction</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>with as a parameter value the name of the property on which to perform the 
merge (the email). What this means is that
+upon successful login using an email, Unomi will look for other profiles that 
have the same email and merge them into
+a single profile. Because of the merge, this should only be done for 
authenticated profiles, otherwise this could be a
+security issue since it could be a way to load data from other profiles by 
merging their data !</p>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_request_examples">2.3. Request examples</h3>
+<div class="sect3">
+<h4 id="_retrieving_your_first_context">2.3.1. Retrieving your first 
context</h4>
+<div class="paragraph">
+<p>You can retrieve a context using curl like this :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl 
http://localhost:8181/cxs/context.js?sessionId=1234</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This will retrieve a JavaScript script that contains a <code>cxs</code> 
object that contains the context with the current user
+profile, segments, scores as well as functions that makes it easier to perform 
further requests (such as collecting
+events using the cxs.collectEvents() function).</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_retrieving_a_context_as_a_json_object">2.3.2. Retrieving a context as 
a JSON object.</h4>
+<div class="paragraph">
+<p>If you prefer to retrieve a pure JSON object, you can simply use a request 
formed like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl 
http://localhost:8181/cxs/context.json?sessionId=1234</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_accessing_profile_properties_in_a_context">2.3.3. Accessing profile 
properties in a context</h4>
+<div class="paragraph">
+<p>By default, in order to optimize the amount of data sent over the network, 
Apache Unomi will not send the content of
+the profile or session properties. If you need this data, you must send a JSON 
object to configure the resulting output
+of the context.js(on) servlet.</p>
+</div>
+<div class="paragraph">
+<p>Here is an example that will retrieve all the session and profile 
properties, as well as the profile&#8217;s segments and
+scores</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST 
http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{
+    "source": {
+        "itemId":"homepage",
+        "itemType":"page",
+        "scope":"example"
+    },
+    "requiredProfileProperties":["*"],
+    "requiredSessionProperties":["*"],
+    "requireSegments":true,
+    "requireScores":true
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The <code>requiredProfileProperties</code> and 
<code>requiredSessionProperties</code> are properties that take an array of 
property names
+that should be retrieved. In this case we use the wildcard character '*' to 
say we want to retrieve all the available
+properties. The structure of the JSON object that you should send is a 
JSON-serialized version of the <a 
href="http://unomi.apache.org/unomi-api/apidocs/org/apache/unomi/api/ContextRequest.html";>ContextRequest</a>
+Java class.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_sending_events_using_the_context_servlet">2.3.4. Sending events using 
the context servlet</h4>
+<div class="paragraph">
+<p>At the same time as you are retrieving the context, you can also directly 
send events in the ContextRequest object as
+illustrated in the following example:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST 
http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{
+    "source":{
+        "itemId":"homepage",
+        "itemType":"page",
+        "scope":"example"
+    },
+    "events":[
+        {
+            "eventType":"view",
+            "scope": "example",
+            "source":{
+                "itemType": "site",
+                "scope":"example",
+                "itemId": "mysite"
+            },
+            "target":{
+                "itemType":"page",
+                "scope":"example",
+                "itemId":"homepage",
+                "properties":{
+                    "pageInfo":{
+                        "referringURL":""
+                    }
+                }
+            }
+        }
+    ]
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Upon received events, Apache Unomi will execute all the rules that match 
the current context, and return an updated context.
+This way of sending events is usually used upon first loading of a page. If 
you want to send events after the page has
+finished loading you could either do a second call and get an updating 
context, or if you don&#8217;t need the context and want
+to send events in a network optimal way you can use the eventcollector servlet 
(see below).</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_sending_events_using_the_eventcollector_servlet">2.3.5. Sending 
events using the eventcollector servlet</h4>
+<div class="paragraph">
+<p>If you only need to send events without retrieving a context, you should 
use the eventcollector servlet that is optimized
+respond quickly and minimize network traffic. Here is an example of using this 
servlet:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST 
http://localhost:8181/cxs/eventcollector \
+-H "Content-Type: application/json" \
+-d @- &lt;&lt;'EOF'
+{
+    "sessionId" : "1234",
+    "events":[
+        {
+            "eventType":"view",
+            "scope": "example",
+            "source":{
+                "itemType": "site",
+                "scope":"example",
+                "itemId": "mysite"
+            },
+            "target":{
+                "itemType":"page",
+                "scope":"example",
+                "itemId":"homepage",
+                "properties":{
+                    "pageInfo":{
+                        "referringURL":""
+                    }
+                }
+            }
+        }
+    ]
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Note that the eventcollector executes the rules but does not return a 
context. If is generally used after a page is loaded
+to send additional events.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_where_to_go_from_here">2.3.6. Where to go from here</h4>
+<div class="ulist">
+<ul>
+<li>
+<p>You can find more <a href="#_useful_apache_unomi_urls">useful Apache Unomi 
URLs</a> that can be used in the same way as the above examples.</p>
+</li>
+<li>
+<p>You may want to know integrate the provided <a href="#_web_tracker">web 
tracker</a> into your web site.</p>
+</li>
+<li>
+<p>Read the <a href="#_twitter_sample">Twitter sample</a> documentation that 
contains a detailed example of how to integrate with Apache Unomi.</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_web_tracker">2.4. Web Tracker</h3>
+<div class="paragraph">
+<p>This extension is providing the web tracker to start collecting visitors 
data on your website.
+The tracker is implemented as an integration of <a 
href="https://github.com/segmentio/analytics.js";>analytics.js</a> for Unomi.</p>
+</div>
+<div class="sect3">
+<h4 id="_getting_started">2.4.1. Getting started</h4>
+<div class="paragraph">
+<p>Extension can be tested at : <code><a 
href="http://localhost:8181/tracker/index.html"; 
class="bare">http://localhost:8181/tracker/index.html</a></code></p>
+</div>
+<div class="paragraph">
+<p>In your page include unomiOptions and include code snippet from 
<code>snippet.min.js</code> :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>&lt;script type="text/javascript"&gt;
+        var unomiOption = {
+            scope: 'realEstateManager',
+            url: 'http://localhost:8181'
+        };
+        window.unomiTracker||(window.unomiTracker={}),function(){function 
e(e){for(unomiTracker.initialize({"Apache 
Unomi":unomiOption});n.length&gt;0;){var 
r=n.shift(),t=r.shift();unomiTracker[t]&amp;&amp;unomiTracker[t].apply(unomiTracker,r)}}for(var
 
n=[],r=["trackSubmit","trackClick","trackLink","trackForm","initialize","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","personalize"],t=0;t&lt;r.length;t++){var
 i=r[t];window.unomiTracker[i]=function(e){return function(){var 
r=Array.prototype.slice.call(arguments);return 
r.unshift(e),n.push(r),window.unomiTracker}}(i)}unomiTracker.load=function(){var
 
n=document.createElement("script");n.type="text/javascript",n.async=!0,n.src=unomiOption.url+"/tracker/unomi-tracker.min.js",n.addEventListener?n.addEventListener("load",function(n){"function"==typeof
 
e&amp;&amp;e(n)},!1):n.onreadystatechange=function(){"complete"!==this.readyState&amp;&amp;"loaded"!==this.readyState||e(window.event)};var
 r=
 
document.getElementsByTagName("script")[0];r.parentNode.insertBefore(n,r)},document.addEventListener("DOMContentLoaded",unomiTracker.load),unomiTracker.page()}();
+&lt;/script&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p><code>window.unomiTracker</code> can be used to send additional events when 
needed.</p>
+</div>
+<div class="paragraph">
+<p>Check analytics.js API <a 
href="https://segment.com/docs/sources/website/analytics.js/";>here</a>.
+All methods can be used on <code>unomiTracker</code> object, although not all 
event types are supported by Unomi intergation.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_contribute">2.4.2. How to contribute</h4>
+<div class="paragraph">
+<p>The source code is in the folder javascript with a package.json, the file 
to update is <code>analytics.js-integration-apache-unomi.js</code> apply your 
modification in this file then use the command <code>yarn build</code> to 
compile a new JS file.
+Then you can use the test page to try your changes <code><a 
href="http://localhost:8181/tracker/index.html"; 
class="bare">http://localhost:8181/tracker/index.html</a></code>.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_tracking_page_views">2.4.3. Tracking page views</h4>
+<div class="paragraph">
+<p>In the initialize call, the tracker will generate an implicit page view 
event, which by default will be populated with
+the following information:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" 
data-lang="javascript">    window.digitalData.page = window.digitalData.page || 
{
+        path: location.pathname + location.hash,
+        pageInfo: {
+            pageName: document.title,
+            pageID : location.pathname + location.hash,
+            pagePath : location.pathname + location.hash,
+            destinationURL: location.href
+        }
+    }</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Now if you want to provide your own custom page information for the initial 
page view, you can simply do it like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-`javascript" 
data-lang="`javascript">    unomiTracker.initialize({
+            scope: 'myScope',
+            url: 'http://unomi:8181', // we use an empty URL to make it 
relative to this page.
+            initialPageProperties: {
+                path: path,
+                pageInfo: {
+                    destinationURL: location.href,
+                    tags: ["tag1", "tag2", "tag3"],
+                    categories: ["category1", "category2", "category3"]
+                },
+                interests: {
+                    "interest1": 1,
+                    "interest2": 2,
+                    "interest3": 3
+                }
+            }
+        });</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>`</p>
+</div>
+<div class="paragraph">
+<p>Also note that the FIRST call to unomiTracker.page() will be IGNORED 
because of this initial page view.This is the
+way that the Analytics.js library handles it.So make sure you are aware of 
this when calling it.This is to avoid having
+two page views on a single call and to be compatible with old versions that 
did use the explicit call.</p>
+</div>
+<div class="paragraph">
+<p>By default the script will track page views, but maybe you want to take 
control over this mechanism of add page views
+to a single page application.In order to generate a page view programmatically 
from Javascript you can use code similar
+to this :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>    &lt;script type="text/javascript"&gt;
+        // This is an example of how to provide more details page properties 
to the view event. This can be useful
+        // in the case of an SPA that wants to provide information about a 
view that has metadata such as categories,
+        // tags or interests.
+        path = location.pathname + location.hash;
+        properties = {
+            path: path,
+            pageInfo: {
+                destinationURL: location.href,
+                tags : [ "tag1", "tag2", "tag3"],
+                categories : ["category1", "category2", "category3"],
+            },
+            interests : {
+                "interest1" : 1,
+                "interest2" : 2,
+                "interest3" : 3
+            }
+        };
+        console.log(properties);
+        // this will trigger a second page view for the same page (the first 
page view is in the tracker snippet).
+        window.unomiTracker.page(properties);
+    &lt;/script&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Here is a more detail view of what you may include in the pageInfo object 
:</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<caption class="title">Table 1. PageInfo Properties</caption>
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Name</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p 
class="tableblock">pageID</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A unique 
identifier in string format for the page. Default value : page path</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p 
class="tableblock">pageName</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A 
user-displayed name for the page. Default value : page title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p 
class="tableblock">pagePath</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The path 
of the page, stored by Unomi. This value should be the same as the one passed 
in the <code>page</code> property of the
+object passed to the unomiTracker call. Default value : page path</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p 
class="tableblock">destinationURL</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The full 
URL for the page view. This doesn&#8217;t have to be a real existing URL it 
could be an internal SPA route. Default value : page URL</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p 
class="tableblock">referringURL</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The 
referringURL also known as the previous URL of the page/screen viewed. Default 
value : page referrer URL</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p 
class="tableblock">tags</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A String 
array of tag identifiers. For example <code>['tag1', 'tag2', 
'tag3']</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p 
class="tableblock">categories</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A String 
array of category identifiers. For example <code>['category1', 'category2', 
'category3']</code></p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The <code>interests</code> object is basically list of interests with 
"weights" attached to them.These interests will be accumulated
+in Apache Unomi on profiles to indicate growing interest over time for 
specific topics.These are freely defined and
+will be accepted by Apache Unomi without needing to declare them previously 
anywhere (the same is true for tags and
+categories).</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_tracking_form_submissions">2.4.4. Tracking form submissions</h4>
+<div class="paragraph">
+<p>Using the web tracker you can also track form submissions. In order to do 
this a few steps are required to get a form&#8217;s
+submission to be tracked and then its form values to be sent as events to 
Apache Unomi. Finally setting up a rule to
+react to the incoming event will help use the form values to perform any 
action that is desired.</p>
+</div>
+<div class="paragraph">
+<p>Let&#8217;s look at a concrete example. Before we get started you should 
know that this example is already available to
+directly test in Apache Unomi at the following URL :</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>http://localhost:8181/tracker</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Simply modify the form values and click submit and it will perform all the 
steps we are describing below.</p>
+</div>
+<div class="paragraph">
+<p>So here is the form we want to track :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>&lt;form id="testFormTracking" action="#" 
name="testFormTracking"&gt;
+    &lt;label for="firstName"&gt;First name&lt;/label&gt;
+    &lt;input type="text" id="firstName" name="firstName" value="John"/&gt;
+
+    &lt;label for="lastName"&gt;Last name&lt;/label&gt;
+    &lt;input type="text" id="lastName" name="lastName" value="Doe"/&gt;
+
+    &lt;label for="email"&gt;Email&lt;/label&gt;
+    &lt;input type="email" id="email" name="email" 
value="[email protected]"/&gt;
+
+    &lt;input type="submit" name="submitButton" value="Submit"/&gt;
+&lt;/form&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>As you can see it&#8217;s composed of three fields - firstName, lastName 
and email - as well as a submit button. In order to
+track it we can add directly under the following snippet :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>&lt;script type="text/javascript"&gt;
+    window.addEventListener("load", function () {
+        var form = document.getElementById('testFormTracking');
+        unomiTracker.trackForm(form, 'formSubmitted', {formName: form.name});
+    });
+&lt;/script&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>What this snippet does is retrieve the form using its element ID and then 
uses the unomiTracker to track form submissions.
+Be careful to always use in the form event name a string that starts with 
<code>form</code> in order for the event to be sent back
+to Unomi. Also the form name is also a mandatory parameter that will be passed 
to Unomi inside a event of type <code>form</code> under
+the <code>target.itemId</code> property name.</p>
+</div>
+<div class="paragraph">
+<p>Here is an example of the event that gets sent back to Apache Unomi:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>{
+  "itemId" : "cd627012-963e-4bb5-97f0-480990b41254",
+  "itemType" : "event",
+  "scope" : "realEstateManager",
+  "version" : 1,
+  "eventType" : "form",
+  "sessionId" : "aaad09aa-88c2-67bd-b106-5a47ded43ead",
+  "profileId" : "48563fd0-6319-4260-8dba-ae421beba26f",
+  "timeStamp" : "2018-11-23T16:32:26Z",
+  "properties" : {
+    "firstName" : "John",
+    "lastName" : "Doe",
+    "email" : "[email protected]",
+    "submitButton" : "Submit"
+  },
+  "source" : {
+    "itemId" : "/tracker/",
+    "itemType" : "page",
+    "scope" : "realEstateManager",
+    "version" : null,
+    "properties" : {
+      "pageInfo" : {
+        "destinationURL" : 
"http://localhost:8181/tracker/?firstName=Bill&amp;lastName=Gates&amp;email=bgates%40microsoft.com";,
+        "pageID" : "/tracker/",
+        "pagePath" : "/tracker/",
+        "pageName" : "Apache Unomi Web Tracker Test Page",
+        "referringURL" : 
"http://localhost:8181/tracker/?firstName=John&amp;lastName=Doe&amp;email=johndoe%40acme.com";
+      },
+      "attributes" : [ ],
+      "consentTypes" : [ ],
+      "interests" : { }
+    }
+  },
+  "target" : {
+    "itemId" : "testFormTracking",
+    "itemType" : "form",
+    "scope" : "realEstateManager",
+    "version" : null,
+    "properties" : { }
+  },
+  "persistent" : true
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>You can see in this event that the form values are sent as properties of 
the event itself, while the form name is sent
+as the <code>target.itemId</code></p>
+</div>
+<div class="paragraph">
+<p>While setting up form tracking, it can be very useful to use the Apache 
Unomi Karaf SSH shell commands : <code>event-tail</code>
+and <code>event-view</code> to check if you are properly receiving the form 
submission events and that they contain the expected
+data. If not, check your tracking code for any errors.</p>
+</div>
+<div class="paragraph">
+<p>Now that the data is properly sent using an event to Apache Unomi, we must 
still use it to perform some kind of actions.
+Using rules, we could do anything from updating the profile to sending the 
data to a third-party server (using a custom-
+developped action of course). In this example we will illustrate how to update 
the profile.</p>
+</div>
+<div class="paragraph">
+<p>In order to do so we will deploy a rule that will copy data coming from the 
event into a profile. But we will need to
+map the form field names to profile names, and this can be done using the 
<code>setPropertyAction</code> that&#8217;s available out of the
+box in the Apache Unomi server.</p>
+</div>
+<div class="paragraph">
+<p>There are two ways to register rules : either by building a custom OSGi 
bundle plugin or using the REST API to directly
+send a JSON representation of the rule to be saved. We will in this example 
use the CURL shell command to make a call to
+the REST API.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl -X POST -k -u karaf:karaf 
https://localhost:9443/cxs/rules \
+  --header "Content-Type: application/json" \
+-d @- &lt;&lt; EOF
+{
+  "itemId": "form-mapping-example",
+  "itemType": "rule",
+  "linkedItems": null,
+  "raiseEventOnlyOnceForProfile": false,
+  "raiseEventOnlyOnceForSession": false,
+  "priority": -1,
+  "metadata": {
+    "id": "form-mapping-example",
+    "name": "Example Form Mapping",
+    "description": "An example of how to map event properties to profile 
properties",
+    "scope": "realEstateManager",
+    "tags": [],
+    "enabled": true,
+    "missingPlugins": false,
+    "hidden": false,
+    "readOnly": false
+  },
+  "condition": {
+    "type": "formEventCondition",
+    "parameterValues": {
+      "formId": "testFormTracking",
+      "pagePath" : "/tracker/"
+    }
+  },
+  "actions": [
+    {
+      "type": "setPropertyAction",
+      "parameterValues": {
+        "setPropertyName": "properties(firstName)",
+        "setPropertyValue": "eventProperty::properties(firstName)",
+        "setPropertyStrategy": "alwaysSet"
+      }
+    },
+    {
+      "type": "setPropertyAction",
+      "parameterValues": {
+        "setPropertyName": "properties(lastName)",
+        "setPropertyValue": "eventProperty::properties(lastName)",
+        "setPropertyStrategy": "alwaysSet"
+      }
+    },
+    {
+      "type": "setPropertyAction",
+      "parameterValues": {
+        "setPropertyName": "properties(email)",
+        "setPropertyValue": "eventProperty::properties(email)",
+        "setPropertyStrategy": "alwaysSet"
+      }
+    }
+  ]
+}
+EOF</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>As you can see in this request, we have a few parameters that need 
explaining:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>-k</code> is used to accept any certificate as we are in this example 
using a default Apache Unomi server configuration that
+comes with its predefined HTTPS certificates</p>
+</li>
+<li>
+<p><code>-u karaf:karaf</code> is the default username/password for 
authenticating to the REST API. To change this value you should
+edit the `etc/users.properties`file and it is required to modify this login 
before going to production.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Finally the rule itself should be pretty self-explanatory but there are a 
few important things to note :</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>the <code>itemId</code> and <code>metadata.id</code> values should be the 
same</p>
+</li>
+<li>
+<p>the <code>scope</code> should be the same as the scope that was setup in 
the tracker initialization</p>
+</li>
+<li>
+<p>the <code>formId</code> parameter must have the form name value</p>
+</li>
+<li>
+<p>the <code>pagePath</code> should be the pagePath passed through the event 
(if you&#8217;re not sure of its value, you could either using
+network debugging in the browser or use the <code>event-tail</code> and 
<code>event-view</code> commands in the Apache Unomi Karaf SSH shell).</p>
+</li>
+<li>
+<p>the setPropertyAction may be repeated as many times as desired to copy the 
values from the event to the profile. Note that
+the <code>setPropertyName</code> will define the property to set on the 
profile and the <code>setPropertyValue</code> will define where the
+value is coming from. In this example the name and the value are the same but 
that is no way a requirement. It could
+even be possible to using multiple <code>setPropertyAction</code> instances to 
copy the same event property into different profile
+properties.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>To check if your rule is properly deployed you can use the following SSH 
shell command :</p>
+</div>
+<div class="paragraph">
+<p><code>unomi:rule-view form-mapping-example</code></p>
+</div>
+<div class="paragraph">
+<p>The parameter is the <code>itemId</code> of the rule. If you want to see 
all the rules deployed in the system you can use the
+command :</p>
+</div>
+<div class="paragraph">
+<p><code>unomi:rule-list 1000</code></p>
+</div>
+<div class="paragraph">
+<p>The <code>1000</code> parameter is the limit of number of objects to 
retrieve. As the number of rules can grow quickly in an Apache
+Unomi instance, it is recommended to put this value a bit high to make sure 
you get the full list of rules.</p>
+</div>
+<div class="paragraph">
+<p>Once the rule is in place, try submitting the form with some values and 
check that the profile is properly updated. One
+recommend way of doing this is to use the <code>event-tail</code> command that 
will output something like this :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>ID                                  |Type         
 |Session                             |Profile                             
|Timestamp                    |Scope          |Persi|
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+cef09b89-6b99-4e4f-a99c-a4159a66b42b|form          
|aaad09aa-88c2-67bd-b106-5a47ded43ead|48563fd0-6319-4260-8dba-ae421beba26f|Fri 
Nov 23 17:52:33 CET 2018 |realEstateManag|true |</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>You can directly see the profile that is being used, so you can then simply 
use the</p>
+</div>
+<div class="paragraph">
+<p><code>unomi:profile-view 48563fd0-6319-4260-8dba-ae421beba26f</code></p>
+</div>
+<div class="paragraph">
+<p>command to see a JSON dump of the profile and check that the form values 
have been properly positioned.</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_configuration">3. Configuration</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_centralized_configuration">3.1. Centralized configuration</h3>
+<div class="paragraph">
+<p>Apache Unomi uses a centralized configuration file that contains both 
system properties and configuration properties.
+These settings are then fed to the OSGi and other configuration files using 
placeholder that look something like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre 
class="highlight"><code>contextserver.publicAddress=${org.apache.unomi.cluster.public.address:-http://localhost:8181}
+contextserver.internalAddress=${org.apache.unomi.cluster.internal.address:-https://localhost:9443}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Default values are stored in a file called 
<code>$MY_KARAF_HOME/etc/custom.system.properties</code> but you should never 
modify
+this file directly, as an override mechanism is available. Simply create a 
file called:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>unomi.custom.system.properties</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>and put your own property values in their to override the defaults OR you 
can use environment variables to also override
+the values in the <code>$MY_KARAF_HOME/etc/custom.system.properties</code>. 
See the next section for more information about that.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 
id="_changing_the_default_configuration_using_environment_variables_i_e_docker_configuration">3.2.
 Changing the default configuration using environment variables (i.e. Docker 
configuration)</h3>
+<div class="paragraph">
+<p>You might want to use environment variables to change the default system 
configuration, especially if you intend to run
+Apache Unomi inside a Docker container. You can find the list of all the 
environment variable names in the following file:</p>
+</div>
+<div class="paragraph">
+<p><a 
href="https://github.com/apache/unomi/blob/master/package/src/main/resources/etc/custom.system.properties";
 
class="bare">https://github.com/apache/unomi/blob/master/package/src/main/resources/etc/custom.system.properties</a></p>
+</div>
+<div class="paragraph">
+<p>If you are using Docker Container, simply pass the environment variables on 
the docker command line or if you are using
+Docker Compose you can put the environment variables in the docker-compose.yml 
file.</p>
+</div>
+<div class="paragraph">
+<p>If you want to "save" the environment values in a file, you can use the 
<code>bin/setenv(.bat)</code> to setup the environment
+variables you want to use.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_changing_the_default_configuration_using_property_files">3.3. 
Changing the default configuration using property files</h3>
+<div class="paragraph">
+<p>If you want to change the default configuration using property files 
instead of environment variables, you can perform
+any modification you want in the 
<code>$MY_KARAF_HOME/etc/unomi.custom.system.properties</code> file.</p>
+</div>
+<div class="paragraph">
+<p>By default this file does not exist and is designed to be a file that will 
contain only your custom modifications to the
+default configuration.</p>
+</div>
+<div class="paragraph">
+<p>For example, if you want to change the HTTP ports that the server is 
listening on, you will need to create the
+following lines in the $MY_KARAF_HOME/etc/unomi.custom.system.properties (and 
create it if you haven&#8217;t yet) file:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>org.osgi.service.http.port.secure=9443
+org.osgi.service.http.port=8181</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>If you change these ports, also make sure you adjust the following settings 
in the same file :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre 
class="highlight"><code>org.apache.unomi.cluster.public.address=http://localhost:8181
+org.apache.unomi.cluster.internal.address=https://localhost:9443</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>If you need to specify an ElasticSearch cluster name, or a host and port 
that are 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>You can use the following properties for the ElasticSearch configuration</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre 
class="highlight"><code>org.apache.unomi.elasticsearch.cluster.name=contextElasticSearch
+# The elasticsearch.adresses may be a comma seperated list of host names and 
ports such as
+# hostA:9200,hostB:9200
+# Note: the port number must be repeated for each host.
+org.apache.unomi.elasticsearch.addresses=localhost:9200</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_secured_events_configuration">3.4. Secured events configuration</h3>
+<div class="paragraph">
+<p>Apache Unomi secures some events by default. It comes out of the box with a 
default configuration that you can adjust
+by using the centralized configuration file override in 
<code>$MY_KARAF_HOME/etc/unomi.custom.system.properties</code></p>
+</div>
+<div class="paragraph">
+<p>You can find the default configuration in the following file:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>$MY_KARAF_HOME/etc/custom.system.properties</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The properties start with the prefix : 
<code>org.apache.unomi.thirdparty.*</code> and here are the default values :</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>org.apache.unomi.thirdparty.provider1.key=${env:UNOMI_THIRDPARTY_PROVIDER1_KEY:-670c26d1cc413346c3b2fd9ce65dab41}
+org.apache.unomi.thirdparty.provider1.ipAddresses=${env:UNOMI_THIRDPARTY_PROVIDER1_IPADDRESSES:-127.0.0.1,::1}
+org.apache.unomi.thirdparty.provider1.allowedEvents=${env:UNOMI_THIRDPARTY_PROVIDER1_ALLOWEDEVENTS:-login,updateProperties}</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The events set in allowedEvents will be secured and will only be accepted 
if the call comes from the specified IP
+address, and if the secret-key is passed in the X-Unomi-Peer HTTP request 
header. The "env:" part means that it will
+attempt to read an environment variable by that name, and if it&#8217;s not 
found it will default to the value after the ":-"
+marker.</p>
+</div>
+<div class="paragraph">
+<p>It is now also possible to use IP address ranges instead of having to list 
all valid IP addresses for event sources. This
+is very useful when working in cluster deployments where servers may be added 
or removed dynamically. In order to support
+this Apache Unomi uses a library called <a 
href="https://seancfoley.github.io/IPAddress/#_Toc525135541";>IPAddress</a> that 
supports
+IP ranges and subnets. Here is an example of how to setup a range:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>org.apache.unomi.thirdparty.provider1.ipAddresses=${env:UNOMI_THIRDPARTY_PROVIDER1_IPADDRESSES:-192.168.1.1-100,::1}</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The above configuration will allow a range of IP addresses between 
192.168.1.1 and 192.168.1.100 as well as the IPv6
+loopback.</p>
+</div>
+<div class="paragraph">
+<p>Here&#8217;s another example using the subnet format:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>org.apache.unomi.thirdparty.provider1.ipAddresses=${env:UNOMI_THIRDPARTY_PROVIDER1_IPADDRESSES:-1.2.0.0/16,::1}</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The above configuration will allow all addresses starting with 1.2 as well 
as the IPv6 loopback address.</p>
+</div>
+<div class="paragraph">
+<p>Wildcards may also be used:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>org.apache.unomi.thirdparty.provider1.ipAddresses=${env:UNOMI_THIRDPARTY_PROVIDER1_IPADDRESSES:-1.2.*.*,::1}</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The above configuration is exactly the same as the previous one.</p>
+</div>
+<div class="paragraph">
+<p>More advanced ranges and subnets can be used as well, please refer to the 
<a href="https://seancfoley.github.io/IPAddress";>IPAddress</a> library 
documentation for details on
+how to format them.</p>
+</div>
+<div class="paragraph">
+<p>If you want to add another provider you will need to add them manually in 
the following file (and make sure you maintain
+the changes when upgrading) :</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>$MY_KARAF_HOME/etc/org.apache.unomi.thirdparty.cfg</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Usually, login events, which operate on profiles and do merge on protected 
properties, must be secured. For each
+trusted third party server, you need to add these 3 lines :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>thirdparty.provider1.key=secret-key
+thirdparty.provider1.ipAddresses=127.0.0.1,::1
+thirdparty.provider1.allowedEvents=login,updateProperties</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_installing_the_maxmind_geoiplite2_ip_lookup_database">3.5. Installing 
the MaxMind GeoIPLite2 IP lookup database</h3>
+<div class="paragraph">
+<p>Apache Unomi 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="sect2">
+<h3 id="_installing_geonames_database">3.6. Installing Geonames database</h3>
+<div class="paragraph">
+<p>Apache Unomi 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 <code>$MY_KARAF_HOME/etc/unomi.custom.system.properties</code> and set 
<code>org.apache.unomi.geonames.forceImport</code> 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="sect2">
+<h3 id="_rest_api_security">3.7. REST API Security</h3>
+<div class="paragraph">
+<p>The Apache Unomi 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>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>Update the keystore and certificate password in 
$MY_KARAF_HOME/etc/unomi.custom.system.properties file :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre 
class="highlight"><code>org.ops4j.pax.web.ssl.keystore=${env:UNOMI_SSL_KEYSTORE:-${karaf.etc}/keystore}
+org.ops4j.pax.web.ssl.password=${env:UNOMI_SSL_PASSWORD:-changeme}
+org.ops4j.pax.web.ssl.keypassword=${env:UNOMI_SSL_KEYPASSWORD:-changeme}</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 class="paragraph">
+<p>Changing the default Karaf password can be done by modifying the 
<code>org.apache.unomi.security.root.password</code> in the
+<code>$MY_KARAF_HOME/etc/unomi.custom.system.properties</code> file</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_scripting_security">3.8. Scripting security</h3>
+<div class="sect3">
+<h4 id="_multi_layer_scripting_filtering_system">3.8.1. Multi-layer scripting 
filtering system</h4>
+<div class="paragraph">
+<p>The scripting security system is multi-layered.</p>
+</div>
+<div class="paragraph">
+<p>For requests coming in through the /cxs/context.json endpoint, the 
following flow is used to secure incoming requests:</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="images/expression-filtering-layers.png" alt="Expression filtering 
layers">
+</div>
+</div>
+<div class="paragraph">
+<p>Conditions submitted through the context.json public endpoint are first 
sanitized, meaning that any scripting directly
+injected is removed. However, as conditions can use sub conditions that 
include scripting, only the first directly
+injected layer of scripts are removed.</p>
+</div>
+<div class="paragraph">
+<p>The second layer is the expression filtering system, that uses an 
allow-listing mechanism to only accept pre-vetted
+expressions (through configuration and deployment on the server side). Any 
unrecognized expression will not be accepted.</p>
+</div>
+<div class="paragraph">
+<p>Finally, once the script starts executing in the scripting engine, a 
filtering class loader will only let the script
+access classes that have been allowed.</p>
+</div>
+<div class="paragraph">
+<p>This multi-layered approach makes it possible to retain a high level of 
security even if one layer is poorly
+configured or abused.</p>
+</div>
+<div class="paragraph">
+<p>For requests coming in through the secure APIs such as rules, only the 
condition sanitizing step is skipped,
+otherwise the rest of the filtering system is the same.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_scripts_and_expressions">3.8.2. Scripts and expressions</h4>
+<div class="paragraph">
+<p>Apache Unomi allows using different types of expressions in the following 
subsystems:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>context.json filters and personalization queries</p>
+</li>
+<li>
+<p>rule conditions and actions parameters</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Apache Unomi uses two integrated scripting languages to provide this 
functionality: OGNL and MVEL.
+OGNL is deprecated and is now disabled by default since 1.5.2 as it is little 
used (and replaced by better performing
+hardcoded property lookups). MVEL is more commonly used in rule actions as in 
the following example:</p>
+</div>
+<div class="paragraph">
+<p>From <a 
href="https://github.com/apache/unomi/blob/unomi-1.5.x/plugins/baseplugin/src/main/resources/META-INF/cxs/rules/sessionAssigned.json";>https://github.com/apache/unomi/blob/unomi-1.5.x/plugins/baseplugin/src/main/resources/META-INF/cxs/rules/sessionAssigned.json</a>:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">{
+  "metadata": {
+    "id": "_ajhg9u2s5_sessionAssigned",
+    "name": "Session assigned to a profile",
+    "description": "Update profile visit information",
+    "readOnly":true
+  },
+
+  "condition": {
+    "type": "booleanCondition",
+    "parameterValues": {
+      "subConditions":[
+        {
+          "type": "eventTypeCondition",
+          "parameterValues": {
+            "eventTypeId": "sessionCreated"
+          }
+        },
+        {
+          "type": "eventTypeCondition",
+          "parameterValues": {
+            "eventTypeId": "sessionReassigned"
+          }
+        }
+
+        ],
+      "operator":"or"
+
+    }
+  },
+
+  "actions": [
+    {
+      "parameterValues": {
+        "setPropertyName": "properties.previousVisit",
+        "setPropertyValue": "profileProperty::lastVisit",
+        "storeInSession": false
+      },
+      "type": "setPropertyAction"
+    },
+    {
+      "parameterValues": {
+        "setPropertyName": "properties.lastVisit",
+        "setPropertyValue": "now",
+        "storeInSession": false
+      },
+      "type": "setPropertyAction"
+    },
+    {
+      "parameterValues": {
+        "setPropertyName": "properties.nbOfVisits",
+        "setPropertyValue": "script::profile.properties.?nbOfVisits != null ? 
(profile.properties.nbOfVisits + 1) : 1",
+        "storeInSession": false
+      },
+      "type": "setPropertyAction"
+    }
+  ]
+
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>As we see in the above example, we use an MVEL script with the 
setPropertyAction to set a property value.
+Starting with version 1.5.2, any expression use in rules MUST be 
allow-listed.</p>
+</div>
+<div class="paragraph">
+<p>OGNL was previously used wherever a parameter could be used, but MVEL could 
only be used with a “script::” prefix.
+Starting with version 1.5.2 OGNL will no longer be allowed and is replaced by 
a compatible “hardcoded” property
+lookup system, while MVEL requires allow-listing the scripts that are to be 
used.</p>
+</div>
+<div class="paragraph">
+<p>By default, Apache Unomi comes with some built-in allowed expressions that 
cover all the internal uses cases.</p>
+</div>
+<div class="paragraph">
+<p>Default allowed MVEL expressions (from <a 
href="https://github.com/apache/unomi/blob/unomi-1.5.x/plugins/baseplugin/src/main/resources/META-INF/cxs/expressions/mvel.json";>https://github.com/apache/unomi/blob/unomi-1.5.x/plugins/baseplugin/src/main/resources/META-INF/cxs/expressions/mvel.json</a>)
 :</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-json" data-lang="json">[
+  "\\Q'systemProperties.goals.'+goalId+'TargetReached'\\E",
+  "\\Q'now-'+since+'d'\\E",
+  "\\Q'scores.'+scoringPlanId\\E",
+  "\\QminimumDuration*1000\\E",
+  "\\QmaximumDuration*1000\\E",
+  "\\Qprofile.properties.?nbOfVisits != null ? (profile.properties.nbOfVisits 
+ 1) : 1\\E",
+  "\\Qsession != null ? session.size + 1 : 0\\E",
+  "\\Q'properties.optimizationTest_'+event.target.itemId\\E",
+  "\\Qevent.target.properties.variantId\\E",
+  "\\Qprofile.properties.?systemProperties.goals.\\E[\\w\\_]*\\QReached != 
null ? (profile.properties.systemProperties.goals.\\E[\\w\\_]*\\QReached) : 
'now'\\E",
+  "\\Qprofile.properties.?systemProperties.campaigns.\\E[\\w\\_]*\\QEngaged != 
null ? (profile.properties.systemProperties.campaigns.\\E[\\w\\_]*\\QEngaged) : 
'now'\\E"
+]</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>If you require or are already using custom expressions, you should add a 
plugin to  Apache Unomi to allow for this.

[... 7417 lines stripped ...]

Reply via email to