Introduction + example
In RunBase classes it is done through the pack and unpack methods (as well as initParmDefault).
For ensuring seemless code upgrade of these classes they also rely on a "version" of the stroed SysLastValue data, which is typically stored in a macro definition. The RunBase internal class state that needs to be preserved between runs is typically done through a local macro.
A typical example is shown below (taken from the Tutorial_RunBaseBatch class):
#define.CurrentVersion(1)
#localmacro.CurrentList
transDate,
custAccount
#endmacro
public container pack()
{
return [#CurrentVersion, #CurrentList];
}
public boolean unpack(container packedClass)
{
Version version = RunBase::getVersion(packedClass);
switch (version)
{
case #CurrentVersion:
[version,#CurrentList] = packedClass;
break;
default:
return false;
}
return true;
}
Just in short, what happens is that:
- We save the packed state of the class with the corresponding version into the SysLastValue table record for this class, which means that all variables in the CurrentList macro need to be "serializable".
- The container will look something like this: [1, 31/3/2017, "US-0001"]
- When we need to retrieve/unpack these values, we retrieve the version as we know it's the first position in the container.
- If the version is still the same as the current version, read the packed container into the variables specified in the local macro
- If the version is different from the current version, return false, which will subsequently run initParmDefault() method to load the default values for the class state variables
Problem statement
- Pack is run and returns a container looking like this (Using the example from above): [1, 31/3/2017, "US-0001"]
- Post-method handler is called on ISV extension 1, and returns the above container + the specific state for ISV 1 solution (let's assume it's just an extra string variable): [1, 31/3/2017, "US-0001", "ISV1"]
- Post-method handler is called on ISV extension 2, and returns the above container + the specific state for ISV 2 solution: [1, 31/3/2017, "US-0001", "ISV1", "ISV2"]
- Unpack is run and assigns the variables from the packed state (assuming it's the right version) to the base class variables.
- ISV2 unpack post-method handler is called and needs to retrieve only the part of the container which is relevant to ISV2 solution
- ISV1 unpack post-method handler is called and needs to do the same
Steps 2 and 3 cannot be done in a reliable way. OK, say we copy over the macro definitions from the base class, assuming also the members are public and can be accessed from our augmentation class or we duplicate all those variables in unpack and hope nothing changes in the future :) - and in unpack we read the sub-part of the container from the base class into that, but how can we ensure the next part is for our extension? ISV1 and ISV2 post-method handlers are not necessarily going to be called in the same order for unpack as they were for pack.
All in all, this just does not work.
Note
The below line is perfectly fine in X++ and will not cause issues, which is why the base unpack() would not fail even if the packed container had state for some of the extensions as well.[cn, value, value2] = ["SomeClass", 4, "SomeValue", "AnotherClass", true, "more values"];
Solution
- packExtension - adds to the end of the packed state (from base or with other ISV extensions) container the part for this extension, prefixing it with the name of the extension
- unpackExtension - look through the packed state container and find the sub-part for this particular extension based on extension name.
- isCandidateExtension - evaluates if the passed in container is possible an extension packed state. For that it needs to consist of the name of the extension + packed state in a container.
Hope this helps!