Lookups aka Dynamic Enumerations

Lookups (aka Dynamic Enumerations) can be used within characteristics to define lists of allowed values. In addition to that, this feature can also be used stand-alone to be able to have enumeration values dynamically modified in the application without the need to restart a server.

Lookups can be used in combination with Characteristics and also for regular fields. Product 360 always had the concept of enumerations for fields. Every field can be configured to use an enumeration which limits the possible values of this field to the enumeration values.

One of the biggest issues of repository based enumerations is, that the actual enumeration entries are (usually) also stored in the repository. This is the most often used way to provide the values for the enumeration, especially in customizing situations as no coding is required. Unfortunately a modification of the repository requires a server (or cluster) restart which can be an issue for critical deployments and doesn't allow the business users to maintain values for a list independently.

The enumeration logic in Product 360 is prepared for dynamic enumerations which have a different persistence than the repository itself. Many enumerations already work in a dynamic way, like Units, Buyers, Suppliers or Catalogs and so on. The same concept is now used to combine the concept of lookups with enumerations.

Background information on Enumerations

Enumerations are defined in the repository's custom area and can be assigned to fields and logical keys. The enumeration in the repository can be seen as the descriptive part of the enumeration. It contains the name of the enumeration, a unique identifier and a description as well as the key class and the enum provider.

Key Class

The key class is the java class which represents the key's datatype of the enumeration entries. Some enumerations use java.lang.String so the key is a String, some use java.lang.Integer or java.lang.Long for numeric keys (like the language enumeration). Others use EntityProxy implementations like the UnitProxy or the PartyProxy for the Unit or Supplier/Buyer enumeration. The important thing here is that the key class must be equal to the class of the field to which the enumeration is assigned to.

So, if you have a field which is based on a field type which has a UnitProxy as class, the enumerations key class must also be a UnitProxy. Why is that? Pretty simple, since the key of a chosen enumeration entry will be stored as value for the field. If a user choses a specific order unit for an item, the chosen order unit's key (which is an instance of UnitProxy) will be stored for this item's order unit field. This is the reason why the key-class and the field type's class must be equal in order to use an enumeration on any of the fields which belong to this field type.

As the class of a field type directly modified the business model of the application, it is not allowed to change it for standard fields at all. The business logic which is implemented for standard fields might break which results in critical exceptions. For reserved fields (Res_*) there usually is no such business logic so a change of the class is possible here (as long as it's a compatible change regarding the persistence class. You can't switch a Res_Int_01 field to now use java.lang.String as there is no way we could convert the string into an integer. It would be possible the other way round of course).

EnumProvider

Every enumeration in the repository has a corresponding enum provider implementation. The EnumProvider interface is used in the application to work with the actual values of an enumeration. It provides access to the keys, labels and external codes of the enumeration entries. The enum provider can either be directly defined in the Enumeration element of the repository or it can be contributed for the enum provider extension point in the application. One or the other must be given for every enumeration and this will be checked during a server start!

So the enum provider is responsible for providing the enumeration entries keys and labels. One very common enum provider is the StdEnumProvider which uses the repository EnumEntry elements as data source. Other enum providers don't use the repository as data source. For example the enum provider for units or suppliers or catalogs. Those return the values for those entities from our database - as Units, Suppliers and Catalogs are actual Entities in the repository and there is a maintenance UI in the application.

So in order to combine the Enumerations with the Lookups we use special EnumProvider implementations which provide seamless access to the lookup values during runtime - just like we have it for Units, Suppliers or Catalogs.

Configure the Repository

When using a lookup for a regular field, we still need to adjust the repository, but only for defining which enumeration (lookup) belongs to which field and not for the actual entries of the enumeration any more. Those can be adjusted from within the application's lookup editing perspective.

To give you the maximum flexibility when configuring the repository we actually provided three different enum provider implementations for lookup values. Each of them has a different key class.

Define the Enumeration in the Repository

As described above the Enumeration in the repository is somewhat the descriptive part with identifier, name and description as, very important, the key class and the enum provider. In order to use a lookup for a field, you need to create an Enumeration for this lookup and then use the enumeration for the field.

LookupValueProxy

Here is an example of the custom manufacturers enumeration which uses the LookupValueProxy as key class.

<enum identifier="Enum.CustomManufacturers">
<name>%enum.CustomManufacturers.name</name>
<description></description>
<class-name>com.heiler.ppm.lookup.core.enumeration.LookupValueEnumProvider</class-name>
<key-class-name>com.heiler.ppm.lookup.core.LookupValueProxy</key-class-name>
<param name="lookupIdentifier" documentation="">CustomManufacturers</param>
</enum>

Long

Example to use it with java.lang.Long as key-class:

<enum identifier="Enum.CustomManufacturersByID">
<name>%enum.CustomManufacturers.name</name>
<description></description>
<class-name>com.heiler.ppm.lookup.core.enumeration.LookupValueInternalIDEnumProvider</class-name>
<key-class-name>java.lang.Long</key-class-name>
<param name="lookupIdentifier" documentation="">CustomManufacturers</param>
</enum>

String

Example to use it with java.lang.String as key-class:

<enum identifier="Enum.CustomManufacturersByCode">
<name>%enum.CustomManufacturers.name</name>
<description></description>
<class-name>com.heiler.ppm.lookup.core.enumeration.LookupValueCodeEnumProvider</class-name>
<key-class-name>java.lang.String</key-class-name>
<param name="lookupIdentifier" documentation="">CustomManufacturers</param>
</enum>

Important attributes:

  • class-name
    The class name must be an implementation of the EnumProvider interface. Three possible enum providers are delivered for the Lookup integration:

    • com.heiler.ppm.lookup.core.enumeration.LookupValueEnumProvider which corresponds with com.heiler.ppm.lookup.core.LookupValueProxy as key-class
      This is the best choice if possible, but the field type needs to be adjusted for it to have LookupValueProxy as key class. However, it provides the full functionality including field transition in tables or export.

    • com.heiler.ppm.lookup.core.enumeration.LookupValueInternalIDEnumProvider which corresponds with java.lang.Long as key-classThis is the second best choice. It still allows you to change the lookup value's code once it has been active, but it won't provide field transition in tables or export

    • com.heiler.ppm.lookup.core.enumeration.LookupValueCodeEnumProvider which corresponds with java.lang.String as key-class
      This should only be used if there is no way around it as in this case the Code of the lookup value will be stored as key. This means that you won't be able to change the code anymore
      as soon as it has been activated and is potentially being used. Also field transitions are not supported with this one. This enum provider is actually better used for proposal enumerations.

  • key-class-namethe class which must be equal to the class-name of the field's field type on which we want to use the enumeration (see above for details). Of course if also must correspond with the EnumProvider implementation.

    • com.heiler.ppm.lookup.core.LookupValueProxy

    • java.lang.Long

    • java.lang.String

  • EnumParam "lookupIdentifier"
    Enumerations can have multiple enum params which typically provide additional settings for the enum provider implementations. In order to use a lookup as the data source for an enumeration we need to defined which lookup that is. So there needs to be an enum-param with the name lookupIdentifier with the identifier of the lookup as value. No worries if this lookup does not yet exist. The application will automatically create it during server start in case it's not yet there.

Adjust Field Type

LookupValueProxy

If you are in the comfortable position to be able to use some Res_Int field for your custom field (and this field is not used already!) you can adjust the field type class.

By doing so, you enable the transition features in the table views or export. So you can access any field of the lookup value transparently from within the context of the item.

Changing the field type is not allowed for standard fields since this may break business logic. It is only allowed for the attributes mentioned here and only for reserved fields!

<field-type identifier="ArticleType.Res_Int_01" proxy-transition-entity-type-ref="LookupValueType">
<object-name>res_Int_01</object-name>
<class-name>com.heiler.ppm.lookup.core.LookupValueProxy</class-name>
<persistence-model-class>com.heiler.ppm.article.db.model.ArticleDetail</persistence-model-class>
<persistence-xpath>/articleRevisions/articleDetail/res_Int_01</persistence-xpath>
<persistence-class-name>java.lang.Long</persistence-class-name>
<fragment-column-access>ArticleDetail.Res_Int_01</fragment-column-access>
<inactive>false</inactive>
<searchable>true</searchable>
<range-max></range-max>
</field-type>

In our example we use the ArticleType.Res_Int_01 field type. It's actual datatype is Long. You can also use a String field for this, but we would recommend to stick with the Int fields as long as you can.

  • proxy-transition-entity-type-ref
    to signal that this field now points to objects of the Entity Type "LookupValueType"

  • class-name
    to define that we now store values of the com.heiler.ppm.lookup.core.LookupValueProxy class in this field (was java.lang.Long)

  • persistence-class
    to let the system know in which physical datatype the LookupValueProxy needs to be converted to, so this must be java.lang.Long (after all, the database column is still a NUMBER/BigInt) (was empty which means it's the same as the class-name)

  • inactive
    in order to be able to use the reserve field type, you need to set it to inactive = false

Long or String

In contrary to the use of the LookupValueProxy as key-class the java.lang.Long or java.lang.String requires no adjustment of the field type besides setting it to inactive = false

In our example we use the ArticleType.Res_Int_01 field type:

<field-type identifier="ArticleType.Res_Int_01">
...
<inactive>false</inactive>
...
</field-type>
  • inactive
    in order to be able to use the reserve field type, you need to set it to inactive = false

Create new Field

After adjusting the field type, you can create your custom field. It's important that this custom field points to the beforehand adjusted FieldType!

<field identifier="Article.CustomManufacturer" category-ref="master-data" enum-ref="Enum.CustomManufacturers" field-type-ref="ArticleType.Res_Int_01" proxy-transition-entity-ref="LookupValue" supports-dataquality="true">
<name>%field.Article.CustomManufacturer.name</name>
<name-from-top>%field.Article.CustomManufacturer.nameFromTop</name-from-top>
<description></description>
<editable>true</editable>
<visible>true</visible>
<visible-from-top>true</visible-from-top>
<mergeable>true</mergeable>
[...]
</field>

Important attributes:

  • enum-ref
    the identifier of the enumeration we created in the step before (in our example: Enum.CustomManufacturers)

  • field-type-ref
    the identifier of the field type we created at the beginning (in our example: ArticleType.Res_Int_01)

  • proxy-transition-entity-refThe identifier of the root entity to which this field now points (must be LookupValue)

    This must only be set in case you used the LookupValueProxy as key-class and adjusted the field type accordingly (see above). Otherwise this attribute must not be empty as the field type doesn't support any transition!

Create the Lookup

There are generally two ways to create a new lookup. The first, and most obvious one is to create the lookup within the Desktop UI or the Service API. This is pretty straight forward and needs no further explanation here. However, since you can use lookups as enumerations we implemented a migration feature into the startup process of the server as well.

During startup the server scans the repository for all enumeration elements which do have the lookupIdentifier enum parameter. For those elements it checks if the lookup for this identifier already exists in the system. If it doesn't, it will use the information provided by the enumeration element to create this new lookup. In addition to that, it will not only create the lookup, but also create lookup values for each enum-entry. This is done only in case the lookup is not there yet.

This feature makes it very easy to provide default lookup values. Additionally, you don't need to open the Desktop UI after you configured the steps described above as the server will create the lookup automatically when you restart it. The users can directly start editing values for it.

This feature can also be used as a migration feature. If you want to migrate a repository based enumeration (whose values are all in the repository as enum-entries), you just need to adjust this enumeration as described above. Just adjust the class-name and the key if necessary. If you do that, you also need to adjust the fields it's used for and so on. As soon as you have the enumeration adjusted you can restart the server and all the exiting entries in the repository will automatically be created as lookup values.

Proposal Enumerations

Pretty similar to the configuration of enumerations, you can also define proposal enumerations for String fields. Proposal enumerations allow the user to enter a value which is not part of the enumeration. So the enumeration entries merely work as a "proposal" for it. Because of this nature, proposal enumerations are only supported at String fields.

Technically it works the same way as with normal enumerations, but you don't set the "enumeration" attribute on the field, but the "proposal-enumeration" attribute. The field must be String, the proposal enumeration's keyClass must be String too.

For proposal enumerations you use also the com.heiler.ppm.lookup.core.enumeration.LookupValueCodeEnumProvider of course