This article examines how Java can be a powerful productivity tool for a Systems Programmer, using the task of synchronizing Master Catalogs as an example.
Systems programmers are often dismissive of Java. They think it is memory hungry, has poor performance and is not useful for real systems programming work.
In fact, the reality is that Java performance on z/OS is good, and being improved all the time.
More importantly, it is a powerful language and when combined with the functions provided by the JZOS Toolkit can be very useful for writing System Programmer utilities.
Example: Synchronizing Master Catalogs
Comparing or synchronizing master catalogs is a common task. You may have several systems with separate master catalogs, or need to merge a master catalog from a Serverpac installation with a live master catalog.
This Java example generates IDCAMS commands to synchronize catalogs. It could be adapted to define new entries only, work with specific HLQs etc.
The power of Java Collections
Java includes a Collections framework. As its name implies, the Collections framework provides functions for working with collections of data – Lists, Maps, Sets etc.
One of the most useful Collections classes is the Map, which maps keys to values. I usually use the HashMap specialization, which uses a hash table to map the values and generally has excellent performance.
Using a Map, the logic for comparing catalogs is simple:
- Add entries from the first catalog to a map using the name as a key
- Get all entries from the second catalog and:
- If there is a matching name in the map – remove the entry from the map and compare the attributes.
- Otherwise, record that the entry is in the second catalog only.
- The remaining entries in the map are in the first catalog only.
Sample code showing the logic:
Map<String, CatalogEntry> cat1Entries
= new HashMap<String, CatalogEntry>();
for (CatalogEntry entry :
getCatalogEntries(catalog1, "**", null))
{
cat1Entries.put(entry.entryName, entry);
}
List<CatalogEntry> cat2Only
= new ArrayList<CatalogEntry>();
for (CatalogEntry cat2Entry :
getCatalogEntries(catalog2, "**", null))
{
CatalogEntry cat1Entry
= cat1Entries.remove(cat2Entry.entryName);
if (cat1Entry != null) // found entry from catalog1
{
if (!cat2Entry.equals(cat1Entry))
{
// same name, different attributes
}
// else entries match
}
else
{
// entry is in catalog 2 only
}
}
// Remaining entries in map are in catalog 1 only
List<CatalogEntry> cat1Only
= new ArrayList<CatalogEntry>(
cat1Entries.values());
Catalog comparison logic
CatalogSync Implementation
The program consists of a CatalogEntry class, which provides the functions to work with catalog entries, and the main program with the comparison logic.
CatalogEntry Class
CatalogEntry instances are created from entries returned from the JZOS CatalogSearch function. The CatalogEntry constructor takes the JZOS CatalogSearch.Entry and extracts the information we are interested in.
import java.util.*;
import com.ibm.jzos.CatalogSearch;
public class CatalogEntry {
public CatalogEntry(CatalogSearch.Entry entry)
{
entryName =
entry.getField("ENTNAME").getFString();
entryType =
entry.getField("ENTYPE").getFString();
relatedNames =
entry.getField("NAME").getFStringArray(44);
if (relatedNames == null)
relatedNames = new String[] {};
volumes =
entry.getField("VOLSER").getFStringArray(6);
if (volumes == null)
volumes = new String[] {};
deviceTypes =
entry.getField("DEVTYP").getIntArray(4);
if (deviceTypes == null)
deviceTypes = new int[] {};
}
public String entryName;
public String entryType;
public String[] relatedNames;
public String[] volumes;
public int[] deviceTypes;
}
CatalogEntry.java class with constructor
The NAME, VOLSER and DEVTYP from CatalogSearch.Entry can be null if the attribute does not exist for that entry. In that case they are set to empty arrays, because that simplifies comparison processing.
Our CatalogEntry class has the following additional methods:
CatalogEntry.getCatalogEntries
getCatalogEntries reads entries from the specified catalog.
It is a static method which is used by the main program to retrieve the list of entries.
getCatalogEntries uses the JZOS CatalogSearch class which provides a Java interface to the z/OS Catalog Search Interface (CSI).
public static List<CatalogEntry> getCatalogEntries(
String catalogname,
String search,
String entryTypes)
{
List<CatalogEntry> result =
new ArrayList<CatalogEntry>();
CatalogSearch catSearch =
new CatalogSearch(search);
catSearch.setCatalogName(catalogname);
catSearch.setEntryTypes(entryTypes);
catSearch.setCatalogName(catalogname);
catSearch.setSingleCatalog(true);
catSearch.addFieldName("ENTYPE");
catSearch.addFieldName("ENTNAME");
catSearch.addFieldName("NAME");
catSearch.addFieldName("DEVTYP");
catSearch.addFieldName("VOLSER");
catSearch.search();
while (catSearch.hasNext())
{
CatalogSearch.Entry entry =
(CatalogSearch.Entry)catSearch.next();
if (entry.isDatasetEntry())
{
result.add(new CatalogEntry(entry));
}
}
return result;
}
CatalogEntry.getCatalogEntries method
getCatalogEntries sets various CSI parameters then invokes the search, returning the result as a List.
Note: The line catSearch.setSingleCatalog(true) says that only the specified catalog will be searched. Otherwise entries from multiple catalogs (e.g. the current master catalog) can be returned. This gives false results when trying to compare 2 catalogs.
For more information on the CatalogSearch function refer to the JZOS CatalogSearch documentation, and the Catalog Search Interface documentation in the z/OS Managing Catalogs manual.
CatalogEntry.equals and CatalogEntry.hashcode
CatalogEntry.equals tests whether 2 catalog entries are equal.
CatalogEntry.hashcode is not used in this program, but is included because when you override the equals function it is good practice to also override the hashcode function. Equals and hashcode are used when the class is used as a key in a HashMap. The rule is that if 2 instances are equal they must have the same hashcode.
This class simply returns the hashcode from the entry name as the CatalogEntry.hashcode.1
@Override
public boolean equals(Object o)
{
if (o == this) return true; // same object
if (!(o instanceof CatalogEntry)) return false;
CatalogEntry ce = (CatalogEntry) o;
return (Objects.equals(
this.entryName, ce.entryName)
&& Objects.equals(
this.entryType, ce.entryType)
&& Arrays.equals(
this.relatedNames, ce.relatedNames)
&& Arrays.equals(
this.volumes, ce.volumes)
&& Arrays.equals(
this.deviceTypes, ce.deviceTypes)
);
}
@Override
public int hashCode()
{
return entryName.hashCode();
}
CatalogEntry.equals and hashcode methods
CatalogEntry.deviceType
This is a utility function to translate the device type from the catalog to a device type for the IDCAMS command.
String deviceType(int devtype)
{
switch (devtype)
{
case 0:
return "0000";
case 0x3010200E:
return "3380";
case 0x3010200F:
return "3390";
default:
return String.format(
"Device type %8X not implemented",
devtype);
}
}
CatalogEntry. deviceType method
CatalogEntry.deleteCommand, defineCommand
The deleteCommand and defineCommand methods generate the IDCAMS commands used to synchronize the catalogs.
The methods use String formatting to insert the values into the commands.
public String deleteCommand(String catalog)
{
switch (entryType)
{
case "A": //NONVSAM
return String.format(
" DELETE -%n" +
" %s -%n" +
" NOSCRATCH -%n"+
" CATALOG(%s)%n",
entryName,
catalog);
case "X": //ALIAS
return String.format(
" DELETE -%n" +
" %s -%n" +
" ALIAS -%n"+
" CATALOG(%s)%n",
entryName,
catalog);
default:
return String.format(
"Delete entry type %s " +
"not implemented, %s%n",
entryType, entryName);
}
}
CatalogEntry.deleteCommand method
public String defineCommand(String catalog)
{
switch (entryType)
{
case "A": //NONVSAM
String volList = volumes[0];
for (int i=1; i < volumes.length; i++)
{
volList += " " + volumes[i];
}
String devList =
deviceType(deviceTypes[0]);
for (int i=1;
i < deviceTypes.length; i++)
{
devList +=
" " + deviceType(deviceTypes[i]);
}
return String.format(
" DEFINE NONVSAM -%n" +
" (NAME(%s) -%n" +
" DEVICETYPES(%s) -%n"+
" VOLUMES(%s) )-%n"+
" CATALOG(%s)%n",
entryName,
devList,
volList,
catalog);
case "X": //ALIAS
return String.format(
" DEFINE ALIAS -%n" +
" (NAME(%s) -%n" +
" RELATE(%s) ) -%n" +
" CATALOG(%s)%n",
entryName,
relatedNames[0],
catalog);
default:
return String.format(
"Define entry type %s " +
"not implemented, %s%n",
entryType, entryName);
}
}
CatalogEntry.defineCommand method
defineCommand handles the case where there are multiple volumes in the entry by building strings with the volumes and device types before inserting them into the command.
The CatalogSync program
This is the main program to generate the catalog synchronization commands.
DANGER
Automatically generated commands can severely damage your system if they contain errors. Before running the commands generated by this program, please make sure that:
- The generated commands are correct, and operating on the correct catalogs.
- All required continuation characters are present so that the statement specifying the target catalog is part of the statement.
- You understand what the commands are doing.
- You have a plan for recovery if an error occurs.
The catalog names are passed as arguments to the program, and the delete/define commands are written to the DD COMMANDS.
This program only deals with NONVSAM and ALIAS entries, so the call to getCatalogEntries specifies “AX” as the entry types. (See the CSI documentation in the IBM Managing Catalogs manual.)
import java.io.*;
import java.util.*;
import com.ibm.jzos.*;
public class CatalogSync
{
public static void main(String[] args)
throws IOException
{
if (args.length != 2)
{
System.out.println(
"Usage: CatalogSync catalog1 catalog2");
return;
}
String cat1 = args[0];
String cat2 = args[1];
try (PrintWriter writer
= new PrintWriter(
FileFactory.newOutputStream(
"//DD:COMMANDS")))
{
Map<String, CatalogEntry> cat1Entries
= new HashMap<String, CatalogEntry>();
for (CatalogEntry entry :
CatalogEntry.getCatalogEntries(
cat1, "**", "AX"))
{
cat1Entries.put(
entry.entryName, entry);
}
for (CatalogEntry cat2entry :
CatalogEntry.getCatalogEntries(
cat2, "**", "AX"))
{
CatalogEntry cat1entry = cat1Entries
.remove(cat2entry.entryName);
if (cat1entry != null)
{
if (!cat2entry.equals(cat1entry))
{
writer.print(
cat2entry.deleteCommand(
cat2));
writer.print(
cat1entry.defineCommand(
cat2));
}
// else entries match
} else
{
writer.print(
cat2entry.deleteCommand(
cat2));
}
}
List<CatalogEntry> cat1Only
= new ArrayList<CatalogEntry>(
cat1Entries.values());
Collections.sort(cat1Only,
new Comparator<CatalogEntry>()
{
public int compare(
CatalogEntry s1, CatalogEntry s2)
{
return
s1.entryName.compareTo(
s2.entryName);
}
});
for (CatalogEntry entry : cat1Only)
{
writer.print(
entry.defineCommand(cat2));
}
}
}
}
CatalogSync.java class
Compiling and Running the Program
This JCL uses the PROCs described in the article Systems Programmer Friendly Java to compile and run the complete program:
//JAVAG EXEC PROC=JAVA8UCG,
// JAVACLS='CatalogSync'
//G.MAINARGS DD *
CATALOG.MASTER
CATALOG.MASTER.COPY
//G.COMMANDS DD DISP=SHR,DSN=USERID.JCL.CNTL(GENCMDS)
Compile and Execution JCL
Conclusion
Java is a powerful language and a useful addition to z/OS. Hopefully this article has given you some ideas about how it could make your job easier.
1: Hash collisions can cause performance problems when using hash tables, so it is recommended that all the members that are used to determine equality are used when calculating a hashcode. In this case I am using the lazy assumption that CatalogEntries used as keys in a hashtable will normally have different entry names.