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 2022/12/08 17:21:44 UTC

svn commit: r1905863 [2/2] - in /unomi/website/manual: 1_7_x/ 1_7_x/connectors/ 1_7_x/images/ 1_7_x/samples/ 2_1_x/ latest/

Added: unomi/website/manual/1_7_x/index.html
URL: http://svn.apache.org/viewvc/unomi/website/manual/1_7_x/index.html?rev=1905863&view=auto
==============================================================================
--- unomi/website/manual/1_7_x/index.html (added)
+++ unomi/website/manual/1_7_x/index.html Thu Dec  8 17:21:44 2022
@@ -0,0 +1,9150 @@
+<!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>
+<li><a href="#_configuration">2.5. Configuration</a>
+<ul class="sectlevel3">
+<li><a href="#_centralized_configuration">2.5.1. Centralized configuration</a></li>
+<li><a href="#_changing_the_default_configuration_using_environment_variables_i_e_docker_configuration">2.5.2. Changing the default configuration using environment variables (i.e. Docker configuration)</a></li>
+<li><a href="#_changing_the_default_configuration_using_property_files">2.5.3. Changing the default configuration using property files</a></li>
+<li><a href="#_secured_events_configuration">2.5.4. Secured events configuration</a></li>
+<li><a href="#_installing_the_maxmind_geoiplite2_ip_lookup_database">2.5.5. Installing the MaxMind GeoIPLite2 IP lookup database</a></li>
+<li><a href="#_installing_geonames_database">2.5.6. Installing Geonames database</a></li>
+<li><a href="#_rest_api_security">2.5.7. REST API Security</a></li>
+<li><a href="#_scripting_security">2.5.8. Scripting security</a></li>
+<li><a href="#_groovy_actions">2.5.9. Groovy Actions</a></li>
+<li><a href="#_scripting_roadmap">2.5.10. Scripting roadmap</a></li>
+<li><a href="#_automatic_profile_merging">2.5.11. Automatic profile merging</a></li>
+<li><a href="#_securing_a_production_environment">2.5.12. Securing a production environment</a></li>
+<li><a href="#_integrating_with_an_apache_http_web_server">2.5.13. Integrating with an Apache HTTP web server</a></li>
+<li><a href="#_changing_the_default_tracking_location">2.5.14. Changing the default tracking location</a></li>
+<li><a href="#_apache_karaf_ssh_console">2.5.15. Apache Karaf SSH Console</a></li>
+<li><a href="#_elasticsearch_authentication_and_security">2.5.16. ElasticSearch authentication and security</a></li>
+</ul>
+</li>
+<li><a href="#_useful_apache_unomi_urls">2.6. Useful Apache Unomi URLs</a></li>
+<li><a href="#_how_profile_tracking_works">2.7. How profile tracking works</a>
+<ul class="sectlevel3">
+<li><a href="#_steps">2.7.1. Steps</a></li>
+</ul>
+</li>
+<li><a href="#_context_request_flow">2.8. Context Request Flow</a></li>
+</ul>
+</li>
+<li><a href="#_queries_and_aggregations">3. Queries and aggregations</a>
+<ul class="sectlevel2">
+<li><a href="#_query_counts">3.1. Query counts</a></li>
+<li><a href="#_metrics">3.2. Metrics</a></li>
+<li><a href="#_aggregations">3.3. Aggregations</a>
+<ul class="sectlevel3">
+<li><a href="#_aggregation_types">3.3.1. Aggregation types</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_profile_import_export">4. Profile import &amp; export</a>
+<ul class="sectlevel2">
+<li><a href="#_importing_profiles">4.1. Importing profiles</a>
+<ul class="sectlevel3">
+<li><a href="#_import_api">4.1.1. Import API</a></li>
+</ul>
+</li>
+<li><a href="#_exporting_profiles">4.2. Exporting profiles</a>
+<ul class="sectlevel3">
+<li><a href="#_export_api">4.2.1. Export API</a></li>
+</ul>
+</li>
+<li><a href="#_configuration_in_details">4.3. Configuration in details</a></li>
+</ul>
+</li>
+<li><a href="#_consent_management">5. Consent management</a>
+<ul class="sectlevel2">
+<li><a href="#_consent_api">5.1. Consent API</a>
+<ul class="sectlevel3">
+<li><a href="#_profiles_with_consents">5.1.1. Profiles with consents</a></li>
+<li><a href="#_consent_type_definitions">5.1.2. Consent type definitions</a></li>
+<li><a href="#_creating_update_a_visitor_consent">5.1.3. Creating / update a visitor consent</a></li>
+<li><a href="#_how_it_works_internally">5.1.4. How it works (internally)</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_privacy_management">6. Privacy management</a>
+<ul class="sectlevel2">
+<li><a href="#_setting_up_access_to_the_privacy_endpoint">6.1. Setting up access to the privacy endpoint</a></li>
+<li><a href="#_anonymizing_a_profile">6.2. Anonymizing a profile</a></li>
+<li><a href="#_downloading_profile_data">6.3. Downloading profile data</a></li>
+<li><a href="#_deleting_a_profile">6.4. Deleting a profile</a></li>
+<li><a href="#_related">6.5. Related</a></li>
+</ul>
+</li>
+<li><a href="#_cluster_setup">7. Cluster setup</a>
+<ul class="sectlevel2">
+<li><a href="#_cluster_setup_2">7.1. Cluster setup</a></li>
+</ul>
+</li>
+<li><a href="#_reference">8. Reference</a>
+<ul class="sectlevel2">
+<li><a href="#_data_model_overview">8.1. Data Model Overview</a></li>
+<li><a href="#_scope">8.2. Scope</a>
+<ul class="sectlevel3">
+<li><a href="#_example">8.2.1. Example</a></li>
+</ul>
+</li>
+<li><a href="#_item">8.3. Item</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition">8.3.1. Structure definition</a></li>
+</ul>
+</li>
+<li><a href="#_metadata">8.4. Metadata</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_2">8.4.1. Structure definition</a></li>
+<li><a href="#_example_2">8.4.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_metadataitem">8.5. MetadataItem</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_3">8.5.1. Structure definition</a></li>
+<li><a href="#_example_3">8.5.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_event">8.6. Event</a>
+<ul class="sectlevel3">
+<li><a href="#_fields">8.6.1. Fields</a></li>
+<li><a href="#_event_types">8.6.2. Event types</a></li>
+</ul>
+</li>
+<li><a href="#_profile">8.7. Profile</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_4">8.7.1. Structure definition</a></li>
+<li><a href="#_example_4">8.7.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_persona">8.8. Persona</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_5">8.8.1. Structure definition</a></li>
+<li><a href="#_example_5">8.8.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_consent">8.9. Consent</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_6">8.9.1. Structure definition</a></li>
+<li><a href="#_example_6">8.9.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_session">8.10. Session</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_7">8.10.1. Structure definition</a></li>
+<li><a href="#_example_7">8.10.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_segment">8.11. Segment</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_8">8.11.1. Structure definition</a></li>
+<li><a href="#_example_8">8.11.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_condition">8.12. Condition</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_9">8.12.1. Structure definition</a></li>
+<li><a href="#_example_9">8.12.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_rule">8.13. Rule</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_10">8.13.1. Structure definition</a></li>
+<li><a href="#_example_10">8.13.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_action">8.14. Action</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_11">8.14.1. Structure definition</a></li>
+<li><a href="#_example_11">8.14.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_list">8.15. List</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_12">8.15.1. Structure definition</a></li>
+<li><a href="#_example_12">8.15.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_goal">8.16. Goal</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_13">8.16.1. Structure definition</a></li>
+<li><a href="#_example_13">8.16.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_campaign">8.17. Campaign</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_14">8.17.1. Structure definition</a></li>
+<li><a href="#_example_14">8.17.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_scoring_plan">8.18. Scoring plan</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_15">8.18.1. Structure definition</a></li>
+<li><a href="#_example_15">8.18.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_data_model_changes_for_apache_unomi_1_5_0">8.19. Data Model changes for Apache Unomi 1.5.0</a>
+<ul class="sectlevel3">
+<li><a href="#_data_model_and_elasticsearch_7">8.19.1. Data model and ElasticSearch 7</a></li>
+<li><a href="#_api_changes">8.19.2. API changes</a></li>
+</ul>
+</li>
+<li><a href="#_important_changes_in_public_servlets_since_version_1_5_5">8.20. Important changes in public servlets since version 1.5.5</a></li>
+<li><a href="#_built_in_event_types">8.21. Built-in Event types</a>
+<ul class="sectlevel3">
+<li><a href="#_login_event_type">8.21.1. Login event type</a></li>
+<li><a href="#_view_event_type">8.21.2. View event type</a></li>
+<li><a href="#_form_event_type">8.21.3. Form event type</a></li>
+<li><a href="#_update_properties_event_type">8.21.4. Update properties event type</a></li>
+<li><a href="#_identify_event_type">8.21.5. Identify event type</a></li>
+<li><a href="#_session_created_event_type">8.21.6. Session created event type</a></li>
+<li><a href="#_goal_event_type">8.21.7. Goal event type</a></li>
+<li><a href="#_modify_consent_event_type">8.21.8. Modify consent event type</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_condition_types">8.22. Built-in condition types</a>
+<ul class="sectlevel3">
+<li><a href="#_existing_condition_type_descriptors">8.22.1. Existing condition type descriptors</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_action_types">8.23. Built-in action types</a>
+<ul class="sectlevel3">
+<li><a href="#_existing_action_types_descriptors">8.23.1. Existing action types descriptors</a></li>
+</ul>
+</li>
+<li><a href="#_updating_events_using_the_context_servlet">8.24. Updating Events Using the Context Servlet</a>
+<ul class="sectlevel3">
+<li><a href="#_solution">8.24.1. Solution</a></li>
+<li><a href="#_defining_rules">8.24.2. Defining Rules</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_integration_samples">9. Integration samples</a>
+<ul class="sectlevel2">
+<li><a href="#_samples">9.1. Samples</a></li>
+<li><a href="#_login_sample">9.2. Login sample</a>
+<ul class="sectlevel3">
+<li><a href="#_warning">9.2.1. Warning !</a></li>
+<li><a href="#_installing_the_samples">9.2.2. Installing the samples</a></li>
+</ul>
+</li>
+<li><a href="#_twitter_sample">9.3. Twitter sample</a>
+<ul class="sectlevel3">
+<li><a href="#_overview">9.3.1. Overview</a></li>
+<li><a href="#_interacting_with_the_context_server">9.3.2. Interacting with the context server</a></li>
+<li><a href="#_retrieving_context_information_from_unomi_using_the_context_servlet">9.3.3. Retrieving context information from Unomi using the context servlet</a></li>
+</ul>
+</li>
+<li><a href="#_example_24">9.4. Example</a>
+<ul class="sectlevel3">
+<li><a href="#_html_page">9.4.1. HTML page</a></li>
+<li><a href="#_javascript">9.4.2. Javascript</a></li>
+</ul>
+</li>
+<li><a href="#_conclusion">9.5. Conclusion</a></li>
+<li><a href="#_annex">9.6. Annex</a></li>
+<li><a href="#_weather_update_sample">9.7. Weather update sample</a></li>
+</ul>
+</li>
+<li><a href="#_connectors">10. Connectors</a>
+<ul class="sectlevel2">
+<li><a href="#_connectors_2">10.1. Connectors</a>
+<ul class="sectlevel3">
+<li><a href="#_call_for_contributors">10.1.1. Call for contributors</a></li>
+</ul>
+</li>
+<li><a href="#_salesforce_connector">10.2. Salesforce Connector</a>
+<ul class="sectlevel3">
+<li><a href="#_getting_started_2">10.2.1. Getting started</a></li>
+<li><a href="#_properties">10.2.2. Properties</a></li>
+<li><a href="#_hot_deploying_updates_to_the_salesforce_connector_for_developers">10.2.3. Hot-deploying updates to the Salesforce connector (for developers)</a></li>
+<li><a href="#_using_the_salesforce_workbench_for_testing_rest_api">10.2.4. Using the Salesforce Workbench for testing REST API</a></li>
+<li><a href="#_setting_up_streaming_push_queries">10.2.5. Setting up Streaming Push queries</a></li>
+<li><a href="#_executing_the_unit_tests">10.2.6. Executing the unit tests</a></li>
+</ul>
+</li>
+<li><a href="#_mailchimp_connector">10.3. MailChimp Connector</a>
+<ul class="sectlevel3">
+<li><a href="#_getting_started_3">10.3.1. Getting started</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_developers">11. Developers</a>
+<ul class="sectlevel2">
+<li><a href="#_building">11.1. Building</a>
+<ul class="sectlevel3">
+<li><a href="#_initial_setup">11.1.1. Initial Setup</a></li>
+<li><a href="#_building_2">11.1.2. Building</a></li>
+<li><a href="#_installing_an_elasticsearch_server">11.1.3. Installing an ElasticSearch server</a></li>
+<li><a href="#_deploying_the_generated_binary_package">11.1.4. Deploying the generated binary package</a></li>
+<li><a href="#_deploying_into_an_existing_karaf_server">11.1.5. Deploying into an existing Karaf server</a></li>
+<li><a href="#_jdk_selection_on_mac_os_x">11.1.6. JDK Selection on Mac OS X</a></li>
+<li><a href="#_running_the_integration_tests">11.1.7. Running the integration tests</a></li>
+<li><a href="#_testing_with_an_example_page">11.1.8. Testing with an example page</a></li>
+</ul>
+</li>
+<li><a href="#_ssh_shell_commands">11.2. SSH Shell Commands</a>
+<ul class="sectlevel3">
+<li><a href="#_using_the_shell">11.2.1. Using the shell</a></li>
+<li><a href="#_lifecycle_commands">11.2.2. Lifecycle commands</a></li>
+<li><a href="#_runtime_commands">11.2.3. Runtime commands</a></li>
+</ul>
+</li>
+<li><a href="#_writing_plugins">11.3. Writing Plugins</a></li>
+<li><a href="#_types_vs_instances">11.4. Types vs. instances</a></li>
+<li><a href="#_plugin_structure">11.5. Plugin structure</a></li>
+<li><a href="#_extension_points">11.6. Extension points</a>
+<ul class="sectlevel3">
+<li><a href="#_actiontype">11.6.1. ActionType</a></li>
+<li><a href="#_conditiontype">11.6.2. ConditionType</a></li>
+<li><a href="#_persona_2">11.6.3. Persona</a></li>
+<li><a href="#_propertymergestrategytype">11.6.4. PropertyMergeStrategyType</a></li>
+<li><a href="#_propertytype">11.6.5. PropertyType</a></li>
+<li><a href="#_rule_2">11.6.6. Rule</a></li>
+<li><a href="#_scoring">11.6.7. Scoring</a></li>
+<li><a href="#_segments">11.6.8. Segments</a></li>
+<li><a href="#_tag">11.6.9. Tag</a></li>
+<li><a href="#_valuetype">11.6.10. ValueType</a></li>
+</ul>
+</li>
+<li><a href="#_custom_plugins">11.7. Custom plugins</a>
+<ul class="sectlevel3">
+<li><a href="#_creating_a_plugin">11.7.1. Creating a plugin</a></li>
+<li><a href="#_deployment_and_custom_definition">11.7.2. Deployment and custom definition</a></li>
+<li><a href="#_predefined_segments">11.7.3. Predefined segments</a></li>
+<li><a href="#_predefined_rules">11.7.4. Predefined rules</a></li>
+<li><a href="#_predefined_properties">11.7.5. Predefined properties</a></li>
+<li><a href="#_predefined_child_conditions">11.7.6. Predefined child conditions</a></li>
+<li><a href="#_predefined_personas">11.7.7. Predefined personas</a></li>
+<li><a href="#_custom_action_types">11.7.8. Custom action types</a></li>
+<li><a href="#_custom_condition_types">11.7.9. Custom condition types</a></li>
+</ul>
+</li>
+<li><a href="#_migration_patches">11.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/context.js?sessionId=1234" class="bare">http://localhost:8181/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/context.json" class="bare">http://localhost:8181/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 /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/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 /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/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" : "john.doe@acme.com"
+            }
+        }
+    ]
+}
+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/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/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/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/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/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="johndoe@acme.com"/&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" : "johndoe@acme.com",
+    "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 class="sect2">
+<h3 id="_configuration">2.5. Configuration</h3>
+<div class="sect3">
+<h4 id="_centralized_configuration">2.5.1. Centralized configuration</h4>
+<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="sect3">
+<h4 id="_changing_the_default_configuration_using_environment_variables_i_e_docker_configuration">2.5.2. Changing the default configuration using environment variables (i.e. Docker configuration)</h4>
+<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="sect3">
+<h4 id="_changing_the_default_configuration_using_property_files">2.5.3. Changing the default configuration using property files</h4>
+<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="sect3">
+<h4 id="_secured_events_configuration">2.5.4. Secured events configuration</h4>
+<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="sect3">
+<h4 id="_installing_the_maxmind_geoiplite2_ip_lookup_database">2.5.5. Installing the MaxMind GeoIPLite2 IP lookup database</h4>
+<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="sect3">
+<h4 id="_installing_geonames_database">2.5.6. Installing Geonames database</h4>
+<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="sect3">
+<h4 id="_rest_api_security">2.5.7. REST API Security</h4>
+<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="sect3">
+<h4 id="_scripting_security">2.5.8. Scripting security</h4>
+<div class="paragraph">
+<p>By default, scripting (using in conditions, segments and rules) is controlled by a custom classloader that is quite
+restrictive and using a white-list/black list system. It is controlled through the following property in the
+<code>unomi.custom.system.properties</code> file:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>org.apache.unomi.scripting.allow=${env:UNOMI_ALLOW_SCRIPTING_CLASSES:-org.apache.unomi.api.Event,org.apache.unomi.api.Profile,org.apache.unomi.api.Session,org.apache.unomi.api.Item,org.apache.unomi.api.CustomItem,ognl.*,java.lang.Object,java.util.Map,java.lang.Integer,org.mvel2.*}
+org.apache.unomi.scripting.forbid=${env:UNOMI_FORBID_SCRIPTING_CLASSES:-}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>If you encounter any errors while trying to access a class in a condition or an action it might be due to this
+restrictive configuration.</p>
+</div>
+<div class="paragraph">
+<p>If you need, for example when adding a custom item type, to adjust these, please be careful as scripts may be called
+directly from the context.json personalization conditions and therefore should be kept minimal.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_groovy_actions">2.5.9. Groovy Actions</h4>
+<div class="paragraph">
+<p>Groovy actions offer the ability to define a set of actions and action types (aka action descriptors) purely from Groovy scripts defined at runtime.</p>
+</div>
+<div class="paragraph">
+<p>Initially submitted to Unomi through a purpose-built REST API endpoint, Groovy actions are then stored in Elasticsearch. When an event matches a rule configured to execute an action, the corresponding action is fetched from Elasticsearch and executed.</p>
+</div>
+<div class="sect4">
+<h5 id="_anatomy_of_a_groovy_action">Anatomy of a Groovy Action</h5>
+<div class="paragraph">
+<p>To be valid, a Groovy action must follow a particular convention which is divided in two parts:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>An annotation used to define the associated action type</p>
+</li>
+<li>
+<p>The function to be executed</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Placed right before the function, the “@Action” annotation contains a set of parameter detailing how the action should be triggered.</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<caption class="title">Table 2. @Action annotation</caption>
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field name</th>
+<th class="tableblock halign-left valign-top">Type</th>
+<th class="tableblock halign-left valign-top">Required</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">id</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">YES</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Id of the action</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">actionExecutor</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">YES</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Action executor contains the name of the script to call for the action type and must be prefixed with “<strong>groovy:</strong>”. The prefix indicates to Unomi which dispatcher to use when processing the action.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">name</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td>
+<td class="tableblock halign-left valign-top"></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Action name</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">hidden</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Boolean</p></td>
+<td class="tableblock halign-left valign-top"></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Define if the action is hidden or not. It is usually used to hide objects in a UI.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">parameters</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">List&lt;<a href="https://github.com/apache/unomi/blob/master/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/annotations/Parameter.java">Parameter</a>&gt;</p></td>
+<td class="tableblock halign-left valign-top"></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The parameters of the actions, also defined by annotations</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">systemTags</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">List&lt;String&gt;</p></td>
+<td class="tableblock halign-left valign-top"></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A (reserved) list of tags for the associated object. This is usually populated through JSON descriptors and is not meant to be modified by end users. These tags may include values that help classify associated objects.</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The function contained within the Groovy Action must be called <code>execute()</code> and its last instruction must be an integer.</p>
+</div>
+<div class="paragraph">
+<p>This integer serves as an indication whether the values of the session and profile should be persisted. In general, the codes used are defined in the <a href="https://github.com/apache/unomi/blob/master/api/src/main/java/org/apache/unomi/api/services/EventService.java">EventService interface</a>.</p>
+</div>
+<div class="paragraph">
+<p>Each groovy actions extends by default a Base script
+<a href="https://github.com/apache/unomi/blob/master/extensions/groovy-actions/services/src/main/resources/META-INF/base/BaseScript.groovy">defined here</a></p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_rest_api">REST API</h5>
+<div class="paragraph">
+<p>Actions can be deployed/updated/deleted via the dedicated <code>/cxs/groovyActions</code> rest endpoint.</p>
+</div>
+<div class="paragraph">
+<p>Deploy/update an Action:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-bash" data-lang="bash">curl -X POST 'http://localhost:8181/cxs/groovyActions' \
+--user karaf:karaf \
+--form 'file=@"&lt;file location&gt;"'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>A Groovy Action can be updated by submitting another Action with the same id.</p>
+</div>
+<div class="paragraph">
+<p>Delete an Action:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-bash" data-lang="bash">curl -X DELETE 'http://localhost:8181/cxs/groovyActions/&lt;Action id&gt;' \
+--user karaf:karaf</code></pre>
+</div>
+</div>

[... 7194 lines stripped ...]