//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.
//
=== Type Management

In order to manage which attributes can be owned by Users, Groups and any object, and which values can be provided,
Apache Syncope defines a simple yet powerful type management system, vaguely inspired by the LDAP/X.500 information
model.

==== Schema

A schema instance describes the values that attributes with that schema will hold; it can be defined plain, derived or
virtual.

It is possible to define i18n labels for each schema, with purpose of improving presentation with Admin and End-user UIs.

===== Plain

Values for attributes with such schema types are provided during user, group or any object create / update.

When defining a plain schema, the following information must be provided:

* Type
** `String`
** `Long` - allows to specify a _conversion pattern_ to / from string, according to 
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/DecimalFormat.html[DecimalFormat^]
** `Double` - allows to specify a _conversion pattern_ to / from string, according to 
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/DecimalFormat.html[DecimalFormat^]
** `Boolean`
** `Date` - allows to specify a _conversion pattern_ to / from string, according to 
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/DateFormat.html[DateFormat^]
** `Enum`
*** enumeration values (mandatory)
*** enumeration labels (optional, values will be used alternatively)
** `Encrypted`
*** secret key (stored or referenced as https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#beans-property-source-abstraction[Spring property^])
*** cipher algorithm
*** whether transparent encryption is to be enabled, e.g. attribute values are stored as encrypted but available as
cleartext on-demand (requires AES ciphering)
** `Binary` - it is required to provide the declared mime type
* Validator class - (optional) Java class validating the value(s) provided for attributes, see 
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java[EmailAddressValidator^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/3_0_X/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java[EmailAddressValidator^]
endif::[]
for reference
* Mandatory condition - http://commons.apache.org/proper/commons-jexl/[JEXL^] expression indicating whether values for 
this schema must be necessarily provided or not; compared to simple boolean value, such condition allows to express
complex statements like 'be mandatory only if this other attribute value is above 14', and so on
* Unique constraint - make sure that no duplicate value(s) for this schema are found
* Multivalue flag - whether single or multiple values are supported
* Read-only flag - whether value(s) for this schema are modifiable only via internal code (say workflow tasks) or 
can be instead provided during ordinary <<provisioning,provisioning>>

===== Derived

Sometimes it is useful to obtain values as arbitrary combinations of other attributes' values: for example, with 
`firstname` and `surname` plain schemas, it is natural to think that `fullname` could be somehow defined as the 
concatenation of `firstname` 's and `surname` 's values, separated by a blank space.

Derived schemas are always read-only and require a http://commons.apache.org/proper/commons-jexl/[JEXL^]
expression to be specified that references plain schema types. +
For the sample above, it would be

 firstname + ' ' + surname

With derived attributes, values are not stored into the <<persistence,internal storage>> but calculated on request, by
evaluating the related JEXL expression

===== Virtual

Virtual attributes are somehow linked from Identity Stores rather than stored internally.

The typical use case is when attribute values can change in the Identity Store without notice, and it is required to
always have access to the most recent values that are available.

It can also be said that virtual schemas are for attributes whose ownership is not that of Syncope but of an Identity Store;
the external resources for such Identity Stores are said to be the _linking resources_.

[TIP]
As best practice, only attributes for which Apache Syncope retains ownership should be modeled as plain attributes;
attributes for which Apache Syncope does not retain ownership should be modeled
as virtual instead.

When defining a virtual schema, the following information must be provided:

* External resource - linking resource
* External attribute - attribute to be linked on the external resource
* Any Type - reference <<anytype,Any Type>> on the external resource
* Read-only flag - whether the external attribute value(s) for this schema can only be read, or whether they can be written to as well

[[virtual-attribute-cache]]
.Virtual Attribute Cache
****
For performance optimization, virtual attributes are managed by an internal cache to control the actual access to 
the linked Identity Stores.

The internal cache implements the 
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/cache/VirAttrCache.java[VirAttrCache^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/3_0_X/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/cache/VirAttrCache.java[VirAttrCache^]
endif::[]
interface, and is configurable.

Some implementations are provided by default - see table below - custom ones can be provided.

[cols="1,2"]
|===

| 
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/cache/CaffeineVirAttrCache.java[CaffeineVirAttrCache^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/3_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/cache/CaffeineVirAttrCache.java[CaffeineVirAttrCache^]
endif::[]
| In-memory cache based on https://github.com/ben-manes/caffeine[Caffeine Cache^].

| 
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/cache/DisabledVirAttrCache.java[DisabledVirAttrCache^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/3_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/cache/DisabledVirAttrCache.java[DisabledVirAttrCache^]
endif::[]
| Pass-through cache which actually does not provide any caching: use when direct access to the Identity Store is required.

|===
****

==== AnyTypeClass

Any type classes are aggregations of plain, derived and virtual schemas, provided with unique identifiers.

Classes can be assigned to <<anytype, Any Types>> and are also available as auxiliary (hence to be specified on a
given user / group / any object instance) and for <<type-extensions,type extensions>>.

==== AnyType

Any types represent the type of identities that Apache Syncope is able to manage; besides the predefined `USER` and 
`GROUP`, more types can be created to model workstations, printers, folders, sensors, services, ...

For all Any Types that are defined, a set of <<anytypeclass, classes>> can be selected so that instances of a given
Any Type will be enabled to populate attributes for schemas in those classes.

.Any types and attributes allowed for Users, Groups and Any Objects
====
Assuming that the following schemas are available:

. plain: `firstname`, `surname`, `email`
. derived: `fullname`
. virtual: `enrollment`

and that the following Any Type classes are defined:

. `minimal` - containing `firstname`, `surname` and `fullname`
. `member` - containing `email` and `enrollment`

and that the `USER` Any Type has only `minimal` assigned, then the following Users are valid (details are simplified to
increase readability):

[source,json]
----
{
  "key": "74cd8ece-715a-44a4-a736-e17b46c4e7e6",
  "type": "USER",
  "realm": "/",
  "username": "verdi",
  "plainAttrs": [
    {
      "schema": "surname",
      "values": [
        "Verdi"
      ]
    },
    {
      "schema": "firstname",
      "values": [
        "Giuseppe"
      ]
    }
  ],
  "derAttrs": [
    {
      "schema": "fullname",
      "values": [
        "Giuseppe Verdi"
      ]
    }
  ]
}

{
  "key": "1417acbe-cbf6-4277-9372-e75e04f97000",
  "type": "USER",
  "realm": "/",
  "username": "rossini",
  "auxClasses": [ "member" ],
  "plainAttrs": [
    {
      "schema": "surname",
      "values": [
        "Rossini"
      ]
    },
    {
      "schema": "firstname",
      "values": [
        "Gioacchino"
      ]
    },
    {
      "schema": "email",
      "values": [
        "gioacchino.rossini@syncope.apache.org"
      ]
    }
  ],
  "derAttrs": [
    {
      "schema": "fullname",
      "values": [
        "Gioacchino Rossini"
      ]
    }
  ],
  "virAttrs": [
    {
      "schema": "enrollment",
      "values": [
        "154322"
      ]
    }
  ]
}
----
====

==== RelationshipType

Relationships allow the creation of a link between a user and an any object, or between two Any Objects; relationship types
define the available link types.

.Relationship between Any Objects (printers)
====
The following any object of type `PRINTER` contains a relationship of type `neighbourhood` with another `PRINTER`
(details are simplified to increase readability):

[source,json]
----
{
  "key": "fc6dbc3a-6c07-4965-8781-921e7401a4a5",
  "type": "PRINTER",
  "realm": "/",
  "name": "HP LJ 1300n",
  "auxClasses": [],
  "plainAttrs": [
    {
      "schema": "model",
      "values": [
        "Canon MFC8030"
      ]
    },
    {
      "schema": "location",
      "values": [
        "1st floor"
      ]
    }
  ],
  "relationships": [
    {
      "type": "neighbourhood",
      "rightType": "PRINTER",
      "rightKey": "8559d14d-58c2-46eb-a2d4-a7d35161e8f8"
    }
  ]
}
----
====

==== Type Extensions

When a user (or an any object) is part of a group, a _membership_ is defined.

It is sometimes useful to define attributes which are bound to a particular membership: if, for example, the
`University A` and `University B` Groups are available, a student might have different e-mail addresses for each
university. How can this be modeled?

Type extensions define a set of <<anytypeclass,classes>> associated to a group, that can be automatically
assigned to a given user (or any object) when becoming a member of such group.

.Membership with type extension
====
With reference to the sample above (details are simplified to increase readability):

[source,json]
----
{
  "key": "c9b2dec2-00a7-4855-97c0-d854842b4b24",
  "type": "USER",
  "realm": "/",
  "username": "bellini",
  "memberships": [
    {
      "type": "Membership",
      "rightType": "GROUP",
      "rightKey": "bf825fe1-7320-4a54-bd64-143b5c18ab97",
      "groupName": "University A",
      "plainAttrs": [
        {
          "schema": "email",
          "values": [
            "bellini@university_a.net"
          ]
        }
      ]
    },
    {
      "type": "Membership",
      "rightType": "GROUP",
      "rightKey": "bf825fe1-7320-4a54-bd64-143b5c18ab96",
      "groupName": "University B",
      "plainAttrs": [
        {
          "schema": "email",
          "values": [
            "bellini@university_b.net"
          ]
        }
      ]
    }
  ]
}
----
====
