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 2023/05/22 14:04:50 UTC

svn commit: r1909986 [4/4] - in /unomi/website/manual: 1_9_x/ 2_3_x/ 2_3_x/connectors/ 2_3_x/images/ 2_3_x/jsonSchema/ 2_3_x/migrations/ 2_3_x/samples/ latest/ latest/connectors/ latest/images/ latest/jsonSchema/ latest/migrations/ latest/samples/

Added: unomi/website/manual/latest/index.html
URL: http://svn.apache.org/viewvc/unomi/website/manual/latest/index.html?rev=1909986&view=auto
==============================================================================
--- unomi/website/manual/latest/index.html (added)
+++ unomi/website/manual/latest/index.html Mon May 22 14:04:49 2023
@@ -0,0 +1,11478 @@
+<!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="#_introducing_profiles_aliases">1.1.1. Introducing profiles aliases</a></li>
+<li><a href="#_scopes_declarations_are_now_required">1.1.2. Scopes declarations are now required</a></li>
+<li><a href="#_json_schemas">1.1.3. JSON Schemas</a></li>
+<li><a href="#_updated_data_model">1.1.4. Updated data model</a></li>
+<li><a href="#_new_web_tracker">1.1.5. New Web Tracker</a></li>
+<li><a href="#_graphql_api_beta">1.1.6. GraphQL API - beta</a></li>
+<li><a href="#_migrate_from_unomi_1_x">1.1.7. Migrate from Unomi 1.x</a></li>
+<li><a href="#_elasticsearch_compatibility">1.1.8. 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>
+<li><a href="#_what_profile_aliases_are_and_how_to_use_them">3.1.10. What profile aliases are and how to use them</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="#_develop_with_unomi_and_json_schemas">5.3. Develop with Unomi and JSON Schemas</a>
+<ul class="sectlevel3">
+<li><a href="#_logs_in_debug_mode">5.3.1. Logs in debug mode</a></li>
+<li><a href="#_validateevent_endpoint">5.3.2. validateEvent endpoint</a></li>
+<li><a href="#_validateevents_endpoint">5.3.3. validateEvents endpoint</a></li>
+</ul>
+</li>
+<li><a href="#_extend_an_existing_schema">5.4. Extend an existing schema</a>
+<ul class="sectlevel3">
+<li><a href="#_when_a_extension_is_needed">5.4.1. When a extension is needed?</a></li>
+<li><a href="#_understanding_how_extensions_are_merged_in_unomi">5.4.2. Understanding how extensions are merged in unomi</a></li>
+<li><a href="#_how_to_add_an_extension_through_the_api">5.4.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_2">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_3">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_4">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_5">13.10.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_profile_aliases">13.11. Profile aliases</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_5">13.11.1. Structure definition</a></li>
+<li><a href="#_example_6">13.11.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_persona">13.12. Persona</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_6">13.12.1. Structure definition</a></li>
+<li><a href="#_example_7">13.12.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_consent">13.13. Consent</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_7">13.13.1. Structure definition</a></li>
+<li><a href="#_example_8">13.13.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_session">13.14. Session</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_8">13.14.1. Structure definition</a></li>
+<li><a href="#_example_9">13.14.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_segment">13.15. Segment</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_9">13.15.1. Structure definition</a></li>
+<li><a href="#_example_10">13.15.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_condition">13.16. Condition</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_10">13.16.1. Structure definition</a></li>
+<li><a href="#_example_11">13.16.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_rule">13.17. Rule</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_11">13.17.1. Structure definition</a></li>
+<li><a href="#_example_12">13.17.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_action">13.18. Action</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_12">13.18.1. Structure definition</a></li>
+<li><a href="#_example_13">13.18.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_list">13.19. List</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_13">13.19.1. Structure definition</a></li>
+<li><a href="#_example_14">13.19.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_goal">13.20. Goal</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_14">13.20.1. Structure definition</a></li>
+<li><a href="#_example_15">13.20.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_campaign">13.21. Campaign</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_15">13.21.1. Structure definition</a></li>
+<li><a href="#_example_16">13.21.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_scoring_plan">13.22. Scoring plan</a>
+<ul class="sectlevel3">
+<li><a href="#_structure_definition_16">13.22.1. Structure definition</a></li>
+<li><a href="#_example_17">13.22.2. Example</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_event_types">13.23. Built-in Event types</a>
+<ul class="sectlevel3">
+<li><a href="#_login_event_type">13.23.1. Login event type</a></li>
+<li><a href="#_view_event_type">13.23.2. View event type</a></li>
+<li><a href="#_form_event_type">13.23.3. Form event type</a></li>
+<li><a href="#_update_properties_event_type">13.23.4. Update properties event type</a></li>
+<li><a href="#_identify_event_type">13.23.5. Identify event type</a></li>
+<li><a href="#_session_created_event_type">13.23.6. Session created event type</a></li>
+<li><a href="#_goal_event_type">13.23.7. Goal event type</a></li>
+<li><a href="#_modify_consent_event_type">13.23.8. Modify consent event type</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_condition_types">13.24. Built-in condition types</a>
+<ul class="sectlevel3">
+<li><a href="#_existing_condition_type_descriptors">13.24.1. Existing condition type descriptors</a></li>
+</ul>
+</li>
+<li><a href="#_built_in_action_types">13.25. Built-in action types</a>
+<ul class="sectlevel3">
+<li><a href="#_existing_action_types_descriptors">13.25.1. Existing action types descriptors</a></li>
+</ul>
+</li>
+<li><a href="#_updating_events_using_the_context_servlet">13.26. Updating Events Using the Context Servlet</a>
+<ul class="sectlevel3">
+<li><a href="#_solution">13.26.1. Solution</a></li>
+<li><a href="#_defining_rules">13.26.2. Defining Rules</a></li>
+</ul>
+</li>
+<li><a href="#_unomi_web_tracker_reference">13.27. Unomi Web Tracker reference</a>
+<ul class="sectlevel3">
+<li><a href="#_custom_events">13.27.1. Custom events</a></li>
+<li><a href="#_integrating_with_tag_managers">13.27.2. Integrating with tag managers</a></li>
+<li><a href="#_cookiesession_handling">13.27.3. Cookie/session handling</a></li>
+<li><a href="#_javascript_api">13.27.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_26">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.</p>
+</div>
+<div class="paragraph">
+<p>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="_introducing_profiles_aliases">1.1.1. Introducing profiles aliases</h4>
+<div class="paragraph">
+<p>Profiles may now have alias IDs, which is a new way to reference profiles using multiple IDs. The Unomi ID still exists, but a new index with aliases can reference a single Unomi profile. This enables more flexible integrations with external systems, as well as provide more flexible and reliable merging mechanisms. A new REST API makes it easy to define, update and remove aliases for profiles. You can read more about <a href="#_what_profile_aliases_are_and_how_to_use_them">profile aliases here</a>.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_scopes_declarations_are_now_required">1.1.2. 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.3. 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.4. 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.</p>
+</div>
+<div class="paragraph">
+<p>If you are using custom events/objects, please refer to the detailed <a href="#_migration_overview">migration guide</a> for more details.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_new_web_tracker">1.1.5. 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 <a href="#_unomi_web_tracking_tutorial">new web tracker here</a>.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_graphql_api_beta">1.1.6. 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.7. 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.8. 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 11 (<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 11 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 11 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 version 11. We do not test with intermediate
+versions so they may or may not work properly. Currently the most tested version is version 11.</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="paragraph">
+<p>If you prefer to use existing HTML and Javascript rather than building your own, all the code we feature in this tutorial is extracted from our tracker sample which is available here: <a href="https://github.com/apache/unomi/blob/master/extensions/web-tracker/wab/src/main/webapp/index.html" class="bare">https://github.com/apache/unomi/blob/master/extensions/web-tracker/wab/src/main/webapp/index.html</a> . However you will still need to use the REST API calls to create the scope and rule to make it all work.</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="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">    &lt;script type="text/javascript" src="/tracker/unomi-web-tracker.min.js"&gt;&lt;/script&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>or you can also use the non-minified version that is available here:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-javascript" data-lang="javascript">    &lt;script type="text/javascript" src="/tracker/unomi-web-tracker.js"&gt;&lt;/script&gt;</code></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-shell" data-lang="shell">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="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-shell" data-lang="shell">    yarn add apache-unomi-tracker</code></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-shell" data-lang="shell">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="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-shell" data-lang="shell">    ssh -p 8102 karaf@localhost</code></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-shell" data-lang="shell">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-shell" data-lang="shell">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 using a condition that checks if the <code>pageViewCount</code> is higher than 5. If it has, <code>variant1</code> will be displayed, otherwise the fallback variant <code>variant2</code> will be used instead.</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.&lt;scope&gt;",
+                                "comparisonOperator" : "greaterThan",
+                                "propertyValueInteger" : 5
+                            }
+                        }
+                    }]
+                }, {
+                    "id": "var2"
+                }]
+            }, variants, false, function (successfulFilters, selectedFilter) {
+                if (selectedFilter) {
+                    document.getElementById(selectedFilter.content).style.display = '';
+                }
+            });</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>As you can see in the above code snippet, a <code>variants</code> array is created with two objects that associated personalization IDs with content IDs. Then we build the personalization object that contains the two IDs and their associated conditions (only a condition on <code>var1</code> is passed in this case) as well as an option to indicate which is the fallback variant in case no conditions are matched.</p>
+</div>
+<div class="paragraph">
+<p>The HTML part of this example looks like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-html" data-lang="html">    &lt;div id="variant1" style="display: none"&gt;
+        You have already seen this page 5 times
+    &lt;/div&gt;
+    &lt;div id="variant2" style="display: none"&gt;
+        Welcome. Please reload this page 5 times until it triggers the personalization change
+    &lt;/div&gt;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>As you can see we hide the variants by default so that there is no "flashing" effect and then use the callback function to display to variant resolve by Unomi&#8217;s personalization engine.</p>
+</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 in a web page</p>
+</li>
+<li>
+<p>Created a scope in which to collect the data</p>
+</li>
+<li>
+<p>Learned how to use the tracker as an NPM library</p>
+</li>
+<li>
+<p>How to view the collected events</p>
+</li>
+<li>
+<p>How to view the current visitor profile</p>
+</li>
+<li>
+<p>How to add a rule to update a profile property</p>
+</li>
+<li>
+<p>How to personalize a web page&#8217;s content based on the property updated by the rule</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Of course this tutorial is just one example of what could be achieved, and hasn&#8217;t even yet introduced more advanced notions such as profile segmentation or Groovy action scripting. The system is capable of much more, for example by directly using its actions to integrate with third-party systems (CRM, social networks, etc..)</p>
+</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 <a href="#_unomi_web_tracker_reference">web tracker, custom events, API, &#8230;&#8203;</a></p>
+</li>
+<li>
+<p>Learn more about <a href="#_segment">segmentation</a></p>
+</li>
+<li>
+<p>View some more <a href="#_integration_samples">samples</a></p>
+</li>
+<li>
+<p>Continue reading 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 class="sect3">
+<h4 id="_what_profile_aliases_are_and_how_to_use_them">3.1.10. What profile aliases are and how to use them</h4>
+<div class="paragraph">
+<p>Profile aliases make it possible to reference profiles using multiple identifiers.
+The profile alias object basically contains a link between the alias ID and the profile ID. The <code>itemId</code> of a profile alias is the actual alias ID, which the <code>profileID</code> field contains the reference to the aliased profile.</p>
+</div>
+<div class="sect4">
+<h5 id="_what_they_are">What they are</h5>
+<div class="imageblock">
+<div class="content">
+<img src="images/profile-alias-overview.png" alt="Profile alias overview">
+</div>
+</div>
+<div class="paragraph">
+<p>Profile aliases:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Make it possible to lookup profiles by main (Unomi) ID or by any other alias ID</p>
+</li>
+<li>
+<p>Aliases are just IDs stored in a dedicated index</p>
+</li>
+<li>
+<p>A profile may have an unlimited number of aliases attached to it.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_how_to_use_them">How to use them</h5>
+<div class="imageblock">
+<div class="content">
+<img src="images/profile-alias-external-ids.png" alt="Profile with external IDs">
+</div>
+</div>
+<div class="paragraph">
+<p>Here are different use cases for profile aliases:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Connect different systems to Unomi such as a CRM, CMS and native mobile app that all have their own iD for a single customer</p>
+</li>
+<li>
+<p>Merging profiles when a visitor is identified</p>
+</li>
+<li>
+<p>Adding new IDs at a later time</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_example">Example</h5>
+<div class="paragraph">
+<p>Here is an example of multiple external aliases pointing to a single Unomi profile</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="images/profile-alias-example.png" alt="Profile alias example">
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_interactions_with_merging">Interactions with merging</h5>
+<div class="paragraph">
+<p>Profile merges have been modified to use aliases starting Unomi 2</p>
+</div>
+<div class="paragraph">
+<p>Upon merge:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Properties are copied to the master profile as before</p>
+</li>
+<li>
+<p>An alias is created for the "master" profile with the ID of the merged profile</p>
+</li>
+<li>
+<p>Merged profiles are now deleted</p>
+</li>
+<li>
+<p>"mergedWith" property is no longer used since we deleted the merged profiles</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_api">API</h5>
+<div class="paragraph">
+<p>/context.json and /eventcollector will now look up profiles by profile ID or aliases from the same cookie (<code>context-profile-id</code>) or body parameters (<code>profileId</code>)</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top"><strong>Verb</strong></th>
+<th class="tableblock halign-left valign-top"><strong>Path</strong></th>
+<th class="tableblock halign-left valign-top"><strong>Description</strong></th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">GET</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">/cxs/profiles/PROFILE_ID_OR_ALIAS</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Retrieves a profile by ID or Alias ID (useful if an external system wants to get a profile)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">GET</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">/cxs/profiles/PROFILE_ID/aliases</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Get all the aliases for a profile</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">POST</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">/cxs/profiles/PROFILE_ID/aliases/ALIAS_ID</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Add an alias to a profile</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">DELETE</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">/cxs/profiles/PROFILE_ID/aliases/ALIAS_ID</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Remove an alias from a profile</p></td>
+</tr>
+</tbody>
+</table>
+</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>

[... 9446 lines stripped ...]