Understanding the %metadata Application Package

The %metadata application package in PeopleSoft is very intriguing.  This app package is unlike any other as we (PeopleSoft Developers) do not have access to the implementation of this package.  When you try to open this package in App Designer, the IDE acts as if the package does not exist.  However, if you correctly reference this package’s contents (sub-packages, classes, methods, properties, etc.) then App Designer does not bat an eye.  It should be well understood that (our) usage of this package is not supported by Oracle as it is undocumented.  However, there are currently no measures in place to prevent blind usage this app package.  While I definitely do not advise the usage of this package in any legitimate PeopleSoft system, I thought it would be a fun educational exercise to try to understand this mysterious app package.

I would like to start this off by making it clear that none of the information I am presenting in this post should be treated as factual.  This is merely my understanding an obscure and undocumented PeopleTools technology.

References to the %metadata package can be seen in some of the delivered code.  From what I can gather, the delivered code uses this package for accessing/manipulating PeopleTools-managed objects.  This package offers a (potentially safer) alternative to accessing/manipulating PeopleTools objects through the metadata tables in the database.

 

Overview of %metadata

There are three major aspects to understand when using the %metadata package.

  • Definitions
  • Managers
  • Keys

Definitions can be thought of as the object types.  So if you want to work with a record object type, then it would be referenced with %metadata as follows:

/* Record Definition */
Local %metadata:RecordDefn:RecordDefn &oRecordDefn;

In order to obtain a reference to an object definition, you must use the manager for the given object type. Managers are responsible for fetching  and creating object definitions.  So to work with a record definition, the record definition manager must first be instantiated as follows:

 /* Record Manager */
 Local %metadata:RecordDefn:RecordDefn_Manager &oRecordDefn_Manager;
 &oRecordDefn_Manager = create %metadata:RecordDefn:RecordDefn_Manager();

In order for the manager to supply a reference to an existing object definition, you will have to supply the manager with the key to the definition.  Keys are name-value pairs that reference a single definition of a given object type.  So if we want to manage a record named “PSM_TEST”, then the key would be created as follows:

 /* Example Key Object representing a record */
 Local %metadata:Key &oRecordKey;
 &oRecordKey = create %metadata:Key(Key:Class_Record, "PSM_TEST");

The key object instantiation is rather strange (syntax-wise) and I will get into more detail on this later.  But for now, we can feed the generated key object to the record manager’s GetDefn method, to obtain a non-updateable reference to the PSM_TEST record object definition.

/* Get reference to a non-updateable record definition */
&oRecordDefn = &oRecordDefn_Manager.GetDefn(&oRecordKey);

If you want to make changes to the record definition, then you can obtain an updatable version of the definition by supplying the generated key to the manager’s GetDefnToUpdate method.

/* Get reference to an updatable record definition */
&oRecordDefn = &oRecordDefn_Manager.GetDefnToUpdate(&oRecordKey);

As I said before, managers are capable of creating new object definitions.  The key is not needed for creating a new object.  The manager’s CreateDefn method will return a reference to a new (empty) record definition.

/* Get reference to a new record definition */
&oRecordDefn = &oRecordDefn_Manager.CreateDefn();

Now that we have a high level overview of the important pieces to using %metadata, I would like to go into more granular detail of each of the pieces so that we can understand how to actually manipulate PeopleTools-managed objects.

 

Understanding %metadata Definitions

%metadata has numerous sub-packages that  each represent a PeopleTools-managed object type.  I gave an example of accessing the Record object type above by referencing the RecordDefn sub-package. Many (if not all) object types can be referenced with %metadata and they are all referenced by a somewhat guessable sub-package naming scheme.  Here are some examples:

 /* Example Definition Objects */
 Local %metadata:MenuDefn:MenuDefn &oMenuDefn;
 Local %metadata:ComponentDefn:ComponentDefn &oComponentDefn;
 Local %metadata:PageDefn:PageDefn &oPageDefn;
 Local %metadata:PeopleCodeProgram:PeopleCodeProgram &oPeopleCodeProgram;
 Local %metadata:AppPackageDefn:AppPackageDefn &oAppPackageDefn;
 Local %metadata:RoleDefn:RoleDefn &oRoleDefn;
 Local %metadata:PermissionListDefn:PermissionListDefn &oPermissionListDefn;

The key takeaway to understanding these objects is that they essentially represent their database “*DEFN” table counterpart.  For example,  in order to understand the %metadata Record Definition, you must first understand the structure of the PSRECDEFN table in the database.  The properties of the Record Definition object should, more or less, reflect the fields that are on the PSRECDEFN table.

 

Understanding %metadata Managers

I have found that each %metadata object definition package contains a manager class.  The exact name of the manger class for any given object definition seems to be the object definition name with “_Manager” appended to it.   As we saw earlier the manger class name for the RecordDefn Record object definition was RecordDefn_Manager.  Here are some examples of other types of managers:

 /* Example Manager Objects */
 Local %metadata:MenuDefn:MenuDefn_Manager &oMenuDefn_Manager;
 Local %metadata:ComponentDefn:ComponentDefn_Manager &oComponentDefn_Manager;
 Local %metadata:PageDefn:PageDefn_Manager &oPageDefn_Manager;
 Local %metadata:PeopleCodeProgram:PeopleCodeProgram_Manager &oPeopleCodeProgram_Manager;
 Local %metadata:AppPackageDefn:AppPackageDefn_Manager &oAppPackageDefn_Manager;
 Local %metadata:RoleDefn:RoleDefn_Manager &oRoleDefn_Manager;
 Local %metadata:PermissionListDefn:PermissionListDefn_Manager &oPermissionListDefn_Manager;

All of the manager classes seem to have a common set of methods for getting and creating their respective object definitions.  I demonstrated earlier the GetDefn, GetDefnToUpdate, and CreateDefn methods for the Record Definition manager class.  I have found that these methods are defined in manager classes of other object definitions as well.  Along with these three methods are a coulple of other commom methods: DefnExists and GetPrivateDefn.  I am unsure of use cases for the GetPrivateDefn method, but the DefnExists method can be use to determine if an object defnition exists for a provided %metadata:key.

 

Understanding %metadata Keys

The keys are used to reference a specific definition for a given object type.  The key object is a required parameter for all manager class’s GetDefn, GetDefnToUpdate, and DefnExists mehods. The instantiation of a key object is rather strange as the key constructor behaves in an overloaded fashion and takes a non-PeopleCode object type as a parameter.  This can be proven by trying to extend the Key object’s constructor in App Designer:

Key_Constructor

As you can see from the picture above, the key’c constructor takes a RepeatedAny object type, which is not a native PeopleCode object type.  This leads me to believe that we are directly referencing a lower-level language (non-PeopleCode) implementation of these (%metadata) objects.  This could be one of the reasons why we do not have access to view the source of the %metadata package in App Designer.

Aside from its odd syntactical references, the key object is fairly straight-forward to understand. The %metadata:key class contains numerous sub-class, integer constants.  Each of the integer constants represent a specific object type. As we saw earlier, I used the Class_Record sub-class to represent the Record Definition object type.  Here are some examples of how to reference other available key sub-classes:

 /* Example Key sub-class, integer constants */
 Local integer &iMenuObjType = Key:Class_Menu;
 Local integer &iComponentObjType = Key:Class_PanelGroup;
 Local integer &iMarketObjType = Key:Class_Market;
 Local integer &iPageObjType = Key:Class_Panel;
 Local integer &iMethodObjType = Key:Class_Method;

The integers returned by these sub-classes are the key in the key-value pairs to construct a %metadata:key to give to a manager for an object definition.  The value in the key-value pair is simply a string.   Earlier I inputted the string “PSM_TEST” to refer to a record definition of a record named PSM_TEST.  To further solidify our understanding of this concept, I would like to provide another example of instantiating a key.  This time, I want to refer to a Component-level PeopleCode Program object. Specifically,  I will reference the PostBuild event to the USERMAINT Component:

 /* Example Key Object representing a PeopleCode Program */
 Local %metadata:Key &oKey = create %metadata:Key(Key:Class_PanelGroup, "USERMAINT", Key:Class_Market, "GBL", Key:Class_Method, "PostBuild");

A similar way to generate keys is to use the AddItem method.  This is my preferred way to generate keys as it is a bit easier to read and understand.  Here is an example of using AddItem to generate a key that references the same Component-level PeopleCode Program object as in the previous example:

 /* Instantiate the Key object */
 Local %metadata:Key &oKey = create %metadata:Key();
 
 /* Add the Component key-value pair */
 &oKey.AddItem(Key:Class_PanelGroup, "USERMAINT");
 
 /* Add the Market key-value pair */
 &oKey.AddItem(Key:Class_Market, "GBL");
 
 /* Add the Event key-value pair */
 &oKey.AddItem(Key:Class_Method, "PostBuild");

The knowledge of which key-value pairs are needed to reference a particular object type can be derived a couple of different ways.  The first way is to consider the key fields of the corresponding “*DEFN” database table of the object type that you are referencing.  The second way is to simply think of what input values App Designer requires for you to view a particular object type.

 

A Complete %metadata Example

I would now like to provide a complete example of using the %metadata application package. In this example, I will be editing the PeopleCode defined in the PostBuild event of the USERMAINT component.

import %metadata:Key;
import %metadata:PeopleCodeProgram:PeopleCodeProgram_Manager;
import %metadata:PeopleCodeProgram:PeopleCodeProgram;

/* Instantiate the Key object */
Local %metadata:Key &oKey = create %metadata:Key();

/* Add the USERMAINT Component key */
&oKey.AddItem(Key:Class_PanelGroup, "USERMAINT");

/* Add the GBL Market key */
&oKey.AddItem(Key:Class_Market, "GBL");

/* Add the PostBuild Event Name key */
&oKey.AddItem(Key:Class_Method, "PostBuild");

/* Instantiate the PeopleCode Program Manager object */
Local %metadata:PeopleCodeProgram:PeopleCodeProgram_Manager &oManager = create %metadata:PeopleCodeProgram:PeopleCodeProgram_Manager();

/* Determine if a PeopleCode Program Definition exists for the given key */
Local boolean &bExists = &oManager.DefnExists(&oKey);

/* Throw an exception if the definiton does not exists */
If Not (&bExists) Then
 throw CreateException(0, 0, "Definition does not exist for the provided key");
End-If;

/* Get the PeopleCode Program Definition */
Local %metadata:PeopleCodeProgram:PeopleCodeProgram &oPeopleCodeProgram = &oManager.GetDefnToUpdate(&oKey);

/* Get the PeopleCode that is definied for the loaded PeopleCode Program */
Local string &sProgram = &oPeopleCodeProgram.GetProgram();

/* Append a mesasagebox to the obtained PeopleCode string */
&sProgram = &sProgram | "messagebox(0,"""",0,0, ""Modifying PeopleCode with PeopleCode!"");";

/* Update the PeopleCode for the Peoplecode Program */
Local any &test1, &test2, &test3;
Local boolean &bUpdatedPeopleCode = &oPeopleCodeProgram.UpdateProgram(&sProgram, &test1, &test2, &test3);

/* Throw an exception if the PeopleCode did not update */
If Not (&bUpdatedPeopleCode) Then
 throw CreateException(0, 0, "PeopleCode did not update");
End-If;

/* Update the PeopleCode Program Definition to save the change to the PeopleCode */
Local boolean &bDefnUpdated = &oPeopleCodeProgram.UpdateDefn();

/* Throw an exception if the definition did not update */
If Not (&bDefnUpdated) Then
 throw CreateException(0, 0, "Definition did not update");
End-If;

 

Another Complete %metadata Example (Updated: 9/4/17)

I received a comment below from Jason about building Role definitions with %metadata.  I think this is a good use case of this package as it can allow a developer to administer PeopleSoft Security in an automated, object-oriented fashion.  Below is an example of how to use %metadata to assign a Permission List to a Role definition.  If the provided Role definition does not already exist, then the code will create a new Role definition and assign the Permission List to it.

import %metadata:Key;
import %metadata:RoleDefn:RoleDefn_Manager;
import %metadata:RoleDefn:RoleDefn;
import %metadata:RoleDefn:Roleclass;

Local %metadata:RoleDefn:RoleDefn &oRoleDefn;
Local %metadata:RoleDefn:Roleclass &oRoleclass;
Local boolean &bSaved;
Local string &sRoleName = "PSM_TEMP1";
Local string &sDescr = "My Descr";
Local string &DescrLong = "My Long Descr";
Local string &sRoleClass = "PTPT1000";

/* Instantiate the Key object */
Local %metadata:Key &oKey = create %metadata:Key();

/* Add the Role Name key */
&oKey.AddItem(Key:Class_RoleName, &sRoleName);

/* Instantiate the Role Defn Manager object */
Local %metadata:RoleDefn:RoleDefn_Manager &oManager = create %metadata:RoleDefn:RoleDefn_Manager();

/* Determine if a Role Definition exists for the given key */
Local boolean &bExists = &oManager.DefnExists(&oKey);

If &bExists Then
 &oRoleDefn = &oManager.GetDefn(&oKey);
 Local integer &i;
 /* Check if the Permission List is already assigned to the Role */
  For &i = 1 To &oRoleDefn.Count_Roleclass
   If (&oRoleDefn.Get_Roleclass(&i).Classid = &sRoleClass) Then
    MessageBox(0, "", 0, 0, "Permission List " | &sRoleClass | " is already assigned to Role " | &sRoleName);
    Return
   End-If;
 End-For;
 
 MessageBox(0, "", 0, 0, "Assigning Permission List " | &sRoleClass | " to existing Role " | &sRoleName);
 /* Get updatable Role definition and assign the Permission List to it */
 &oRoleDefn = &oManager.GetDefnToUpdate(&oKey);
 &oRoleclass = &oRoleDefn.Append_Roleclass(&oRoleDefn.Count_Roleclass);
 &oRoleclass.Classid = &sRoleClass;
 &bSaved = &oRoleDefn.UpdateDefn();
Else
 MessageBox(0, "", 0, 0, "Creating new Role " | &sRoleName | " and assigning Permission List " | &sRoleClass);
 /* Create new Role definition and assign the Permission List to it */
 &oRoleDefn = &oManager.CreateDefn();
 &oRoleDefn.Rolename = &sRoleName;
 &oRoleDefn.Descr = &sDescr;
 &oRoleDefn.Descrlong = &DescrLong;
 &oRoleclass = &oRoleDefn.Append_Roleclass(&oRoleDefn.Count_Roleclass);
 &oRoleclass.Classid = &sRoleClass;
 &bSaved = &oRoleDefn.SaveNewDefn();
End-If;

If Not &bSaved Then
 throw CreateException(0, 0, "Error saving Role definition");
End-If;

 

Yet Another Complete %metadata Example (Updated: 2/27/18)

There was an interesting post on the Oracle Developer Community asking how we can Pragmatically Edit PeopleCode.  The user needed a way to dynamically assign PeopleCode programs to Component PreBuild events to overcome an Event Mapping bug.  Here is an example of how we can add a PreBuild event PeopleCode program to the USERMAINT Component.

import %metadata:Key;
import %metadata:PeopleCodeProgram:PeopleCodeProgram_Manager;
import %metadata:PeopleCodeProgram:PeopleCodeProgram;

/* Set the Component and Market name that you want to add a PreBuild Event to */
Local string &sComponent = "USERMAINT";
Local string &sMarket = "GBL";
Local string &sEvent = "PreBuild";

/* Instantiate the Key object */
Local %metadata:Key &oKey = create %metadata:Key();

/* Add the USERMAINT Component key */
&oKey.AddItem(Key:Class_PanelGroup, &sComponent);

/* Add the GBL Market key */
&oKey.AddItem(Key:Class_Market, &sMarket);

/* Add the PostBuild Event Name key */
&oKey.AddItem(Key:Class_Method, &sEvent);

/* Instantiate the PeopleCode Program Manager object */
Local %metadata:PeopleCodeProgram:PeopleCodeProgram_Manager &oManager = create %metadata:PeopleCodeProgram:PeopleCodeProgram_Manager();

/* Determine if a PeopleCode Program Definition exists for the given key */
Local boolean &bExists = &oManager.DefnExists(&oKey);

/* Return if the PeopleCode Program already exists */
If (&bExists) Then
 throw CreateException(0, 0, "Definition already exists");
 Return;
End-If;

Local %metadata:PeopleCodeProgram:PeopleCodeProgram &oPeopleCodeProgram = &oManager.CreateDefn();

/* Set the properties of the PeopleCode Program */
&oPeopleCodeProgram.LastUpdDttm = %Datetime;
&oPeopleCodeProgram.LastUpdOprId = %OperatorId;

&oPeopleCodeProgram.ObjectID#0# = Key:Class_PanelGroup;
&oPeopleCodeProgram.ObjectValue#0# = &sComponent;

&oPeopleCodeProgram.ObjectID#1# = Key:Class_Market;
&oPeopleCodeProgram.ObjectValue#1# = &sMarket;

&oPeopleCodeProgram.ObjectID#2# = Key:Class_Method;
&oPeopleCodeProgram.ObjectValue#2# = &sEvent;

Local string &sCode = "/* My Code Goes Here */";

/* Update the PeopleCode for the Peoplecode Program */
Local any &test1, &test2, &test3;
Local boolean &bUpdatedPeopleCode = &oPeopleCodeProgram.UpdateProgram(&sCode, &test1, &test2, &test3);

/* Throw an exception if the PeopleCode did not update */
If Not (&bUpdatedPeopleCode) Then
 throw CreateException(0, 0, &test1 | " - Start Pos: " | &test2 | " End Pos: " | &test3);
End-If;

/* Save the new PeopleCode Program */
Local boolean &bDefnSaved = &oPeopleCodeProgram.SaveNewDefn();

/* Throw an exception if the save failed */
If Not (&bDefnSaved) Then
 throw CreateException(0, 0, "Failed to save the new PeopleCode Program definition");
End-If;

While I did not provide concrete examples of how to manipulate every possible object type with %metadata, I hope that I shined enough light on the subject to provide direction on how to go about manipulating any particular object type.  I believe that after gaining an understanding of the major aspects (Definitions, Managers, and Keys) of %metadata, one can fairly easily stumble their way through the usage of this package.

I think there can be many interesting use cases of the %metadata application package. I am personally putting this package to use by building out a PIA-based (online) PeopleSoft IDE. At the moment my online IDE is just a PeopleCode event editor with a horrible UI, but it is worth mentioning that I have had great success so far with using this package to view/update PeopleCode on any of the PepleCode events.  My biggest hurdle at the moment is coming with a JavaScript-based PeopleCode syntax highlighter/parser.  Here is a post documenting the Online PeopleCode Editor Project.