Jan 29

i5Toolkit is slowing things down significantly

I did a bit of digging around to find out why the response times were so bad with the test PHP programs we have written so far. Turns out the toolkit API’s have a large overhead attached to them and you should use the DB2 functions where possible for DB2 access!

I wrote a simple test to see what the benefit is, the original pages took about 5 seconds to load! The following page is almost instantaneous!


<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<HTML><HEAD>
<META content=”text/html; charset=iso-8859-1″ http-equiv=Content-Type></HEAD>
<BODY>
<?php
//include(“i5toolkit/Toolkit_classes.php”);
// include the file which holds the user info
include(“../scripts/config.php”);
// connect to the i5
$options = array(“i5_lib”=>”cprojdta”);
$conn = db2_connect(“”,”",”",$options);
if (is_bool ( $conn ) && $conn == FALSE) {
die ( i5_errormsg () );
}
$result = db2_exec($conn,”select * from users”);
echo(var_dump(db2_fetch_both($result)));?>
<table border=”1″>
<tr><td width=”150″>First Name</td><td width=”150″>Last Name</td></tr><?php
while ($row = db2_fetch_both($result)) {
echo(“<tr><td>” .$row['FIRSTNAME'] .”</td><td>” .$row['LASTNAME'] .”</td></tr><br />”);
}
db2_close($conn);
?>
</table>
</BODY>
</HTML>

So if you are developing PHP code to access the DB2 database I would suggest looking at the DB2_ functions before the i5_ functions…

Chris..

Jan 29

Nice to hear you did OK!

It is always good to get feedback about how you are perceived and how your products are working in the market place. We recently went through a major new install of our Receiver Apply program(RAP) product for a very large fast food chain in Germany. The install was not the easiest and we did have to work pretty hard to ensure the product provided the functionality the customer required. A number of issues came up, mainly due to new functionality we had developed specifically to meet this customers needs which while it always works well in the development environment always seems to have teething problems in the field… The biggest problem came about due to changes IBM had made when they released the i6.1 code, we have posted how IBM had changed the layout of the data structure within the journal entries but had failed to update the header files or documentation provided to the developers (You can read more here). Thankfully the customer was very understanding and was willing to work with us on building a replacement solution. Now the project is coming to a successful conclusion and the customer is very happy to be a reference not only for our products but also for the support we provided during the install.

Here is what he had to say.

“The Quality and effectiveness of the software are excellent plus the level of support provided during the installation was outstanding. Shield responded to all of our needs in record time even though some of those problems were outside of Shield’s control. I can only say I would recommend Shield and its products to anyone looking at a solution for High Availability or Disaster Recovery in the IBM ‘i’ space….. Pablo”

If you would like to speak with Pablo about his experiences let us know..

Chrs…

Jan 29

PHP for i #4

In this post we will add the same functionality we added in the C for i #4 post to the PHP Project.

The way we will display the details of the user will be different to a 5250 screen, we don’t monitor for Function Keys in the Web Browser so we need to find an alternative method to allow the user to get the additional details. We will use a link against each entry which will allow the correct record to be identified and displayed back to the user.

The only change we need to make in the index.php file is to add the link in the output. We will also correct a problem we didn’t identify where the loop we used to create the table rows added an empty row to the end of the table. Here is the code which replaces the existing table row build. We are adding a link to each row which will request a new dspdet.php page to be called when the user clicks the link.


do {
$rec = i5_fetch_row($file,I5_READ_NEXT);
if($rec) { ?>
<tr><td><a href=”dspdet.php?id=<?php echo(i5_bookmark($file)); ?>”>Details</a></td><td><?php echo($rec[0]); ?></td><td><?php echo($rec[1]);?></td></tr><?php
}
} while($rec);

The biggest change is the use of the i5_bookmark() function. We will use this to identify the records in the list and pass it onto our new page to seek the correct record.
We have added a new page in this instance but we could have coded the existing page up to know if the list had to be displayed or details of a specific record. Here is the code, we used the i5_data_seek() function to seek the record based on the id passed in via the $_REQUEST array. Then we simply pulled the record from the file position and display the information to the user.


<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<HTML>
<HEAD>
<META content=”text/html; charset=iso-8859-1″ http-equiv=Content-Type>
</HEAD>
<BODY>
<?php
//include(“i5toolkit/Toolkit_classes.php”);
// include the file which holds the user info
include(“../scripts/config.php”);
// connect to the i5
$conn = i5_connect($server,$usr,$pwd,array (I5_OPTIONS_JOBNAME => “PHPPROJ” ));
// check that we connected OK or die()
if (is_bool ( $conn ) && $conn == FALSE) {
die ( i5_errormsg () );
}
// open the file if not opened return the error
$file = i5_open(‘CPROJDTA/USERS’,I5_OPEN_READ,$conn);
if ($file === false) {
$tab = i5_error();
echo(var_dump($tab));
}
//locate the record
if(false === i5_data_seek($file,$_REQUEST['id'])) {
$tab = i5_error();
echo(var_dump($tab));
}
else {
// get the array of data
if(!$rec = i5_fetch_assoc($file,I5_READ_SEEK)) {
$tab = i5_error();
echo(var_dump($tab));
}
}

?>
<table>
<tr>
<td><a href=”index.php”>Return to list</a></td>
</tr>
<tr>
<td><label>First Name</label></td>
<td><?php echo($rec["FIRSTNAME"]); ?></td>
</tr>
<tr>
<td><label>Last Name</label></td>
<td><?php echo($rec["LASTNAME"]); ?></td>
</tr>
<tr>
<td><label>Address</label></td>
<td><?php echo($rec["ADDR1"]); ?></td>
</tr>
<tr>
<td><label>Address</label></td>
<td><?php echo($rec["ADDR2"]); ?></td>
</tr>
<tr>
<td><label>City</label></td>
<td><?php echo($rec["CITY"]); ?></td>
</tr>
<tr>
<td><label>State</label></td>
<td><?php echo($rec["STATE"]); ?></td>
</tr>
<tr>
<td><label>Zip Code</label></td>
<td><?php echo($rec["ZIP"]); ?></td>
</tr>
<tr>
<td><label>Country</label></td>
<td><?php echo($rec["COUNTRY"]); ?></td>
</tr>
<tr>
<td><label>Telephone</label></td>
<td><?php echo($rec["TELNUM"]); ?></td>
</tr>
<tr>
<td><label>Email</label></td>
<td><?php echo($rec["EMAIL"]); ?></td>
</tr>
</table>
<?php
// close the file and free resource
i5_free_file($file);
// close the connection
i5_close($conn);
?>
</BODY>
</HTML>

Thats it! It’s a bit simpler than the CPROJ programs because we don’t have to worry about function keys as refreshing lists etc. I am still concerned about the speed of the responses from our system though? This is a fairly simplistic example of reading data from a DB2 database and displaying it back to the user. We are seeing multiple second response times for each request? I hope when the new system arrives that we will see a significant increase in the responses…

If you have any requests for specific coding tasks let us know, we will see if we can implement something into the projects we have shown here to demonstrate how we would tackle the task.

Chris…

Jan 28

C for i #4

The next part of the project will add the ability to add records to the database file. The F6 key will be used to prompt the user for the data to be added to the file. Once the file has been updated we need to cause a refresh of the list to contain the newly added record.

The first changes we will make is to the Panel Group, because we need to allow the user to input the data we need to create a new panel group that allows the data to be entered. The data layout is exactly the same as the data displayed when the user takes the option to display the details, so we can take the same panel and copy it before updating the relevant fields to allow input plus renaming the panel to ADDUSRPNL.


.* -------------------------------------------------------------------

:PANEL NAME=ADDUSRPNL
HELP='addusrpnl/'
KEYL=usrkeys
ENTER='RETURN 500'
TOPSEP=SYSNAM.
User Details
.*
.* --- Data area -------------

:DATA DEPTH='*'
SCROLL=YES
LAYOUT=1
BOTSEP=SPACE.
.*
:DATACOL WIDTH=25.
:DATACOL WIDTH='*'.
.*
:DATAI VAR=fname
HELP='usrpnl/fname'
USAGE=INOUT.
First Name

:DATAI VAR=lname
HELP='usrpnl/lname'
USAGE=INOUT.
Last Name

:DATAI VAR=addr1
HELP='usrpnl/addr1'
USAGE=INOUT.
Address

:DATAI VAR=addr2
HELP='usrpnl/addr2'
USAGE=INOUT.
Address

:DATAI VAR=city
HELP='usrpnl/city'
USAGE=INOUT.
City

:DATAI VAR=state
HELP='usrpnl/state'
USAGE=INOUT.
State

:DATAI VAR=zip
HELP='usrpnl/zip'
USAGE=INOUT.
Zip Code

:DATAI VAR=cntry
HELP='usrpnl/cntry'
USAGE=INOUT.
Country

:DATAI VAR=telnum
HELP='usrpnl/telnum'
USAGE=INOUT.
Telephone

:DATAI VAR=email
HELP='usrpnl/email'
USAGE=INOUT.
Email

:EDATA.

:EPANEL.

We also need to add the function key to the key list.

:KEYI KEY=F6 HELP=helpf6
ACTION='call exitpgm'
VARUPD=NO.
F6=Add

The program which drives the panel group needs a bit more work. First we need to add the relevant header files into our UIMHDR file.


#include <quigetv.h> /* Get Variable record */
#include <quidltl.h> /* Delete List */
#include <euifkcl.h> /* Function Key Exit */

Now we can add the function key processing. The first thing we add is the function definitions for process the function key request from UIM plus a subsequent function that will retrieve the data from the user, update the file and then update the list in the panel group.

void ProcessFuncKeyAct(Qui_FKC_t *); This will be called from the switch process that filters the requests from the panel group.
int add_usr(char *); To do the work of displaying the input panel, writing the new record to the file and updating the panel list.
The above code should be placed above the entry point to the program where the other pre-declarations sit.
Now we will add the filter in the switch process to pick up the function key request. This is the complete function so if you are following by coding the program yourself, simply paste this code over the existing function.

void UIMExit(Qui_ALC_t *uimExitStr) {
Qui_ALC_t *listOptAction;
Qui_FKC_t *funcKeyAction;
int CallType;

CallType = uimExitStr->CallType;
switch(CallType) {
case 1: {
funcKeyAction = (Qui_FKC_t *) uimExitStr;
ProcessFuncKeyAct(funcKeyAction);
break;
}
case 2: {
break;
}
case 3: {
listOptAction = (Qui_ALC_t *) uimExitStr;
ProcessListOption(listOptAction);
break;
}
case 4: {
break;
}
case 5: {
break;
}
case 6: {
break;
}
case 7: {
break;
}
case 8: {
break;
}
}
return;
}

The function below will be called when a function key is pressed.


/* process function key requests */
void ProcessFuncKeyAct(Qui_FKC_t *funcKeyAct) {
Qui_FKC_t FKeyAct;

FKeyAct = *funcKeyAct;
/* F6 key pressed add user */
if(FKeyAct.FunctionKey == 6) {
add_usr(FKeyAct.ApplHandle);
}
return;
}

The following code is what does the work. First we have to clear the variable pool, the variables will have been set when the last list entry was added to the panel group so if we don’t do this the entry panel will be initialised with the existing data. Next we display the panel to collect the data from the user, if the user presses enter we will read the data from the panel group and write it to the file. As the new entry needs to be added before the list is shown to the user we will clear out the existing list and reload it using the function we already have developed to show the list initially. If the user presses F12 or F3 we do not try to add the entry to the file!


int add_usr(char *applHandle) {
_RFILE *fp; /* file Ptr */
_RIOFB_T *fdbk; /* Feed back Ptr */
USRREC UsrRec; /* File struct */
int functionRequested, /* Function requested by user */
*funcReq = &functionRequested; /* Pointer to function requested */
EC_t Error_Code = {0}; /* Error_Code Struct */

Error_Code.EC.Bytes_Provided = sizeof(Error_Code);
/* clear the variable pool otherwise the last put variables returned */
memset(&UsrRec, ' ', _USR_REC);
QUIPUTV(applHandle,
&UsrRec,
_USR_REC,
"USRDET ",
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf("Error Code received PUTV %.7s\n",Error_Code.EC.Exception_Id);
return;
}
/* display the panel to get the data */
QUIDSPP(applHandle,
funcReq,
"ADDUSRPNL ",
REDISPLAY_NO,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf("Error Code received %.7s\n",Error_Code.EC.Exception_Id);
return;
}
/* if user pressed enter add the data to the file */
if(*funcReq == 500) {
/* open the file for update */
if((fp =_Ropen("USERS","rr+")) == NULL) {
printf("filed to open the file\n");
return -1;
}
QUIGETV(applHandle,
&UsrRec,
_USR_REC,
"USRDET ",
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf("Error Code received GETV %.7s\n",Error_Code.EC.Exception_Id);
return;
}
fdbk = _Rwrite(fp,&UsrRec,_USR_REC);
if(fdbk->num_bytes != _USR_REC) {
printf("Failed to add record\n");
_Rclose(fp);
exit(-1);
}
/* clear the existing Subfile and request new list */
QUIDLTL(applHandle,
"USRLIST ",
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf("Error Code received DLTL %.7s\n",Error_Code.EC.Exception_Id);
_Rclose(fp);
return;
}
load_entries(applHandle);
_Rclose(fp);
}
return;
}

Thats all there is to it, you can handle function keys, display an input panel, add a new entry to the file, and update a list on the screen. The next part will look at taking an existing entry and updating after doing some level of data validation.

Chris…

Jan 28

Looking for Support?

Are you looking for support with your IBM ‘i’? If so we have skills we can offer. We have been working with the IBM ‘i’ since it announcement and have developed indepth skills for High Availability, Systems Management and Programming for the IBM ‘i’.

Our hope is to find small companies who are running the IBM ‘i’ and need our help in managing the IBM ‘i’ on a day to day basis without incurring the cost of full time staff. We can provide onsite or remote assistance depending on your need. Our internal systems provide us with the opportunity to test and develop solutions without exposing your production systems. You decide how much support you need and only pay for the support you take.

Interested? Give us a call 519-940-1192…

Chris…

Jan 27

Adding phpMyAdmin to MySQL

As part of the PHP for i series we will be using a MySQL database as well as the DB2 database to show how to interact with each.

When we installed ZendServer we also took the default MySQL install, once installed we started the servers using the menus provided before starting a QP2TERM session where we set the root password for MySQL. To do this simply start the QP2TERM session CALL QP2TERM, cd to the /usr/local/mysql directory, enter the following command ./bin/mysqladmin -u root password 'yourpassword'.

As we are avid users of phpMyAdmin we installed it in the phpproj virtualhost site so we can manage the MySQL install more effectively. You can download phpmyadmin from sourceforge or one of the mirrors, unzip the code into the base directory of your website (ours is htdocs) before creating the config file. There are a couple of options to create the config file which are explained in the manual, we simply renamed the config.sample.inc.php to config.inc.php and edited the content. As we are only going to use cookies for access we only had to add a blowfish secret in the appropriate section. Dont forget to restart the MySQL servers once you have completed these actions.

To use PHP you point your browser at the appropriate website and add the phpmyadmin directory (http://myserver/phpmyadmin/ in our case because we unzipped the files into phpmyadmin directory the the servers root directory) . You will need to sign in using root and the password you had set earlier. Then create a user called phpuser with a password, create a new database called phpproj and a new table called users. The content of the users table should be the same as the DB2 table except for a new field which we added to the start of the row. This field needs to be of type INT and have the Auto increment flag set and Not NULL. We will use this id for retrieving specific records in a later exercise.

You should now have a table which looks like the following.

Users table in phpmyadmin

Users Table phpMyAdmin view

We will be adding data to the table using the phpusers profile so it is important to set the privileges to the table for the user. We added all privileges but you can decide the best options to suit your environment.

Thats all for this post, next we will be showing how to select a user in the PHPPROJ and display the additional information for each user using both DB2 and MySQL.

Chris…

Jan 25

C for i #3

This post will cover the start of adding a UIM interface to the C Project. The UIM interface adds a CUA compliant interface to the project which is important when you later add the LookSoftware interface over the 5250 screens we are developing.

We have a stock method which we tend to use for the UIM processing, the following code is built upon this base code.

We have created a new header file called UIMHDR.c, this header file contains the includes which are specific to the UIM. Here is the header file.


#include <quidspp.h> /* Display Panel */
#include <quicloa.h> /* close application */
#include <quiputv.h> /* put variable */
#include <quiaddle.h> /* add a list entry */
#include <quiopnda.h> /* open display application */
#include <euialcl.h> /* listopt exit */

#define HELPFULL_NO “NO”
#define CLOSEOPT_NORMAL “M”
#define REDISPLAY_NO “N”
#define EXTEND_NO “N”
#define APPSCOPE_CALLER -1
#define EXITPARM_STR 0
#define EXITPROG_BUFLEN 20

We also added some chnages to the COMMON header file as seen below

#include <stdio.h> /* Standard Input/Output */
#include <stdlib.h> /* Standard Library */
#include <recio.h> /* Record IO */
#include <qusec.h> /* Error Code structs */

/* External File declaration and structure declaration & defines. */
#pragma mapinc("usrf","*LIBL/USERS(USRREC)","both","","","DATA_F")
#include "usrf"
typedef DATA_F_USRREC_both_t USRREC;
#define _USR_REC sizeof(USRREC)

#define _CPYRGHT "Copyright @ Shield Advanced Solutions Ltd 1997-2010"

/* structure for error code passed to API's */
typedef struct EC_x {
Qus_EC_t EC;
char Exception_Data[48];
} EC_t;

The main change is to add the QUSEC.H file and define a structure we will use for capturing errors returned from the PI calls.

We will only use certain API’s for this exercise so as we develop the project further we will add more includes and defines in this file if they are specific to UIM programs.
There is nothing really special about the content so the comments should be enough.

Next we built the panel group based on the information we already have. The basic process will be to display a list of entries which just show the First and Last name of the users we have in the DB file. We will provide a list option(5) to display all of the data stored about the user.


.*******************************************************************
.*
.* Part name: DSPUSRPNL
.*
.* Author name..: Chris Hird
.* Date created : January 2010
.*
.* Function…..: Display the list of users
.*
.* Date Author Revision
.*
.* @Copyright Shield Advanced Solutions Ltd. Canada
.*
.*******************************************************************
.* (1)
:PNLGRP HLPSHELF=list
DFTMSGF=hlpmsgf.

:COPYR.
(c) Copyright Shield Advanced Solutions (Canada) Inc. 2010

.* Import all help from panel group SDRHLP ——

:IMPORT NAME=’*’ PNLGRP=usrhlp.
.*(2)
.* ——————————————————————-
.* Variable classes
.*

:CLASS NAME=chr10cls BASETYPE=’CHAR 10′.
:ECLASS.

:CLASS NAME=chr15cls BASETYPE=’CHAR 15′.
:ECLASS.

:CLASS NAME=chr20cls BASETYPE=’CHAR 20′.
:ECLASS.

:CLASS NAME=chr25cls BASETYPE=’CHAR 25′.
:ECLASS.

:CLASS NAME=chr35cls BASETYPE=’CHAR 35′.
:ECLASS.

:CLASS NAME=actcls BASETYPE=’ACTION’.
:ECLASS.

:CLASS NAME=exitcl BASETYPE=’CHAR 20′.
:ECLASS.

:CLASS NAME=chr255cls BASETYPE=’CHAR 255′.
:ECLASS.

.* ——————————————————————-
.*(3)
:VAR NAME=fname CLASS=chr20cls.
:VAR NAME=lname CLASS=chr20cls.
:VAR NAME=addr1 CLASS=chr25cls.
:VAR NAME=addr2 CLASS=chr25cls.
:VAR NAME=city CLASS=chr15cls.
:VAR NAME=state CLASS=chr15cls.
:VAR NAME=zip CLASS=chr10cls.
:VAR NAME=cntry CLASS=chr15cls.
:VAR NAME=telnum CLASS=chr15cls.
:VAR NAME=email CLASS=chr35cls.
:VAR NAME=opt CLASS=actcls.
:VAR NAME=csrvar CLASS=chr10cls.
:VAR NAME=exitpgm CLASS=exitcl.
:VAR NAME=parms CLASS=chr255cls.

.* ——————————————————————-
.*(4)
:VARRCD NAME=usrdet
VARS=’lname fname addr1 addr2 city state zip cntry telnum email’.

:VARRCD NAME=cursor
VARS=’csrvar’.

:VARRCD NAME=exitprog
VARS=’exitpgm’.

.* ——————————————————————-
.*(5)
:LISTDEF NAME=usrlist
VARS=’opt lname fname addr1 addr2 city state zip cntry telnum email’.

.* ——————————————————————-
.*(6)
.* — File panel’s keys ——

:KEYL NAME=usrkeys HELP=fkeys.

:KEYI KEY=F1 HELP=helpf1
ACTION=HELP.

:KEYI KEY=F3 HELP=helpf3
ACTION=’EXIT SET’
VARUPD=NO.
F3=Exit

:KEYI KEY=F12 HELP=helpf12
ACTION=’CANCEL SET’
VARUPD=NO.
F12=Cancel

:KEYI KEY=F21 HELP=helpf21
ACTION=’CMDLINE’
PRIORITY=40.
F21=Command Line

:KEYI KEY=ENTER HELP=enter
ACTION=ENTER.

:KEYI KEY=HELP HELP=help
ACTION=HELP.

:KEYI KEY=PAGEDOWN HELP=pagedown
ACTION=PAGEDOWN.

:KEYI KEY=PAGEUP HELP=pageup
ACTION=PAGEUP.

:KEYI KEY=PRINT HELP=print
ACTION=PRINT.

:EKEYL.

.* ——————————————————————-
.*(7)
:PANEL NAME=USRPNL HELP=’usrpnl/’
KEYL=usrkeys
CSRVAR=csrvar
ENTER=’RETURN 500′
TOPSEP=SYSNAM.
Work with Users

.* — File list area ————-

:LIST DEPTH=’*’ LISTDEF=usrlist
ACTOR=UIM
MAXHEAD=4
PARMS=parms
SCROLL=YES
BOTSEP=SPACE.

:TOPINST.
Type options, press Enter.

.* List options ——————
.*(8)
:LISTACT OPTION=5
HELP=’usrpnl/dspdet’
ENTER=’CALL exitpgm’
USREXIT=’CALL exitpgm’.
5=Display Detail

.* Columns and headings ———-

:LISTCOL VAR=opt HELP=’usrpnl/opt’
USAGE=INOUT
MAXWIDTH=3.
Opt

:LISTCOL VAR=lname HELP=’usrpnl/lname’
USAGE=OUT
MAXWIDTH=20.
Last Name

:LISTCOL VAR=fname HELP=’usrpnl/fname’
USAGE=OUT
MAXWIDTH=20.
First Name

:LISTCOL VAR=addr1 HELP=’usrpnl/addr1′
USAGE=OUT
MAXWIDTH=25.
Address

:LISTCOL VAR=addr2 HELP=’usrpnl/addr2′
USAGE=OUT
MAXWIDTH=25.
Address

:LISTCOL VAR=city HELP=’usrpnl/city’
USAGE=OUT
MAXWIDTH=15.
City

:LISTCOL VAR=state HELP=’usrpnl/state’
USAGE=OUT
MAXWIDTH=15.
State

:LISTCOL VAR=zip HELP=’usrpnl/zip’
USAGE=OUT
MAXWIDTH=10.
ZipCode

:LISTCOL VAR=cntry HELP=’usrpnl/cntry’
USAGE=OUT
MAXWIDTH=15.
Country

:LISTCOL VAR=telnum HELP=’usrpnl/telnum’
USAGE=OUT
MAXWIDTH=15.
Telephone

:LISTCOL VAR=email HELP=’usrpnl/email’
USAGE=OUT
MAXWIDTH=35.
Email

.*(9)
:LISTVIEW COLS=’opt lname fname’.

:ELIST.

:EPANEL.

.* ——————————————————————-
.*(10)
:PANEL NAME=DETPNL
HELP=’usrpnl/dets’
KEYL=usrkeys
ENTER=’RETURN 500′
TOPSEP=SYSNAM.
User Details
.*
.* — Data area ————-

:DATA DEPTH=’*’
SCROLL=YES
LAYOUT=1
BOTSEP=SPACE.
.*
:DATACOL WIDTH=25.
:DATACOL WIDTH=’*’.
.*
:DATAI VAR=fname
HELP=’usrpnl/fname’
USAGE=OUT.
First Name

:DATAI VAR=lname
HELP=’usrpnl/lname’
USAGE=OUT.
Last Name

:DATAI VAR=addr1
HELP=’usrpnl/addr1′
USAGE=OUT.
Address

:DATAI VAR=addr2
HELP=’usrpnl/addr2′
USAGE=OUT.
Address

:DATAI VAR=city
HELP=’usrpnl/city’
USAGE=OUT.
City

:DATAI VAR=state
HELP=’usrpnl/state’
USAGE=OUT.
State

:DATAI VAR=zip
HELP=’usrpnl/zip’
USAGE=OUT.
Zip Code

:DATAI VAR=cntry
HELP=’usrpnl/cntry’
USAGE=OUT.
Country

:DATAI VAR=telnum
HELP=’usrpnl/telnum’
USAGE=OUT.
Telephone

:DATAI VAR=email
HELP=’usrpnl/email’
USAGE=OUT.
Email

:EDATA.

:EPANEL.
:EPNLGRP.

Here are the notes, the numbers are shown in the code above as .*(??) so if you are wondering about where each note corresponds to just look for the identifier. .* denotes a comment in UIM and is for the entire line that follows the .*.

1. First we will add the help information, copyright information and message file. We have not created any of these objects yet.
2. Next we have to create the variable classes we will use. This just defines a class based on the base class ie char10cls is based on char 10 etc. We will add different classes as we progress so it is important to understand what each class is used for.
3. We then create the variables based on the classes we have defined. These classes are not objects as you would see in object oriented programming. They have no methods etc.
4. Once we have the variables we define the variable records, this is how we pass structures between the C program and the panel group.
5. List definitions simply state what variables make up a list entry (record) in this instance they are the same as the variable records we pass in.
6. We have to define the function keys we will want to be used. Any key not defined will not be accepted by the panel group.
7. The first panel we create is for the list of records we will pass in. You can see the links to the list definition.
8. These are the options we will show to the user and are the only ones UIM will respond to. If you enter a different one it will not error but simply return without changing the screen at all.
9. You can see how we have defined every element to the list columns but we have only said display the fname(First Name) lname(Last Name) variable in the list view. We want the list entries to be complete so when you select option 5 to display the entry we dont have to go back to the file to get additional data.
10. This is the detail panel where all of the data is shown for individually selected records.

The following is the code to read the data from the file and display it through the panel group.

/******************************************************************************/
/* Program: DSPUSRNL */
/* Program Description : Display a List of Users */
/* */
/* */
/* Purpose : C Programming Project (Using a panel Group) */
/* Files : */
/* */
/* Author: C Hird */
/* */
/* */
/* Copyright Shield Advanced Solutions Limited 2010 */
/******************************************************************************/
#include /* UIM Includes */
#include /* Common header */
#pragma comment(copyright,_CPYRGHT)

#define PNLGRP “DSPUSRPNL *LIBL ”
#define ILEPGMLIB “DSPUSRPNL *LIBL ”
#define EXITPROG “EXITPROG ”

/* 1 */
extern void UIMExit(Qui_ALC_t *);
extern void AppWrkUsrLst(void);
void ProcessListOption(Qui_ALC_t *);
int load_entries(char *);

/* 2 */
int main(int argc,char *argv[]) {
int CallType;
char **tmp_ptr;
Qui_ALC_t *call_lopt;
/* 3 */
if(argc == 1) {
AppWrkUsrLst();
}
else {
tmp_ptr = argv;
call_lopt = (Qui_ALC_t *) tmp_ptr[1];
UIMExit(call_lopt);
}
return 0;
}

/* 4 */
void AppWrkUsrLst(void) {
int Function_Requested,
*Func_Req = &Function_Requested;
char applHandle[8];
char varBuffer[130];
char tmp[2];
EC_t Error_Code = {0};

Error_Code.EC.Bytes_Provided = sizeof(Error_Code);

QUIOPNDA(applHandle,
PNLGRP,
APPSCOPE_CALLER,
EXITPARM_STR,
HELPFULL_NO,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf(“Error Code received %.7s\n”,Error_Code.EC.Exception_Id);
exit(-1);
}

memset(varBuffer,’ ‘,EXITPROG_BUFLEN);
memcpy(varBuffer,ILEPGMLIB,20);

QUIPUTV(applHandle,
varBuffer,
EXITPROG_BUFLEN,
“EXITPROG “,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf(“Error Code received %.7s\n”,Error_Code.EC.Exception_Id);
exit(-1);
}

/* set up the display data here */
if(load_entries(applHandle) == 0) {
QUIDSPP(applHandle,
Func_Req,
“USRPNL “,
REDISPLAY_NO,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf(“Error Code received %.7s\n”,Error_Code.EC.Exception_Id);
exit(-1);
}
}

QUICLOA(applHandle,
CLOSEOPT_NORMAL,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf(“Error Code received %.7s\n”,Error_Code.EC.Exception_Id);
exit(-1);
}
exit(0);
}

/* 5 */
void UIMExit(Qui_ALC_t *uimExitStr) {
Qui_ALC_t *listOptAction;
int CallType;

CallType = uimExitStr->CallType;
switch(CallType) {
case 1: {
break;
}
case 2: {
break;
}
case 3: {
listOptAction = (Qui_ALC_t *) uimExitStr;
ProcessListOption(listOptAction);
break;
}
case 4: {
break;
}
case 5: {
break;
}
case 6: {
break;
}
case 7: {
break;
}
case 8: {
break;
}
}
return;
}

/* 6 */
void ProcessListOption(Qui_ALC_t *listOptAction) {
Qui_ALC_t listOpt; /* Structure for List Option */
char ListEntryHandle[4]; /* handle to list entry */
int functionRequested, /* Function requested by user */
*funcReq = &functionRequested; /* Pointer to function requested */
EC_t Error_Code = {0}; /* Error Code struct */

listOpt = *listOptAction;
Error_Code.EC.Bytes_Provided = sizeof(Error_Code);

/* display the content */
if(listOpt.ListOption == 5) {
QUIDSPP(listOpt.ApplHandle,
funcReq,
“DETPNL “,
REDISPLAY_NO,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf(“Error Code received %.7s\n”,Error_Code.EC.Exception_Id);
return;
}
return;
}
return;
}

/* 7 */
int load_entries(char *applHandle ) {
_RFILE *fp; /* file Ptr */
_RIOFB_T *fdbk; /* Feed back Ptr */
USRREC UsrRec; /* File struct */
EC_t Error_Code = {0}; /* Error_Code Struct */

Error_Code.EC.Bytes_Provided = sizeof(Error_Code);

if((fp =_Ropen(“USERS”,”rr”)) == NULL) {
printf(“filed to open the file\n”);
return -1;
}
fdbk = _Rreadf(fp,&UsrRec,_USR_REC,__DFT);
if(fdbk->num_bytes == EOF) {
printf(“No Records in the file\n”);
_Rclose(fp);
return -1;
}

do {
/* add the list entries */
QUIADDLE(applHandle,
&UsrRec,
_USR_REC,
“USRDET “,
“USRLIST “,
“NEXT”,
” “,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
_Rclose(fp);
return -1;
}
fdbk = _Rreadn(fp,&UsrRec,_USR_REC,__DFT);
}while(fdbk->num_bytes == _USR_REC);
_Rclose(fp);
return 0;
}

The program has a dual purpose it will load up the panel group when first called and also acts as the exit program for the panel group. You could create multiple exit programs for individual actions but we feel this way keeps things faily simple.

1. We define the functions to be used in the program
2. This is the entry point of the program.
3. We check the number of arguments passed to the program, if its 1 we know this is the first time the program is called so call the AppWrkUsrLst function.
4. This function sets up the panel group and loads the relevant data for the list.
5. If there is more than 1 parameter passed on the call to the program this function is called. We use this function to decide what request has been made from the panel group. We are only coding up for a list option request at this time so we simply break if any others are called and the program exits normally.
6. If the request is for a list option this function is called, we check for the right option(5) and if it is we display the detail panel which shows the detail. All of the data is available in the list entry so we load up the variable pool when the list entry is selected.
7. This is the function which loads the list entries to the panel. You can see it is the same program as we had before but instead of writing out to STDOUT (printf) we are adding list entries before returning.

Once you have this code compiled in the CPROJPGM library, add CPROJDTA and CPROJPGM to the library list in a 5250 session and call DSPUSRPNL program you should get a screen which looks like the following.

DspUsrPnl

Display Users

Thats all there is to it. Any problems or questions let us know.

Chris….

Jan 21

ZendServer is Faster!

I had left the testing for a while as I have a functioning install of the original PHP server, but I finally bit the bullet yesterday and installed it on our second i520 running V5R4.

Installation instructions were a bit sketchy only because there is a lot of information to go through. First problem was the installation failed asking for a PTF Si35761 for 5722DG1. The zip file I down loaded from the Zend site had a couple of PTF’s for the OS so I wrongly thought this was all that I needed. Anyhow we downloaded the latest Group PTF’s for V5R4 for Java Http and TCP which was about 720mb in total, installed and applied the PTF’s before IPL’ing.

Then the install went well and everything looked OK. We found the IBM ‘i’ 5250 interface and took the option to install the MySQL option (its not installed automatically). This is where we had a number of problems, first we had to remove the existing install manually! Why Zend can’t ship a remove option is strange? Anyhow we cleared up everything in the /usr/local directory which looked like it was MySQL connected. The we installed the option which is triggered by taking the MySQL Management Menu from ZENDSVR/ZSMENU. Everything installed OK but we remembered that MySQL MUST be installed using the QSECOFR profile! So we removed all of the objects again manually before kicking off the install again. This time it failed with an error message complaining about the /etc/my.cnf file already existing! The startup of the subsystem was still launched but the program submission failed? We took the option to remove everything again before re-installing. This time we had a different message first stating the install failed but then straight after another saying it was successful? We looked at the jobs and found the program was in message wait again with CPF1338 stating the job submission had failed. A quick look at the joblog showed that the QUSRSYS/MYSQL message queue was missing? We created it and then everything started working!

The next challenge was to set up VistualHosting. We looked at the documentation to see if it described how to set it up, it doesn’t. But a bit of digging around came up with the following solution.

1. Copy the /www/zserver/conf/httpd.conf file to your webserver config directory. We have the same setup as the test system we are using for the C & PHP projects we are running. So our main server config is in /www/webserver/conf.
2. Copy the /www/zserver/conf/fastcgi.conf to /www/webserver/conf directory, then update the file to set the location of the socket files ‘IpcDir /www/webserver/logs’
3. Update the httpd.conf file to use VirtualNamedHost.

# Configuration originally created by Create HTTP Server wizard on Tue Apr 11 01:53:18 CDT 2006
LoadModule proxy_module /QSYS.LIB/QHTTPSVR.LIB/QZSRCORE.SRVPGM
LoadModule proxy_http_module /QSYS.LIB/QHTTPSVR.LIB/QZSRCORE.SRVPGM
LoadModule proxy_connect_module /QSYS.LIB/QHTTPSVR.LIB/QZSRCORE.SRVPGM
LoadModule proxy_ftp_module /QSYS.LIB/QHTTPSVR.LIB/QZSRCORE.SRVPGM
#LoadModule zend_enabler_module /QSYS.LIB/APACHE2Z.LIB/MOD_ZFAST.SRVPGM

LoadModule zend_enabler_module /QSYS.LIB/QHTTPSVR.LIB/QZFAST.SRVPGM

Listen 192.168.200.11:80
DocumentRoot /www/webserver/htdocs
Options -ExecCGI -FollowSymLinks -SymLinksIfOwnerMatch -Includes -IncludesNoExec -Indexes -MultiViews
LogFormat “%h %l %u %t \”%r\” %>s %b \”%{Referer}i\” \”%{User-Agent}i\”" combined
LogFormat “%{Cookie}n \”%r\” %t” cookie
LogFormat “%{User-agent}i” agent
LogFormat “%{Referer}i -> %U” referer
LogFormat “%h %l %u %t \”%r\” %>s %b” common
LogMaint logs/access_log 7 0
LogMaint logs/error_log 7 0
CustomLog logs/access_log combined
NameVirtualHost 192.168.200.11:80
SetEnvIf “User-Agent” “Mozilla/2″ nokeepalive
SetEnvIf “User-Agent” “JDK/1\.0″ force-response-1.0
SetEnvIf “User-Agent” “Java/1\.0″ force-response-1.0
SetEnvIf “User-Agent” “RealPlayer 4\.0″ force-response-1.0
SetEnvIf “User-Agent” “MSIE 4\.0b2;” nokeepalive
SetEnvIf “User-Agent” “MSIE 4\.0b2;” force-response-1.0

ProxyPreserveHost On
DirectoryIndex index.php index.html
CgiConvMode %%MIXED/MIXED%%
TimeOut 30000
KeepAlive Off
HotBackup Off

#AddCharset UTF-8 .utf8
#AddCharset utf-8 .utf8
#AddCharset utf-7 .utf7
AddCharset UTF-8 .htm .html

# zend fastcgi
# Directive name “FastCgiConfig” is not recognized. (argh)
AddType application/x-httpd-php .php
AddHandler fastcgi-script .php

#ProxyPass / http://127.0.0.1:8000/
#ProxyPassReverse / http://127.0.0.1:8000/

# Deny most requests for any file
<Directory />
order allow,deny
allow from all
AllowOverride all
</Directory>

# Allow requests for files in document root
<Directory /www/webserver/htdocs>
Options FollowSymLinks
order allow,deny
allow from all
AllowOverride all
</Directory>
#JobQGenie test
<VirtualHost 1292.168.200.11:80>
ServerName www.jobqgenie.local
DocumentRoot /www/jobqgenie/htdocs
</VirtualHost>
#webrap test
<VirtualHost 192.168.200.11:80>
ServerName www.webrap.local
DocumentRoot /www/webrap/htdocs
</VirtualHost>

We then restarted the HTTP server WEBSERVER and the pages are all served correctly and very much faster than with the old server technology! I like the fact that this is no longer using the proxypass technology to invoke the interpreter!

So if you have an opportunity I would suggest installing the ZendServer on your IBM ‘i’. You can install side by side with the existing setup but according to the forums there are a few challenges.

We will add all of our existing project files and set up to this system for the future posts about C for IBM ‘i’ and PHP for IBM ‘i’.

Chris…

Jan 20

PHP for ‘i’ #3

Just thought I would post up the code to access the database using the i5Toolkit classes.


<DOCTYPE EN? Transitional 4.0 HTML DTD W3C ?- PUBLIC>
<HTML><HEAD>
<META content=”text/html; charset=iso-8859-1″ http-equiv=Content-Type></HEAD>
<BODY>
// use the toolkit classes
include(“i5toolkit/Toolkit_classes.php”);
// include the file which holds the user info
include(“../scripts/config.php”);
// connect to the i5
try {
$conn = new i5_Connection($server, $usr, $pwd);
$conn->set_options(‘PHPPROJ’);
$conn->connect();
}
catch (Exception $e) {
echo(“Failed to connect. “);
echo($e->getMessage());
die();
}
// connect to the file
try {
$file = new i5_NativeFileAccess($conn,’CPROJDTA’, ‘USERS’, I5_OPEN_READ);
}
catch (Exception $e){
echo(“Failed to open file. “);
echo($e->getMessage());
}
// get a list of the file fields
$list = $file->list_fields();
if(!$list) {
echo(“Could not get a list of the fields”);
}?>
<table>
<tr><td><?php echo($list[0]); ?></td><td><?php echo($list[1]);?></td></tr><?php
do {
$rec = $file->fetch_row(I5_READ_NEXT); ?>
<tr><td><?php echo($rec[0]); ?></td><td><?php echo($rec[1]);?></td></tr><?php
} while($rec);
?>
</table>
</body></html>

A couple of points, firstly the try catch methods allow us to capture the error messages returned and display them back out in your browser. I have also used a couple of echo(); functions just to show the difference. The code appears to be much shorter but that is because the bulk of the work is being done in the i5Toolkit classes. That is all you need, I hope it makes sense to those of you who are trying the examples? Next in this series will look at doing the same kind of file read but against the MySQL database shipped with ZendCore.

If you are following the C for IBM ‘i’ series we will be adding a UIM interface over the database records in the next post.

Have fun…

Chris…

Jan 18

PHP for ‘i’ #2

In this post I will describe how to connect to the DB2 database files we have created in the C for ‘i’ project and output them into your browser.

When you install the ZendCore product it creates its own base website in ‘/www/zendcore’ and within this directory is a set of PHP classes (i5Toolkit_classes.php) that we will use to access i5 Objects in later exercises. I am not one for spreading objects all over the system so I will create a link to those classes within the PHPPROJ website, plus if I update ZendCore and they ship new classes or updates I don’t have to go through and update all instances of the classes.

If you have followed our instructions the following should work, however if you have set up your own structure you will have to change the instructions as required.

First of all we need to be in a QP2TERM session so simply call qp2term from any command line. This will place you in a qsh session where we can create a symbolic link to the directory. You can add a link from the WRKLNK interface as well but I prefer to do it this way.
Change directory cd /www/phpproj/htdocs then issue the following command ln -s /www/zendcore/htdocs/i5Toolkit_library i5toolkit This will create a symbolic link from your web directory to the i5Toolkit_library in the zendcore web directory. Addressing the PHP classes will be the same as if the toolkit was installed in your web directory and you don’t eat up your DASD resources.

I am also going to add a couple of new directories, I want to protect some information so I will store it outside of the web root directory (/www/phpproj/htdocs). Create the new directories ‘/www/phpproj/scripts’ and ‘/www/phpproj/logs’.
We told the http server (WEBERVER) that we wanted any logs for our visrtualhost to be placed in the ‘/www/phpproj/logs’ directory, as it didn’t exist the server could not write the access and error log. Restart the WEBSERVER instance of the HTTP server and the logs will be generated when required in the /www/phpproj/logs directory. They could be important for identifying any problems later.

Now we need to create a file ‘/www/phpproj/scripts/config.php’, how you create it is up to you. Just make sure you create it with the correct CCSID (819 in our case). Then add the following content to the file.

$usr = 'yourusername';
$pwd = 'youruserpassword';
$server = 'localhost';
?>

This file will be included in our php files and contains the variables we want to protect. Its not toally secure but its an acceptable method of hiding content.

Now we will update the index.php file we created in the first post to reflect the following.


< DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META content="text/html; charset=iso-8859-1" http-equiv=Content-Type></HEAD>
<BODY>
//include("i5toolkit/Toolkit_classes.php");
// include the file which holds the user info
include("../scripts/config.php");
// connect to the i5
$conn = i5_connect($server,$usr,$pwd,array (I5_OPTIONS_JOBNAME => "PHPPROJ" ));
// check that we connected OK or die()
if (is_bool ( $conn ) && $conn == FALSE) {
die ( i5_errormsg () );
}
// open the file if not opened return the error
$file = i5_open('CPROJDTA/USERS',I5_OPEN_READ,$conn);
if ($file === false) {
$tab = i5_error();
echo(var_dump($tab));
}
// list out the fields as the headers could just use text
// if cannot just dump and exit
$list = i5_list_fields($file);
if ($list ==! false) { ?>
<TABLE><TBODY>
<TR><TD><?php echo($list[0]); ?></TD><TD><?php echo($list[1]);?></TD></TR><?php
}
else {
$tab = i5_error();
echo(var_dump($tab));
exit();
}
// loop through the entries in the file and display
do {
$rec = i5_fetch_array($file,I5_READ_NEXT); ?>
<TR><TD><?php echo($rec[0]); ?></TD><TD><?php echo($rec[1]);?></TD></TR><?php
} while($rec);
// close the file and free resource
i5_free_file($file);
// close the connection
i5_close($conn);
?>
</TBODY></TABLE>
</BODY></HTML>

You will notice how we mix html and php code in the same file, this is because the file is read by the php interpreter which only adds content between the tags, the rest it leaves as is before sending the file to the browser.

I have not used the i5Toolkit_classes in this exercise, but instead used the i5 functions which are shipped as part of the zendcore. I will change the code later on the show how I can use those classes, but for the time being I have simply commented out the reference to the classes.

Basic program thread is,

  1. Connect to the i5. $server,$usr,$pwd is defined in the ‘../scripts/config.php’ file.
  2. Open the file we created in the CPROJ project. (CPROJDTA/USERS).
  3. List the file fields, we simply take the field name from the returned array and display in a table row
  4. Read the (NEXT) record until no more are returned. The open postions the cursor just before the first record
  5. Output the record array contents ( [0] = LASTNAME [1] = FIRSTNAME)
  6. Close the file and the connection before exiting.

You should see the following in your browser when you enter the URL ‘www.phpproj.local/’. Sorry but the post will not format the ourput correctly! it is all lined up in your browser…

LASTNAME FIRSTNAME
Blogs Fred
Hird Christopher
Smiths John

Thats it for this post hope you enjoyed the trip…..

Chris…