src.com.ibm.as400.access.MemberList Maven / Gradle / Ivy
Show all versions of jt400-jdk8 Show documentation
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: MemberList.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 2009-2010 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
package com.ibm.as400.access;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Represents a list of database file members.
*
* Implementation note: This class internally calls the "List Database File Members" API (QUSLMBR).
* Information from formats MBRL0100 and MBRL0200 is retrieved.
*
*
* This class is mostly based on a prototype contributed by Mihael Schmidt.
*
* @see MemberDescription
* @see AS400File
*/
public class MemberList
{
private static final SimpleDateFormat dateTimeFormat_ = new SimpleDateFormat("yyMMddHHmmss");
private static final String QUSLMBR_FORMAT_100 = "MBRL0100";
private static final String QUSLMBR_FORMAT_200 = "MBRL0200";
private static final QSYSObjectPathName USERSPACE_PATH = new QSYSObjectPathName("QTEMP", "JT4QUSLMBR", "USRSPC"); // user space QTEMP/JT4QUSLMBR
private AS400 system_;
private QSYSObjectPathName path_;
private String memberSelection_ = "*ALL"; // default: all members
private final Map memberDescriptions_ = new HashMap();
private final List attributes_ = new ArrayList();
// Offsets for format MBRL0200, "List Data Section", of API QUSLMBR.
private static final int OFFSET_MEMBER_NAME = 0;
private static final int OFFSET_SOURCE_TYPE = 10;
private static final int OFFSET_CREATION_DATE_TIME = 20;
private static final int OFFSET_LAST_SOURCE_CHANGE_DATE = 33;
private static final int OFFSET_MEMBER_TEXT_DESCRIPTION = 46;
private static final int OFFSET_MEMBER_TEXT_DESCRIPTION_CCSID = 96;
private final AS400Bin4 intConverter_ = new AS400Bin4();
/**
* Constructs a MemberList object.
*
* @param file A database file.
*/
public MemberList(AS400File file)
{
if (file == null) throw new NullPointerException("file");
system_ = file.getSystem();
path_ = new QSYSObjectPathName(file.getLibraryName(), file.getFileName(), "FILE");
String memberName = file.getMemberName();
if (memberName == null || memberName.length() == 0)
{
// default to *ALL
}
else if (memberName.startsWith("*")) // *FIRST, *LAST, etc.
{
// The "member name" parameter of the QUSLMBR API must be either:
// - a specific member name;
// - a generic member name (a wildcarded name pattern); or
// - special value *ALL, indicating "all members".
memberSelection_ = "*ALL"; // force it to *ALL
if (Trace.traceOn_)
{
Trace.log(Trace.DIAGNOSTIC, "Setting member selection to *ALL. Member name from AS400File object:", memberName);
}
}
else
{
memberSelection_ = memberName;
}
}
/**
* Constructs a MemberList object.
*
* Note: Generic names are only supported for the 'member' part of the IFS path.
* To retrieve all members, the IFS path should point to the database file.
*
* @param system AS400 system object.
* @param path IFS path to the database file or member.
*/
public MemberList(AS400 system, QSYSObjectPathName path)
{
if (system == null) throw new NullPointerException("system");
if (path == null) throw new NullPointerException("path");
system_ = system;
path_ = path;
String memberName = path_.getMemberName();
if (memberName == null || memberName.length() == 0)
{
memberSelection_ = "*ALL";
if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Setting member selection to *ALL. Member name from QSYSObjectPathName object is: " + memberName);
}
else
{
memberSelection_ = memberName;
}
}
/**
* Constructs a MemberList object.
*
* @param system AS400 system object
* @param libraryName Library where the physical file is located.
* @param objectName The name of the physical file.
*/
public MemberList(AS400 system, String libraryName, String objectName)
{
this(system, new QSYSObjectPathName(libraryName, objectName, "FILE"));
}
/**
* Determine the format to use on the API call, depending on the attribute.
*
* @param attribute Attribute to be retrieved
* @return format string
*/
private String lookupFormat(int attribute)
{
String format;
switch (attribute)
{
case MemberDescription.MEMBER_NAME:
format = QUSLMBR_FORMAT_100;
break;
case MemberDescription.SOURCE_TYPE:
case MemberDescription.CREATION_DATE_TIME:
case MemberDescription.LAST_SOURCE_CHANGE_DATE:
case MemberDescription.MEMBER_TEXT_DESCRIPTION:
case MemberDescription.MEMBER_TEXT_DESCRIPTION_CCSID:
format = QUSLMBR_FORMAT_200;
break;
default:
if (Trace.traceOn_) Trace.log(Trace.WARNING, "Unrecognized attribute key:", attribute);
format = QUSLMBR_FORMAT_200;
}
return format;
}
/**
* Removes all member descriptions from this object.
*/
public void clear()
{
memberDescriptions_.clear();
}
/**
* Clears the attribute list which specifies which attributes should be retrieved.
*/
public void clearAttributeList()
{
attributes_.clear();
}
/**
* Adds an attribute to the attribute list which specifies which attributes of the member
* are to be retrieved. Constants that specify attributes are available in class {@link MemberDescription MemberDescription}.
*
* @param attribute The attribute to be added.
*/
public void addAttribute(int attribute)
{
attributes_.add(new Integer(attribute));
}
/**
* Reloads all member descriptions that have been specified via {@link #addAttribute addAttribute()}.
*
* @throws ObjectDoesNotExistException If a system object necessary for the call does not exist on the system.
* @throws InterruptedException If this thread is interrupted.
* @throws IOException If an error occurs while communicating with the system.
* @throws ErrorCompletingRequestException If an error occurs before the request is completed.
* @throws AS400SecurityException If a security or authority error occurs.
*/
public void refresh()
throws AS400SecurityException, ErrorCompletingRequestException, InterruptedException,
IOException, ObjectDoesNotExistException
{
clear();
load();
}
/**
* Loads all members from the specified file(s).
*
* @throws ObjectDoesNotExistException If an object necessary for the call does not exist on the system.
* @throws InterruptedException If this thread is interrupted.
* @throws IOException If an error occurs while communicating with the system.
* @throws ErrorCompletingRequestException If an error occurs before the request is completed.
* @throws AS400SecurityException If a security or authority error occurs.
* @throws AS400Exception If the program on the server sends an escape message.
*/
public void load()
throws AS400Exception, AS400SecurityException, ErrorCompletingRequestException,
IOException, InterruptedException, ObjectDoesNotExistException
{
ProgramCall program = new ProgramCall(system_, "/QSYS.LIB/QUSLMBR.PGM", buildProgramParameters(getFormat())); // this API is not threadsafe
// Determine the needed scope of synchronization.
Object lockObject;
boolean willRunProgramsOnThread = program.isStayOnThread();
if (willRunProgramsOnThread) {
// The calls will run in the job of the JVM, so lock for entire JVM.
lockObject = USERSPACE_PATH;
}
else {
// The calls will run in the job of the Remote Command Host Server, so lock on the connection.
lockObject = system_;
}
synchronized (lockObject)
{
// Create a user space in QTEMP to receive output from program call.
UserSpace us = new UserSpace(system_, USERSPACE_PATH.getPath());
us.setMustUseProgramCall(true);
if (!willRunProgramsOnThread)
{
us.setMustUseSockets(true);
// Force the use of sockets when running natively but not on-thread.
// We have to do it this way since UserSpace will otherwise make a native ProgramCall, and will use a different QTEMP library than that used by the host server.
}
try
{
us.create(65535, true, "JT400", (byte) 0x00, "Userspace for loading members","*ALL"); // set public authority to *ALL
if (!program.run()) {
throw new AS400Exception(program.getMessageList());
}
byte[] usBuf = new byte[65535]; // local buffer to hold bytes read from user space
// Read the "generic header" from the user space, into the local buffer.
// Note: For description of general layout of the "list API" headers, see:
// http://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/topic/apiref/listGeneral.htm
int numBytesRead = us.read(usBuf, 0, 0, 0x90); // just read the needed header fields
if (numBytesRead < 0x90) // verify that we at least got a header, up through "CCSID" field
{
Trace.log(Trace.ERROR, "Failed to read the generic header. Number of bytes read: " + numBytesRead);
throw new InternalErrorException(InternalErrorException.UNKNOWN, numBytesRead);
}
// Parse the header, to get the offsets to the various sections.
// (Generic header) Offset to header section:
int offsetToHeaderSection = BinaryConverter.byteArrayToInt(usBuf, 0x74);
// (Generic header) Header section size:
int headerSectionSize = BinaryConverter.byteArrayToInt(usBuf, 0x78);
// (Generic header) Offset to list data section:
int offsetToListDataSection = BinaryConverter.byteArrayToInt(usBuf, 0x7C);
// (Generic header) List data section size:
int listDataSectionSize = BinaryConverter.byteArrayToInt(usBuf, 0x80);
// (Generic header) Number of list entries:
int numberOfListEntries = BinaryConverter.byteArrayToInt(usBuf, 0x84);
// (Generic header) Size of each entry:
int sizeOfEachEntry = BinaryConverter.byteArrayToInt(usBuf, 0x88);
// (Generic header) CCSID of data in the user space
int entryCCSID = BinaryConverter.byteArrayToInt(usBuf, 0x8C);
// (Generic header) Subsetted list indicator:
//String subsettedListIndicator = conv.byteArrayToString(usBuf, 0x95, 1);
if (entryCCSID == 0) entryCCSID = system_.getCcsid();
// From the API spec: "The coded character set ID for data in the list entries. If 0, then the data is not associated with a specific CCSID and should be treated as hexadecimal data."
final CharConverter conv = new CharConverter(entryCCSID);
// Read the "header section" into the local buffer.
numBytesRead = us.read(usBuf, offsetToHeaderSection, 0, headerSectionSize);
if (numBytesRead < headerSectionSize)
{
Trace.log(Trace.ERROR, "Failed to read the header section. Number of bytes read: " + numBytesRead);
throw new InternalErrorException(InternalErrorException.UNKNOWN, numBytesRead);
}
// (Header section) File library name used:
final String libraryNameUsed = conv.byteArrayToString(usBuf, 10, 10).trim();
// Read the "list data section" into the local buffer.
if (listDataSectionSize > usBuf.length) {
usBuf = new byte[listDataSectionSize+1]; // allocate a larger buffer
}
numBytesRead = us.read(usBuf, offsetToListDataSection, 0, listDataSectionSize);
if (numBytesRead < listDataSectionSize)
{
Trace.log(Trace.ERROR, "Failed to read the list data section. Number of bytes read: " + numBytesRead);
throw new InternalErrorException(InternalErrorException.UNKNOWN, numBytesRead);
}
// Parse the list data returned in the user space.
String format = getFormat();
for (int i = 0; i < numberOfListEntries; i++)
{
byte[] entryBuf = new byte[sizeOfEachEntry];
System.arraycopy(usBuf, i * sizeOfEachEntry, entryBuf, 0, sizeOfEachEntry);
readMemberInfoFromUserspaceEntry(entryBuf, format, conv, libraryNameUsed);
}
}
finally {
// Delete the temporary user space, to allow other threads to re-create and use it.
try { us.delete(); }
catch (Exception e) {
Trace.log(Trace.ERROR, "Exception while deleting temporary user space", e);
}
}
}
}
/**
* Builds the program parameter list for a program call to QUSLMBR (List Database File Members).
*
* @return Program parameter list for QUSLMBR
*/
private ProgramParameter[] buildProgramParameters(String format)
throws UnsupportedEncodingException
{
ProgramParameter[] parameterList = new ProgramParameter[6];
// Qualified user space name:
parameterList[0] = new ProgramParameter(CharConverter.stringToByteArray(system_, USERSPACE_PATH.toQualifiedObjectName()));
// Format name:
parameterList[1] = new ProgramParameter(CharConverter.stringToByteArray(system_, format));
// Qualified database file name:
parameterList[2] = new ProgramParameter(CharConverter.stringToByteArray(system_, path_.toQualifiedObjectName()));
// Member name:
parameterList[3] = new ProgramParameter(new AS400Text(10).toBytes(memberSelection_));
// Override processing: (1 == "overrides are processed")
parameterList[4] = new ProgramParameter(CharConverter.stringToByteArray(system_, "1"));
// Error code:
parameterList[5] = new ErrorCodeParameter();
return parameterList;
}
/**
* Reads all available information from the userspace entry regardless of the attributes to
* be retrieved. The attributes to be retrieved determines in which format the entry is in the
* userspace.
*
* @param entryBuf Raw bytes of the entry
* @param format Content and format name (f. e. MBRL0100, see {@link #getFormat() getFormat()}
* @param charConverter CharConverter object used for translating bytes to String
* @param usedLibraryName The resolved library name used for the API call.
*
*/
private void readMemberInfoFromUserspaceEntry(byte[] entryBuf, String format, CharConverter charConverter, String usedLibraryName)
{
String memberName = charConverter.byteArrayToString(entryBuf, OFFSET_MEMBER_NAME, 10).trim();
// get existing member description or create a new one
MemberDescription memberDescription = (MemberDescription) memberDescriptions_.get(memberName);
if (memberDescription == null)
{
QSYSObjectPathName memberPath = new QSYSObjectPathName(path_.getPath());
try {
memberPath.setMemberName(memberName);
memberPath.setLibraryName(usedLibraryName);
}
catch (java.beans.PropertyVetoException pve) { // will never happen
Trace.log(Trace.ERROR, "Error ignored.", pve);
}
memberDescription = new MemberDescription(system_, memberPath);
memberDescription.setAttribute(MemberDescription.MEMBER_NAME, memberName);
memberDescriptions_.put(memberName, memberDescription);
}
if (QUSLMBR_FORMAT_200.equals(format) )
{
memberDescription.setAttribute(MemberDescription.SOURCE_TYPE,
charConverter.byteArrayToString(entryBuf, OFFSET_SOURCE_TYPE, 10).trim());
memberDescription.setAttribute(MemberDescription.MEMBER_TEXT_DESCRIPTION,
charConverter.byteArrayToString(entryBuf, OFFSET_MEMBER_TEXT_DESCRIPTION, 50).trim());
memberDescription.setAttribute(MemberDescription.MEMBER_TEXT_DESCRIPTION_CCSID,
new Integer(intConverter_.toInt(entryBuf, OFFSET_MEMBER_TEXT_DESCRIPTION_CCSID)));
memberDescription.setAttribute(MemberDescription.LAST_SOURCE_CHANGE_DATE,
transformDate(charConverter.byteArrayToString(entryBuf, OFFSET_LAST_SOURCE_CHANGE_DATE, 13)));
memberDescription.setAttribute(MemberDescription.CREATION_DATE_TIME,
transformDate(charConverter.byteArrayToString(entryBuf, OFFSET_CREATION_DATE_TIME, 13)));
}
}
/**
* Returns an array of retrieved member descriptions. If no member descriptions could be
* retrieved because there are no members or because of an error, an empty array is returned.
* If no members has been retrieved yet due to no call to load(), then an empty array is
* returned.
*
* @return Array of retrieved member descriptions
*/
public MemberDescription[] getMemberDescriptions()
{
return (MemberDescription[]) memberDescriptions_.values().toArray(new MemberDescription[memberDescriptions_.size()]);
}
/**
* Returns the format for the call to QUSLMBR depending on the attributes to be retrieved.
*
* @return format name (either MBRL0100 or MBRL0200 for now)
*/
private String getFormat()
{
String format = QUSLMBR_FORMAT_100;
for (Iterator iter = attributes_.iterator(); iter.hasNext(); )
{
String tmpFormat = lookupFormat(((Integer) iter.next()).intValue());
if (format.compareTo(tmpFormat) < 0)
{
format = tmpFormat;
}
}
return format;
}
/**
* Parses the date string, which should be in the format CYYMMDDHHMMSS.
*
* @param dateString Date string
*
* @return Date object representing the passed date string, or null if the
* date string is null, empty or not parseable.
*/
private Date transformDate(String dateString)
{
Date retVal = null;
if (dateString == null || dateString.length() == 0)
{
// nothing => returns null
}
else
{
try {
synchronized (dateTimeFormat_) // date formats are not synchronized
{
retVal = dateTimeFormat_.parse(dateString.substring(1));
}
}
catch(ParseException pe) { // return null
if (Trace.traceOn_) Trace.log(Trace.ERROR, "Ignored error while parsing date string: " + dateString, pe);
}
}
return retVal;
}
}