| // |
| // 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 `<category name>` 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 <bundleId> <fileName>` 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] |
| |