Configuration Content

General

Contents with an alias in the _config namespace are called configuration contents. Their distinct purpose is to express configuration of some kind (see further below for examples).

{
  "importerMetadata": {
    "alias": "_config/myConfig"
  },
  "system": {
    "contentType": "myConfigContent"
  },
  "aspects": {
    "myConfigAspect": {
      "param1": "overridden value"
    }
  }
}

Configuration contents behave just as any other content but with one exception – they may have a twin content expressing default configuration. This default content must have an alias with the same name part but with the _defaults namespace.

{
  "importerMetadata": {
    "alias": "_defaults/myConfig"
  },
  "system": {
    "contentType": "myConfigContent"
  },
  "aspects": {
    "myConfigAspect": {
      "param1": "default value",
      "param2": "default value"
    }
  }
}

When referring to a configuration content from another content only the name part of the alias (myConfig in the examples above) is used. This is referred to as a config ID.

{
  "importerMetadata": {
    "alias": "_config/myConfigList"
  },
  "system": {
    "contentType": "myConfigListContent"
  },
  "aspects": {
    "myConfigListAspect": {
      "listOfConfigurations": ["myConfig"]
    }
  }
}

Configuration Overlaying

At runtime, references to configuration contents are resolved in both the _defaults and _config namespaces and if both contents exist they are merged into one effective configuration object. This is called configuration overlaying and makes it possible for projects to partly override system defaults without modifying an system content and at the same time benefit from updated or new system defaults when upgrading to later versions of ACE.

In the previous example, the reference to myConfig will be resolved to both _defaults/myConfig and _config/myConfig and produce the effective configuration shown below.

{
  "myConfigAspect": {
    "param1": "overridden value",
    "param2": "default value"
  }
}

Overlaying is performed per aspect on the union of all aspects found in the default and custom configuration contents with the following rules applied:

  • Values of primitive types are replaced.
  • Lists (equivalent to JSON arrays) are replaced.
  • Maps (equivalent to JSON objects) are merged. For existing keys value is replaced.

Note: Configuration overlaying should not be confused with content import and its updateMode which allows for multiple files to define or redefine parts of the same content. Read more about update mode.

System Configuration Contents

Callbacks Configuration

This content is used to configure lifecycle hooks and variants. How to implement and configure mappers, composers and lifecycle hooks is described in Callback API.

Property Value
Config ID aceCallbacksConfig
Content type aceCallbacksConfigContent
Aspect aceCallbacksConfig
Data type aceCallbacksConfig

The data type aceCallbacksConfig is defined as:

Field Data Type Description
onConflictHooks map(content type -> list(callback config IDs)) Adds one or several on-conflict hooks for the types listed.
onContentCloneHooks map(content type -> list(callback config IDs)) Adds one or several on-content-clone hooks for the types listed. Only executed in the Content Developer application
onContentInitHooks map(content type -> list(callback config IDs)) Adds one or several on-content-init hooks for the types listed. Only executed in the Content Developer application.
preDeleteHooks map(content type -> list(callback config IDs)) Adds one or several pre-delete hooks for the types listed.
preStoreHooks map(content type -> list(callback config IDs)) Adds one or several pre-store hooks for the types listed.
variants map(variant name -> variant config ID) Configures the variant variant name defined by the variant config given by its variant config ID.

ACE comes with a default configuration which can be overlayed in the project by creating a corresponding content in the _config namespace. Example:

{
  "system": {
    "contentType": "aceCallbacksConfigContent"
  },
  "aspects" : {
    "aceCallbacksConfig" : {
      "variants" : {
        "example": "example.variant"
      },
      "preStoreHooks" : {
        "exampleContentType" : [ "example.preStore1", "example.preStore2" ]
      }
    }
  }
}

Variant Configuration

This content is used to define and configure a variant. A variant defines which mappers and composers will be called when getting content in the given variant.

Property Value
Config ID <project defined>
Content type aceVariantContent
Aspect aceVariant
Data type aceVariant

The data type aceVariant is defined as:

Field Data Type Description
defaultComposers list(composer config ID) Ordered list of composers to execute for all types that are not listed in composers.
composers map(content type -> list(composer config IDs)) Ordered list of composers to execute for the given type.
mappers map(aspect -> mapper config ID) Which mapper should execute for a given aspect.

Read more on how to implement and configure mappers and composers in the chapter on Callback API.

ACE comes with default configuration for all system variants. These can be overlayed in the project by creating a corresponding content in the _config namespace. Project defined variants are also created with an alias in the _config namespace. Example:

{
  "importerMetadata": {
    "alias": "_config/example.variant"
  },
  "system": {
    "contentType": "aceVariantContent"
  },
  "aspects": {
    "aceVariant": {
      "defaultComposers": [ "ace.includeContent" ],
      "composers": {
        "exampleContentType": [ "example.composer" ]
      },
      "mappers": {
        "aceCategorization": "ace.ExcludeAspect"
      }
    }
  }
}

Module System (Experimental)

The ACE module system lets you organize configuration in modules. This lets you keep logically related configuration separate, so that e.g. different teams can work on the same variant without having to work with the same configuration content. It also makes it easier to turn features on and off without affecting other features.

The module system is an extension to the configuration described above (called "global configuration" here) - where global configuration is used, it works as described above, and modules are ignored. This is done on a per-variant (for reads) or per-type (for writes) basis.

Defining a module

A module is defined by a content of type aceModuleContent, with two mandatory aspects: aceModule and aceCallbacksConfig. The first is just informational and is not really used, the second is important part. It works basically the same as the global configuration, in that it can include lifecycle hooks and variants.

Example

This module includes a variant named myVariant and a pre-store hook for a type called myType.

{
  "system": {
    "contentType": "aceModuleContent"
  },
  "aspects" : {
    "aceModule": {
      "_type": "aceModule",
      "name": "Module 1"
    },
    "aceCallbacksConfig" : {
      "_type" : "aceCallbacksConfig",
      "variants" : {
        "myVariant"          : "myModule.variant"
      },
      "preStoreHooks" : {
        "my_Type" : ["my_Type.PreStore"]
      }
    }
  }
}

Using modules

To make the system use your module, you must add it to the global configuration in the aceModuleSystem aspect. That aspect has a list of modules, which defines which modules are applied, and in which order. Order is important: whenever modules are used, the first module to define callbacks wins and later modules are ignored.

Example

This module system aspect sets up two modules, where myModule is applied first and otherModule second.

    "aceModuleSystem": {
      "_type": "aceModuleSystem",
      "modules": ["_config/myModule", "_config/otherModule"]
    }

Variants in modules

When modules are used, a read in a variant first looks for the variant in the global configuration. If a variant is found, modules are ignored and only callbacks from that variant configuration are used, as if there were no modules.

If the global configuration does not have the variant configured at all, then for each aspect in the content we look for a module with a mapper for the type and variant requested and use the first mapper found. Aspects that have no mapper in any module are passed on unmapped.

Then we look for the first module with a composer chain for the type and variant requested and use that composer chain, ignoring any further modules. If no module has composers for the type and variant, we find the first module with a default composer chain in that variant and use that, ignoring any further modules. If we still don't have a composer chain (because no module has default composers for that variant), no composers are applied.

Lifecycle hooks in modules

For lifecycle hooks, we simply first look in the global configuration, and then the modules in order and as soon as we find a list of hooks (even if empty) we stop looking and use those hooks.