//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
=== Writing Plugins

Unomi is architected so that users can provided extensions in the form of plugins.

=== Types vs. instances

Several extension points in Unomi rely on the concept of type: a plugin defines a prototype for what the actual
items will be once parameterized with values known only at runtime. This is similar to the concept of classes in
object-oriented programming: types define classes, providing the expected structure and which fields are expected to
be provided at runtime, that are then instantiated when needed with actual values.

So for example we have the following types vs instances:

- ConditionTypes vs Conditions
- ActionTypes vs Actions
- PropertyTypes vs Properties (for profiles and sessions)

=== Plugin structure

Being built on top of Apache Karaf, Unomi leverages OSGi to support plugins. A Unomi plugin is, thus, an OSGi
bundle specifying some specific metadata to tell Unomi the kind of entities it provides. A plugin can provide the
following entities to extend Unomi, each with its associated definition (as a JSON file), located in a specific spot
within the `META-INF/cxs/` directory of the bundle JAR file:

|====
|Entity |Location in `cxs` directory

|ActionType |actions
|ConditionType |conditions
|Persona |personas
|PropertyMergeStrategyType |mergers
|PropertyType |properties then profiles or sessions subdirectory then `&lt;category name&gt;` directory
|Rule |rules
|Scoring |scorings
|Segment |segments
|ValueType |values
|====

http://aries.apache.org/modules/blueprint.html[Blueprint] is used to declare what the plugin provides and inject
any required dependency. The Blueprint file is located, as usual, at `OSGI-INF/blueprint/blueprint.xml` in the bundle JAR file.

The plugin otherwise follows a regular maven project layout and should depend on the Unomi API maven artifact:

[source,xml]
----
<dependency>
    <groupId>org.apache.unomi</groupId>
    <artifactId>unomi-api</artifactId>
    <version>...</version>
</dependency>
----

Some plugins consists only of JSON definitions that are used to instantiate the appropriate structures at runtime
while some more involved plugins provide code that extends Unomi in deeper ways.

In both cases, plugins can provide more that one type of extension. For example, a plugin could provide both `ActionType`s and `ConditionType`s.

=== Extension points

In this section the value types that may be used as extension points are presented. Examples of these types will be
given in the next section with more details.

==== ActionType

`ActionType`s define new actions that can be used as consequences of Rules being triggered. When a rule triggers, it
creates new actions based on the event data and the rule internal processes, providing values for parameters defined
in the associated `ActionType`. Example actions include: “Set user property x to value y” or “Send a message to service x”.

==== ConditionType

`ConditionType`s define new conditions that can be applied to items (for example to decide whether a rule needs to be
triggered or if a profile is considered as taking part in a campaign) or to perform queries against the stored Unomi
data. They may be implemented in Java when attempting to define a particularly complex test or one that can better be
optimized by coding it. They may also be defined as combination of other conditions. A simple condition could be:
“User is male”, while a more generic condition with parameters may test whether a given property has a specific value:
“User property x has value y”.

==== Persona

A persona is a "virtual" profile used to represent categories of profiles, and may also be used to test how a
personalized experience would look like using this virtual profile. A persona can define predefined properties and
sessions. Persona definition make it possible to “emulate” a certain type of profile, e.g : US visitor, non-US visitor, etc.

==== PropertyMergeStrategyType

A strategy to resolve how to merge properties when merging profile together.

==== PropertyType

Definition for a profile or session property, specifying how possible values are constrained, if the value is
multi-valued (a vector of values as opposed to a scalar value). `PropertyType`s can also be categorized using
systemTags or file system structure, using sub-directories to organize definition files.

==== Rule

`Rule`s are conditional sets of actions to be executed in response to incoming events. Triggering of rules is guarded
by a condition: the rule is only triggered if the associated condition is satisfied. That condition can test the
event itself, but also the profile or the session. Once a rule triggers, a list of actions can be performed as
consequences. Also, when rules trigger, a specific event is raised so that other parts of Unomi can react accordingly.

==== Scoring

`Scoring`s are set of conditions associated with a value to assign to profiles when matching so that the associated
users can be scored along that dimension. Each scoring element is evaluated and matching profiles' scores are
incremented with the associated value.

==== Segments

`Segment`s represent dynamically evaluated groups of similar profiles in order to categorize the associated users.
To be considered part of a given segment, users must satisfies the segment’s condition. If they match, users are
automatically added to the segment. Similarly, if at any given point during, they cease to satisfy the segment’s
condition, they are automatically removed from it.

==== Tag

`Tag`s are simple labels that are used to classify all other objects inside Unomi.

==== ValueType

Definition for values that can be assigned to properties ("primitive" types).

=== Custom plugins

Apache Unomi is a pluggeable server that may be extended in many ways. This document assumes you are familiar with the
<<Data Model Overview,Apache Unomi Data Model>> . This document is mostly a reference document on the different things that may
be used inside an extension. If you are looking for complete samples, please see the <<Samples,samples page>>.

==== Creating a plugin

An plugin is simply a Maven project, with a Maven pom that looks like this:

[source]
----
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.apache.unomi</groupId>
        <artifactId>unomi-plugins</artifactId>
        <version>${project.version}</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>unomi-plugin-example</artifactId>
    <name>Apache Unomi :: Plugins :: Example</name>
    <description>A sample example of a Unomi plugin</description>
    <version>${project.version}</version>
    <packaging>bundle</packaging>

    <dependencies>
        <!-- This dependency is not required but generally used in plugins -->
        <dependency>
            <groupId>org.apache.unomi</groupId>
            <artifactId>unomi-api</artifactId>
            <version>${project.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
                        <Import-Package>
                            sun.misc;resolution:=optional,
                            *
                        </Import-Package>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
----

A plugin may contain many different kinds of Apache Unomi objects, as well as custom OSGi services or anything that
is needed to build your application.

==== Deployment and custom definition

When you deploy a custom bundle with a custom definition (see "Predefined xxx" chapters under) for the first time, the
definition will automatically be deployed at your bundle start event *if it does not exist*.
After that if you redeploy the same bundle, the definition will not be redeployed, but you can redeploy it manually
using the command `unomi:deploy-definition &lt;bundleId&gt; &lt;fileName&gt;` If you need to modify an existing
definition when deploying the module, see <<Migration patches>>.

==== Predefined segments

You may provide pre-defined segments by simply adding a JSON file in the src/main/resources/META-INF/cxs/segments directory of
your Maven project. Here is an example of a pre-defined segment:

[source]
----
{
  "metadata": {
    "id": "leads",
    "name": "Leads",
    "scope": "systemscope",
    "description": "You can customize the list below by editing the leads segment.",
    "readOnly":true
  },
  "condition": {
    "parameterValues": {
      "subConditions": [
        {
          "parameterValues": {
            "propertyName": "properties.leadAssignedTo",
            "comparisonOperator": "exists"
          },
          "type": "profilePropertyCondition"
        }
      ],
      "operator" : "and"
    },
    "type": "booleanCondition"
  }
}
----

Basically this segment uses a condition to test if the profile has a property `leadAssignedTo` that exists. All profiles
that match this condition will be part of the pre-defined segment.

==== Predefined rules

You may provide pre-defined rules by simply adding a JSON file in the src/main/resources/META-INF/cxs/rules directory of
your Maven project. Here is an example of a pre-defined rule:

[source]
----
{
    "metadata" : {
        "id": "evaluateProfileSegments",
        "name": "Evaluate segments",
        "description" : "Evaluate segments when a profile is modified",
        "readOnly":true
    },

    "condition" : {
        "type": "profileUpdatedEventCondition",
        "parameterValues": {
        }
    },

    "actions" : [
        {
            "type": "evaluateProfileSegmentsAction",
            "parameterValues": {
            }
        }
    ]

}
----

In this example we provide a rule that will execute when a predefined composed condition of type
"profileUpdatedEventCondition" is received. See below to see how predefined composed conditions are declared.
Once the condition is matched, the actions will be executed in sequence. In this example there is only a single
action of type "evaluateProfileSegmentsAction" that is defined so it will be executed by Apache Unomi's rule engine.
You can also see below how custom actions may be defined.

==== Predefined properties

By default Apache Unomi comes with a set of pre-defined properties, but in many cases it is useful to add additional
predefined property definitions. You can create property definitions for session or profile properties by creating them
in different directories.

For session properties you must create a JSON file in the following directory in your Maven project:

[source]
----
src/main/resources/META-INF/cxs/properties/sessions
----

For profile properties you must create the JSON file inside the directory in your Maven project:

[source]
----
src/main/resources/META-INF/cxs/properties/profiles
----

Here is an example of a property definition JSON file

[source]
----
{
    "metadata": {
        "id": "city",
        "name": "City",
        "systemTags": ["properties", "profileProperties", "contactProfileProperties"]
    },
    "type": "string",
    "defaultValue": "",
    "automaticMappingsFrom": [ ],
    "rank": "304.0"
}
----

==== Predefined child conditions

You can define new predefined conditions that are actually conditions inheriting from a parent condition and setting
pre-defined parameter values. You can do this by creating a JSON file in:

[source]
----
src/main/resources/META-INF/cxs/conditions
----

Here is an example of a JSON file that defines a profileUpdateEventCondition that inherits from a parent condition of
type eventTypeCondition.

[source]
----
{
  "metadata": {
    "id": "profileUpdatedEventCondition",
    "name": "profileUpdatedEventCondition",
    "description": "",
    "systemTags": [
      "event",
      "eventCondition"
    ],
    "readOnly": true
  },
  "parentCondition": {
    "type": "eventTypeCondition",
    "parameterValues": {
      "eventTypeId": "profileUpdated"
    }
  },

  "parameters": [
  ]
}
----

==== Predefined personas

Personas may also be pre-defined by creating JSON files in the following directory:

[source]
----
src/main/resources/META-INF/cxs/personas
----

Here is an example of a persona definition JSON file:

[source]
----
{
    "persona": {
        "itemId": "usVisitor",
        "properties": {
            "description": "Represents a visitor browsing from inside the continental US",
            "firstName": "U.S.",
            "lastName": "Visitor"
        },
        "segments": []
    },
    "sessions": [
        {
            "itemId": "aa3b04bd-8f4d-4a07-8e96-d33ffa04d3d9",
            "profileId": "usVisitor",
            "properties": {
                "operatingSystemName": "OS X 10.9 Mavericks",
                "sessionCountryName": "United States",
                "location": {
                    "lat":37.422,
                    "lon":-122.084058
                },
                "userAgentVersion": "37.0.2062.120",
                "sessionCountryCode": "US",
                "deviceCategory": "Personal computer",
                "operatingSystemFamily": "OS X",
                "userAgentName": "Chrome",
                "sessionCity": "Mountain View"
            },
            "timeStamp": "2014-09-18T11:40:54Z",
            "lastEventDate": "2014-09-18T11:40:59Z",
            "duration": 4790
        }
    ]
}
----

You can see that it's also possible to define sessions for personas.

==== Custom action types

Custom action types are a powerful way to integrate with external systems by being able to define custom logic that will
be executed by an Apache Unomi rule. An action type is defined by a JSON file created in the following directory:

[source]
----
src/main/resources/META-INF/cxs/actions
----

Here is an example of a JSON action definition:

[source]
----
{
  "metadata": {
    "id": "addToListsAction",
    "name": "addToListsAction",
    "description": "",
    "systemTags": [
      "demographic",
      "availableToEndUser"
    ],
    "readOnly": true
  },
  "actionExecutor": "addToLists",
  "parameters": [
    {
      "id": "listIdentifiers",
      "type": "string",
      "multivalued": true
    }
  ]
}
----

The `actionExecutor` identifier refers to a service property that is defined in the OSGi Blueprint service registration.
Note that any OSGi service registration may be used, but in these examples we use OSGi Blueprint. The definition for the
above JSON file will be found in a file called `src/main/resources/OSGI-INF/blueprint/blueprint.xml` with the following
content:

[source]
----
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

    <reference id="profileService" interface="org.apache.unomi.api.services.ProfileService"/>
    <reference id="eventService" interface="org.apache.unomi.api.services.EventService"/>

    <!-- Action executors -->

    <service interface="org.apache.unomi.api.actions.ActionExecutor">
        <service-properties>
            <entry key="actionExecutorId" value="addToLists"/>
        </service-properties>
        <bean class="org.apache.unomi.lists.actions.AddToListsAction">
            <property name="profileService" ref="profileService"/>
            <property name="eventService" ref="eventService"/>
        </bean>
    </service>

</blueprint>
----

You can note here the `actionExecutorId` that corresponds to the `actionExecutor` in the JSON file.

The implementation of the action is available here : https://github.com/apache/unomi/blob/master/extensions/lists-extension/actions/src/main/java/org/apache/unomi/lists/actions/AddToListsAction.java[org.apache.unomi.lists.actions.AddToListsAction]

==== Custom condition types

Custom condition types are different from predefined child conditions because they implement their logic using Java classes.
They are also declared by adding a JSON file into the `conditions` directory:

[source]
----
src/main/resources/META-INF/cxs/conditions
----

Here is an example of JSON custom condition type definition:

[source]
----
{
  "metadata": {
    "id": "matchAllCondition",
    "name": "matchAllCondition",
    "description": "",
    "systemTags": [
      "logical",
      "profileCondition",
      "eventCondition",
      "sessionCondition",
      "sourceEventCondition"
    ],
    "readOnly": true
  },
  "conditionEvaluator": "matchAllConditionEvaluator",
  "queryBuilder": "matchAllConditionESQueryBuilder",

  "parameters": [
  ]
}
----

Note the `conditionEvaluator` and the `queryBuilder` values. These reference OSGi service properties that are declared
in an OSGi Blueprint configuration file (other service definitions may also be used such as Declarative Services or even
Java registered services). Here is an example of an OSGi Blueprint definition corresponding to the above JSON condition
type definition file.

[source]
----
src/main/resources/OSGI-INF/blueprint/blueprint.xml

<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

    <service
            interface="org.apache.unomi.persistence.elasticsearch.conditions.ConditionESQueryBuilder">
        <service-properties>
            <entry key="queryBuilderId" value="matchAllConditionESQueryBuilder"/>
        </service-properties>
        <bean class="org.apache.unomi.plugins.baseplugin.conditions.MatchAllConditionESQueryBuilder"/>
    </service>

    <service interface="org.apache.unomi.persistence.elasticsearch.conditions.ConditionEvaluator">
        <service-properties>
            <entry key="conditionEvaluatorId" value="matchAllConditionEvaluator"/>
        </service-properties>
        <bean class="org.apache.unomi.plugins.baseplugin.conditions.MatchAllConditionEvaluator"/>
    </service>

</blueprint>
----

You can find the implementation of the two classes here :

* https://github.com/apache/unomi/blob/master/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/MatchAllConditionESQueryBuilder.java[org.apache.unomi.plugins.baseplugin.conditions.MatchAllConditionESQueryBuilder]
* https://github.com/apache/unomi/blob/master/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/MatchAllConditionEvaluator.java[org.apache.unomi.plugins.baseplugin.conditions.MatchAllConditionEvaluator]

