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/10/04 15:34:28 UTC

svn commit: r1904405 [2/3] - in /unomi/website/manual: 2_0_x/ 2_0_x/connectors/ 2_0_x/images/ 2_0_x/jsonSchema/ 2_0_x/migrations/ 2_0_x/samples/ latest/

Added: unomi/website/manual/2_0_x/index.html
URL: http://svn.apache.org/viewvc/unomi/website/manual/2_0_x/index.html?rev=1904405&view=auto
==============================================================================
--- unomi/website/manual/2_0_x/index.html (added)
+++ unomi/website/manual/2_0_x/index.html Tue Oct  4 15:34:28 2022
@@ -0,0 +1,10805 @@
+<!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 2.x - Documentation</title>
+<link rel="stylesheet" href="./apache.css">
+</head>
+<body class="article toc2 toc-left">
+<div id="header">
+<h1>Apache Unomi 2.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="#_whats_new">1. What&#8217;s new</a>
+<ul class="sectlevel2">
+<li><a href="#_whats_new_in_apache_unomi_2_0">1.1. What&#8217;s new in Apache Unomi 2.0</a>
+<ul class="sectlevel3">
+<li><a href="#_scopes_declarations_are_now_required">1.1.1. Scopes declarations are now required</a></li>
+<li><a href="#_json_schemas">1.1.2. JSON Schemas</a></li>
+<li><a href="#_updated_data_model">1.1.3. Updated data model</a></li>
+<li><a href="#_new_web_tracker">1.1.4. New Web Tracker</a></li>
+<li><a href="#_graphql_api_beta">1.1.5. GraphQL API - beta</a></li>
+<li><a href="#_migrate_from_unomi_1_x">1.1.6. Migrate from Unomi 1.x</a></li>
+<li><a href="#_elasticsearch_compatibility">1.1.7. Elasticsearch compatibility</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_discover_unomi">2. Discover Unomi</a>
+<ul class="sectlevel2">
+<li><a href="#_quick_start_with_docker">2.1. Quick start with Docker</a></li>
+<li><a href="#_quick_start_manually">2.2. Quick Start manually</a></li>
+<li><a href="#_getting_started_with_unomi">2.3. Getting started with Unomi</a>
+<ul class="sectlevel3">
+<li><a href="#_prerequisites">2.3.1. Prerequisites</a></li>
+<li><a href="#_running_unomi">2.3.2. Running Unomi</a></li>
+</ul>
+</li>
+<li><a href="#_unomi_web_tracking_tutorial">2.4. Unomi web tracking tutorial</a>
+<ul class="sectlevel3">
+<li><a href="#_installing_the_web_tracker_in_a_web_page">2.4.1. Installing the web tracker in a web page</a></li>
+<li><a href="#_creating_a_scope_to_collect_the_data">2.4.2. Creating a scope to collect the data</a></li>
+<li><a href="#_using_tracker_in_your_own_javascript_projects">2.4.3. Using tracker in your own JavaScript projects</a></li>
+<li><a href="#_viewing_collected_events">2.4.4. Viewing collected events</a></li>
+<li><a href="#_viewing_the_current_profile">2.4.5. Viewing the current profile</a></li>
+<li><a href="#_adding_a_rule">2.4.6. Adding a rule</a></li>
+<li><a href="#_adding_personalization">2.4.7. Adding personalization</a></li>
+<li><a href="#_conclusion">2.4.8. Conclusion</a></li>
+<li><a href="#_next_steps">2.4.9. Next steps</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_apache_unomi_recipes_and_requests">3. Apache Unomi Recipes and requests</a>
+<ul class="sectlevel2">
+<li><a href="#_recipes">3.1. Recipes</a>
+<ul class="sectlevel3">
+<li><a href="#_introduction">3.1.1. Introduction</a></li>
+<li><a href="#_enabling_debug_mode">3.1.2. Enabling debug mode</a></li>
+<li><a href="#_how_to_read_a_profile">3.1.3. How to read a profile</a></li>
+<li><a href="#_how_to_update_a_profile_from_the_public_internet">3.1.4. How to update a profile from the public internet</a></li>
+<li><a href="#_how_to_search_for_profile_events">3.1.5. How to search for profile events</a></li>
+<li><a href="#_how_to_create_a_new_rule">3.1.6. How to create a new rule</a></li>
+<li><a href="#_how_to_search_for_profiles">3.1.7. How to search for profiles</a></li>
+<li><a href="#_getting_updating_consents">3.1.8. Getting / updating consents</a></li>
+<li><a href="#_how_to_send_a_login_event_to_unomi">3.1.9. How to send a login event to Unomi</a></li>
+</ul>
+</li>
+<li><a href="#_request_examples">3.2. Request examples</a>
+<ul class="sectlevel3">
+<li><a href="#_retrieving_your_first_context">3.2.1. Retrieving your first context</a></li>
+<li><a href="#_retrieving_a_context_as_a_json_object">3.2.2. Retrieving a context as a JSON object.</a></li>
+<li><a href="#_accessing_profile_properties_in_a_context">3.2.3. Accessing profile properties in a context</a></li>
+<li><a href="#_sending_events_using_the_context_servlet">3.2.4. Sending events using the context servlet</a></li>
+<li><a href="#_sending_events_using_the_eventcollector_servlet">3.2.5. Sending events using the eventcollector servlet</a></li>
+<li><a href="#_where_to_go_from_here">3.2.6. Where to go from here</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_configuration">4. Configuration</a>
+<ul class="sectlevel2">
+<li><a href="#_centralized_configuration">4.1. Centralized configuration</a></li>
+<li><a href="#_changing_the_default_configuration_using_environment_variables_i_e_docker_configuration">4.2. Changing the default configuration using environment variables (i.e. Docker configuration)</a></li>
+<li><a href="#_changing_the_default_configuration_using_property_files">4.3. Changing the default configuration using property files</a></li>
+<li><a href="#_secured_events_configuration">4.4. Secured events configuration</a></li>
+<li><a href="#_installing_the_maxmind_geoiplite2_ip_lookup_database">4.5. Installing the MaxMind GeoIPLite2 IP lookup database</a></li>
+<li><a href="#_installing_geonames_database">4.6. Installing Geonames database</a></li>
+<li><a href="#_rest_api_security">4.7. REST API Security</a></li>
+<li><a href="#_scripting_security">4.8. Scripting security</a>
+<ul class="sectlevel3">
+<li><a href="#_multi_layer_scripting_filtering_system">4.8.1. Multi-layer scripting filtering system</a></li>
+<li><a href="#_scripts_and_expressions">4.8.2. Scripts and expressions</a></li>
+<li><a href="#_scripting_expression_filtering_configuration_parameters">4.8.3. Scripting expression filtering configuration parameters</a></li>
+<li><a href="#_groovy_actions">4.8.4. Groovy Actions</a></li>
+<li><a href="#_scripting_roadmap">4.8.5. Scripting roadmap</a></li>
+</ul>
+</li>
+<li><a href="#_automatic_profile_merging">4.9. Automatic profile merging</a></li>
+<li><a href="#_securing_a_production_environment">4.10. Securing a production environment</a></li>
+<li><a href="#_integrating_with_an_apache_http_web_server">4.11. Integrating with an Apache HTTP web server</a></li>
+<li><a href="#_changing_the_default_tracking_location">4.12. Changing the default tracking location</a></li>
+<li><a href="#_apache_karaf_ssh_console">4.13. Apache Karaf SSH Console</a></li>
+<li><a href="#_elasticsearch_authentication_and_security">4.14. ElasticSearch authentication and security</a>
+<ul class="sectlevel3">
+<li><a href="#_user_authentication">4.14.1. User authentication !</a></li>
+<li><a href="#_ssl_communication">4.14.2. SSL communication</a></li>
+<li><a href="#_permissions">4.14.3. Permissions</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_json_schemas_2">5. JSON schemas</a>
+<ul class="sectlevel2">
+<li><a href="#_introduction_2">5.1. Introduction</a>
+<ul class="sectlevel3">
+<li><a href="#_what_is_a_json_schema">5.1.1. What is a JSON Schema</a></li>
+<li><a href="#_key_concepts">5.1.2. Key concepts</a></li>
+<li><a href="#_how_are_json_schema_used_in_unomi">5.1.3. How are JSON Schema used in Unomi</a></li>
+</ul>
+</li>
+<li><a href="#_json_schema_api">5.2. JSON schema API</a>
+<ul class="sectlevel3">
+<li><a href="#_list_existing_schemas">5.2.1. List existing schemas</a></li>
+<li><a href="#_read_a_schema">5.2.2. Read a schema</a></li>
+<li><a href="#_create_update_a_json_schema_to_validate_an_event">5.2.3. Create / update a JSON schema to validate an event</a></li>
+<li><a href="#_deleting_a_schema">5.2.4. Deleting a schema</a></li>
+<li><a href="#_error_management">5.2.5. Error Management</a></li>
+<li><a href="#_details_on_invalid_events">5.2.6. Details on invalid events</a></li>
+</ul>
+</li>
+<li><a href="#_extend_an_existing_schema">5.3. Extend an existing schema</a>
+<ul class="sectlevel3">
+<li><a href="#_when_a_extension_is_needed">5.3.1. When a extension is needed?</a></li>
+<li><a href="#_understanding_how_extensions_are_merged_in_unomi">5.3.2. Understanding how extensions are merged in unomi</a></li>
+<li><a href="#_how_to_add_an_extension_through_the_api">5.3.3. How to add an extension through the API</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_graphql_api">6. GraphQL API</a>
+<ul class="sectlevel2">
+<li><a href="#_introduction_3">6.1. Introduction</a></li>
+<li><a href="#_enabling_the_api">6.2. Enabling the API</a></li>
+<li><a href="#_endpoints">6.3. Endpoints</a></li>
+<li><a href="#_graphql_schema">6.4. GraphQL Schema</a></li>
+</ul>
+</li>
+<li><a href="#_migrations">7. Migrations</a>
+<ul class="sectlevel2">
+<li><a href="#_from_version_1_6_to_2_0">7.1. From version 1.6 to 2.0</a></li>
+<li><a href="#_migration_overview">7.2. Migration Overview</a></li>
+<li><a href="#_updating_applications_consuming_unomi">7.3. Updating applications consuming Unomi</a>
+<ul class="sectlevel3">
+<li><a href="#_data_model_changes">7.3.1. Data Model changes</a></li>
+<li><a href="#_create_json_schemas">7.3.2. Create JSON schemas</a></li>
+</ul>
+</li>
+<li><a href="#_migrating_your_existing_data">7.4. Migrating your existing data</a>
+<ul class="sectlevel3">
+<li><a href="#_elasticsearch_version_and_capacity">7.4.1. Elasticsearch version and capacity</a></li>
+<li><a href="#_migrate_custom_data">7.4.2. Migrate custom data</a></li>
+<li><a href="#_perform_the_migration">7.4.3. Perform the migration</a></li>
+</ul>
+</li>
+<li><a href="#_from_version_1_5_to_1_6">7.5. From version 1.5 to 1.6</a></li>
+<li><a href="#_from_version_1_4_to_1_5">7.6. From version 1.4 to 1.5</a>
+<ul class="sectlevel3">
+<li><a href="#_data_model_and_elasticsearch_7">7.6.1. Data model and ElasticSearch 7</a></li>
+<li><a href="#_api_changes">7.6.2. API changes</a></li>
+<li><a href="#_migration_steps">7.6.3. Migration steps</a></li>
+</ul>
+</li>
+<li><a href="#_important_changes_in_public_servlets_since_version_1_5_5_and_2_0_0">7.7. Important changes in public servlets since version 1.5.5 and 2.0.0</a></li>
+</ul>
+</li>
+<li><a href="#_queries_and_aggregations">8. Queries and aggregations</a>
+<ul class="sectlevel2">
+<li><a href="#_query_counts">8.1. Query counts</a></li>
+<li><a href="#_metrics">8.2. Metrics</a></li>
+<li><a href="#_aggregations">8.3. Aggregations</a>
+<ul class="sectlevel3">
+<li><a href="#_aggregation_types">8.3.1. Aggregation types</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_profile_import_export">9. Profile import &amp; export</a>
+<ul class="sectlevel2">
+<li><a href="#_importing_profiles">9.1. Importing profiles</a>
+<ul class="sectlevel3">
+<li><a href="#_import_api">9.1.1. Import API</a></li>
+</ul>
+</li>
+<li><a href="#_exporting_profiles">9.2. Exporting profiles</a>
+<ul class="sectlevel3">
+<li><a href="#_export_api">9.2.1. Export API</a></li>
+</ul>
+</li>
+<li><a href="#_configuration_in_details">9.3. Configuration in details</a></li>
+</ul>
+</li>
+<li><a href="#_consent_management">10. Consent management</a>
+<ul class="sectlevel2">
+<li><a href="#_consent_api">10.1. Consent API</a>
+<ul class="sectlevel3">
+<li><a href="#_profiles_with_consents">10.1.1. Profiles with consents</a></li>
+<li><a href="#_consent_type_definitions">10.1.2. Consent type definitions</a></li>
+<li><a href="#_creating_update_a_visitor_consent">10.1.3. Creating / update a visitor consent</a></li>
+<li><a href="#_how_it_works_internally">10.1.4. How it works (internally)</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_privacy_management">11. Privacy management</a>
+<ul class="sectlevel2">
+<li><a href="#_setting_up_access_to_the_privacy_endpoint">11.1. Setting up access to the privacy endpoint</a></li>
+<li><a href="#_anonymizing_a_profile">11.2. Anonymizing a profile</a></li>
+<li><a href="#_downloading_profile_data">11.3. Downloading profile data</a></li>
+<li><a href="#_deleting_a_profile">11.4. Deleting a profile</a></li>
+<li><a href="#_related">11.5. Related</a></li>
+</ul>
+</li>
+<li><a href="#_cluster_setup">12. Cluster setup</a>
+<ul class="sectlevel2">
+<li><a href="#_cluster_setup_2">12.1. Cluster setup</a></li>
+</ul>
+</li>
+<li><a href="#_reference">13. Reference</a>
+<ul class="sectlevel2">
+<li><a href="#_useful_apache_unomi_urls">13.1. Useful Apache Unomi URLs</a></li>
+<li><a href="#_how_profile_tracking_works">13.2. How profile tracking works</a>
+<ul class="sectlevel3">
+<li><a href="#_steps">13.2.1. Steps</a></li>
+</ul>
+</li>
+<li><a href="#_context_request_flow">13.3. Context Request Flow</a></li>
+<li><a href="#_data_model_overview">13.4. Data Model Overview</a></li>
+<li><a href="#_scope">13.5. Scope</a>
+<ul class="sectlevel3">
+<li><a href="#_example">13.5.1. Example</a></li>
+</ul>
+</li>
+<li><a href="#_item">13.6. Item</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition">13.6.1. Structure definition</a></li>
+</ul>
+</li>
+<li><a href="#_metadata">13.7. Metadata</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_2">13.7.1. Structure definition</a></li>
+<li><a href="#_example_2">13.7.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_metadataitem">13.8. MetadataItem</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_3">13.8.1. Structure definition</a></li>
+<li><a href="#_example_3">13.8.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_event">13.9. Event</a>
+<ul class="sectlevel3">
+<li><a href="#_fields">13.9.1. Fields</a></li>
+<li><a href="#_event_types">13.9.2. Event types</a></li>
+</ul>
+</li>
+<li><a href="#_profile">13.10. Profile</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_4">13.10.1. Structure definition</a></li>
+<li><a href="#_example_4">13.10.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_persona">13.11. Persona</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_5">13.11.1. Structure definition</a></li>
+<li><a href="#_example_5">13.11.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_consent">13.12. Consent</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_6">13.12.1. Structure definition</a></li>
+<li><a href="#_example_6">13.12.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_session">13.13. Session</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_7">13.13.1. Structure definition</a></li>
+<li><a href="#_example_7">13.13.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_segment">13.14. Segment</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_8">13.14.1. Structure definition</a></li>
+<li><a href="#_example_8">13.14.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_condition">13.15. Condition</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_9">13.15.1. Structure definition</a></li>
+<li><a href="#_example_9">13.15.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_rule">13.16. Rule</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_10">13.16.1. Structure definition</a></li>
+<li><a href="#_example_10">13.16.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_action">13.17. Action</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_11">13.17.1. Structure definition</a></li>
+<li><a href="#_example_11">13.17.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_list">13.18. List</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_12">13.18.1. Structure definition</a></li>
+<li><a href="#_example_12">13.18.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_goal">13.19. Goal</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_13">13.19.1. Structure definition</a></li>
+<li><a href="#_example_13">13.19.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_campaign">13.20. Campaign</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_14">13.20.1. Structure definition</a></li>
+<li><a href="#_example_14">13.20.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_scoring_plan">13.21. Scoring plan</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_15">13.21.1. Structure definition</a></li>
+<li><a href="#_example_15">13.21.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_event_types">13.22. Built-in Event types</a>
+<ul class="sectlevel3">
+<li><a href="#_login_event_type">13.22.1. Login event type</a></li>
+<li><a href="#_view_event_type">13.22.2. View event type</a></li>
+<li><a href="#_form_event_type">13.22.3. Form event type</a></li>
+<li><a href="#_update_properties_event_type">13.22.4. Update properties event type</a></li>
+<li><a href="#_identify_event_type">13.22.5. Identify event type</a></li>
+<li><a href="#_session_created_event_type">13.22.6. Session created event type</a></li>
+<li><a href="#_goal_event_type">13.22.7. Goal event type</a></li>
+<li><a href="#_modify_consent_event_type">13.22.8. Modify consent event type</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_condition_types">13.23. Built-in condition types</a>
+<ul class="sectlevel3">
+<li><a href="#_existing_condition_type_descriptors">13.23.1. Existing condition type descriptors</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_action_types">13.24. Built-in action types</a>
+<ul class="sectlevel3">
+<li><a href="#_existing_action_types_descriptors">13.24.1. Existing action types descriptors</a></li>
+</ul>
+</li>
+<li><a href="#_updating_events_using_the_context_servlet">13.25. Updating Events Using the Context Servlet</a>
+<ul class="sectlevel3">
+<li><a href="#_solution">13.25.1. Solution</a></li>
+<li><a href="#_defining_rules">13.25.2. Defining Rules</a></li>
+</ul>
+</li>
+<li><a href="#_unomi_web_tracker_reference">13.26. Unomi Web Tracker reference</a>
+<ul class="sectlevel3">
+<li><a href="#_custom_events">13.26.1. Custom events</a></li>
+<li><a href="#_integrating_with_tag_managers">13.26.2. Integrating with tag managers</a></li>
+<li><a href="#_cookiesession_handling">13.26.3. Cookie/session handling</a></li>
+<li><a href="#_javascript_api">13.26.4. JavaScript API</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_integration_samples">14. Integration samples</a>
+<ul class="sectlevel2">
+<li><a href="#_samples">14.1. Samples</a></li>
+<li><a href="#_login_sample">14.2. Login sample</a>
+<ul class="sectlevel3">
+<li><a href="#_warning">14.2.1. Warning !</a></li>
+<li><a href="#_installing_the_samples">14.2.2. Installing the samples</a></li>
+</ul>
+</li>
+<li><a href="#_twitter_sample">14.3. Twitter sample</a>
+<ul class="sectlevel3">
+<li><a href="#_overview">14.3.1. Overview</a></li>
+<li><a href="#_interacting_with_the_context_server">14.3.2. Interacting with the context server</a></li>
+<li><a href="#_retrieving_context_information_from_unomi_using_the_context_servlet">14.3.3. Retrieving context information from Unomi using the context servlet</a></li>
+</ul>
+</li>
+<li><a href="#_example_24">14.4. Example</a>
+<ul class="sectlevel3">
+<li><a href="#_html_page">14.4.1. HTML page</a></li>
+<li><a href="#_javascript">14.4.2. Javascript</a></li>
+</ul>
+</li>
+<li><a href="#_conclusion_2">14.5. Conclusion</a></li>
+<li><a href="#_annex">14.6. Annex</a></li>
+<li><a href="#_weather_update_sample">14.7. Weather update sample</a></li>
+</ul>
+</li>
+<li><a href="#_connectors">15. Connectors</a>
+<ul class="sectlevel2">
+<li><a href="#_connectors_2">15.1. Connectors</a>
+<ul class="sectlevel3">
+<li><a href="#_call_for_contributors">15.1.1. Call for contributors</a></li>
+</ul>
+</li>
+<li><a href="#_salesforce_connector">15.2. Salesforce Connector</a>
+<ul class="sectlevel3">
+<li><a href="#_getting_started">15.2.1. Getting started</a></li>
+<li><a href="#_properties">15.2.2. Properties</a></li>
+<li><a href="#_hot_deploying_updates_to_the_salesforce_connector_for_developers">15.2.3. Hot-deploying updates to the Salesforce connector (for developers)</a></li>
+<li><a href="#_using_the_salesforce_workbench_for_testing_rest_api">15.2.4. Using the Salesforce Workbench for testing REST API</a></li>
+<li><a href="#_setting_up_streaming_push_queries">15.2.5. Setting up Streaming Push queries</a></li>
+<li><a href="#_executing_the_unit_tests">15.2.6. Executing the unit tests</a></li>
+</ul>
+</li>
+<li><a href="#_mailchimp_connector">15.3. MailChimp Connector</a>
+<ul class="sectlevel3">
+<li><a href="#_getting_started_2">15.3.1. Getting started</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a href="#_developers">16. Developers</a>
+<ul class="sectlevel2">
+<li><a href="#_building">16.1. Building</a>
+<ul class="sectlevel3">
+<li><a href="#_initial_setup">16.1.1. Initial Setup</a></li>
+<li><a href="#_building_2">16.1.2. Building</a></li>
+<li><a href="#_installing_an_elasticsearch_server">16.1.3. Installing an ElasticSearch server</a></li>
+<li><a href="#_deploying_the_generated_binary_package">16.1.4. Deploying the generated binary package</a></li>
+<li><a href="#_deploying_into_an_existing_karaf_server">16.1.5. Deploying into an existing Karaf server</a></li>
+<li><a href="#_jdk_selection_on_mac_os_x">16.1.6. JDK Selection on Mac OS X</a></li>
+<li><a href="#_running_the_integration_tests">16.1.7. Running the integration tests</a></li>
+<li><a href="#_testing_with_an_example_page">16.1.8. Testing with an example page</a></li>
+</ul>
+</li>
+<li><a href="#_ssh_shell_commands">16.2. SSH Shell Commands</a>
+<ul class="sectlevel3">
+<li><a href="#_using_the_shell">16.2.1. Using the shell</a></li>
+<li><a href="#_lifecycle_commands">16.2.2. Lifecycle commands</a></li>
+<li><a href="#_runtime_commands">16.2.3. Runtime commands</a></li>
+</ul>
+</li>
+<li><a href="#_writing_plugins">16.3. Writing Plugins</a></li>
+<li><a href="#_types_vs_instances">16.4. Types vs. instances</a></li>
+<li><a href="#_plugin_structure">16.5. Plugin structure</a></li>
+<li><a href="#_extension_points">16.6. Extension points</a>
+<ul class="sectlevel3">
+<li><a href="#_actiontype">16.6.1. ActionType</a></li>
+<li><a href="#_conditiontype">16.6.2. ConditionType</a></li>
+<li><a href="#_persona_2">16.6.3. Persona</a></li>
+<li><a href="#_propertymergestrategytype">16.6.4. PropertyMergeStrategyType</a></li>
+<li><a href="#_propertytype">16.6.5. PropertyType</a></li>
+<li><a href="#_rule_2">16.6.6. Rule</a></li>
+<li><a href="#_scoring">16.6.7. Scoring</a></li>
+<li><a href="#_segments">16.6.8. Segments</a></li>
+<li><a href="#_tag">16.6.9. Tag</a></li>
+<li><a href="#_valuetype">16.6.10. ValueType</a></li>
+</ul>
+</li>
+<li><a href="#_custom_plugins">16.7. Custom plugins</a>
+<ul class="sectlevel3">
+<li><a href="#_creating_a_plugin">16.7.1. Creating a plugin</a></li>
+<li><a href="#_deployment_and_custom_definition">16.7.2. Deployment and custom definition</a></li>
+<li><a href="#_predefined_segments">16.7.3. Predefined segments</a></li>
+<li><a href="#_predefined_rules">16.7.4. Predefined rules</a></li>
+<li><a href="#_predefined_properties">16.7.5. Predefined properties</a></li>
+<li><a href="#_predefined_child_conditions">16.7.6. Predefined child conditions</a></li>
+<li><a href="#_predefined_personas">16.7.7. Predefined personas</a></li>
+<li><a href="#_custom_action_types">16.7.8. Custom action types</a></li>
+<li><a href="#_custom_condition_types">16.7.9. Custom condition types</a></li>
+</ul>
+</li>
+<li><a href="#_migration_patches">16.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="_whats_new">1. What&#8217;s new</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_whats_new_in_apache_unomi_2_0">1.1. What&#8217;s new in Apache Unomi 2.0</h3>
+<div class="paragraph">
+<p>Apache Unomi 2 is a new release focused on improving core functionalities and robustness of the product.
+The introduction of tighter data validation with JSON Schemas required some changes in the product data model, which presented an opportunity for noticeable improvements in the overall performance.</p>
+</div>
+<div class="paragraph">
+<p>This new release also introduces a first (beta) version of the Unomi GraphQL API.</p>
+</div>
+<div class="sect3">
+<h4 id="_scopes_declarations_are_now_required">1.1.1. Scopes declarations are now required</h4>
+<div class="paragraph">
+<p>Scopes declarations are now required in Unomi 2. When submitting an event and specifying a scope,
+that scope must already be declared on the platform.</p>
+</div>
+<div class="paragraph">
+<p>Scopes can be easily created via the corresponding REST API (<code>cxs/scopes</code>)</p>
+</div>
+<div class="paragraph">
+<p>For example, an "apache" scope can be created using the following API call.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl --location --request POST 'http://localhost:8181/cxs/scopes' \
+-u 'karaf:karaf' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+"itemId": "apache",
+"itemType": "scope"
+}'</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_json_schemas">1.1.2. JSON Schemas</h4>
+<div class="paragraph">
+<p>Apache Unomi 2 introduces support for <a href="https://json-schema.org/specification.html">JSON Schema</a> for all of its publicly exposed endpoints.
+Data received by Apache Unomi 2 will first be validated against a known schema to make sure it complies with an expected payload.
+If the received payload does not match a known schema, it will be rejected by Apache Unomi 2.</p>
+</div>
+<div class="paragraph">
+<p>Apache Unomi 2 also introduces a set of administrative endpoints allowing new schemas and/or schemas extensions to be registered.</p>
+</div>
+<div class="paragraph">
+<p>More details about JSON Schemas implementation are available in the <a href="#_json_schemas_2">corresponding section</a> of the documentation.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_updated_data_model">1.1.3. Updated data model</h4>
+<div class="paragraph">
+<p>The introduction of JSON schema required us to modify Apache Unomi data model, One of the key differences is the removal of open maps.</p>
+</div>
+<div class="paragraph">
+<p>The properties field in the events objects provided by unomi are now restricted by JSON schema.
+This means object properties must be declared in a JSON schema for an event to be accepted.</p>
+</div>
+<div class="paragraph">
+<p>A new property, flattenedProperties has been introduced to the event object, this property has been added to store the properties as
+flattened in Elasticsearch and should avoid mapping explosion for dynamic properties.</p>
+</div>
+<div class="paragraph">
+<p>If there is dynamic properties that you want to send with your event, you should use the flattenedProperties field of the event.</p>
+</div>
+<div class="paragraph">
+<p>It&#8217;s also necessary to specify the format of the values which are added to flattenedProperties by JSON schema but these value will be
+stored as flattened and will not create dynamic mapping contrary to the properties field of the events.</p>
+</div>
+<div class="paragraph">
+<p>Here is an example for objects that used dynamic properties for URL parameters:</p>
+</div>
+<div class="paragraph">
+<p>The following event example in Apache Unomi 1.x:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>{
+    "eventType":"view",
+    "scope":"digitall",
+    "properties":{
+        "URLParameters":{
+           "utm_source":"source"
+        }
+    },
+    "target":{
+        "scope":"digitall",
+        "itemId":"30c0a9e3-4330-417d-9c66-4c1beec85f08",
+        "itemType":"page",
+        "properties":{
+           "pageInfo":{
+              "pageID":"30c0a9e3-4330-417d-9c66-4c1beec85f08",
+              "nodeType":"jnt:page",
+              "pageName":"Home",
+              ...
+           },
+           "attributes":{},
+           "consentTypes":[]
+        }
+    },
+    "source":{
+        "scope":"digitall",
+        "itemId":"ff5886e0-d75a-4061-9de9-d90dfc9e18d8",
+        "itemType":"site"
+    }
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Is replaced by the following in Apache Unomi 2.x:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>{
+    "eventType":"view",
+    "scope":"digitall",
+    "flattenedProperties":{
+        "URLParameters":{
+           "utm_source":"source"
+        }
+    },
+    "target":{
+        "scope":"digitall",
+        "itemId":"30c0a9e3-4330-417d-9c66-4c1beec85f08",
+        "itemType":"page",
+        "properties":{
+           "pageInfo":{
+              "pageID":"30c0a9e3-4330-417d-9c66-4c1beec85f08",
+              "nodeType":"jnt:page",
+              "pageName":"Home",
+              ...
+           },
+           "attributes":{},
+           "consentTypes":[]
+        }
+    },
+    "source":{
+        "scope":"digitall",
+        "itemId":"ff5886e0-d75a-4061-9de9-d90dfc9e18d8",
+        "itemType":"site"
+    }
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>If using the default Apache 1.x data model, our Unomi 2 migration process will handle the data model changes for you.
+If you are using custom events/objects, please refer to the detailed migration guide for more details.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_new_web_tracker">1.1.4. New Web Tracker</h4>
+<div class="paragraph">
+<p>Apache Unomi 2.0 Web Tracker, located in <code>extensions/web-tracker/</code> has been completely rewritten. It is no longer based on an external library and is fully self-sufficient. It is based on an external contribution that has been used in production on many sites.</p>
+</div>
+<div class="paragraph">
+<p>You can find more information about the new web tracker here: [TODO ADD LINK]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_graphql_api_beta">1.1.5. GraphQL API - beta</h4>
+<div class="paragraph">
+<p>Apache Unomi 2.0 sees the introduction of a new (beta) GraphQL API.
+Available behind a feature flag (the API disabled by default), the GraphQL API is available for you to play with.</p>
+</div>
+<div class="paragraph">
+<p>More details about how to enable/disable the GraphQL API are available in the <a href="#_graphql_api">corresponding section</a> of the documentation.</p>
+</div>
+<div class="paragraph">
+<p>We welcome tickets/PRs to improve its robustness and progressively make it ready for prime time.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_migrate_from_unomi_1_x">1.1.6. Migrate from Unomi 1.x</h4>
+<div class="paragraph">
+<p>To facilitate migration we prepared a set of scripts that will automatically handle the migration of your data from Apache Unomi 1.5+ to Apache Unomi 2.0.</p>
+</div>
+<div class="paragraph">
+<p>It is worth keeping in mind that for Apache Unomi 2.0 we do not support “hot” migration,
+the migration process will require a shutdown of your cluster to guarantee that no new events will be collected while data migration is in progress.</p>
+</div>
+<div class="paragraph">
+<p>Special caution must be taken if you declared custom events as our migration scripts can only handle objects we know of.
+More details about migration (incl. of custom events) is available in the corresponding section <a href="#_migrations">corresponding section</a> of the documentation.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_elasticsearch_compatibility">1.1.7. Elasticsearch compatibility</h4>
+<div class="paragraph">
+<p>We currently recommend using Elasticsearch 7.17.5 with Apache Unomi 2.0,
+this ensure you are on a recent version that is not impacted by the log4j vulnerabilities (fixed in Elasticsearch 7.16.3).</p>
+</div>
+<div class="paragraph">
+<p>This version increase is releated to Apache Unomi 2.0 makeing use of a new Elasticsearch field type
+called <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/flattened.html">Flattened</a>,
+and although it was available in prior versions of Elasticsearch, we do not recommend using those
+due to the above-mentioned log4j vulnerabilities.</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_discover_unomi">2. Discover Unomi</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_quick_start_with_docker">2.1. Quick start with Docker</h3>
+<div class="paragraph">
+<p>Begin by creating a <code>docker-compose.yml</code> file with the following content:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>version: '3.8'
+services:
+    elasticsearch:
+    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5
+    environment:
+        - discovery.type=single-node
+    ports:
+        - 9200:9200
+    unomi:
+    # Unomi version can be updated based on your needs
+    image: apache/unomi:2.0.0
+    environment:
+        - UNOMI_ELASTICSEARCH_ADDRESSES=elasticsearch:9200
+        - UNOMI_THIRDPARTY_PROVIDER1_IPADDRESSES=0.0.0.0/0,::1,127.0.0.1
+    ports:
+        - 8181:8181
+        - 9443:9443
+        - 8102:8102
+    links:
+        - elasticsearch
+    depends_on:
+        - elasticsearch</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>From the same folder, start the environment using <code>docker-compose up</code> and wait for the startup to complete.</p>
+</div>
+<div class="paragraph">
+<p>Try accessing <a href="https://localhost:9443/cxs/cluster" class="bare">https://localhost:9443/cxs/cluster</a> with username/password: karaf/karaf . You might get a certificate warning in your browser, just accept it despite the warning it is safe.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_quick_start_manually">2.2. Quick Start manually</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-17-5" class="bare">https://www.elastic.co/downloads/past-releases/elasticsearch-7-17-5</a> (please &lt;strong&gt;make sure&lt;/strong&gt; you use the proper version : 7.17.5)</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>Trying our integration <a href="#_samples">samples page</a></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_getting_started_with_unomi">2.3. 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.3.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_2">ElasticSearch compatibility</h5>
+<div class="paragraph">
+<p>Starting with version 2.0.0 Apache Unomi adds compatibility with ElasticSearch 7.17.5 . It is highly recommended to use the
+ElasticSearch version specified in the documentation whenever possible. 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.3.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">quick start with docker</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="_unomi_web_tracking_tutorial">2.4. Unomi web tracking tutorial</h3>
+<div class="paragraph">
+<p>In this tutorial we will guide through the basic steps of getting started with a web tracking project. You will see how to integrate the built-in web tracker with an existing web site and what this enables.</p>
+</div>
+<div class="sect3">
+<h4 id="_installing_the_web_tracker_in_a_web_page">2.4.1. Installing the web tracker in a web page</h4>
+<div class="paragraph">
+<p>Using the built-in tracker is pretty simple, simply add the following code to your HTML page :</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>&lt;script type="text/javascript" src="/tracker/unomi-web-tracker.min.js"&gt;&lt;/script&gt;</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>or you can also use the non-minified version that is available here:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>&lt;script type="text/javascript" src="/tracker/unomi-web-tracker.js"&gt;&lt;/script&gt;</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This will only load the tracker. To initialize it use a snipper like the following code:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">    &lt;script type="text/javascript"&gt;
+        (function () {
+            const unomiTrackerTestConf = {
+                "scope": "unomi-tracker-test",
+                "site": {
+                    "siteInfo": {
+                        "siteID": "unomi-tracker-test"
+                    }
+                },
+                "page": {
+                    "pageInfo": {
+                        "pageID": "unomi-tracker-test-page",
+                        "pageName": document.title,
+                        "pagePath": document.location.pathname,
+                        "destinationURL": document.location.origin + document.location.pathname,
+                        "language": "en",
+                        "categories": [],
+                        "tags": []
+                    },
+                    "attributes": {},
+                    "consentTypes": []
+                },
+                "events:": [],
+                "wemInitConfig": {
+                    "contextServerUrl": document.location.origin,
+                    "timeoutInMilliseconds": "1500",
+                    "contextServerCookieName": "context-profile-id",
+                    "activateWem": true,
+                    "trackerSessionIdCookieName": "unomi-tracker-test-session-id",
+                    "trackerProfileIdCookieName": "unomi-tracker-test-profile-id"
+                }
+            }
+
+            // generate a new session
+            if (unomiWebTracker.getCookie(unomiTrackerTestConf.wemInitConfig.trackerSessionIdCookieName) == null) {
+                unomiWebTracker.setCookie(unomiTrackerTestConf.wemInitConfig.trackerSessionIdCookieName, unomiWebTracker.generateGuid(), 1);
+            }
+
+            // init tracker with our conf
+            unomiWebTracker.initTracker(unomiTrackerTestConf);
+
+            unomiWebTracker._registerCallback(() =&gt; {
+                console.log("Unomi tracker test successfully loaded context", unomiWebTracker.getLoadedContext());
+            }, 'Unomi tracker test callback example');
+
+            // start the tracker
+            unomiWebTracker.startTracker();
+        })();
+    &lt;/script&gt;</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_creating_a_scope_to_collect_the_data">2.4.2. Creating a scope to collect the data</h4>
+<div class="paragraph">
+<p>You might notice the <code>scope</code> used in the snippet. All events sent to Unomi must be associated with a scope, that must have been created before events are accepted. So in order to make sure the events are collected with the above Javascript code, we must create a scope with the following request.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-sh" data-lang="sh">curl --location --request POST 'http://localhost:8181/cxs/scopes' \
+  --header 'Authorization: Basic a2FyYWY6a2FyYWY=' \
+  --header 'Content-Type: application/json' \
+  --data-raw '{
+    "itemId": "unomi-tracker-test",
+    "metadata": {
+      "id": "unomi-tracker-test",
+      "name": "Unomi tracker Test Scope"
+    }
+  }'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The authorization is the default username/password for the REST API, which is <code>karaf:karaf</code> and you that should definitely be changed as soon as possible by modifying the <code>etc/users.properties</code> file.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_using_tracker_in_your_own_javascript_projects">2.4.3. Using tracker in your own JavaScript projects</h4>
+<div class="paragraph">
+<p>The tracker also exists as an NPM library that you can integrate with your own Javascript projects. You can find the library here:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>https://www.npmjs.com/package/apache-unomi-tracker</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Here&#8217;s an example on how to use it:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>yarn add apache-unomi-tracker</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>You can then simply use it in your JS code using something like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">import {useTracker} from "apache-unomi-tracker";
+
+(function () {
+    const unomiWebTracker = useTracker();
+    const unomiTrackerTestConf = {
+        "scope": "unomi-tracker-test",
+        "site": {
+            "siteInfo": {
+                "siteID": "unomi-tracker-test"
+            }
+        },
+        "page": {
+            "pageInfo": {
+                "pageID": "unomi-tracker-test-page",
+                "pageName": document.title,
+                "pagePath": document.location.pathname,
+                "destinationURL": document.location.origin + document.location.pathname,
+                "language": "en",
+                "categories": [],
+                "tags": []
+            },
+            "attributes": {},
+            "consentTypes": []
+        },
+        "events:": [],
+        "wemInitConfig": {
+            "contextServerUrl": document.location.origin,
+            "timeoutInMilliseconds": "1500",
+            "contextServerCookieName": "context-profile-id",
+            "activateWem": true,
+            "trackerSessionIdCookieName": "unomi-tracker-test-session-id",
+            "trackerProfileIdCookieName": "unomi-tracker-test-profile-id"
+        }
+    }
+
+    // generate a new session
+    if (unomiWebTracker.getCookie(unomiTrackerTestConf.wemInitConfig.trackerSessionIdCookieName) == null) {
+        unomiWebTracker.setCookie(unomiTrackerTestConf.wemInitConfig.trackerSessionIdCookieName, unomiWebTracker.generateGuid(), 1);
+    }
+
+    // init tracker with our conf
+    unomiWebTracker.initTracker(unomiTrackerTestConf);
+
+    unomiWebTracker._registerCallback(() =&gt; {
+        console.log("Unomi tracker test successfully loaded context", unomiWebTracker.getLoadedContext());
+    }, 'Unomi tracker test callback example');
+
+    // start the tracker
+    unomiWebTracker.startTracker();
+})();</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_viewing_collected_events">2.4.4. Viewing collected events</h4>
+<div class="paragraph">
+<p>There are multiple ways to view the events that were received. For example, you could use the following cURL request:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-sh" data-lang="sh">curl --location --request POST 'http://localhost:8181/cxs/events/search' \
+  --header 'Authorization: Basic a2FyYWY6a2FyYWY=' \
+  --header 'Content-Type: application/json' \
+  --data-raw '{
+    "sortby" : "timeStamp:desc",
+    "condition" : {
+      "type" : "matchAllCondition"
+    }
+  }'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Another (powerful) way to look at events is to use the SSH Console. You can connect to it with the following shell command:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>ssh -p 8102 karaf@localhost</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Using the same username password (karaf:karaf) and then you can use command such as :</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>event-tail</code> to view in realtime the events as they come in (CTRL+C to stop)</p>
+</li>
+<li>
+<p><code>event-list</code> to view the latest events</p>
+</li>
+<li>
+<p><code>event-view EVENT_ID</code> to view the details of a specific event</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_viewing_the_current_profile">2.4.5. Viewing the current profile</h4>
+<div class="paragraph">
+<p>By default, Unomi uses a cookie called context-profile-id to keep track of the current profile. You can use this the value of this cookie which contains a UUID to lookup the details of the profile. For example with the SSH console you can simply to:</p>
+</div>
+<div class="literalblock">
+<div class="content">
+<pre>profile-view PROFILE_UUID</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Which will print out the details of the profile with the associated ID.
+Another interesting command is <code>profile-list</code> to list all the recently modified profiles</p>
+</div>
+<div class="paragraph">
+<p>You could also retrieve the profile details using the REST API by using a request such as this one:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-sh" data-lang="sh">curl --location --request GET 'http://localhost:8181/cxs/profiles/PROFILE_UUID' \
+--header 'Authorization: Basic a2FyYWY6a2FyYWY=' \</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_adding_a_rule">2.4.6. Adding a rule</h4>
+<div class="paragraph">
+<p>Rules are a powerful ways to react in real-time to incoming events. For example a rule could update a profile when a certain event comes in, either copying values from the event or performing some kind of computation when the event occurs, including accessing remote systems such as a Salesforce CRM (see the Salesforce connector sample).</p>
+</div>
+<div class="paragraph">
+<p>In this example we will simply setup a basic rule that will react to the <code>view</code> event and set a property in the current profile.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-sh" data-lang="sh">curl --location --request POST 'http://localhost:8181/cxs/rules' \
+--header 'Authorization: Basic a2FyYWY6a2FyYWY=' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+    "metadata": {
+        "id": "viewEventRule",
+        "name": "View event rule",
+        "description": "Increments a property on a profile to indicate that this rule executed successfully when a view event occurs"
+    },
+    "condition": {
+        "type": "eventTypeCondition",
+        "parameterValues": {
+            "eventTypeId": "view"
+        }
+    },
+    "actions": [
+        {
+            "type": "incrementPropertyAction",
+            "parameterValues": {
+                "propertyName": "pageViewCount"
+            }
+        }
+    ]
+}'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The above rule will execute when a view event is received (which is automatically sent by the tracker when a page is loaded) and increments a property called <code>pageViewCount</code> on the user&#8217;s profile.</p>
+</div>
+<div class="paragraph">
+<p>You can then reload then page and check with the <code>profile-view PROFILE_UUID</code> SSH command that the profile was updated with the new property and that it is incremented on each page reload.</p>
+</div>
+<div class="paragraph">
+<p>You can also use the <code>rule-list</code> command to display all the rules in the system and the <code>rule-tail</code> to watch in real-time which rules are executed. The <code>rule-view RULE_ID</code> command will let you view the contents of a rule.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_adding_personalization">2.4.7. Adding personalization</h4>
+<div class="paragraph">
+<p>The last step is to use the newly added property to the profile to perform some page personalization. In order to do that we will use the tracker&#8217;s API to register a personalization that will be</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">            variants = {
+                "var1" : {
+                    content : "variant1",
+                },
+                "var2" : {
+                    content : "variant2",
+                }
+            }
+            unomiWebTracker.registerPersonalizationObject({
+                "id": "testPersonalization",
+                "strategy": "matching-first",
+                "strategyOptions": {"fallback": "var2"},
+                "contents": [{
+                    "id": "var1",
+                    "filters": [{
+                        "condition": {
+                            "type": "profilePropertyCondition",
+                            "parameterValues": {
+                                "propertyName" : "properties.pageViewCount",
+                                "comparisonOperator" : "greaterThan",
+                                "propertyValueInteger" : 5
+                            }
+                        }
+                    }]
+                }, {
+                    "id": "var2"
+                }]
+            }, variants, false, function (successfulFilters, selectedFilter) {
+                if (selectedFilter) {
+                    document.getElementById(selectedFilter.content).style.display = '';
+                }
+            });</code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_conclusion">2.4.8. Conclusion</h4>
+<div class="paragraph">
+<p>What have we achieved so far ?</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Installed a tracker
+-</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_next_steps">2.4.9. Next steps</h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Learn more about the tracker, custom events, API, etc&#8230;&#8203;</p>
+</li>
+<li>
+<p>Learn more about segmentation,</p>
+</li>
+<li>
+<p>Read Unomi&#8217;s user manual to see all that is possible with this technology</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_apache_unomi_recipes_and_requests">3. Apache Unomi Recipes and requests</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_recipes">3.1. Recipes</h3>
+<div class="sect3">
+<h4 id="_introduction">3.1.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="_enabling_debug_mode">3.1.2. Enabling debug mode</h4>
+<div class="paragraph">
+<p>Although the examples provided in this documentation are correct (they will work "as-is"),
+you might be tempted to modify them to fit your use case, which might result in errors.</p>
+</div>
+<div class="paragraph">
+<p>The best approach during development is to enable Apache Unomi debug mode, which will provide
+you with more detailed logs about events processing.</p>
+</div>
+<div class="paragraph">
+<p>The debug mode can be activated via the karaf SSH console (default credentials are karaf/karaf):</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>ubuntu@ip-10-0-3-252:~/$ ssh -p 8102 karaf@localhost
+Password authentication
+Password:
+        __ __                  ____
+       / //_/____ __________ _/ __/
+      / ,&lt;  / __ `/ ___/ __ `/ /_
+     / /| |/ /_/ / /  / /_/ / __/
+    /_/ |_|\__,_/_/   \__,_/_/
+
+  Apache Karaf (4.2.15)
+
+Hit '&lt;tab&gt;' for a list of available commands
+and '[cmd] --help' for help on a specific command.
+Hit 'system:shutdown' to shutdown Karaf.
+Hit '&lt;ctrl-d&gt;' or type 'logout' to disconnect shell from current session.
+
+karaf@root()&gt; log:set DEBUG org.apache.unomi.schema.impl.SchemaServiceImpl</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>You can then either watch the logs via your preferred logging mechanism (docker logs, log file, &#8230;&#8203;) or
+simply tail the logs to the terminal you used to enable debug mode.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>karaf@root()&gt; log:tail
+08:55:28.128 DEBUG [qtp1422628821-128] Schema validation found 2 errors while validating against schema: https://unomi.apache.org/schemas/json/events/view/1-0-0
+08:55:28.138 DEBUG [qtp1422628821-128] Validation error: There are unevaluated properties at following paths $.source.properties
+08:55:28.140 DEBUG [qtp1422628821-128] Validation error: There are unevaluated properties at following paths $.source.itemId, $.source.itemType, $.source.scope, $.source.properties
+08:55:28.142 ERROR [qtp1422628821-128] An event was rejected - switch to DEBUG log level for more information</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The example above shows schema validation failure at the <code>$.source.properties</code> path.
+Note that the validation will output one log line for the exact failing path and a log line for its parent,
+therefore to find the source of a schema validation issue it&#8217;s best to start from the top.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_read_a_profile">3.1.3. 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" \
+--data-raw '{
+    "source": {
+        "itemId":"homepage",
+        "itemType":"page",
+        "scope":"example"
+    },
+    "requiredProfileProperties":["*"],
+    "requiredSessionProperties":["*"],
+    "requireSegments":true,
+    "requireScores":true
+}'</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">3.1.4. 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,
+defining the corresponding JSON schema and you&#8217;re ready to update profiles using events.</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" \
+--data-raw '{
+  "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"
+      }
+    }
+  ]
+}'</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>Now that our rule is defined, the next step is to create a scope and a JSON Schema corresponding to the event to be submitted.</p>
+</div>
+<div class="paragraph">
+<p>We will start by creating a scope called "example" scope:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl --location --request POST 'http://localhost:8181/cxs/scopes' \
+-u 'karaf:karaf' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+"itemId": "example",
+"itemType": "scope"
+}'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The next step consist in creating a JSON Schema to validate our event.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>curl --location --request POST 'http://localhost:8181/cxs/jsonSchema' \
+-u 'karaf:karaf' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+    "$id": "https://unomi.apache.org/schemas/json/events/contactInfoSubmitted/1-0-0",
+    "$schema": "https://json-schema.org/draft/2019-09/schema",
+    "self": {
+        "vendor": "org.apache.unomi",
+        "name": "contactInfoSubmitted",
+        "format": "jsonschema",
+        "target": "events",
+        "version": "1-0-0"
+    },
+    "title": "contactInfoSubmittedEvent",
+    "type": "object",
+    "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }],
+    "properties": {
+        "source" : {
+          "$ref" : "https://unomi.apache.org/schemas/json/item/1-0-0"
+        },
+        "target" : {
+          "$ref" : "https://unomi.apache.org/schemas/json/item/1-0-0"
+        },
+        "properties": {
+          "type": "object",
+          "properties": {
+            "firstName": {
+              "type": ["null", "string"]
+            },
+            "lastName": {
+              "type": ["null", "string"]
+            },
+            "email": {
+              "type": ["null", "string"]
+            }
+          }
+        }
+    },
+    "unevaluatedProperties": false
+}'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>You can notice the following in the above schema:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>We are creating a schema of type "events" ("self.target" equals "events")</p>
+</li>
+<li>
+<p>The name of this schema is "contactInfoSubmitted", this MUST match the value of the "eventType" field in the event itself (below)</p>
+</li>
+<li>
+<p>To simplify our schema declaration, we&#8217;re referring to an already existing schema (<a href="https://unomi.apache.org/schemas/json/item/1-0-0" class="bare">https://unomi.apache.org/schemas/json/item/1-0-0</a>) to validate the "source" and "target" properties. Apache Unomi ships with a set of predefined JSON Schemas, detailed here: <a href="https://github.com/apache/unomi/tree/master/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas" class="bare">https://github.com/apache/unomi/tree/master/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas</a>.</p>
+</li>
+<li>
+<p><code>"unevaluatedProperties": false</code> indicates that the event should be rejected if it contains any additional metadata.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Finally, 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" \
+--data-raw '{
+    "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"
+            }
+        }
+    ]
+}'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The event we just submitted can be retrieved using the following request:</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" \
+--data-raw '{
+  "offset" : 0,
+  "limit" : 20,
+  "condition" : {
+    "type": "eventPropertyCondition",
+    "parameterValues" : {
+      "propertyName" : "properties.firstName",
+      "comparisonOperator" : "equals",
+      "propertyValue" : "John"
+    }
+  }
+}'</code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_troubleshooting_common_errors">Troubleshooting common errors</h5>
+<div class="paragraph">
+<p>There could be two types of common errors while customizing the above requests:
+* The schema is invalid
+* The event is invalid</p>
+</div>
+<div class="paragraph">
+<p>While first submitting the schema during its creation, Apache Unomi will validate it is syntaxically correct (JSON)
+but will not perform any further validation. Since the schema will be processed for the first time when events are submitted,
+errors might be noticeable at that time.</p>
+</div>
+<div class="paragraph">
+<p>Those errors are usually self-explanatory, such as this one pointing to an incorrect lcoation for the "firstName" keyword:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>09:35:56.573 WARN [qtp1421852915-83] Unknown keyword firstName - you should define your own Meta Schema. If the keyword is irrelevant for validation, just use a NonValidationKeyword</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>If an event is invalid, the logs will contain details about the part of the event that did not validate against the schema.
+In the example below, an extra property "abcd" was added to the event:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code>12:27:04.269 DEBUG [qtp1421852915-481] Schema validation found 1 errors while validating against schema: https://unomi.apache.org/schemas/json/events/contactInfoSubmitted/1-0-0
+12:27:04.272 DEBUG [qtp1421852915-481] Validation error: There are unevaluated properties at following paths $.properties.abcd
+12:27:04.273 ERROR [qtp1421852915-481] An event was rejected - switch to DEBUG log level for more information</code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_to_search_for_profile_events">3.1.5. 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" \
+--data-raw '{
+  "offset" : 0,
+  "limit" : 20,
+  "condition" : {
+    "type": "eventPropertyCondition",
+    "parameterValues" : {
+      "propertyName" : "profileId",
+      "comparisonOperator" : "equals",
+      "propertyValue" : "PROFILE_ID"
+    }
+  }
+}'</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">3.1.6. 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" \
+--data-raw '{
+  "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"
+    }
+  ]
+}'</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">3.1.7. 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" \
+--data-raw '{
+  "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"
+          }
+        }
+      ]
+    }
+  }
+}'</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">3.1.8. 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">3.1.9. 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">3.2. Request examples</h3>
+<div class="sect3">
+<h4 id="_retrieving_your_first_context">3.2.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">3.2.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">3.2.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">3.2.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":"https://apache.org/"
+                    }
+                }
+            }
+        }
+    ]
+}
+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">3.2.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":"https://apache.org/"
+                    }
+                }
+            }
+        }
+    ]
+}
+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">3.2.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>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>
+</div>
+<div class="sect1">
+<h2 id="_configuration">4. Configuration</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_centralized_configuration">4.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">4.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">4.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>

[... 8752 lines stripped ...]