Solved

Java issue with AS400 Library

Posted on 2013-11-21
6
795 Views
Last Modified: 2013-11-22
Can someone please help me troubleshoot an issue I am having with a program I wrote which executes a stored procedure (.PGM) on an AS400 machine?

The stored procedure is very simple, and I don't believe there is any possibility that the problem I am having could be caused by the code within the AS400 (I could be wrong but my AS400 architect assures me the problem is in the way the Java code is written).

The AS400 stored procedure takes 5 parameters:  3 inputs and 2 output.  Here is the description:

Input 1: string - up to 50 chars.
Input 2: string - up to 50 chars.
Input 3: string - up to 50 chars.
Output 1: hard coded to return 0019
Output 2: string up to 200 characters.

The program just takes the 3 inputs and concatenates them together into one string and returns the result as output 2.

So....

If run with these parameter:

1. "aaaaa"
2. "bbbbb"
3. "ccccc"

I would expect the following outputs:

1. "0019"
2. "aaaaabbbbbccccc"

This is in fact the exact output I get as long as I keep the length specified on line 27 at 15:

parameterList[4] = new ProgramParameter(15);

Open in new window


If I change that length to something like 30 like is shown below (on line 27), then I get this for output 2:

1. "0019"
2. "aaaaabbbbbccccc0019"

I cannot understand why the contents of Output 1 gets lumped together with output 2 when I change the length of the output 2 parameter.

There are also other strange behaviors I have noticed when I change the length of the input parameters instead of keeping them all at 5, but I won't go into that now.  For now I would be happy with understanding what is wrong here.

If you Google "as400 programCall Class" you will easily find the documentation to the AS400 class and the sample code I used to help me write this program.




	public static String[] as400SimpleTest() throws ClassNotFoundException {
		String[] resultSet = new String[2];
		CharConverter charConverter = new CharConverter();

		AS400 as400 = new AS400("<<ip address>>", "<<username>>", "<<password>>");

		try {
			as400.connectService(AS400.DATAQUEUE);
		} catch (Exception exp) {
			System.err.println("error=" + exp);
		}

		ProgramCall pgm = new ProgramCall(as400);
		boolean pgmrun = false;

		ProgramParameter[] parameterList = new ProgramParameter[5];

		parameterList[0] = new ProgramParameter((new AS400Text(5))
				.toBytes("aaaaa"));
		parameterList[1] = new ProgramParameter((new AS400Text(5))
				.toBytes("bbbbb"));
		parameterList[2] = new ProgramParameter((new AS400Text(5))
				.toBytes("ccccc"));

		// output parameters
		parameterList[3] = new ProgramParameter(4);
		parameterList[4] = new ProgramParameter(30);

		try {
			pgm.setProgram("/QSYS.LIB/I5TOGEM.LIB/TESTPARM04.PGM",
					parameterList);
		} catch (PropertyVetoException e) {
			e.printStackTrace();
		}
		try {
			pgmrun = pgm.run();
		} catch (AS400SecurityException e) {
			e.printStackTrace();
		} catch (ErrorCompletingRequestException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ObjectDoesNotExistException e) {
			e.printStackTrace();
		}

		if (pgmrun != true) {
			AS400Message[] msgList = pgm.getMessageList();
			for (int i = 0; i < msgList.length; i++)
				// change this to resumeutil.addlog
				System.out.println(msgList[i].getText());
		} else {

			byte[] outputCodedata = parameterList[3].getOutputData();
			System.out.println("Arraysize of output 1: "
					+ outputCodedata.length);
			System.out.println("Contents of output 1: "
					+ Arrays.toString(outputCodedata));
			byte[] outputMessagedata = parameterList[4].getOutputData();
			System.out.println("Arraysize of output 2: "
					+ outputMessagedata.length);
			System.out.println("Contents of output 2: "
					+ Arrays.toString(outputMessagedata));

			String outputCode = charConverter.byteArrayToString(outputCodedata)
					.trim();
			System.out.println("Length of output 1: " + outputCode.length());

			String outputMessage = charConverter.byteArrayToString(
					outputMessagedata).trim();
			System.out.println("Length of output 2: " + outputMessage.length());

			resultSet[0] = outputCode;
			resultSet[1] = outputMessage;
		}
		as400.disconnectService(AS400.COMMAND);
		return resultSet;
	}

Open in new window


Here is the output of the code above:

[statistics] connecting to socket on port 3428
[statistics] connected
Arraysize of output 1: 4
Contents of output 1: [-16, -16, -15, -7]
Arraysize of output 2: 30
Contents of output 2: [-127, -127, -127, -127, -127, -126, -126, -126, -126, -126, -125, -125, -125, -125, -125, -16, -16, -15, -7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Length of output 1: 4
Length of output 2: 19
errorCode: 0019
errorReason: aaaaabbbbbccccc0019
[statistics] disconnected

Open in new window

0
Comment
Question by:jbaird123
  • 3
  • 3
6 Comments
 
LVL 34

Expert Comment

by:Gary Patterson
ID: 39667680
What language is the called program written in, and how are the parameters declared in the called program?

Numeric parameter passing can be tricky, and there are cases where the OS maps passed numeric values to a DEC 15.5  (zoned numeric data) - so 15 bytes is sometimes a "magic number".  

Don't know for sure that is what is happening here, but provide the language and parm list and I can probably tell you more.

Anyway, when passing parameters, it is usually important that the lengths and types in the calling program exactly match the lengths and types declared in the called program.  

- Gary Patterson
0
 

Author Comment

by:jbaird123
ID: 39667768
Hi Gary,

Thanks for checking in again!

I have never worked with an AS400 until yesterday, so bear with me.  I have attached a screenshot which my developer sent me showing the description of the procedure being executed.  I believe it is called "pcml".  Does that give you enough information about the AS400?  If not, let me know and I'll check with the developer in the morning.

I have one concern about your comment:

Anyway, when passing parameters, it is usually important that the lengths and types in the calling program exactly match the lengths and types declared in the called program.  

The concern I have is that I am trying to build a generic procedure which can execute any stored procedure on an AS400 provided it meets certain criteria:

1. The procedure may have any number of input parameters as long as they are all strings.  There is no specific limit to the size of the string (we could  define an arbitrary size if needed to make this work).  It is not necessary to have any input paramters.

2. Every procedure must have 2 output parameters at the end - one for "error code" and the second for "error description".  Even if there are no input parameters, 2 outputs are required.

I have noticed that if I set the size of all input and output parameters (in Java, not on the AS400) to something large like 2000, then there are no issues.  Every parameter takes up 2000 bytes, which causes some overhead, but at least it works.  I suppose I could live with that, but since it seems ugly and since I don't really understand what is going on, I am hesitant to implement the solution that way.  

Thoughts?

Thanks!
as400.png
0
 

Author Comment

by:jbaird123
ID: 39669217
Gary,

I was able to obtain the AS400 source code:
h Option(*Nodebugio:*Srcstmt)           
 *                                       
d Input_parm1        s             50a     
d Input_parm2        s             50a     
d Input_parm3        s             50a     
d Output_parm1    s               4s 0   
d Output_parm2    s           200a                          
 *                                                            
 /Free                                                       
                                                              
  Output_parm1    = 19;                                      
  Output_parm2    = Input_parm1 + Input_parm2 + Input_parm3; 
                                                              
  *inlr = *on;                                               
/End-free                                                        
                                                                   
 * -------------------------------------------------------------- 
 *    *inzsr Subroutine                                           
 * -------------------------------------------------------------- 
                                                                   
c     *inzsr        Begsr                                         
c     *entry        Plist                               
c                   Parm                    Input_parm1 
c                   Parm                    Input_parm2 
c                   Parm                    Input_parm3 
c                   Parm                    Output_parm1
c                   Parm                    Output_parm2
                                                         
c                   Endsr                

Open in new window

Thanks,
0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
LVL 34

Accepted Solution

by:
Gary Patterson earned 500 total points
ID: 39669392
The PCML is adequate.  PCML is "Program Call Markup Language", and is just an XML document that describes the interface to a program (*PGM) object.  

This particular PCML document tells us that this is an RPG program, and describes the data types and lengths of each parameter in detail - it is intended for just the purpose you describe - creating generic interfaces that allow calling programs (often remote calls) to properly format calling parameters.

An aside:  The terms "stored procedure" and "program" are not interchangable in AS400 context.  A program is an object of type *PGM that is created by compiling native language source code (C, CL, COBOL, RPG, etc) using a native compiler.  A stored procedure is an interface in DB2 that allows a *PGM object to be executed through a DB2 SQL CALL rather in addition to an operating system CALL.

SQL CALL -> Stored procedure -> Underlying *PGM object
Operating system CALL -> *PGM object.

In order to perform a stored procedure call, you need a *PGM object (or a Java program) PLUS an SQL stored procedure definition (SQL Stored Procedure Language stored procedures seem like they violate this rule, but when you create an SQL SPL procedure the system actually generates C source code and compiles it into a *PGM object for you behind the scenes, so the rule remains intact).

The interface you are using is for calling *PGM objects directly, bypassing DB2.  No stored procedure definition is needed in this case.

Back to the question:

In general, the length and type (and encoding - we'll talk about that later) of your parameters in the calling and called program need to be of the same length and type.  

I understand that you don't particularly like that that requirement, since it complicates things, and have found a mechanism that works in a particular case by over-allocating the parameter lengths in your calling program, but I can assure you that this is not a "winning" general strategy.  

I've dealt with parameter mismatch problems many, many times over the past 20 years or so of programming in the S/36, S/38, AS/400, iSeries, and IBM i environments.

There are cases where mis-matched parameters may be made to work - or at least may work in specific circumstances - but it is not a reliable general mechanism, and if you make it a practice, you will eventually end up with data corruption and very difficult to isolate errors.  

Here's an illustration of the problem.  Most of the time, in the CALLED program, the parameters are allocated in contiguous memory.  So using this program an an example, the parameters get mapped like this:

Parm 1 in bytes 1-50
Parm 2 in bytes 51-100
Parm 3 in bytes 101-150
Parm 4 in bytes 151-200
Parm 5 in bytes 201-204
Parm 6 in bytes 205-404

So if you allocate everything with the correct data length and type, life is good.  Each parm moves into it's slot nicely when the program is called, and when the called program ends, the return parameters each move back nicely.

If you underallocate lengths in your calling program, you run the risk of the tail end of each underallocated parameter containing garbage, since whatever happened to be in that storage at the time the program is called is left alone when the shorter parameter from the calling program is moved in.  The program may function just fine the first time it is called, but not on subsequent calls - or manifest even worse behaviors.

If you overallocate lengths in the calling program, sometimes things can go ok, as you've seen.  As long as the system moves the parameters in order (and there is no guarantee of that, though that is what generally happens), there is no problem with input parameters, but since portions of your parameters (at least from the calling programs perspective) are now overlaying each other in the called program, you'll often get squirrely results on I/O and O parameters - like you've seen.

So if you move 2000 bytes into bytes 1-50, then move 2000 bytes into 51-100, etc, everything goes OK on input - as long as you don't pass more than 50 bytes of meaningful data in each of these parameters - and if you know to do that, you should know to just declare the parm the correct length.  If you pass 51 bytes of meaningful data, then when the second parm is moved in, it will overlay and destroy the last byte of the previous parameter.  Even worse, when those parameters get modified in the called program, and the program ends, and wrong-sized chunks of data get moved back to your calling program, you will get odd results like the ones you experienced.

Parameter passing rules are complicated.  There are different parameter passing mechanisms (pass by value and pass by reference), and different interfaces have different rules and idiosyncrasies.

There is no way I can cover all of the vagaries and "why's" in a forum like this, but I can say that if you obey one very basic rule, you'll avoid all of the most common problems:  

Match parameter encoding, data type, and length in the calling and called programs.

Note that in this particular case, the Java classes you are using are transparently handling the "encoding" issue for you.  Your Java program uses Unicode, while the AS/400 RPG program is (most likely) using EBCDIC.  

Since encoding is a common issue, the JT400 classes handle it for you transparently.

Also, I don't think you are declaring your numeric parameter correctly - looks to me like you are passing it as a 4 byte character field instead of a 4 digit zoned numeric.  You got lucky because these two data types happen to map OK for positive integers - but it is still wrong, and sets a bad example in code.  Other numeric formats aren't so forgiving.

If you have PCML for every *PGM (PCML generation is a compile option), and can locate it, one strategy for building a generic interface would be to retrieve the PCML to get parameter count, length, type and usage - that is what PCML is intended for.

- Gary Patterson
0
 

Author Closing Comment

by:jbaird123
ID: 39669611
Thank, you Gary.  This is perfect, and it explains exactly what was happening.  I verified this by changing the length of my first parameter to 49, and then I passed only a single character "a" for that first parameter and then a "b" (50) and "c" (also 50).  The result was that the AS400 picked up the first character "a" and then padded 48 spaces after it, and then grabbed the "b" from the second parameter.

It makes perfect sense.

So, based on your recommendation, I will modify my framework so that parameter length is required (which will allow me to properly size my parameters), but I think I will simplify it somewhat and require all parameters to be passed as strings.  At least this will allow me to avoid any issues with differing datatytes.  

Thanks again!  I've been struggling with this issue all week.  :)


Cheers!
0
 
LVL 34

Expert Comment

by:Gary Patterson
ID: 39669773
You can definitely simplify your life if you can arrange to pass character parameters only.  

If course, that is not always and option, so it is a good idea to understand what to do in other cases.

Happy to be able to help.

- Gary Patterson
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
In this post we will learn how to connect and configure Android Device (Smartphone etc.) with Android Studio. After that we will run a simple Hello World Program.
Viewers will learn about if statements in Java and their use The if statement: The condition required to create an if statement: Variations of if statements: An example using if statements:
This theoretical tutorial explains exceptions, reasons for exceptions, different categories of exception and exception hierarchy.

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now