RPG API Web Service Tutorial By Don Denoncourt CAS Severn, Inc. ddenoncourt@cassevern.com I. II. III. IV. Create the SQL Stored Procedure over the RPG API. Create the Web service host and client Web projects in WDSc. Create the Account JavaBean to hold values returned from the RPG API. Download the IBM Toolbox for Java [jt400.jar] from the iSeries to your WDSc projects V. Download the iBatis jar files to your WDSc projects VI. Create the iBatis configuration XML specify the JDBC connection data and xml specific to the WebService VII. Create the iBatis XML to associate the SQL Stored Procedure with the Java Accounts “data structure” JavaBean VIII. Create a utility Java class to process the iBatis configuration XML IX. Create the GetAccount JavaBean that wraps a call to the RPG API and builds an array of Account objects. X. Create a JUnit test and test the utility. XI. Create the Search Accounts Web Service. XII. Consume the Search Accounts Web Service XIII. Testing the Client Web Applications Web Service Proxy classes with a JSP I. Create the SQL Stored Procedure over the RPG API. 1. Upload or key the GETACCTS RPG that is in the appendix of this article and compile it to [yourlib] 2. From a 5350 command line, key STRSQL and press Enter 3. In the SQL editor, key CREATE PROCEDURE and press F4 4. Key the following and press Enter Procedure: GETACCTS Library: [yourlib] RESULT SETS: 1 Language: RPGLE PARAMETER STYLE: 1 5. In the second panel, key the following and press Enter: Program: getaccts Library: [yourlib] 6. In the subfile panel, key the following three entries and press Enter: Usage Parameter Type Length Scale IN kount DEC 5 0 OUT error CHAR 1 OUT errorMsg CHAR 50 7. The SQL Stored Procedure should then be created. Look over the SQL syntax for a moment. In a production environment I would suggest copying and pasting that statement to comment lines in the RPG (which is in the appendix at the end of the tutorial.) II. Create the Web service host and client Web projects in WDSc. 1. To start WDSc, in the lower left-hand corner of Windows, click Start|All Programs|IBM Rational|IBM WebSphere Development Studio Client for iSeries V6.0|IBM WebSphere Development Studio Client for iSeries. If you are prompted for workspace, accept the default value and click OK. 2. From WDSc’s main application menu, select File|New|Project… 3. In the New Project/Select a wizard panel, select Dynamic Web Project 4. In the New Dynamic Web Project panel. Click the Show Advanced button Key a name of WebServiceHost (note that the Context Root will automatically be changed to be the same as the project name.) Change the Servlet version to 2.3 Change the Target server to WebSphere Application Server v5.1 Express (the WAS 5.1 Test Environment in on two “Optional Software” CDs in your WDSc 6.0 installation CD set.) Click Finish If you are prompted with “Confirm Perspective Switch” Click Yes 5. Run the wizard again and create a project called WebServiceClient Be sure to specify Servlet version 2.3 and Target server of WebSphere v5.1 Express Note that this lab is using WebSphere v5.1 Express only to circumvent problems with WebSphere 6.0 when multiple WDSc projects from multiple labs add and remove and otherwise confuse WebSphere 6.0 to the point that it is unusable without administration. III. Create the Account JavaBean to hold values returned from the RPG API. 1. In Project Explorer (see Figure 1 below,)select the WebServiceHost project, open the Java Resources/JavaSource folder, right-click on that folder, and select New|Class. 2. In the New Java Class panel, key the case-sensitive Package of com.denoncourt 3. Key the case-sensitive Name of Account 4. Click Finish. 5. WDSc will open the newly created Account.java file in the Java editor. 6. Between the class’s curly braces, add private String number = null; private String name = null; private String balance = null; public Account() {} 7. File|Save to force a compile. 8. To generate the required getter and setter functions in the JavaBean you will use a WDSc wizard. In Project Explorer, expand WebServiceHost/Java Resources/JavaSource/com.denoncourt/Account.java/Account; select the balance, name, and number nodes (see Figure 2); then right-click and select Source|Generate Getters and Setters. 9. In the Generate Getters and Setters panel, accept the defaults to generate getter and setters for all three fields, and click OK. 10. File|Save and the Account.java class is now ready to hold information returned from the RPG API. Figure 1: WDSc’s Project Explorer view Figure 2: The Account class’s balance, name, and number private attributes in the Project Explorer view. IV. Download the IBM Toolbox for Java [jt400.jar] from the iSeries to your WDSc projects 1. Open a DOS window, click Start, click Run…, and key CMD 2. Write down of the directory name as you will later need to traverse to that directory from Windows Explorer. 3. FTP to the iSeries, change the IFS directory, and download the toolbox in binary mode with the following steps: C:\Documents and Settings\username>ftp [host name or ip of your iSeries] ftp> cd /qibm/proddata/http/public/jt400/lib ftp> bin ftp> get jt400.jar ftp> quit C:\Documents and Settings\username>exit 4. Now you need to copy the jt400 jar file to the WebService projects in WDSc Open Windows Explorer, by right mousing on the Windows Start button and selecting Explore Traverse to the directory noted in step 2) Find jt400.jar, select it, right mouse and select Copy 5. In WDSc’s Project Explorer, traverse to Dynamic Web Projects, WebServiceHost, WebContent, WEB-INF, lib 6. Right mouse on lib and select paste V. Download the iBatis jar files to your WDSc projects Downloads are available from http://ibatis.apache.org/javadownloads.html 1. Open a DOS window, click Start, click Run…, and key CMD 2. Write down of the directory name as you will need to traverse to that directory from Windows Explorer. 3. In Windows Explorer (right mouse on the Windows Start button and select Explore,) traverse to the download directory and extract the iBatis download. 4. Under the extracted directory called iBatis/lib you will see three downloaded iBatis jar files. Copy these to the WebService projects in WDSc Select the three iBatis jar files in Windows Explorer, right mouse, and select Copy 5. In WDSc’s Project Explorer, traverse to Dynamic Web Projects, WebServiceHost, WebContent, WEB-INF, lib Right mouse on lib and select paste VI. Create the iBatis configuration XML specify the JDBC connection data and xml specific to the WebService 1. In WDSc’s Project Explorer, traverse to Dynamic Web Projects, WebServiceHost, Java Resources, JavaSource, com.denoncourt 2. Right mouse on com.denoncourt and select New|Other… 3. Click Show All Wizards 4. Expand the XML folder, select XML, and click Next 5. Click the Create XML file from scratch and click Next 6. Key a case-sensitive File name of sqlMapConfig.xml and click Finish 7. When the xml opens in WDSc’s editor replace its contents with: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-config-2.dtd"> <sqlMapConfig> <transactionManager type="JDBC"> <dataSource type="SIMPLE"> <property name="JDBC.Driver" value="com.ibm.as400.access.AS400JDBCDriver"/> <property name="JDBC.ConnectionURL" value="jdbc:as400://YourISeriesIP/yourlib"/> <property name="JDBC.Username" value="yourusername"/> <property name="JDBC.Password" value="yourpassword"/> </dataSource> </transactionManager> <sqlMap resource="com/denoncourt/Account.xml"/> </sqlMapConfig> Replace YourISeriesIP, yourusername, and yourpassword 8. 9. File|Save or Control-S to save the file. VII. Create the iBatis XML to associate the SQL Stored Procedure with the Java Accounts “data structure” JavaBean 1. In WDSc’s Project Explorer, traverse to Dynamic Web Projects, WebServiceHost, Java Resources, JavaSource, com.denoncourt 2. Right mouse on com.denoncourt and select New|Other… 3. Click Show All Wizards 4. Expand the XML folder, select XML If you are prompted with a “Confirm Enablement” dialog, click OK Click Next 5. Click the Create XML file from scratch and click Next 6. Key a case-sensitive File name of Account.xml and click Finish 7. When the xml opens in WDSc’s editor replace its contents with the following: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd"> <sqlMap namespace="Account"> <parameterMap id="entryPList" class="java.util.Map" > <parameter property="count" jdbcType="NUMERIC" javaType="java.math.BigDecimal" mode="IN"/> <parameter property="errorCode" jdbcType="CHAR" javaType="java.lang.String" mode="OUT"/> <parameter property="errorDesc" jdbcType="CHAR" javaType="java.lang.String" mode="OUT"/> </parameterMap> <resultMap id="resultMap" class="com.denoncourt.Account"> <result property="number" column="AccountNo"/> <result property="name" column="AccountName"/> <result property="balance" column="Balance"/> </resultMap> <procedure id="getAccounts" parameterMap="entryPList" resultMap="resultMap"> call yourlib.getaccts(?,?,?) </procedure> </sqlMap> 8. Change the call statement to specify the library procedure name you specified when you created the stored procedure. 9. Save the file VIII. Create a utility Java class to process the iBatis configuration XML 1. In Project Explorer, select the WebServiceHost project, expand the Java Resources/JavaSource folder, select JavaSource, right-click and select New|Class. 2. In the New Java Class panel, key a case-sensitive Package of com.denoncourt, key a case-sensitive name of SqlConfig and click Finish. 3. WDSc will open the newly created SqlConfig.java file in the Java editor. 4. Replace the contents of the file with the following: package com.denoncourt; import java.io.Reader; import com.ibatis.common.resources.Resources; import com.ibatis.sqlmap.client.SqlMapClient; import com.ibatis.sqlmap.client.SqlMapClientBuilder; public class SqlConfig { private static final SqlMapClient sqlMap; static { try { String resource = "com/denoncourt/sqlMapConfig.xml"; Reader reader = Resources.getResourceAsReader(resource); sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException( "Error initializing SqlConfig class. Cause: " + e); } } public static SqlMapClient getSqlMapInstance() { return sqlMap; } } 5. Save the file IX. Create the GetAccount JavaBean that wraps a call to the RPG API and builds an array of Account objects. 1. In Project Explorer, select the WebServiceHost project, open the Java Resources/JavaSource folder, then right-click and select New|Class. 2. In the New Java Class panel, key a case-sensitive Package of com.denoncourt 3. Key a case-sensitive Name of GetAccounts 4. Click Finish. 5. WDSc will open the newly created GetAccounts.java file in the Java editor. 6. Replace the file contents with: package com.denoncourt; import java.math.BigDecimal; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import com.ibatis.sqlmap.client.SqlMapClient; public class GetAccounts { private static SqlMapClient sqlMap = SqlConfig.getSqlMapInstance(); public Account[] search(final int count) throws IllegalArgumentException, SQLException { HashMap entryPList = new HashMap(); entryPList.put("count", new BigDecimal(count)); entryPList.put("errorCode", null); entryPList.put("errorDesc", null); List list = sqlMap.queryForList( "getAccounts", entryPList); String errorCode = (String)entryPList.get("errorCode"); if (errorCode.charAt(0) == '1') { String message = (String)entryPList.get("errorDesc"); throw new IllegalArgumentException(message.trim()); } return (Account[])list.toArray(new Account[0]); } } 7. Save the file. X. Create a JUnit test and test the utility. This step is used to be sure the Java code works before generating a WebService and having it fail within a more complex environment. Select the GetAccounts.java file in the Project Explorer view Right mouse and select New|Other In the Select a wizard panel, select Java|Junit|JUnit Test Case and click Next When you get the popup ‘The JUnit library “junit.jar” is not in the build path. Do you want to add it?’ Click Yes. 5. In the New JUnit Test Case panel, accept the defaults and click Finish 6. Replace the generated GetAccountsTest source with: 1. 2. 3. 4. package com.denoncourt; import java.sql.SQLException; import com.denoncourt.Account; import junit.framework.TestCase; public class GetAccountsTest extends TestCase { public void testSearch() { GetAccounts rpg = new GetAccounts(); Account[] accounts = null; try { accounts = rpg.search(5); } catch (SQLException e) { fail(e.toString()); } assertEquals(5, accounts.length); for (int i = 0; i < accounts.length; i++) { assertEquals(Integer.toString(i+1), accounts[i].getNumber()); assertEquals("Denoncourt", accounts[i].getName().trim()); assertEquals(Integer.toString(i+1)+".00", accounts[i].getBalance()); } try { accounts = rpg.search(105); } catch (IllegalArgumentException iae) { assertEquals("Invalid Count", iae.getMessage()); } catch (SQLException e) { fail(e.toString()); } } } 7. File|Save 8. To run the test, in the Project Explorer view, select GetAccountsTest, right-mouse and select Run|JUnit Test. 9. Click on the JUnit tab in the lower portion of WDSc, you should see a long green bar to identify success. XI. Create the Search Accounts Web Service. 1. In Project Explorer, in the WebServiceHost project,.select GetAcounts.java 2. Right-click and select Web Services|Create Web Service. 3. In Web services dialog, leave the Web service type as JavaBean Web service, select Generate a Proxy, and uncheck Start Web service in Web Project. 4. Click Finish. The wizard will create a set of Web service classes and deploy the services to the WebServiceHost Web application. The wizard will also generate a WebSphere profile as shown in Figure 3. 5. In the Servers view, select the generated “WebSphere Express v5.1 Test Environment,” right-mouse and select Publish. 6. When the “Publishing successful” dialog is displayed, click OK. Figure 3: The Servers view lists WebSphere profiles available for deployment and test of Web services. XII. Consume the Search Accounts Web Service The web service will be used by a different project – to simulate a web application on a different machine in a different network. 1. In Project Explorer, traverse to the WebServiceHost project’s WebContent/wsdl/com/denoncourt folder, select GetAccounts.wsdl directory, right-click and select Web Services|Generate Client 2. In the Web Service panel, leave Client proxy type at the default of Java proxy, check the ”Overwrite files without warning,” and ”Create Folders when necessary” checkboxes and click Next 3. In the Web Service Selection Page panel, accept the default WSDL and click Next 4. In the Client Environment Configuration panel, change the Client project dropdown to specify WebServiceClient, and click Finish The client java proxy code will be generated in WebServiceClient/Java Resources/JavaSource/. Those classes were completely generated with information in the WSDL. The classes do not have the ability to call the stored procedure on the iSeries. All the classes know how to do is to communicate, via the low-level SOAP protocol, to a Web Service called GetAccounts and build Account JavaBean objects from the response XIII. Testing the Client Web Applications Web Service Proxy classes with a JSP There are only three classes, of the ten proxy classes generated, in the WebServiceClient project that you need to be aware of. The first is the Account class, which is simply a JavaBean that holds the account information returned by the Web service. The second is the GetAccountsServiceLocator, which is used to find and get a handle to the get accounts web service out in the Web world. The third is the GetAccounts class which proxies the GetAccounts Web service provided by the WebServiceClient web application. These three classes can easily be used in a client application to request account information. For example: Account[] accts = new GetAccountsServiceLocator().getGetAccounts().search(5); To see how you might use the Web Service proxies in a JSP: 1. In Project Explorer, open the WebServiceClient project, select the WebContent folder, then right-click and select New|JSP File 2. In the New JSP file panel, key a File Name of TestGetAccounts.jsp and click Finish. 3. In the JSP editor, click the Source tab and replace the contents of TestGetAccounts.jsp with: <%@ page import="com.denoncourt.*" %> <HTML> <BODY> <% int kount = 0; try { if (request.getParameter("count") != null) { kount = java.lang.Integer.parseInt(request.getParameter("count")); Account[] accts = new GetAccountsServiceLocator(). getGetAccounts().search(kount); %> <table > <tr><th>Num</th><th>Name</th><th>Balance</th></tr> <% for (int i = 0; i < accts.length; i ++) { %> <tr> <td> <%= accts[i].getNumber() %> </td> <td> <%= accts[i].getName() %> </td> <td> <%= accts[i].getBalance() %> </td> </tr> <% } /* close for loop */ %> </table> <% } /* if count in HTTP request */ } catch (Exception e) { out.println(e.toString()); } %> <form action="TestGetAccounts.jsp" method="POST"> Enter a count:&nbsp; <input type="text" name="count" value="<%=kount%>"/> <input type="submit" value="submit"/> </form> </BODY> </HTML> 4. Save the JSP then, in the Project Explorer view, select TestGetAccounts.jsp, right-mouse, and select Run|Run on Server… 5. In the Server selection panel, click the “Choose and existing server” radio button and select “WebSphere Express V5.1 Test Environment” and click Finish. The JSP will open in WDSc’s integrated Web browser. 6. Test the JSP page a couple of times with numbers 1–100. 7. Try it again with a number larger than 100. The JSP responds with the message “java.lang.IllegalArgumentException: Invalid Count.” 8. Just to show you that your Web Service is available to any client tool, change the URL in WDSc’s web browser to http://localhost:7080/WebServiceHost/wsdl/com/denoncourt/GetAccounts.wsdl With the WSDL itself available on the Web, any developer with network access and an IDE, such as WDSc, can generate client code to consume the Web service. Sharing the Wealth with Web Services Web services are a great mechanism for sharing business processes among various client applications. For example, to share the Web services with .Net and ColdFusion applications, I simply send the Web service’s WSDL XML to the .Net and ColdFusion developers, who then use their favorite IDE to generate the low-level TCP/IP code required to communicate with my Java- and RPG-based Web services. Conversely, you can use WDSc to create the low-level TCP/IP code required to consume Web services provided by various business partners. It doesn’t matter what language those Web services are implemented with because all you need is the WSDL XML file. That’s the beauty of Web services — sharing business processes across the Internet without the need to know their implementation. Appendix: The GETACCTS ILE RPG program: D RESULTSET D ACCOUNTNO D AccountName D Balance DCount DS OCCURS(100) 1 8 79 S 7 78 89 0 2 LIKE(CountIn) DError S LIKE(ErrorIn) DErrorMsg S LIKE(ErrorMsgIn) DIndex S 5 0 C *ENTRY PLIST C Count PARM CountIn C PARM Error ErrorIn C PARM ErrorMsg ErrorMsgIn /FREE IF Count > 100 or Count < 1; eval Error = '1'; eval ErrorMsg = 'Invalid Count'; eval Count = 0; else; for Index = 1 to Count; %occur(ResultSet) = Index; eval AccountNo = Index; eval AccountName = 'Denoncourt'; eval Balance = Index; ENDFOR; eval Error = '0'; eval ErrorMsg = ''; ENDIF; /END-FREE C/exec sql C+ SET RESULT SETS ARRAY :ResultSet C+ FOR :Count ROWS C/end-exec C RETURN 5 0 1 50 Tip: the code in TestGetAccounts.jsp can easily be changed to handle dynamic IP addresses: Account[] accts = new GetAccountsServiceLocator(). getGetAccounts( new URL("http://127.0.0.1:7080/Host/services/GetAccounts") ).search(3); Where the URL string could be a variable.