Library lists have long been our mainstay, but with server programs it's not so easy to set that initial library list.
The IBM i has a number of unique features, things that set it apart from nearly every other machine. A good example is the single-level store that allows hundreds of users to easily use the same program in limited memory. Another is bi-directional parameters on program calls. I remember trying to emulate that in UNIX; it wasn't pretty. A big one, though, that we green-screeners take for granted is the library list, particularly the initial library list that we get when we sign on. The library list is so ingrained in us that it's hard to imagine life without it, but in the world of Service-Oriented Architecture (SOA), we have to rethink our approach. In this article, I'll explain the complexities of library lists and even give you a slick RPG IV technique to get around the issue.
Wait a Minute. What's a Library List?
OK, in case you're tuning in from Planet Microsoft, where there are no library lists, let me explain the concept. On the i, every object--program, file, data area, whatever--has a library name and an object name. Well, to be precise, all objects in the QSYS file system act this way. I could go off on a bit of a tangent describing the hierarchical structure of the Integrated File System (IFS), but let's stick to the basics for now.
When opening a file or program, especially in the olden days, you typically specified only the object name. The library name was not specified and defaulted to a special value *LIBL, which stands for "library list." When the operating system saw the special *LIBL value, it would scan each library in the order it appears in the library list, looking for the specified object. The first one it found was selected for use (if none were found, an exception occurred).
This is an incredibly powerful technique. It allows you to use the same programs for multiple companies by simply having duplicate files in different libraries, one for each company, and then setting up your library list. Or to test a new version of a program by simply putting it into a test library and then putting that test library higher in your library list. Over the years, the library list concept has evolved: it's been split into two separate components, one that's system-wide and one that's specific to the job. A couple of special library slots have been added--a current library and a product library--each of which provides a slightly different way of augmenting the library list. But in general, the user portion of the library list is still the primary vehicle through which we segregate users into specific operating environments within the machine.
The Microsoft and UNIX folks are probably complaining right now that they have similar capabilities through the various environment settings, such as PATH. This is true to a point, but it is limited more to finding programs, less to data, whereas the object lookup for any object--whether it's a file or a program--is consistent on the i (just another example of the integrated nature of the platform).
The Library List Life Cycle
The library list life cycle is a simple one: a user signs on and is given an initial library list. This library list comes from the job description associated with the user profile. Many people never even see this; the system provides a default job description, QDFTJOBD, which in turn defaults to the system value for the user library list, QUSRLIBL. As shipped, QUSRLIBL has two libraries: QGPL and QTEMP. So, after a slightly circuitous route, the default library list for a job is QGPL and QTEMP.
Well, not quite! In addition to the library list from the job description, and in fact preceding those libraries, are the libraries defined in the system value QSYSLIBL. This value also has a default shipped value, which includes QSYS, QSYS2, QHLPSYS, and QUSRSYS.
So in summary, the default library list for a user profile, if you don't do any other manipulation, is typically QSYS, QSYS2, QHLPSYS, QUSRSYS, QGPL, and QTEMP. This is usually insufficient for an application. Indeed, most applications require multiple libraries to be added to the library list. A standard ERP package usually has two production libraries: one for program objects and one for data objects. As I noted above, this allows a lot of flexibility in setting up multiple environments. They may also have additional libraries for modifications or extensions, as well as utility libraries and so on.
Setting Up an Application Library List
Typically, when users sign on, they take one of two paths. If users are limited to a single environment, they have a hard-coded initial program that sets up their library list. This is often the case for end users; they use the primary production environment and that's all. In more complex installations with end users who access multiple production environments or programmers who access multiple development environments, those users may have an initial menu that allows them to select the environment they wish to run in. In other cases, power users or developers have a command that allows them to switch environments.
In all cases, a program is called that typically sets the library list. This is usually a CL program, since manipulating the job attributes is a bit easier in CL, although in cases where the library list comes from a file, an RPG program may be written that reads the file and then executes a CHGLIBL command to set up the environment.
Library Lists and SOA
If you review the options above, you'll notice that several of them have an interaction with the user as part of the process. Whether it's an initial menu or a command line call, the program needs to talk to the user. In an SOA environment, you don't have that luxury. Remember, your user interface is not a green-screen; it may be a Java EE application running in WebSphere, a .NET application running in Windows, or a Rich UI client running in a browser. You'll interact to the system using a remote connection, and you will not be able to invoke a green-screen program to set your library list.
For example, Java EE applications use IBM's Java Toolbox to create a connection that in turn executes commands in a batch job named QZRCSRVS running in QUSRWRK. Since you can't call a 5250 program in that environment, the very first thing you have to do is isolate the code that actually sets up the environment and make sure it can be called without requiring a user interaction (that is, a 5250 screen). Then you have to call it. How often you have to call it depends on the architecture: stateful connections require only a single call at startup time, while stateless sessions need the environment to be initialized on every request.
Initializing the Initializer
A chicken-and-egg situation still exists, however. In order to execute the environment initialization program, the initial library list for the user profile needs to at least be inclusive enough to run the program that sets the library list. If the program is a CL program with hard-coded CHGLIBL commands, it's not that hard; just call the program using a fully qualified name. But if the program reads the libraries from a file or calls other programs, you'll need to invoke it to be sure that the program can access the appropriate files.
The simplest option is to be sure that your initialization programs and any files they use reside in a library that is contained in your system library list. Every job on the system has those libraries, so any job, including those invoked remotely, are assured of having those libraries available. A related technique is to make sure that the user profiles you use to invoke remote jobs have the appropriate libraries in their initial library list. Both of these techniques rely on system settings, but they're easy to implement. A quick note on how these programs work: typically, they'll take a look at either the user profile used to invoke the program or an environment parameter that was passed in, or both, to determine the appropriate library list.
Another option is to call the program using a fully qualified name to invoke the initial program. This is a slightly different technique, and it adds an additional variable to the mix, namely the library that the initial program was called from. If the program is an RPG program, it is very easy to get the library the program was called from:
D xsLib 81 90
By using the program status data structure (as specified by the first "S" in the first line), the 10 characters from position 81 to 90 identify the library the program was called from. You can then use that as part of your setup logic. Then, simply put a copy of this program into a specific library for each environment, and in the program code a SELECT statement to set up the appropriate hard-coded library list, using a call to QCMDEXC. Simple, but not particularly flexible. It no longer relies on system settings, but you're still basically hard-coding the library lists.
The most flexible design uses a database file to build the library list. You can add a database file to the first option; you simply need to make sure the database file is in the same library as the environment initialization program. If you have complete control over your environment, this isn't a bad way to go. Use the user profile and/or an environment code passed into the setup program as the key to the database file, read through the file setting up the library list, and then use QCMDEXC to execute a CHGLIBL command.
But let's say you don't have the option of putting the setup objects into the system library list. At best, you can ensure that the initialization program and the file used to build the library list reside in the same library. What you want is to override the file to the appropriate library. You could do that in a CL program, except no simple way exists for a CL program to determine the library it was called from; your first program must be the RPG program. But still, you need to have an override. How we've gotten around this in the past is to make the file user-opened by specifying the USROPN keyword and then, in the *INSZR subroutine, formatting an OVRDBF command and executing it prior to opening the file.
The tip part of today's article is an easier way to do the override using the relatively new EXTFILE keyword. Take a look at the following specifications:
FLIBLIST IF E K DISK EXTFILE(FileName) USROPN
D FileName S 21
You have a file named LIBLIST with USROPN specified, but also with the keyword EXTFILE specified, with the parameter FileName. You also see a 21-character alpha field named FileName. Many of us oldsters immediately recognize the magic number 21: it's the maximum number of characters for a fully qualified object name. In this case, we're allowed to put a fully qualified name, library/filename, into the field prior to opening the file, and it will work just like an override. Here's the code:
FileName = %trim(xsLib) + '/LIBLIST';
It's a free-form version of the *INZSR subroutine, and in it I set the FileName field equal to the file name LIBLIST, but it's qualified by the library that the program was called from (this assumes that you've also coded the program status data structure as I showed earlier in the article).
This is a very slick technique. You can copy this program into several different libraries, and whichever library you call the program from, it will attempt to open the LIBLIST file in that library. Note that the EXTPGM keyword also supports this sort of soft-coding. While I still prefer using unqualified object names and the power of the library list to support my application architecture on the i, this is a good way to integrate that technique with the world of SOA.