• No results found

Chapter 6. The Java CICS API

6.13 Terminal services

JCICS terminal services allow interaction with the user terminal. You can send data to and receive data from the terminal, and send cursor and device control instructions to the terminal.

Alas, using these services is not for the faint of heart because they require intimate

knowledge of the 3270 datastream format (see 3270 Data Stream Programmer’s Reference,

GA23-0059).

There is one terminal service, however, that is relatively straightforward to use. If a JCICS program is started from a terminal, the Task.out variable (an instance of PrintWriter) allows you to write to that terminal, which is much like the standard Java System.out stream:

Task.getTask().out.println("Hello World!");

That said, there is good news. During the residency, we developed a little package boldly called JBMS (Java Basic Mapping Support), which provides an easy-to-use interface to the 3270 terminal, which is much like the corresponding BMS service in traditional CICS programming. Be aware, however, that the usual IBM Redbooks publication disclaimer applies: It is experimental code, not thoroughly tested, and certainly not ready for production purposes. It can come in handy, however, if you want to develop a simple front-end for an application without going through all of the effort to create a full-fledged Web interface.

We only show some particularly interesting parts of the JBMS package in the remainder of this section.

Figure 6-8 JBMS class hierarchy

A brief explanation of all classes follows.

Component

This is the abstract superclass for all classes that represent elements on the window. All components except

Screen

have a parent

Component

that must be passed to the constructor. A

Component

has a row/column position that is relative to its enclosing

Component

(if any) and a flag that indicates whether the component is visible, that is, must be displayed on the window at all.

Chapter 6. The Java CICS API 127

Container

This is an abstract superclass for all components that contain other components. The idea is that you add related items to a

container

using a relative position. So, if you later decide that you must move a container to another position on the window, you simply change the container’s position rather than the position of each individual item in the container.

Panel

Right now, a

panel

does not have additional functionality over a container. The idea is that it can add visual elements, such as a border as an indication of grouping.

For convenience, we provide a subclass, PfKeyPanel, which you can use to show PF-key assignments.

Screen

This container represents an entire 3270 screen. It is responsible for generating and sending the outbound 3270 data stream to the terminal, and for receiving and parsing the inbound data stream.

Again, there is a convenience subclass, MenuScreen, which can be used to easily create a menu screen showing several items and an input field for the user’s choice.

Field

This is the abstract superclass of all basic components of a screen, that is, the actual fields that make up the screen.

Label

Represents fields that are intended for display only, that is, that are not for data entry by the user. However, the text displayed need not be static but can also be changed during the dialogue with the user, for example, to show an error message.

Text

This is a field for data entry. Optionally, you can set a formatter, that is, an instance of java.text.Format, to validate and format data entered by the user.

Next, we show you a sample screen created using the JBMS package (Figure 6-9 on page 128) and the program that created it (Example 6-27 on page 128).

Figure 6-9 Sample screen produced by a JBMS program

Example 6-27 shows the program to generate this screen.

Example 6-27 Sample JBMS program

package com.ibm.itso.sg245275; import java.text.DecimalFormat; import com.ibm.cics.server.*; import com.ibm.itso.sg245275.jbms.*; import com.ibm.itso.sg245275.jbms.Field.Color; import com.ibm.itso.sg245275.vsam.VsamMap; public class BmsDemo {

private Text acctno;

private Text firstname, lastname, midinit; private Text phone;

private Text[] addr = new Text[3]; private PfKeyPanel pfkeys;

private VsamMap accounts = new VsamMap("ACCTFILE", 5); // (1) public BmsDemo(TerminalPrincipalFacility term) throws InvalidRequestException, LengthErrorException, NotAllocatedException,

TerminalException {

Screen screen = createScreen(); byte aid;

String record = null; boolean end = false;

Chapter 6. The Java CICS API 129

do {

fillFields(record); // (2)

aid = screen.displayAndReceive(term); // (3) } while (aid != AIDValue.PF3 && screen.validate() != null); // (4) switch (aid) { // ... omitted } } term.clear(); }

private String makeRecord() { // ... omitted

}

private void fillFields(String record) { // ... omitted

}

private Screen createScreen() { // (5) Screen screen = new Screen();

Label label;

label = new Label(screen, 1, 1, "Account management"); label.setForegroundColor(Color.PINK);

label = new Label(screen, 4, 1, "Acct no:"); label.setForegroundColor(Color.TURQUOISE);

acctno = new Text(screen, 4, 10, 5); // (6) acctno.setFormat(new DecimalFormat("00000"));

// Name panel.

Panel namePanel = new Panel(screen, 6, 0); // (7) label = new Label(namePanel, 0, 1, "Name: ");

label.setForegroundColor(Color.TURQUOISE); lastname = new Text(namePanel, 0, 10, 18); label = new Label(namePanel, 0, 29, ","); label.setForegroundColor(Color.TURQUOISE); firstname = new Text(namePanel, 0, 31, 12); midinit = new Text(namePanel, 0, 44, 1);

label = new Label(screen, 8, 1, "Phone: "); label.setForegroundColor(Color.TURQUOISE); phone = new Text(screen, 8, 10, 10);

// Address panel.

Panel addressPanel = new Panel(screen, 10, 0); label = new Label(addressPanel, 0, 1, "Address: "); label.setForegroundColor(Color.TURQUOISE);

addr[0] = new Text(addressPanel, 0, 10, 24); addr[1] = new Text(addressPanel, 1, 10, 24); addr[2] = new Text(addressPanel, 2, 10, 24);

// Create PF key bar.

pfkeys = new PfKeyPanel(screen); pfkeys.setText(1, "HELP"); // ... (omitted)

// Initial cursor position.

return screen; }

public static void main(CommAreaHolder ca) { // ... omitted

} }

Notes on Example 6-27 on page 128:

򐂰 The data displayed on this screen comes from a VSAM file. We use the VsamMap class developed in 6.11, “File control” on page 115, to access the data.

򐂰 Populate the fields with values from the record.

򐂰 Display the screen, and receive the user response. The fields that make up the screen are updated with the data that the user enters, and we get back an AID value that tells us what key the user pressed to send the data (for example, the ENTER key, or a PF key).

򐂰 If the user entered invalid data (in this example, non-numeric data in the acctname field), start all over again, unless he pressed PF3 for exit.

򐂰 This method creates the screen, adding labels for informative text, and fields for data entry.

򐂰 This is the account number field. We set up a formatter that causes the data to be displayed with leading zeros and validates the user input.

򐂰 For easier maintenance, we group related fields, such as the name and address fields together, with offsets relative to the enclosing container. To move a group of related fields, only change the position of the container rather than of each contained element.

򐂰 Set the initial field, that is, the one that the cursor is positioned on when the screen is displayed.

In the remainder of this section, we show you some implementation details. The most interesting parts are constructing the data to be sent to the 3270 terminal (in 3270 parlance, the

outbound data stream

), and parsing the data being sent back by the terminal (the

inbound

data stream

).

Example 6-28 shows the method that creates the 3270 data stream and sends it to the terminal.

Example 6-28 Screen.display()

public void display(TerminalPrincipalFacility term) throws InvalidRequestException, LengthErrorException, NotAllocatedException, TerminalException

{

ByteArrayOutputStream bos = new ByteArrayOutputStream(); // (1) bos.write(0);

bos.write(0);

to3270(bos); // (2)

if (getInitialField() != null) { // (3) // Position cursor to initial field.

initialField.insertBufferAddress(bos, true); bos.write(Order.INSERT_CURSOR);

}

term.send(bos.toByteArray()); // (4)

Chapter 6. The Java CICS API 131

Notes on Example 6-28 on page 130:

򐂰 Create a ByteOutputStream to hold the outbound 3270 data stream.

򐂰 Create the 3270 data stream, as shown in Example 6-29.

򐂰 If an initial field was specified, write a 3270 instruction to set the current buffer position and another one to move the cursor to that position.

򐂰 This is the JCICS call to send the data stream to the terminal.

To generate the data stream, we recursively iterate over all components, as shown in Example 6-29.

Example 6-29 Container.to3270()

protected void to3270(ByteArrayOutputStream bos) {

if (isVisible()) { // (1)

for (Iterator iter = components.iterator(); iter.hasNext();) { // (2) Component component = (Component) iter.next();

component.to3270(bos); // (3)

} } }

Notes on Example 6-29:

򐂰 If this Container is visible at all:

– Iterate over all contained components.

– Tell each in turn to generate its part of the data stream, which is a recursive call if the current component is itself a container.

Finally, we must generate the 3270 stream for a single

field

, which is the most complicated part because here we finally must know about the 3270 data stream format.

Basically, the outbound 3270 data stream consists of command bytes (orders) followed by parameters. To define a field, use the Start Field (SF) or Start Field Extended (SFE) order. In our code, we use SFE.

A field begins at the current buffer position, which you can change with the Set Buffer Address (SBA) order and ends right before the next field begins. SBA is followed by a two-byte buffer address.

Therefore, to start a new field, we first set the current buffer position, and then define the field using SFE and finally create an empty

marker

field.

A 3270 field can have several attributes, such as color, intensity, and whether the field is protected (no data can be entered into the field). Each attribute is recognized by an attribute type byte followed by an attribute value byte (see Table 6-2 on page 132). One of those attributes, the 3270 attribute, is always present, and is stored in the 3270 display buffer at the first position of the field (it does not display on the screen). The other attributes are optional.

The SFE order is followed by a single byte that indicates how many type/value byte pairs follow.

Table 6-2 Some 3270 attributes and their possible values

Example 6-30 shows how the data stream for a single field is generated.

Example 6-30 Field.to3270()

protected void to3270(ByteArrayOutputStream bos) { if (isVisible()) {

insertBufferAddress(bos, false); // (1) bos.write(Order.START_FIELD_EXT); // Start field extended // (2) bos.write(countExtendedAttributes()); // Number of extended attributes // (3) // Send extended attributes, as type/value byte pairs. // (4) writeAttribute(bos, AttributeType.STD3270, (byte) Util.encode(attributes)); writeAttributeConditional(bos, AttributeType.EXT_HILITE, extHilite); writeAttributeConditional(bos, AttributeType.FOREGROUND, foregroundColor); try {

bos.write(text.getBytes()); } catch (IOException e) {

// Can’t happen with a ByteArrayOutputStream }

// Send a dummy marker field to indicate end of current field // (5) bos.write(Order.START_FIELD);

bos.write(Util.encode(Attribute.AUTOSKIP)); }

}

Notes on Example 6-30:

򐂰 First we must tell the terminal the position of the field. The 3270 expects the position as a buffer address that is encoded in a rather peculiar format (see the full source code for details).

Type code

Attribute type Values

0xC0 3270 Field attribute

Value is a combination of bits. Some combinations have special meanings (autoskip, nondisplay).

Value must be EBCDIC encoded.

0x20 Field is protected 0x10 Field is numeric

0x30 Field is autoskip (no entry) 0x08 Intensified

0x0C Nondisplay (password) 0x41 Extended highlighting 0x00 Default

0xF0 Normal (as determined by 3270 field attribute)

0xF1 Blink

0xF2 Reverse video 0xF4 Underscore 0x42 Foreground color 0xF0 Neutral

0xF1 Blue ...

Chapter 6. The Java CICS API 133

򐂰 SFE expects the number of attribute/value pairs.

򐂰 Write attribute/value pairs to the data stream.

򐂰 The 3270 assumes that a field ends where the next field starts, so we write a dummy marker field to indicate the end of the current one.

To get user input, we must receive and parse the inbound 3270 data stream, as shown in Example 6-31.

Example 6-31 Screen.receive() - Receive input from terminal, parse response, and update fields

/**

* Receive input from terminal, and parse data stream. *

* @return AID (Action Identifier) of the key pressed {one of the * {@link com.ibm.cics.server.AIDValue AIDValue}constants). */

public byte receive(TerminalPrincipalFacility term) throws

InvalidRequestException, LengthErrorException, NotAllocatedException, TerminalException {

DataHolder dah = new DataHolder(); try {

term.receive(dah); // (1)

} catch (EndOfChainIndicatorException expected) {

// Always thrown from TerminalPrincipalFacility.receive(). // (2) // We can safely ignore it.

}

parseResponse(dah.value); // (3)

return term.getAIDbyte(); // (4)

}

Notes on Example 6-31:

򐂰 JCICS call to receive the response from the terminal.

򐂰 Term.receive() always throws an EndOfChainIndicatorException (because the underlying EXEC CICS API always sets the corresponding RESP condition), and we can safely ignore it.

򐂰 Parse the response and return the AID that caused the screen to be sent.

The inbound 3270 consists of Set Buffer Address (SBA) orders that indicate which field is being sent next, followed by the actual data (that is, whatever the user entered into the field). So, we inspect the data stream, looking for SBA orders and finding the corresponding JBMS Field object for each order, as shown in Example 6-32. Then we extract the field data and update the Field object accordingly.

Example 6-32 Parsing the response received from the 3270 terminal

private void parseResponse(byte[] resp) {

// Start at index 1 (index 0 has a SET_BUFFER_ADDRESS for first field). for (int pos = 1; pos < resp.length; pos++) { // (1) // Get buffer address.

int bufaddr = ((resp[pos++] << 8) | (resp[pos++] & 0xFF)) & 0xFFFF; // (2) // Find end of current segment (look for next SBA).

int end;

for (end = pos; end < resp.length && resp[end] != Order.SET_BUFFER_ADDRESS; end++) ;

// Find the field this segment corresponds to.

Field field = findFieldByAddress(bufaddr); // (3) if (field != null) {

// Found field; set its contents to the data received.

String fieldContents = new String(resp, pos, end - pos); // (4) field.setText(fieldContents);

}

pos = end; }

}

Notes on Example 6-29 on page 131:

򐂰 Scan the data stream, and examine it for SBA orders.

򐂰 Extract the buffer address (next two bytes after SBA).

򐂰 Look up the field that this buffer address refers to.

򐂰 Extract the field data, and set the new field text.