|
|
Home » Starter Kit » TOC » Chapter 21
Chapter 21 - So You Think You Understand File Overrides "Try using OvrScope(*Job)." How many times have you heard this advice when a file override wasn't working as intended? Changing your application to use a job-level override may produce the intended results, but doing so is a bit like replacing a car's engine because it has a fouled spark plug. Actually, with a fully functional new engine, the car will always run right again. A job-level override, on the other hand, may or may not produce the desired results, depending on your application's design. And even if the application works today, an ill-advised job-level override coupled with modifications may introduce application problems in the future. If you're considering skipping this article because you believe you already understand file overrides, think again! I know many programmers, some excellent, who sincerely believe they understand this powerful feature of OS/400 — after all, they've been using overrides in their applications for years. However, I've yet to find anyone who does fully understand overrides. So, read on, surprise yourself, and learn once and for all how the system processes file overrides. Then put this knowledge to work to get the most out of overrides in your applications. Anatomy of Jobs Before examining file overrides closely, you need to be familiar with the parts of a job's anatomy integral to the function of overrides. The call stack and activation groups both play a key role in determining the effect overrides have in your applications. Jobs typically consist of a chain of active programs, with one program calling another. The call stack is simply an ordered list of these active programs. When a job starts, the system routes it to the beginning program to execute and designates that program as the first entry in the call stack. If the program then calls another program, the system assigns the newly called program to the second call stack entry. This process can continue, with the second program calling a third, the third calling a fourth, and so on, each time adding the new program to the end of the call stack. The call stack therefore reflects the depth of program calls. Consider the following call stack: ProgramA You can see four active programs in this call stack. In this example, the system called ProgramA as its first program when the job started. ProgramA then called ProgramB, which in turn called ProgramC. Last, ProgramC called ProgramD. Because these are nested program calls, each program is at a different layer in the call stack. These layers are known as call levels. In the example, ProgramA is at call level 1, indicating the fact that it is the first program called when the job started. ProgramB, ProgramC, and ProgramD are at call levels 2, 3, and 4, respectively. As programs end, the system removes them from the call stack, reducing the number of call levels. For instance, when ProgramD ends, the system removes it from the call stack, and the job then consists of only three call levels. If ProgramC then ends, the job consists of only two call levels, with ProgramA and ProgramB making up the call stack. This process continues until ProgramA ends, at which time the job ends. So far, you've seen that when one program calls another, the system creates a new, higher call level at which the called program runs. The called program then begins execution, and when it ends, the system removes it from the call stack, returning control to the calling program at the previous call level. That's the simple version, but there's a little more to the picture. First, it's possible for one program to pass control to another program without the newly invoked program running at a higher call level. For instance, with CL's TfrCtl (Transfer Control) command, the system replaces (in the call stack) the program issuing the command with the program to which control is to be transferred. Not only does this action result in the invoked program running at the same call level as the invoking program, but the invoking program is also completely removed from the chain of programs making up the call stack. Hence, control can't be returned to the program that issued the TfrCtl command. Instead, when the newly invoked program ends, control returns to the program at the immediately preceding call level. You may recall that earlier I said that as programs end, the system removes them from the call stack. In reality, when a program ends, the system removes from the call stack not only the ending program but also any program at a call level higher than that of the ending program. You might be thinking about our example and scratching your head, wondering, "How can ProgramB end before ProgramC?" Consider the fact that ProgramD can send an escape message to ProgramB's program message queue. This event results in the system returning control to ProgramB's error handler. This return of control to ProgramB results in the system removing from the call stack all programs at a call level higher than ProgramB — namely, ProgramC and ProgramD. ProgramB's design then determines whether it is removed from the call stack. If it handles the exception, ProgramB is not removed from the call stack; instead, processing continues in ProgramB. You should also note that under normal circumstances, the call stack begins with several system programs before any user-written programs appear. In fact, system programs will likely appear throughout your call stack. This point is important only to demonstrate that the call stack isn't simply a representation of user-written programs as they are called. In addition to an understanding of a job's call levels, you need a basic familiarity with activation groups to comprehend file overrides. You're probably familiar with the fact that a job is a structure with its own allocated system resources, such as open data paths (ODPs) and storage for program variables. These resources are available to programs executed within that job but are not available to other jobs. Activation groups, introduced with the Integrated Language Environment (ILE), are a further division of jobs into smaller substructures. As is the case with jobs, activation groups consist of private system resources, such as ODPs and storage for program variables. An activation group's allocated resources are available only to program objects that are assigned to, and running in, that particular activation group within the job. You assign ILE program objects to an activation group when you create the program objects. Then, when you execute these programs, the system creates the activation group (or groups) to which the programs are assigned. A job can consist of multiple activation groups, none of which can access the resources unique to the other activation groups within the job. For example, although multiple activation groups within a job may open the same file, each activation group can maintain its own private ODP. In such a case, programs assigned to the same activation group can use the ODP, but programs assigned to a different activation group don't have access to the same ODP. A complete discussion of activation groups could span volumes. For now, it's sufficient simply to note that activation groups exist, that they are substructures of a job, and that they can contain their own set of resources not available to other activation groups within the job. Override Rules The rules governing the effect overrides have on your applications fall into three primary areas: the override scope, overrides to the same file, and the order in which the system processes overrides. After examining the details of each of these areas, we'll look at a few miscellaneous rules. Scoping an Override An override's scope determines the range of influence the override will have on your applications. You can scope an override to the following levels:
You specify an override's scope when you issue the override, by using the override command's OvrScope (Override scope) parameter. Figure 1 depicts an ILE application's view of a job's structure, along with the manner in which you can specify overrides. First, notice that two activation groups, the default activation group and a named activation group, make up the job. All jobs have as part of their structure the default activation group and can optionally have one or more named activation groups. Original Program Model (OPM) programs can run only in the default activation group. Figure 1 shows two OPM programs, Program1 and Program2, both running in the default activation group. Because OPM programs can't be assigned to a named activation group, jobs that run only OPM programs consist solely of the default activation group. ILE program objects, on the other hand, can run in either the default activation group or a named activation group, depending on how you assign the program objects to activation groups. If any of a job's program objects are assigned to a named activation group, the job will have as part of its structure that named activation group. In fact, if the job's program objects are assigned to different named activation groups, the job will have each different named activation group as part of its structure. Figure 1 shows five ILE programs: Program3 and Program4 are both running in the default activation group, and Program5, Program6, and Program7 are running in a named activation group. The figure not only depicts the types of program objects that can run in the default activation group and in a named activation group; it also shows the valid levels to which you can scope overrides. Programs running in the default activation group, whether OPM or ILE, can issue overrides scoped to the job level or to the call level. ILE programs running in a named activation group can scope overrides not only to these two levels but to the activation group level as well. Figure 1 portrays each of these possibilities. Overriding the Same File Multiple Times One feature of call-level overrides is the ability to combine multiple overrides for the same file so that each of the different overridden attributes applies. Consider the following program fragments, which issue the OvrPrtF (Override with Printer File) command: ProgramA: OvrPrtF File(Report) OutQ(Sales01) +
OvrScope(*CallLvl)
Call Pgm(ProgramB)
ProgramB: OvrPrtF File(Report) Copies(3) +
OvrScope(*CallLvl)
Call Pgm(PrintPgm)
When program PrintPgm opens and spools printer file Report, the overrides from both programs are combined, resulting in the spooled file being placed in output queue Sales01 with three copies set to be printed. Now, consider the following program fragment: ProgramC: OvrPrtF File(Report) OutQ(Sales01) +
OvrScope(*CallLvl)
OvrPrtF File(Report) Copies(3) +
OvrScope(*CallLvl)
Call Pgm(PrintPgm)
What do you think happens? You might expect this program to be functionally equivalent to the two previous programs, but it isn't. Within a single call level, only the most recent override is in effect. In other words, the most recent override replaces the previous override in effect. In the case of ProgramC, the Copies(3) override is in effect, but the OutQ(Sales01) override is not. This feature provides a convenient way to replace an override within a single call level without the need to first delete the previous override. It's also fun to show programmers ProgramA and ProgramB, explain that things worked flawlessly, and then ask them to help you figure out why things didn't work right after you changed the application to look like ProgramC! When they finally figure out that only the most recent override within a program is in effect, show them your latest modification — ProgramA: OvrPrtF File(Report) OutQ(Sales01) +
OvrScope(*CallLvl)
TfrCtl Pgm(ProgramB)
ProgramB: OvrPrtF File(Report) Copies(3) +
OvrScope(*CallLvl)
Call Pgm(PrintPgm)
— and watch them go berserk again! This latest change is identical to the first iteration of ProgramA and ProgramB, except that rather than issue a Call to ProgramB from ProgramA, you use the TfrCtl command to invoke ProgramB. Remember, TfrCtl doesn't start a new call level. ProgramB will simply replace ProgramA on the call stack, thereby running at the same call level as ProgramA. Because the call level doesn't change, the overrides aren't combined. You may need to point out to the programmers that they didn't really figure it out at all when they determined that only the most recent override within a program is in effect. The rule is: Only the most recent override within a call level is in effect. The Order of Applying Overrides You've seen the rules concerning the applicability of overrides. In the course of a job, many overrides may be issued. In fact, as you've seen, many may be issued for a single file. When many overrides are issued for a single file, the system constructs a single override from the overridden attributes in effect from all the overrides. This type of override is called a merged override. Merged overrides aren't simply the result of accumulating the different overridden file attributes, though. The system must also modify, or replace, applicable attributes that have been overridden multiple times and remove overrides when an applicable request to delete overrides is issued. To determine the merged override, the system follows a distinct set of rules that govern the order in which overrides are processed. The system processes the overrides for a file when it opens the file and uses the following sequence to check and apply overrides:
This ordering of overrides can get tricky! It is without a doubt the least-understood aspect of file overrides and the source of considerable confusion and errors. To aid your understanding, let's look at an example. Figure 2A shows a job with 10 call levels, programs in the default activation group and in two named activation groups (AG1 and AG2), and overrides within each call level and each activation group. Before we look at how the system processes these overrides, see whether you can determine the file that ProgramJ at call level 10 will open, as well as the attribute values that will be in effect due to the job's overrides. In fact, try the exercise twice, the first time without referring to the ordering rules. Figure 2B reveals the results of the job's overrides. Did you arrive at these results in either of your tries? Let's walk, step by step, through the process of determining the overrides in effect for this example. Step 1 — call-level overrides up to and including the call level of the oldest procedure in the activation group containing the file open Checking call level 10 shows that the system opens file Report1 in activation group AG1. The oldest procedure in activation group AG1 appears at call level 2. Therefore, in step 1, the system processes call-level overrides beginning with call level 10 and working up the call stack through call level 2. When the system processes call level 2, step 1 is complete.
Active overrides at this point: Copies(7) Active overrides at this point: CPI(13.3) Copies(6) Active overrides at this point: LPI(9) CPI(13.3) Copies(4) Step 1 is now complete. Call level 2 contains the oldest procedure in activation group AG1 (the activation group containing the file open). Step 2 — the most recent activation-grouplevel overrides for the activation group containing the file open The system now checks for the most recently issued activation-grouplevel override within activation group AG1, where file Report1 was opened.
Active overrides at this point: Step 2 is now complete. The system discontinues searching for activation-grouplevel overrides because this is the most recently issued activation-grouplevel override in activation group AG1. Step 3 — call-level overrides lower than the call level of the oldest procedure in the activation group containing the file open Remember, call level 2 is the call level of the oldest procedure in activation group AG1. The system begins processing call-level overrides at the call level preceding call level 2. In this case, there is only one call level lower than call level 2.
Active overrides at this point: Step 3 is now complete. The call stack has been processed through call level 1. Step 4 — the most recent job-level overrides The system finishes processing overrides by checking for the most recently issued job-level override for file Report1.
Active overrides at this point: Step 4 is now complete. The system discontinues searching for job-level overrides because this is the most recently issued job-level override. This completes the application of overrides. The final merged override that will be applied in call level 10 is LPI(12) CPI(13.3) FormFeed(*Cut) OutQ(Prt01) Copies(8) All other attribute values come from the file description for printer file Report1. It's easy to see how this process could be confusing and lead to the introduction of errors in applications! Now, let's make the process even more confusing! In the previous example, our HLL program (ProgramJ) opened file Report1, and no programs issued an override to the file name. What do you think happens when you override the file name to a different file using the ToFile parameter on the OvrPrtF command? Once the system issues an override that changes the file, it searches for overrides to the new file, not the original. Let's look at a slightly modified version of our example. Figure 2C contains the new programs. Only two of the original programs have been changed in this new example. In ProgramC at call level 3, the ToFile parameter has been added to the OvrPrtF command, changing the file to be opened from Report1 to Report2. And ProgramB at call level 2 now overrides printer file Report2 rather than Report1. Figure 2D shows the results of the overrides. Again, let's step through the process of determining the overrides in effect for this example. Step 1 — call-level overrides up to and including the call level of the oldest procedure in the activation group containing the file open Checking call level 10 shows that the system opens file Report1 in activation group AG1. The oldest procedure in activation group AG1 appears at call level 2. Therefore, in step 1, the system processes call-level overrides beginning with call level 10 and working up the call stack through call level 2. When the system processes call level 2, step 1 is complete.
Active overrides at this point: Copies(7) Active overrides at this point: CPI(13.3) Copies(6) Active overrides at this point: Step 1 is now complete. Call level 2 contains the oldest procedure in activation group AG1 (the activation group containing the file open). Step 2 — the most recent activation-grouplevel overrides for the activation group containing the file open The system now checks for the most recently issued activation-grouplevel override within activation group AG1 where file Report1 (actually Report2 now) was opened.
Active overrides at this point: ToFile(Report2) LPI(7.5) CPI(13.3) FormType(FormB) Copies(3) Step 2 is now complete. The system discontinues searching for activation-grouplevel overrides because this is the most recently issued activation-grouplevel override in activation group AG1. Step 3 — call-level overrides lower than the call level of the oldest procedure in the activation group containing the file open Again, call level 2 is the call level of the oldest procedure in activation group AG1. The system begins processing call-level overrides at the call level preceding call level 2 (i.e., call level 1).
Step 3 is now complete. The call stack has been processed through call level 1. Step 4 — the most recent job-level overrides The system finishes processing overrides by checking for the most recently issued job-level override for file Report2.
Step 4 is now complete. There are no job-level overrides for file Report2. This completes the application of overrides. The final merged override that will be applied to printer file Report2 in call level 10 is LPI(7.5) CPI(13.3) FormType(FormB) Copies(3) All other attribute values come from the file description for printer file Report2. Protecting an Override In some cases, you may want to protect an override from the effect of other overrides to the same file. In other words, you want to ensure that an override issued in a program is the override that will be applied when you open the overridden file. You can protect an override from being changed by overrides from lower call levels, the activation group level, and the job level by specifying Secure(*Yes) on the override command. Figure 3 shows excerpts from two programs, ProgramA and ProgramB, running in the default activation group and with call-level overrides only. ProgramA simply issues an override to set the output queue attribute value for printer file Report1 and then calls ProgramB. ProgramB in turn calls two HLL programs, HLLPrtPgm1 and HLLPrtPgm2, both of which function to print report Report1. Before the call to each of these programs, ProgramB issues an override to file Report1 to change the output queue attribute value. When you call ProgramA, the system first issues a call-level override that sets Report1's output queue attribute to value Prt01. Next, ProgramA calls ProgramB, thereby creating a new call level. ProgramB begins by issuing a call-level override, setting Report1's output queue attribute value to Prt02. Notice that the OvrPrtF command specifies the Secure parameter with a value of *Yes. ProgramB then calls HLL program HLLPrtPgm1 to open and print Report1. Because this call-level OvrPrtF command specifies Secure(*Yes), the system does not apply call-level overrides from lower call levels — namely, the override in ProgramA that sets the output queue attribute value to Prt01. HLLPrtPgm1 therefore places the report in output queue Prt02. ProgramB continues with yet another call-level override, setting Report1's output queue attribute value to Prt03. Because this override occurs at the same call level as the first override in ProgramB, the system replaces the call level's override. However, this new override doesn't specify Secure(*Yes). Therefore, the system uses the call-level override from call level 1. This override changes the output queue attribute value from Prt03 to Prt01. ProgramB finally calls HLLPrtPgm2 to open and spool Report1 to output queue Prt01. These two overrides in ProgramB clearly demonstrate the behavioral difference between an unsecured and a secured override. Explicitly Removing an Override The system automatically removes overrides at certain times, such as when a call level ends, when an activation group ends, and when the job ends. However, you may want to remove the effect of an override at some other time. The DltOvr (Delete Override) command makes this possible, letting you explicitly remove overrides. With this command, you can delete overrides at the call level, the activation group level, or the job level as follows: Call level: DltOvr File(File1) OvrScope(*)
Activation Value *ActGrpDfn is the default value for the DltOvr command's OvrScope (Override scope) parameter. If you don't specify parameter OvrScope on the DltOvr command, this value is used. The command's File parameter also supports special value *All, letting you extend the reach of the DltOvr command. This option gives you a convenient way to remove overrides for several files with a single command. Miscellanea I've covered quite a bit of ground with these rules of overriding files. In addition to the rules you've already seen, I'd like to introduce you to a few tidbits you might find useful. You've probably grown accustomed to the way a CL program lets you know when you've coded something erroneously — the program crashes with an exception! However, specify a valid, yet wrong, file name on an override, and the system gives you no warning you've done so. This seemingly odd behavior is easily explained. Consider the following code: OvrPrtF File(Report1) OutQ(Prt01) Call Pgm(HLLPrtPgm) However, HLLPrtPgm opens file Report2, not Report1. The system happily spools Report2 without any regard to the override. Although this is clearly a mistake in that you've specified the wrong file name in the OvrPrtF command, the system has no way of knowing this. The system can't know your intentions. Remember, this override could be used somewhere else in the job, perhaps even in a different call level. The second tidbit involves a unique override capability that exists with the OvrPrtF command. OvrPrtF's File parameter supports special value *PrtF, letting you extend the reach of an override to all printer files (within the override scoping rules, of course). All rules concerning the application of overrides still apply. Special value *PrtF simply gives you a way to include multiple files with a single override command. Also, you may recall an earlier reference to program QCmdExc and how its use affects the scope of an override. This program's primary purpose is to serve as a vehicle that lets HLL programs execute system commands. You can therefore use QCmdExc from within an HLL program to issue a file override. Remember that when you issue an override using this method, the call level is that of the process that invoked QCmdExc. You should note that override commands may or may not affect system commands. For more information about overrides and system commands, see "Overrides and System Commands,". Important Additional Override Information With the major considerations of file overrides covered, let's now take a brief look at some additional override information of note. Overriding the Scope of Open File At times, you'll want to share a file's ODP among programs in your application. For instance, when you use the OpnQryF (Open Query File) command, you must share the ODP created by OpnQryF or your application won't use the ODP created by OpnQryF. To share the ODP, you specify Share(*Yes) on the OvrDbF (Override with Database File) command. You can also explicitly control the scope of open files (ODPs) using the OpnScope (Open scope) parameter on the OvrDbF command. You can override the open scope to the activation group level and the job level. Non-File Overrides In addition to file overrides, the system provides support for overriding message files and program device entries used in communications applications. You can override the message file used by programs by using the OvrMsgF (Override with Message File) command. However, the rules for applying overrides with OvrMsgF are quite different from those with other override commands. You can override only the name of the message file used, not the attributes. During the course of normal operations, the system frequently sends various types of messages to various types of message queues. OvrMsgF provides a way for you to specify that when sending a message for a particular message ID, the system should first check the message file specified in the OvrMsgF for the identified message. If the message is found, the system sends the message using the information from this message file. If the message isn't found, the system sends the message using the information from the original message file. Using the OvrICFDevE (Override ICF Program Device Entry) command, you can issue overrides for program device entries. Overrides for program device entries let you override attributes of the Intersystem Communications Function (ICF) file that provides the link between your programs and the remote systems or devices with which your program communicates. Overrides and Multithreaded Jobs The system provides limited support for overrides in multithreaded jobs. Some restrictions apply to the provided support. The system supports the following override commands:
The system ignores any other override commands in multithreaded jobs. File Redirection You can use overrides to redirect input or output to a file of a different type. For instance, you may have an application that writes directly to tape using a tape file. If at some time you'd like to print the information that's written to tape, you can use an override to accomplish your task. When you redirect data to a different file type, you use the override appropriate for the new target file. In the case of our example, you would override from the tape file to a printer file using the OvrPrtF command. I mention file redirection so that you know it's a possibility. Of course, many restrictions apply when using file redirection, so if you decide you'd like to use the technique, refer to the documentation. IBM's File Management provides more information about file redirection. You can find this manual on the Internet at IBM's iSeries Information Center (http://publib.boulder.ibm.com/pubs/html/as400/infocenter.htm). Surprised? Did you find any surprises along the way? My guess is that you did. The important thing is that now you should be able to find your way around file overrides without having to resort to job-level overrides in desperation! This article is excerpted from the book Starter Kit for the IBM iSeries and AS/400 by Gary Guthrie and Wayne Madden (NEWS/400 Books, 2001) Gary Guthrie is a NEWS/400 technical editor and technical support consultant with more than 20 years of progressive IT experience. You can reach him at gguthrie@iseriesnetwork.com. |
| Sponsored Links | Featured Links | |
Penton Technology Media Connected Home | SQL Server Magazine | Windows IT Pro Report Bugs | Contact Us | Comments/Suggestions | Terms of Use | Privacy Statement | Trademarks See Membership Levels | Subscribe | Free E-mail Newsletters | Free RSS Feeds | My Profile | Upgrade Now | Renew Now © 2010 Penton Media, Inc. System i is a trademark of International Business Machines Corporation and is used by Penton Media, Inc., under license. SystemiNetwork.com is published independently of International Business Machines Corporation, which is not responsible in any way for the content. Penton Media, Inc., is solely responsible for the editorial content and control of the System iNetwork. |